// Copyright (C) 2002-2007 Nikolaus Gebhardt // This file is part of the "Irrlicht Engine". // For conditions of distribution and use, see copyright notice in irrlicht.h #include "CSoftwareDriver.h" #include "IrrCompileConfig.h" #ifdef _IRR_COMPILE_WITH_SOFTWARE_ #include "CSoftwareTexture.h" #include "os.h" #include "S3DVertex.h" namespace irr { namespace video { //! constructor CSoftwareDriver::CSoftwareDriver(const core::dimension2d& windowSize, bool fullscreen, io::IFileSystem* io, video::IImagePresenter* presenter) : CNullDriver(io, windowSize), RenderTargetTexture(0), RenderTargetSurface(0), CurrentTriangleRenderer(0), ZBuffer(0), Texture(0) { #ifdef _DEBUG setDebugName("CSoftwareDriver"); #endif // create backbuffer BackBuffer = new CImage(ECF_A1R5G5B5, windowSize); BackBuffer->fill(SColor(0)); // get presenter Presenter = presenter; // create z buffer ZBuffer = video::createZBuffer(BackBuffer->getDimension()); // create triangle renderers TriangleRenderers[ETR_FLAT] = createTriangleRendererFlat(ZBuffer); TriangleRenderers[ETR_FLAT_WIRE] = createTriangleRendererFlatWire(ZBuffer); TriangleRenderers[ETR_GOURAUD] = createTriangleRendererGouraud(ZBuffer); TriangleRenderers[ETR_GOURAUD_WIRE] = createTriangleRendererGouraudWire(ZBuffer); TriangleRenderers[ETR_TEXTURE_FLAT] = createTriangleRendererTextureFlat(ZBuffer); TriangleRenderers[ETR_TEXTURE_FLAT_WIRE] = createTriangleRendererTextureFlatWire(ZBuffer); TriangleRenderers[ETR_TEXTURE_GOURAUD] = createTriangleRendererTextureGouraud(ZBuffer); TriangleRenderers[ETR_TEXTURE_GOURAUD_WIRE] = createTriangleRendererTextureGouraudWire(ZBuffer); TriangleRenderers[ETR_TEXTURE_GOURAUD_NOZ] = createTriangleRendererTextureGouraudNoZ(); TriangleRenderers[ETR_TEXTURE_GOURAUD_ADD] = createTriangleRendererTextureGouraudAdd(ZBuffer); // select render target setRenderTarget(BackBuffer); // select the right renderer selectRightTriangleRenderer(); } //! destructor CSoftwareDriver::~CSoftwareDriver() { // delete Backbuffer BackBuffer->drop(); // delete triangle renderers for (s32 i=0; idrop(); // delete zbuffer if (ZBuffer) ZBuffer->drop(); // delete current texture if (Texture) Texture->drop(); if (RenderTargetTexture) RenderTargetTexture->drop(); if (RenderTargetSurface) RenderTargetSurface->drop(); } //! switches to a triangle renderer void CSoftwareDriver::switchToTriangleRenderer(ETriangleRenderer renderer) { video::IImage* s = 0; if (Texture) s = ((CSoftwareTexture*)Texture)->getTexture(); CurrentTriangleRenderer = TriangleRenderers[renderer]; CurrentTriangleRenderer->setBackfaceCulling(Material.BackfaceCulling == true); CurrentTriangleRenderer->setTexture(s); CurrentTriangleRenderer->setRenderTarget(RenderTargetSurface, ViewPort); } //! void selects the right triangle renderer based on the render states. void CSoftwareDriver::selectRightTriangleRenderer() { ETriangleRenderer renderer = ETR_FLAT; if (Texture) { if (!Material.GouraudShading) renderer = (!Material.Wireframe) ? ETR_TEXTURE_FLAT : ETR_TEXTURE_FLAT_WIRE; else { if (Material.Wireframe) renderer = ETR_TEXTURE_GOURAUD_WIRE; else { if (Material.MaterialType == EMT_TRANSPARENT_ADD_COLOR || Material.MaterialType == EMT_TRANSPARENT_ALPHA_CHANNEL || Material.MaterialType == EMT_TRANSPARENT_VERTEX_ALPHA) { // simply draw all transparent stuff with the same renderer. at // least it is transparent then. renderer = ETR_TEXTURE_GOURAUD_ADD; } else if (!Material.ZBuffer && !Material.ZWriteEnable) renderer = ETR_TEXTURE_GOURAUD_NOZ; else { renderer = ETR_TEXTURE_GOURAUD; } } } } else { if (!Material.GouraudShading) renderer = (!Material.Wireframe) ? ETR_FLAT : ETR_FLAT_WIRE; else renderer = (!Material.Wireframe) ? ETR_GOURAUD : ETR_GOURAUD_WIRE; } switchToTriangleRenderer(renderer); } //! presents the rendered scene on the screen, returns false if failed bool CSoftwareDriver::endScene( s32 windowId, core::rect* sourceRect ) { CNullDriver::endScene(); Presenter->present(BackBuffer, windowId, sourceRect ); return true; } //! queries the features of the driver, returns true if feature is available bool CSoftwareDriver::queryFeature(E_VIDEO_DRIVER_FEATURE feature) const { switch (feature) { case EVDF_RENDER_TO_TARGET: return true; default: return false; }; } //! sets transformation void CSoftwareDriver::setTransform(E_TRANSFORMATION_STATE state, const core::matrix4& mat) { TransformationMatrix[state] = mat; } //! sets the current Texture bool CSoftwareDriver::setTexture(video::ITexture* texture) { if (texture && texture->getDriverType() != EDT_SOFTWARE) { os::Printer::log("Fatal Error: Tried to set a texture not owned by this driver.", ELL_ERROR); return false; } if (Texture) Texture->drop(); Texture = texture; if (Texture) Texture->grab(); selectRightTriangleRenderer(); return true; } //! sets a material void CSoftwareDriver::setMaterial(const SMaterial& material) { Material = material; for (u32 i = 0; i < 1; ++i) { setTexture(Material.getTexture(i)); setTransform ((E_TRANSFORMATION_STATE) ( ETS_TEXTURE_0 + i ), material.getTextureMatrix(i)); } } //! clears the zbuffer bool CSoftwareDriver::beginScene(bool backBuffer, bool zBuffer, SColor color) { CNullDriver::beginScene(backBuffer, zBuffer, color); if (backBuffer) BackBuffer->fill( color ); if (ZBuffer && zBuffer) ZBuffer->clear(); return true; } //! sets a render target bool CSoftwareDriver::setRenderTarget(video::ITexture* texture, bool clearBackBuffer, bool clearZBuffer, SColor color) { if (texture && texture->getDriverType() != EDT_SOFTWARE) { os::Printer::log("Fatal Error: Tried to set a texture not owned by this driver.", ELL_ERROR); return false; } if (RenderTargetTexture) RenderTargetTexture->drop(); RenderTargetTexture = texture; if (RenderTargetTexture) { RenderTargetTexture->grab(); setRenderTarget(((CSoftwareTexture*)RenderTargetTexture)->getTexture()); } else { setRenderTarget(BackBuffer); } if (RenderTargetSurface && (clearBackBuffer || clearZBuffer)) { if (clearZBuffer) ZBuffer->clear(); if (clearBackBuffer) ((video::CImage*)RenderTargetSurface)->fill( color ); } return true; } //! sets a render target void CSoftwareDriver::setRenderTarget(video::CImage* image) { if (RenderTargetSurface) RenderTargetSurface->drop(); RenderTargetSurface = image; RenderTargetSize.Width = 0; RenderTargetSize.Height = 0; Render2DTranslation.X = 0; Render2DTranslation.Y = 0; if (RenderTargetSurface) { RenderTargetSurface->grab(); RenderTargetSize = RenderTargetSurface->getDimension(); } setViewPort(core::rect(0,0,RenderTargetSize.Width,RenderTargetSize.Height)); if (ZBuffer) ZBuffer->setSize(RenderTargetSize); } //! sets a viewport void CSoftwareDriver::setViewPort(const core::rect& area) { ViewPort = area; //TODO: the clipping is not correct, because the projection is affected. // to correct this, ViewPortSize and Render2DTranslation will have to be corrected. core::rect rendert(0,0,RenderTargetSize.Width,RenderTargetSize.Height); ViewPort.clipAgainst(rendert); ViewPortSize = ViewPort.getSize(); Render2DTranslation.X = (ViewPortSize.Width / 2) + ViewPort.UpperLeftCorner.X; Render2DTranslation.Y = ViewPort.UpperLeftCorner.Y + ViewPortSize.Height - (ViewPortSize.Height / 2);// + ViewPort.UpperLeftCorner.Y; if (CurrentTriangleRenderer) CurrentTriangleRenderer->setRenderTarget(RenderTargetSurface, ViewPort); } //! draws a vertex primitive list void CSoftwareDriver::drawVertexPrimitiveList(const void* vertices, u32 vertexCount, const u16* indexList, u32 primitiveCount, E_VERTEX_TYPE vType, scene::E_PRIMITIVE_TYPE pType) { const u16* indexPointer=0; core::array newBuffer; switch (pType) { case scene::EPT_LINE_STRIP: { switch (vType) { case EVT_STANDARD: { for (u32 i=0; i < primitiveCount-1; ++i) draw3DLine(((S3DVertex*)vertices)[indexList[i]].Pos, ((S3DVertex*)vertices)[indexList[i+1]].Pos, ((S3DVertex*)vertices)[indexList[i]].Color); } break; case EVT_2TCOORDS: { for (u32 i=0; i < primitiveCount-1; ++i) draw3DLine(((S3DVertex2TCoords*)vertices)[indexList[i]].Pos, ((S3DVertex2TCoords*)vertices)[indexList[i+1]].Pos, ((S3DVertex2TCoords*)vertices)[indexList[i]].Color); } break; case EVT_TANGENTS: { for (u32 i=0; i < primitiveCount-1; ++i) draw3DLine(((S3DVertexTangents*)vertices)[indexList[i]].Pos, ((S3DVertexTangents*)vertices)[indexList[i+1]].Pos, ((S3DVertexTangents*)vertices)[indexList[i]].Color); } break; } } return; case scene::EPT_LINE_LOOP: drawVertexPrimitiveList(vertices, vertexCount, indexList, primitiveCount-1, vType, scene::EPT_LINE_STRIP); switch (vType) { case EVT_STANDARD: draw3DLine(((S3DVertex*)vertices)[indexList[primitiveCount-1]].Pos, ((S3DVertex*)vertices)[indexList[0]].Pos, ((S3DVertex*)vertices)[indexList[primitiveCount-1]].Color); break; case EVT_2TCOORDS: draw3DLine(((S3DVertex2TCoords*)vertices)[indexList[primitiveCount-1]].Pos, ((S3DVertex2TCoords*)vertices)[indexList[0]].Pos, ((S3DVertex2TCoords*)vertices)[indexList[primitiveCount-1]].Color); break; case EVT_TANGENTS: draw3DLine(((S3DVertexTangents*)vertices)[indexList[primitiveCount-1]].Pos, ((S3DVertexTangents*)vertices)[indexList[0]].Pos, ((S3DVertexTangents*)vertices)[indexList[primitiveCount-1]].Color); break; } return; case scene::EPT_LINES: { switch (vType) { case EVT_STANDARD: { for (u32 i=0; i < 2*primitiveCount; i+=2) draw3DLine(((S3DVertex*)vertices)[indexList[i]].Pos, ((S3DVertex*)vertices)[indexList[i+1]].Pos, ((S3DVertex*)vertices)[indexList[i]].Color); } break; case EVT_2TCOORDS: { for (u32 i=0; i < 2*primitiveCount; i+=2) draw3DLine(((S3DVertex2TCoords*)vertices)[indexList[i]].Pos, ((S3DVertex2TCoords*)vertices)[indexList[i+1]].Pos, ((S3DVertex2TCoords*)vertices)[indexList[i]].Color); } break; case EVT_TANGENTS: { for (u32 i=0; i < 2*primitiveCount; i+=2) draw3DLine(((S3DVertexTangents*)vertices)[indexList[i]].Pos, ((S3DVertexTangents*)vertices)[indexList[i+1]].Pos, ((S3DVertexTangents*)vertices)[indexList[i]].Color); } break; } } return; case scene::EPT_TRIANGLE_FAN: { // TODO: don't convert fan to list newBuffer.reallocate(primitiveCount*3); for( u32 t=0; t void CSoftwareDriver::drawClippedIndexedTriangleListT(const VERTEXTYPE* vertices, s32 vertexCount, const u16* indexList, s32 triangleCount) { if (!RenderTargetSurface || !ZBuffer || !triangleCount) return; if (!checkPrimitiveCount(triangleCount)) return; // arrays for storing clipped vertices core::array clippedVertices; core::array clippedIndices; // calculate inverse world transformation core::matrix4 worldinv(TransformationMatrix[ETS_WORLD]); worldinv.makeInverse(); // calculate view frustum planes scene::SViewFrustum frustum(TransformationMatrix[ETS_PROJECTION] * TransformationMatrix[ETS_VIEW]); // copy and transform clipping planes ignoring far plane core::plane3df planes[5]; // ordered by near, left, right, bottom, top for (int p=0; p<5; ++p) worldinv.transformPlane(frustum.planes[p+1], planes[p]); core::EIntersectionRelation3D inout[3]; // is point in front or back of plane? // temporary buffer for vertices to be clipped by all planes core::array tClpBuf; int t; int i; for (i=0; i textureSize(0,0); f32 zDiv; if (Texture) textureSize = ((CSoftwareTexture*)Texture)->getTexture()->getDimension(); f32 transformedPos[4]; // transform all points in the list core::matrix4 matrix(TransformationMatrix[ETS_PROJECTION]); matrix *= TransformationMatrix[ETS_VIEW]; matrix *= TransformationMatrix[ETS_WORLD]; s32 ViewTransformWidth = (ViewPortSize.Width>>1); s32 ViewTransformHeight = (ViewPortSize.Height>>1); for (i=0; i<(int)clippedVertices.size(); ++i) { transformedPos[0] = currentVertex->Pos.X; transformedPos[1] = currentVertex->Pos.Y; transformedPos[2] = currentVertex->Pos.Z; transformedPos[3] = 1.0f; matrix.multiplyWith1x4Matrix(transformedPos); zDiv = transformedPos[3] == 0.0f ? 1.0f : (1.0f / transformedPos[3]); tp->Pos.X = (s32)(ViewTransformWidth * (transformedPos[0] * zDiv) + (Render2DTranslation.X)); tp->Pos.Y = (Render2DTranslation.Y - (s32)(ViewTransformHeight * (transformedPos[1] * zDiv))); tp->Color = currentVertex->Color.toA1R5G5B5(); tp->ZValue = (TZBufferType)(32767.0f * zDiv); tp->TCoords.X = (s32)(currentVertex->TCoords.X * textureSize.Width); tp->TCoords.X <<= 8; tp->TCoords.Y = (s32)(currentVertex->TCoords.Y * textureSize.Height); tp->TCoords.Y <<= 8; ++currentVertex; ++tp; } // draw all transformed points from the index list CurrentTriangleRenderer->drawIndexedTriangleList(&TransformedPoints[0], clippedVertices.size(), clippedIndices.pointer(), clippedIndices.size()/3); } //! Draws a 3d line. void CSoftwareDriver::draw3DLine(const core::vector3df& start, const core::vector3df& end, SColor color) { core::vector3df vect = start.crossProduct(end); vect.normalize(); vect *= Material.Thickness*0.3f; S3DVertex vtx[4]; vtx[0].Color = color; vtx[1].Color = color; vtx[2].Color = color; vtx[3].Color = color; vtx[0].Pos = start; vtx[1].Pos = end; vtx[2].Pos = start + vect; vtx[3].Pos = end + vect; u16 idx[12] = {0,1,2, 0,2,1, 0,1,3, 0,3,1}; drawIndexedTriangleList(vtx, 4, idx, 4); } //! clips a triangle against the viewing frustum void CSoftwareDriver::clipTriangle(f32* transformedPos) { } //! creates the clipping planes from the matrix void CSoftwareDriver::createPlanes(const core::matrix4& mat) { Frustum = scene::SViewFrustum(mat); } //! Only used by the internal engine. Used to notify the driver that //! the window was resized. void CSoftwareDriver::OnResize(const core::dimension2d& size) { // make sure width and height are multiples of 2 core::dimension2d realSize(size); if (realSize.Width % 2) realSize.Width += 1; if (realSize.Height % 2) realSize.Height += 1; if (ScreenSize != realSize) { if (ViewPort.getWidth() == ScreenSize.Width && ViewPort.getHeight() == ScreenSize.Height) { ViewPort = core::rect(core::position2d(0,0), realSize); } ScreenSize = realSize; bool resetRT = (RenderTargetSurface == BackBuffer); BackBuffer->drop(); BackBuffer = new CImage(ECF_A1R5G5B5, realSize); if (resetRT) setRenderTarget(BackBuffer); } } //! returns the current render target size const core::dimension2d& CSoftwareDriver::getCurrentRenderTargetSize() const { return RenderTargetSize; } //! draws an 2d image, using a color (if color is other then Color(255,255,255,255)) and the alpha channel of the texture if wanted. void CSoftwareDriver::draw2DImage(const video::ITexture* texture, const core::position2d& destPos, const core::rect& sourceRect, const core::rect* clipRect, SColor color, bool useAlphaChannelOfTexture) { if (texture) { if (texture->getDriverType() != EDT_SOFTWARE) { os::Printer::log("Fatal Error: Tried to copy from a surface not owned by this driver.", ELL_ERROR); return; } if (useAlphaChannelOfTexture) ((CSoftwareTexture*)texture)->getImage()->copyToWithAlpha( ((CImage*)RenderTargetSurface), destPos, sourceRect, color, clipRect); else ((CSoftwareTexture*)texture)->getImage()->copyTo( ((CImage*)RenderTargetSurface), destPos, sourceRect, clipRect); } } //! Draws a 2d line. void CSoftwareDriver::draw2DLine(const core::position2d& start, const core::position2d& end, SColor color) { ((CImage*)RenderTargetSurface)->drawLine(start, end, color ); } //! draw a 2d rectangle void CSoftwareDriver::draw2DRectangle(SColor color, const core::rect& pos, const core::rect* clip) { if (clip) { core::rect p(pos); p.clipAgainst(*clip); if(!p.isValid()) return; ((CImage*)RenderTargetSurface)->drawRectangle(p, color); } else { if(!pos.isValid()) return; ((CImage*)RenderTargetSurface)->drawRectangle(pos, color); } } //!Draws an 2d rectangle with a gradient. void CSoftwareDriver::draw2DRectangle(const core::rect& pos, SColor colorLeftUp, SColor colorRightUp, SColor colorLeftDown, SColor colorRightDown, const core::rect* clip) { // TODO: implement draw2DRectangle(colorLeftUp, pos, clip); } //! \return Returns the name of the video driver. Example: In case of the Direct3D8 //! driver, it would return "Direct3D8.1". const wchar_t* CSoftwareDriver::getName() const { return L"Irrlicht Software Device 1.0"; } //! Returns type of video driver E_DRIVER_TYPE CSoftwareDriver::getDriverType() const { return EDT_SOFTWARE; } //! Returns the transformation set by setTransform const core::matrix4& CSoftwareDriver::getTransform(E_TRANSFORMATION_STATE state) const { return TransformationMatrix[state]; } //! Creates a render target texture. ITexture* CSoftwareDriver::createRenderTargetTexture(const core::dimension2d& size, const c8* name) { CImage* img = new CImage(video::ECF_A1R5G5B5, size); ITexture* tex = new CSoftwareTexture(img, name, true); img->drop(); return tex; } //! Clears the ZBuffer. void CSoftwareDriver::clearZBuffer() { if (ZBuffer) ZBuffer->clear(); } //! Returns an image created from the last rendered frame. IImage* CSoftwareDriver::createScreenShot() { return new CImage(BackBuffer->getColorFormat(), BackBuffer); } //! Returns the maximum amount of primitives (mostly vertices) which //! the device is able to render with one drawIndexedTriangleList //! call. u32 CSoftwareDriver::getMaximalPrimitiveCount() const { return 0x00800000; } } // end namespace video } // end namespace irr #endif // _IRR_COMPILE_WITH_SOFTWARE_ namespace irr { namespace video { //! creates a video driver IVideoDriver* createSoftwareDriver(const core::dimension2d& windowSize, bool fullscreen, io::IFileSystem* io, video::IImagePresenter* presenter) { #ifdef _IRR_COMPILE_WITH_SOFTWARE_ return new CSoftwareDriver(windowSize, fullscreen, io, presenter); #else return 0; #endif } } // end namespace video } // end namespace irr