// Copyright (C) 2002-2012 Nikolaus Gebhardt // This file is part of the "Irrlicht Engine". // For conditions of distribution and use, see copyright notice in irrlicht.h #include "IrrCompileConfig.h" #include "CSoftwareDriver.h" #ifdef _IRR_COMPILE_WITH_SOFTWARE_ #include "CSoftwareTexture.h" #include "CBlit.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), BackBuffer(0), Presenter(presenter), WindowId(0), SceneSourceRect(0), RenderTargetTexture(0), RenderTargetSurface(0), CurrentTriangleRenderer(0), ZBuffer(0), Texture(0) { #ifdef _DEBUG setDebugName("CSoftwareDriver"); #endif // create backbuffer BackBuffer = new CImage(ECF_A1R5G5B5, windowSize); if (BackBuffer) { BackBuffer->fill(SColor(0)); // create z buffer ZBuffer = video::createZBuffer(BackBuffer->getDimension()); } DriverAttributes->setAttribute("MaxTextures", 1); DriverAttributes->setAttribute("MaxIndices", 1<<16); DriverAttributes->setAttribute("MaxTextureSize", 1024); DriverAttributes->setAttribute("Version", 1); // 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 setRenderTargetImage(BackBuffer); // select the right renderer selectRightTriangleRenderer(); } //! destructor CSoftwareDriver::~CSoftwareDriver() { // delete Backbuffer if (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==ECFN_DISABLED) && Material.ZWriteEnable == video::EZW_OFF) 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); } //! 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: case EVDF_TEXTURE_NSQUARE: return FeatureEnabled[feature]; default: return false; }; } //! Create render target. IRenderTarget* CSoftwareDriver::addRenderTarget() { CSoftwareRenderTarget* renderTarget = new CSoftwareRenderTarget(this); RenderTargets.push_back(renderTarget); return renderTarget; } //! sets transformation void CSoftwareDriver::setTransform(E_TRANSFORMATION_STATE state, const core::matrix4& mat) { TransformationMatrix[state] = mat; } //! sets the current Texture bool CSoftwareDriver::setActiveTexture(u32 stage, 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; OverrideMaterial.apply(Material); for (u32 i = 0; i < 1; ++i) { setActiveTexture(i, Material.getTexture(i)); setTransform ((E_TRANSFORMATION_STATE) ( ETS_TEXTURE_0 + i ), material.getTextureMatrix(i)); } } bool CSoftwareDriver::beginScene(u16 clearFlag, SColor clearColor, f32 clearDepth, u8 clearStencil, const SExposedVideoData& videoData, core::rect* sourceRect) { CNullDriver::beginScene(clearFlag, clearColor, clearDepth, clearStencil, videoData, sourceRect); WindowId=videoData.D3D9.HWnd; SceneSourceRect = sourceRect; clearBuffers(clearFlag, clearColor, clearDepth, clearStencil); return true; } bool CSoftwareDriver::endScene() { CNullDriver::endScene(); return Presenter->present(BackBuffer, WindowId, SceneSourceRect); } ITexture* CSoftwareDriver::createDeviceDependentTexture(const io::path& name, IImage* image) { CSoftwareTexture* texture = new CSoftwareTexture(image, name, false); return texture; } ITexture* CSoftwareDriver::createDeviceDependentTextureCubemap(const io::path& name, const core::array& image) { return 0; } bool CSoftwareDriver::setRenderTargetEx(IRenderTarget* target, u16 clearFlag, SColor clearColor, f32 clearDepth, u8 clearStencil) { if (target && target->getDriverType() != EDT_SOFTWARE) { os::Printer::log("Fatal Error: Tried to set a render target not owned by this driver.", ELL_ERROR); return false; } if (RenderTargetTexture) RenderTargetTexture->drop(); CSoftwareRenderTarget* renderTarget = static_cast(target); RenderTargetTexture = (renderTarget) ? renderTarget->getTexture() : 0; if (RenderTargetTexture) { RenderTargetTexture->grab(); setRenderTargetImage(((CSoftwareTexture*)RenderTargetTexture)->getTexture()); } else { setRenderTargetImage(BackBuffer); } clearBuffers(clearFlag, clearColor, clearDepth, clearStencil); return true; } //! sets a render target void CSoftwareDriver::setRenderTargetImage(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 = core::dimension2du(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); } void CSoftwareDriver::drawVertexPrimitiveList(const void* vertices, u32 vertexCount, const void* indexList, u32 primitiveCount, E_VERTEX_TYPE vType, scene::E_PRIMITIVE_TYPE pType, E_INDEX_TYPE iType) { switch (iType) { case (EIT_16BIT): { drawVertexPrimitiveList16(vertices, vertexCount, (const u16*)indexList, primitiveCount, vType, pType); break; } case (EIT_32BIT): { os::Printer::log("Software driver can not render 32bit buffers", ELL_ERROR); break; } } } //! draws a vertex primitive list void CSoftwareDriver::drawVertexPrimitiveList16(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: drawVertexPrimitiveList16(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], true); // 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) { } //! 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() == (s32)ScreenSize.Width && ViewPort.getHeight() == (s32)ScreenSize.Height) { ViewPort = core::rect(core::position2d(0,0), core::dimension2di(realSize)); } ScreenSize = realSize; bool resetRT = (RenderTargetSurface == BackBuffer); if (BackBuffer) BackBuffer->drop(); BackBuffer = new CImage(ECF_A1R5G5B5, realSize); if (resetRT) setRenderTargetImage(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( RenderTargetSurface, destPos, sourceRect, color, clipRect); else ((CSoftwareTexture*)texture)->getImage()->copyTo( RenderTargetSurface, destPos, sourceRect, clipRect); } } //! Draws a 2d line. void CSoftwareDriver::draw2DLine(const core::position2d& start, const core::position2d& end, SColor color) { drawLine(RenderTargetSurface, start, end, color ); } //! Draws a pixel void CSoftwareDriver::drawPixel(u32 x, u32 y, const SColor & color) { BackBuffer->setPixel(x, y, color, true); } //! 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; drawRectangle(RenderTargetSurface, p, color); } else { if(!pos.isValid()) return; drawRectangle(RenderTargetSurface, 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 Driver 1.0"; } //! Returns type of video driver E_DRIVER_TYPE CSoftwareDriver::getDriverType() const { return EDT_SOFTWARE; } //! returns color format ECOLOR_FORMAT CSoftwareDriver::getColorFormat() const { if (BackBuffer) return BackBuffer->getColorFormat(); else return CNullDriver::getColorFormat(); } //! 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::addRenderTargetTexture(const core::dimension2d& size, const io::path& name, const ECOLOR_FORMAT format) { IImage* img = createImage(video::ECF_A1R5G5B5, size); ITexture* tex = new CSoftwareTexture(img, name, true); img->drop(); addTexture(tex); tex->drop(); return tex; } void CSoftwareDriver::clearBuffers(u16 flag, SColor color, f32 depth, u8 stencil) { if ((flag & ECBF_COLOR) && RenderTargetSurface) RenderTargetSurface->fill(color); if ((flag & ECBF_DEPTH) && ZBuffer) ZBuffer->clear(); } //! Returns an image created from the last rendered frame. IImage* CSoftwareDriver::createScreenShot(video::ECOLOR_FORMAT format, video::E_RENDER_TARGET target) { if (target != video::ERT_FRAME_BUFFER) return 0; if (BackBuffer) { IImage* tmp = createImage(BackBuffer->getColorFormat(), BackBuffer->getDimension()); BackBuffer->copyTo(tmp); return tmp; } else return 0; } //! 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; } bool CSoftwareDriver::queryTextureFormat(ECOLOR_FORMAT format) const { return format == ECF_A1R5G5B5; } } // 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