From 7ae68647e9c9bb54c21c05da19e3f6bbe6f9bf7a Mon Sep 17 00:00:00 2001 From: hybrid Date: Thu, 9 Jun 2011 07:08:47 +0000 Subject: [PATCH] Next large merge from trunk, revisions 3729-3830 git-svn-id: svn://svn.code.sf.net/p/irrlicht/code/branches/ogl-es@3831 dfc29bdd-3216-0410-991c-e03cc46cb475 --- changes.txt | 13 + examples/10.Shaders/main.cpp | 1 + examples/13.RenderToTexture/main.cpp | 20 +- examples/22.MaterialViewer/main.cpp | 28 +- include/EDriverFeatures.h | 3 + include/IAttributeExchangingObject.h | 2 +- include/ICameraSceneNode.h | 26 + include/IGUITabControl.h | 20 +- include/IMeshManipulator.h | 74 +- include/IOSOperator.h | 13 +- include/ISceneManager.h | 26 +- include/IVideoDriver.h | 2 +- include/IrrCompileConfig.h | 16 +- include/SColor.h | 2 +- include/SIrrCreationParameters.h | 17 + include/irrArray.h | 4 +- include/irrTypes.h | 2 + include/vector2d.h | 17 +- source/Irrlicht/CAnimatedMeshHalfLife.cpp | 8 +- source/Irrlicht/CAnimatedMeshHalfLife.h | 4 +- source/Irrlicht/CBlit.h | 51 ++ source/Irrlicht/CCameraSceneNode.cpp | 24 +- source/Irrlicht/CColladaFileLoader.cpp | 2 +- source/Irrlicht/CColladaMeshWriter.cpp | 264 +++--- source/Irrlicht/CColladaMeshWriter.h | 5 + source/Irrlicht/CD3D8Driver.cpp | 145 ++-- source/Irrlicht/CD3D8Driver.h | 2 +- source/Irrlicht/CD3D9Driver.cpp | 303 ++++--- source/Irrlicht/CD3D9Driver.h | 20 +- source/Irrlicht/CD3D9Texture.cpp | 4 +- source/Irrlicht/CDMFLoader.cpp | 5 +- source/Irrlicht/CFileSystem.cpp | 53 +- source/Irrlicht/CGUIColorSelectDialog.cpp | 9 +- source/Irrlicht/CGUIEnvironment.cpp | 12 +- source/Irrlicht/CGUIFont.cpp | 5 +- source/Irrlicht/CGUITabControl.cpp | 164 ++-- source/Irrlicht/CGUITabControl.h | 17 +- source/Irrlicht/CGeometryCreator.cpp | 3 +- source/Irrlicht/CImage.cpp | 50 -- source/Irrlicht/CImage.h | 6 - source/Irrlicht/CImageLoaderDDS.cpp | 118 +-- source/Irrlicht/CImageLoaderDDS.h | 15 +- source/Irrlicht/CIrrDeviceFB.cpp | 2 +- source/Irrlicht/CIrrDeviceLinux.cpp | 19 +- source/Irrlicht/CIrrDeviceSDL.cpp | 2 +- source/Irrlicht/CIrrDeviceWin32.cpp | 4 +- source/Irrlicht/CIrrDeviceWinCE.cpp | 2 +- source/Irrlicht/CMY3DMeshFileLoader.cpp | 2 - source/Irrlicht/CMY3DMeshFileLoader.h | 1 - source/Irrlicht/CMeshManipulator.cpp | 806 ++++++++++-------- source/Irrlicht/CMeshManipulator.h | 16 +- source/Irrlicht/CNullDriver.cpp | 2 +- source/Irrlicht/CNullDriver.h | 2 +- source/Irrlicht/COCTLoader.cpp | 8 +- source/Irrlicht/COSOperator.cpp | 10 +- source/Irrlicht/COSOperator.h | 10 +- source/Irrlicht/COpenGLDriver.cpp | 313 +++++-- source/Irrlicht/COpenGLDriver.h | 12 +- source/Irrlicht/COpenGLExtensionHandler.cpp | 2 + source/Irrlicht/COpenGLExtensionHandler.h | 13 +- source/Irrlicht/COpenGLSLMaterialRenderer.cpp | 8 +- source/Irrlicht/COpenGLTexture.cpp | 120 ++- source/Irrlicht/CSoftwareDriver.cpp | 18 +- source/Irrlicht/CSoftwareDriver.h | 2 +- source/Irrlicht/CSoftwareDriver2.cpp | 19 +- source/Irrlicht/CSoftwareDriver2.h | 2 +- source/Irrlicht/CTerrainSceneNode.cpp | 48 +- source/Irrlicht/CTerrainSceneNode.h | 5 +- source/Irrlicht/Irrlicht.aps | Bin 34548 -> 61604 bytes source/Irrlicht/Irrlicht.rc | Bin 4594 -> 4598 bytes source/Irrlicht/Irrlicht9.0.vcproj | 12 +- source/Irrlicht/MacOSX/CIrrDeviceMacOSX.mm | 4 +- source/Irrlicht/MacOSX/OSXClipboard.mm | 22 +- tests/2dmaterial.cpp | 49 +- tests/anti-aliasing.cpp | 6 +- tests/createImage.cpp | 6 +- tests/draw2DImage.cpp | 67 +- tests/drawPixel.cpp | 32 +- tests/drawRectOutline.cpp | 9 +- tests/guiDisabledMenu.cpp | 2 +- tests/ioScene.cpp | 5 +- tests/irrArray.cpp | 55 ++ tests/lightMaps.cpp | 20 +- tests/lights.cpp | 48 +- tests/main.cpp | 9 +- tests/material.cpp | 4 +- .../media/Burning's Video-guiDisabledMenu.png | Bin 0 -> 740 bytes tests/media/Burning's Video-renderMipmap.png | Bin 0 -> 812 bytes tests/media/Burning's Video-terrainGap.png | Bin 0 -> 2971 bytes .../Burning's Video-terrainSceneNode-1.png | Bin 0 -> 31249 bytes .../Burning's Video-terrainSceneNode-2.png | Bin 0 -> 33309 bytes ...ing's Video-textureMatrixInMixedScenes.png | Bin 1204 -> 0 bytes tests/media/Direct3D 9.0-renderMipmap.png | Bin 0 -> 917 bytes .../Irrlicht Software Driver 1.0-projMat.png | Bin 408 -> 333 bytes ... Driver 1.0-textureMatrixInMixedScenes.png | Bin 1167 -> 0 bytes tests/media/OpenGL-guiDisabledMenu.png | Bin 742 -> 0 bytes tests/media/OpenGL-renderMipmap.png | Bin 0 -> 1098 bytes tests/media/OpenGL-terrainSceneNode-1.png | Bin 37019 -> 0 bytes tests/media/OpenGL-terrainSceneNode-2.png | Bin 34107 -> 0 bytes tests/media/fireball.png | Bin 0 -> 974 bytes tests/media/ter1.png | Bin 0 -> 41095 bytes tests/meshLoaders.cpp | 12 +- tests/mrt.cpp | 9 +- tests/projectionMatrix.cpp | 12 +- tests/renderTargetTexture.cpp | 112 ++- tests/sceneCollisionManager.cpp | 2 +- tests/screenshot.cpp | 63 ++ tests/terrainSceneNode.cpp | 72 +- tests/testQuaternion.cpp | 4 +- tests/testUtils.h | 11 +- tests/testVector2d.cpp | 8 + tests/testXML.cpp | 332 ++++---- tests/tests.cbp | 1 + tests/tests_vc10.vcxproj | 1 + tests/tests_vc8.vcproj | 4 + tests/tests_vc9.vcproj | 4 + tests/textureFeatures.cpp | 239 +++++- tests/textureRenderStates.cpp | 63 +- tests/transparentMaterials.cpp | 90 +- tests/videoDriver.cpp | 10 +- tests/viewPort.cpp | 17 +- tests/writeImageToFile.cpp | 16 +- 122 files changed, 2698 insertions(+), 1757 deletions(-) create mode 100644 tests/media/Burning's Video-guiDisabledMenu.png create mode 100644 tests/media/Burning's Video-renderMipmap.png create mode 100644 tests/media/Burning's Video-terrainGap.png create mode 100644 tests/media/Burning's Video-terrainSceneNode-1.png create mode 100644 tests/media/Burning's Video-terrainSceneNode-2.png delete mode 100644 tests/media/Burning's Video-textureMatrixInMixedScenes.png create mode 100644 tests/media/Direct3D 9.0-renderMipmap.png delete mode 100644 tests/media/Irrlicht Software Driver 1.0-textureMatrixInMixedScenes.png delete mode 100644 tests/media/OpenGL-guiDisabledMenu.png create mode 100644 tests/media/OpenGL-renderMipmap.png delete mode 100644 tests/media/OpenGL-terrainSceneNode-1.png delete mode 100644 tests/media/OpenGL-terrainSceneNode-2.png create mode 100644 tests/media/fireball.png create mode 100644 tests/media/ter1.png create mode 100644 tests/screenshot.cpp diff --git a/changes.txt b/changes.txt index 479d4f38..17ce35e0 100644 --- a/changes.txt +++ b/changes.txt @@ -1,5 +1,9 @@ Changes in 1.8 (??.??.2011) + - Renamed IOSOperator::getOperationSystemVersion to getOperatingSystemVersion. Changed return type from wchar_t to core::stringc, as that's the internal representation the name is built on. + + - Added IGUITabControl::insertTab, IGUITabControl::removeTab, IGUITabControl::clear and IGUITabControl::getTabAt + - Added IGUIListBox::getItemAt - Added IGUITable::getColumnWidth @@ -262,6 +266,15 @@ The following names can be queried for the given types: ----------------------------- Changes in 1.7.3 (??.??.2011) + - Fix crash in collada (.dae) loading + + - Fix memory-leaks in example 22 MaterialViewer + + - Fix array::erase which did destroy objects more than once when used with a range (thx @ RedDragCZ for reporting + testcase). + + - Copy now all membervariables for CCameraSceneNode when cloning. + + - ICameraSceneNode::IsOrthogonal is correctly serialized and cloned now. - CGUIScrollBar passes unused mousemove-events now to parent element. Fixes focus-bug in ComboBox reported by REDDemon here: http://irrlicht.sourceforge.net/phpBB2/viewtopic.php?t=43474&highlight= diff --git a/examples/10.Shaders/main.cpp b/examples/10.Shaders/main.cpp index b8e8c927..e1ec1179 100644 --- a/examples/10.Shaders/main.cpp +++ b/examples/10.Shaders/main.cpp @@ -322,6 +322,7 @@ int main() node->setPosition(core::vector3df(0,-10,50)); node->setMaterialTexture(0, driver->getTexture("../../media/wall.bmp")); node->setMaterialFlag(video::EMF_LIGHTING, false); + node->setMaterialFlag(video::EMF_BLEND_OPERATION, true); node->setMaterialType((video::E_MATERIAL_TYPE)newMaterialType2); smgr->addTextSceneNode(gui->getBuiltInFont(), diff --git a/examples/13.RenderToTexture/main.cpp b/examples/13.RenderToTexture/main.cpp index 196a4807..98ba8aae 100644 --- a/examples/13.RenderToTexture/main.cpp +++ b/examples/13.RenderToTexture/main.cpp @@ -75,18 +75,11 @@ int main() smgr->setAmbientLight(video::SColor(0,60,60,60)); /* - The next is just some standard stuff: Add a user controlled camera to - the scene, disable mouse cursor, and add a test cube and let it rotate - to make the scene more interesting. + The next is just some standard stuff: Add a test cube and let it rotate + to make the scene more interesting. The user defined camera and cursor + setup is made later on, right before the render loop. */ - // add fps camera - scene::ICameraSceneNode* fpsCamera = smgr->addCameraSceneNodeFPS(); - fpsCamera->setPosition(core::vector3df(-50,50,-150)); - - // disable mouse cursor - device->getCursorControl()->setVisible(false); - // create test cube scene::ISceneNode* test = smgr->addCubeSceneNode(60); @@ -145,6 +138,13 @@ int main() text->setOverrideColor(video::SColor(100,255,255,255)); } + // add fps camera + scene::ICameraSceneNode* fpsCamera = smgr->addCameraSceneNodeFPS(); + fpsCamera->setPosition(core::vector3df(-50,50,-150)); + + // disable mouse cursor + device->getCursorControl()->setVisible(false); + /* Nearly finished. Now we need to draw everything. Every frame, we draw the scene twice. Once from the fixed camera into the render target diff --git a/examples/22.MaterialViewer/main.cpp b/examples/22.MaterialViewer/main.cpp index 785ef727..36d71cd1 100755 --- a/examples/22.MaterialViewer/main.cpp +++ b/examples/22.MaterialViewer/main.cpp @@ -311,7 +311,8 @@ public: { ControlAmbientColor->drop(); ControlDiffuseColor->drop(); - ControlEmissiveColor->drop(); + if ( ControlEmissiveColor ) + ControlEmissiveColor->drop(); ControlSpecularColor->drop(); } @@ -346,7 +347,7 @@ public: } // Update all changed colors in the light data - void updateMaterialColors(video::SLight & lightData) + void updateLightColors(video::SLight & lightData) { if ( ControlAmbientColor->isDirty() ) lightData.AmbientColor = video::SColorf( ControlAmbientColor->getColor() ); @@ -362,7 +363,8 @@ public: ControlAmbientColor->resetDirty(); ControlDiffuseColor->resetDirty(); ControlSpecularColor->resetDirty(); - ControlEmissiveColor->resetDirty(); + if ( ControlEmissiveColor ) + ControlEmissiveColor->resetDirty(); } protected: @@ -500,6 +502,8 @@ struct SMeshNodeControl TextureControl2->drop(); if ( ControlVertexColors ) ControlVertexColors->drop(); + if ( AllColorsControl ) + AllColorsControl->drop(); } void init(scene::IMeshSceneNode* node, IrrlichtDevice * device, const core::position2d & pos, const wchar_t * description) @@ -666,6 +670,12 @@ struct SLightNodeControl { } + virtual ~SLightNodeControl() + { + if ( AllColorsControl ) + AllColorsControl->drop(); + } + void init(scene::ILightSceneNode* node, gui::IGUIEnvironment* guiEnv, const core::position2d & pos, const wchar_t * description) { if ( Initialized || !node || !guiEnv) // initializing twice or with invalid data not allowed @@ -683,7 +693,7 @@ struct SLightNodeControl return; video::SLight & lightData = SceneNode->getLightData(); - AllColorsControl->updateMaterialColors(lightData); + AllColorsControl->updateLightColors(lightData); } protected: @@ -797,7 +807,7 @@ protected: return false; Device->setWindowCaption( DriverTypeNames[Config.DriverType] ); Device->setEventReceiver(this); - + scene::ISceneManager* smgr = Device->getSceneManager(); video::IVideoDriver * driver = Device->getVideoDriver (); gui::IGUIEnvironment* guiEnv = Device->getGUIEnvironment(); @@ -807,7 +817,7 @@ protected: gui::IGUIFont* font = guiEnv->getFont("../../media/fonthaettenschweiler.bmp"); if (font) skin->setFont(font); - + // remove some alpha value because it makes those menus harder to read otherwise video::SColor col3dHighLight( skin->getColor(gui::EGDC_APP_WORKSPACE) ); col3dHighLight.setAlpha(255); @@ -818,7 +828,7 @@ protected: // Add some textures which are useful to test material settings createDefaultTextures(driver); - // create a menu + // create a menu gui::IGUIContextMenu * menuBar = guiEnv->addMenu(); menuBar->addItem(L"File", -1, true, true); @@ -850,7 +860,7 @@ protected: video::SColorf(1.0f, 1.0f, 1.0f), 100.0f); LightControl.init(nodeLight, guiEnv, core::position2d(270,20), L"light" ); - + // one large cube around everything. That's mainly to make the light more obvious. scene::IMeshSceneNode* backgroundCube = smgr->addCubeSceneNode (200.0f, 0, -1, core::vector3df(0, 0, 0), core::vector3df(45, 0, 0), @@ -1001,6 +1011,8 @@ protected: } } driver->addTexture (io::path("GRAYSCALE_A8R8G8B8"), imageA8R8G8B8); + + imageA8R8G8B8->drop(); } // Load a texture and make sure nodes know it when more textures are available. diff --git a/include/EDriverFeatures.h b/include/EDriverFeatures.h index 31216e24..ce442253 100644 --- a/include/EDriverFeatures.h +++ b/include/EDriverFeatures.h @@ -115,6 +115,9 @@ namespace video //! Support for different blend functions. Without, only ADD is available EVDF_BLEND_OPERATIONS, + //! Support for texture coord transformation via texture matrix + EVDF_TEXTURE_MATRIX, + //! Only used for counting the elements of this enum EVDF_COUNT }; diff --git a/include/IAttributeExchangingObject.h b/include/IAttributeExchangingObject.h index ab4e4293..0185de83 100644 --- a/include/IAttributeExchangingObject.h +++ b/include/IAttributeExchangingObject.h @@ -43,7 +43,7 @@ struct SAttributeReadWriteOptions s32 Flags; //! Optional filename - const c8* Filename; + const fschar_t* Filename; }; diff --git a/include/ICameraSceneNode.h b/include/ICameraSceneNode.h index 63616602..8f955345 100644 --- a/include/ICameraSceneNode.h +++ b/include/ICameraSceneNode.h @@ -166,8 +166,34 @@ namespace scene /** @see bindTargetAndRotation() */ virtual bool getTargetAndRotationBinding(void) const =0; + //! Writes attributes of the camera node + virtual void serializeAttributes(io::IAttributes* out, io::SAttributeReadWriteOptions* options=0) const + { + ISceneNode::serializeAttributes(out, options); + + if (!out) + return; + out->addBool ("IsOrthogonal", IsOrthogonal ); + } + + //! Reads attributes of the camera node + virtual void deserializeAttributes(io::IAttributes* in, io::SAttributeReadWriteOptions* options=0) + { + ISceneNode::deserializeAttributes(in, options); + if (!in) + return; + + if ( in->findAttribute("IsOrthogonal") ) + IsOrthogonal = in->getAttributeAsBool("IsOrthogonal"); + } + protected: + void cloneMembers(ICameraSceneNode* toCopyFrom) + { + IsOrthogonal = toCopyFrom->IsOrthogonal; + } + bool IsOrthogonal; }; diff --git a/include/IGUITabControl.h b/include/IGUITabControl.h index 1fc8746f..fccde3d2 100644 --- a/include/IGUITabControl.h +++ b/include/IGUITabControl.h @@ -23,8 +23,10 @@ namespace gui IGUITab(IGUIEnvironment* environment, IGUIElement* parent, s32 id, core::rect rectangle) : IGUIElement(EGUIET_TAB, environment, parent, id, rectangle) {} - //! Returns number of tab if in tabcontrol. - /** Can be accessed later IGUITabControl::getTab() by this number. */ + //! Returns zero based index of tab if in tabcontrol. + /** Can be accessed later IGUITabControl::getTab() by this number. + Note that this number can change when other tabs are inserted or removed . + */ virtual s32 getNumber() const = 0; //! sets if the tab should draw its background @@ -58,6 +60,16 @@ namespace gui //! Adds a tab virtual IGUITab* addTab(const wchar_t* caption, s32 id=-1) = 0; + //! Insert the tab at the given index + /** \return The tab on success or NULL on failure. */ + virtual IGUITab* insertTab(s32 idx, const wchar_t* caption, s32 id=-1) = 0; + + //! Removes a tab from the tabcontrol + virtual void removeTab(s32 idx) = 0; + + //! Clears the tabcontrol removing all tabs + virtual void clear() = 0; + //! Returns amount of tabs in the tabcontrol virtual s32 getTabCount() const = 0; @@ -80,6 +92,10 @@ namespace gui //! Returns which tab is currently active virtual s32 getActiveTab() const = 0; + //! get the the id of the tab at the given absolute coordinates + /** \return The id of the tab or -1 when no tab is at those coordinates*/ + virtual s32 getTabAt(s32 xpos, s32 ypos) const = 0; + //! Set the height of the tabs virtual void setTabHeight( s32 height ) = 0; diff --git a/include/IMeshManipulator.h b/include/IMeshManipulator.h index 88fd6c09..e3573dbf 100644 --- a/include/IMeshManipulator.h +++ b/include/IMeshManipulator.h @@ -44,6 +44,14 @@ namespace scene apply(scene::SVertexColorSetAlphaManipulator(alpha), mesh); } + //! Sets the alpha vertex color value of the whole mesh to a new value. + /** \param buffer Meshbuffer on which the operation is performed. + \param alpha New alpha value. Must be a value between 0 and 255. */ + void setVertexColorAlpha(IMeshBuffer* buffer, s32 alpha) const + { + apply(scene::SVertexColorSetAlphaManipulator(alpha), buffer); + } + //! Sets the colors of all vertices to one color /** \param mesh Mesh on which the operation is performed. \param color New color. */ @@ -52,17 +60,27 @@ namespace scene apply(scene::SVertexColorSetManipulator(color), mesh); } + //! Sets the colors of all vertices to one color + /** \param buffer Meshbuffer on which the operation is performed. + \param color New color. */ + void setVertexColors(IMeshBuffer* buffer, video::SColor color) const + { + apply(scene::SVertexColorSetManipulator(color), buffer); + } + //! Recalculates all normals of the mesh. /** \param mesh: Mesh on which the operation is performed. \param smooth: If the normals shall be smoothed. \param angleWeighted: If the normals shall be smoothed in relation to their angles. More expensive, but also higher precision. */ - virtual void recalculateNormals(IMesh* mesh, bool smooth = false, bool angleWeighted = false) const = 0; + virtual void recalculateNormals(IMesh* mesh, bool smooth = false, + bool angleWeighted = false) const=0; //! Recalculates all normals of the mesh buffer. /** \param buffer: Mesh buffer on which the operation is performed. \param smooth: If the normals shall be smoothed. \param angleWeighted: If the normals shall be smoothed in relation to their angles. More expensive, but also higher precision. */ - virtual void recalculateNormals(IMeshBuffer* buffer, bool smooth = false, bool angleWeighted = false) const = 0; + virtual void recalculateNormals(IMeshBuffer* buffer, + bool smooth = false, bool angleWeighted = false) const=0; //! Recalculates tangents, requires a tangent mesh /** \param mesh Mesh on which the operation is performed. @@ -74,6 +92,16 @@ namespace scene bool recalculateNormals=false, bool smooth=false, bool angleWeighted=false) const=0; + //! Recalculates tangents, requires a tangent mesh buffer + /** \param buffer Meshbuffer on which the operation is performed. + \param recalculateNormals If the normals shall be recalculated, otherwise original normals of the buffer are used unchanged. + \param smooth If the normals shall be smoothed. + \param angleWeighted If the normals shall be smoothed in relation to their angles. More expensive, but also higher precision. + */ + virtual void recalculateTangents(IMeshBuffer* buffer, + bool recalculateNormals=false, bool smooth=false, + bool angleWeighted=false) const=0; + //! Scales the actual mesh, not a scene node. /** \param mesh Mesh on which the operation is performed. \param factor Scale factor for each axis. */ @@ -136,28 +164,31 @@ namespace scene \param m transformation matrix. */ _IRR_DEPRECATED_ virtual void transformMesh(IMesh* mesh, const core::matrix4& m) const {return transform(mesh,m);} - //! Clones a static IMesh into a modifiable SMesh. - /** All meshbuffers in the returned SMesh - are of type SMeshBuffer or SMeshBufferLightMap. - \param mesh Mesh to copy. - \return Cloned mesh. If you no longer need the - cloned mesh, you should call SMesh::drop(). See - IReferenceCounted::drop() for more information. */ - virtual SMesh* createMeshCopy(IMesh* mesh) const = 0; - //! Creates a planar texture mapping on the mesh /** \param mesh: Mesh on which the operation is performed. \param resolution: resolution of the planar mapping. This is the value specifying which is the relation between world space and texture coordinate space. */ - virtual void makePlanarTextureMapping(IMesh* mesh, f32 resolution=0.001f) const =0; + virtual void makePlanarTextureMapping(IMesh* mesh, f32 resolution=0.001f) const=0; //! Creates a planar texture mapping on the meshbuffer /** \param meshbuffer: Buffer on which the operation is performed. \param resolution: resolution of the planar mapping. This is the value specifying which is the relation between world space and texture coordinate space. */ - virtual void makePlanarTextureMapping(scene::IMeshBuffer* meshbuffer, f32 resolution=0.001f) const =0; + virtual void makePlanarTextureMapping(scene::IMeshBuffer* meshbuffer, f32 resolution=0.001f) const=0; + + //! Creates a planar texture mapping on the buffer + /** This method is currently implemented towards the LWO planar mapping. A more general biasing might be required. + \param mesh Mesh on which the operation is performed. + \param resolutionS Resolution of the planar mapping in horizontal direction. This is the ratio between object space and texture space. + \param resolutionT Resolution of the planar mapping in vertical direction. This is the ratio between object space and texture space. + \param axis The axis along which the texture is projected. The allowed values are 0 (X), 1(Y), and 2(Z). + \param offset Vector added to the vertex positions (in object coordinates). + */ + virtual void makePlanarTextureMapping(scene::IMesh* mesh, + f32 resolutionS, f32 resolutionT, + u8 axis, const core::vector3df& offset) const=0; //! Creates a planar texture mapping on the meshbuffer /** This method is currently implemented towards the LWO planar mapping. A more general biasing might be required. @@ -167,7 +198,18 @@ namespace scene \param axis The axis along which the texture is projected. The allowed values are 0 (X), 1(Y), and 2(Z). \param offset Vector added to the vertex positions (in object coordinates). */ - virtual void makePlanarTextureMapping(scene::IMeshBuffer* buffer, f32 resolutionS, f32 resolutionT, u8 axis, const core::vector3df& offset) const =0; + virtual void makePlanarTextureMapping(scene::IMeshBuffer* buffer, + f32 resolutionS, f32 resolutionT, + u8 axis, const core::vector3df& offset) const=0; + + //! Clones a static IMesh into a modifiable SMesh. + /** All meshbuffers in the returned SMesh + are of type SMeshBuffer or SMeshBufferLightMap. + \param mesh Mesh to copy. + \return Cloned mesh. If you no longer need the + cloned mesh, you should call SMesh::drop(). See + IReferenceCounted::drop() for more information. */ + virtual SMesh* createMeshCopy(IMesh* mesh) const = 0; //! Creates a copy of the mesh, which will only consist of S3DVertexTangents vertices. /** This is useful if you want to draw tangent space normal @@ -186,7 +228,9 @@ namespace scene you no longer need the cloned mesh, you should call IMesh::drop(). See IReferenceCounted::drop() for more information. */ - virtual IMesh* createMeshWithTangents(IMesh* mesh, bool recalculateNormals=false, bool smooth=false, bool angleWeighted=false, bool recalculateTangents=true) const = 0; + virtual IMesh* createMeshWithTangents(IMesh* mesh, + bool recalculateNormals=false, bool smooth=false, + bool angleWeighted=false, bool recalculateTangents=true) const=0; //! Creates a copy of the mesh, which will only consist of S3DVertex2TCoord vertices. /** \param mesh Input mesh diff --git a/include/IOSOperator.h b/include/IOSOperator.h index 2b8ea36e..1e374364 100644 --- a/include/IOSOperator.h +++ b/include/IOSOperator.h @@ -6,6 +6,7 @@ #define __I_OS_OPERATOR_H_INCLUDED__ #include "IReferenceCounted.h" +#include "irrString.h" namespace irr { @@ -14,12 +15,15 @@ namespace irr class IOSOperator : public virtual IReferenceCounted { public: - - //! Destructor - virtual ~IOSOperator() {} + //! Get the current operation system version as string. + virtual const core::stringc& getOperatingSystemVersion() const = 0; //! Get the current operation system version as string. - virtual const wchar_t* getOperationSystemVersion() const = 0; + /** \deprecated Use getOperatingSystemVersion instead. This method will be removed in Irrlicht 1.9. */ + _IRR_DEPRECATED_ const wchar_t* getOperationSystemVersion() const + { + return core::stringw(getOperatingSystemVersion()).c_str(); + } //! Copies text to the clipboard virtual void copyToClipboard(const c8* text) const = 0; @@ -44,4 +48,3 @@ public: } // end namespace #endif - diff --git a/include/ISceneManager.h b/include/ISceneManager.h index 6edf0947..f4b700c3 100644 --- a/include/ISceneManager.h +++ b/include/ISceneManager.h @@ -722,19 +722,23 @@ namespace scene //! Adds a billboard scene node to the scene graph. /** A billboard is like a 3d sprite: A 2d element, - which always looks to the camera. It is usually used for things like explosions, fire, - lensflares and things like that. - \param parent: Parent scene node of the billboard. Can be null. If the parent moves, - the billboard will move too. - \param position: Position of the space relative to its parent + which always looks to the camera. It is usually used for things + like explosions, fire, lensflares and things like that. + \param parent Parent scene node of the billboard. Can be null. + If the parent moves, the billboard will move too. + \param size Size of the billboard. This size is 2 dimensional + because a billboard only has width and height. + \param position Position of the space relative to its parent where the billboard will be placed. - \param size: Size of the billboard. This size is 2 dimensional because a billboard only has - width and height. - \param id: An id of the node. This id can be used to identify the node. - \param colorTop: The color of the vertices at the top of the billboard (default: white). - \param colorBottom: The color of the vertices at the bottom of the billboard (default: white). + \param id An id of the node. This id can be used to identify + the node. + \param colorTop The color of the vertices at the top of the + billboard (default: white). + \param colorBottom The color of the vertices at the bottom of + the billboard (default: white). \return Pointer to the billboard if successful, otherwise NULL. - This pointer should not be dropped. See IReferenceCounted::drop() for more information. */ + This pointer should not be dropped. See + IReferenceCounted::drop() for more information. */ virtual IBillboardSceneNode* addBillboardSceneNode(ISceneNode* parent = 0, const core::dimension2d& size = core::dimension2d(10.0f, 10.0f), const core::vector3df& position = core::vector3df(0,0,0), s32 id=-1, diff --git a/include/IVideoDriver.h b/include/IVideoDriver.h index 07dc317a..766ab9ec 100644 --- a/include/IVideoDriver.h +++ b/include/IVideoDriver.h @@ -1357,7 +1357,7 @@ namespace video //! Make a screenshot of the last rendered frame. /** \return An image created from the last rendered frame. */ - virtual IImage* createScreenShot() =0; + virtual IImage* createScreenShot(video::ECOLOR_FORMAT format=video::ECF_UNKNOWN, video::E_RENDER_TARGET target=video::ERT_FRAME_BUFFER) =0; //! Check if the image is already loaded. /** Works similar to getTexture(), but does not load the texture diff --git a/include/IrrCompileConfig.h b/include/IrrCompileConfig.h index 8edb679b..b8845189 100644 --- a/include/IrrCompileConfig.h +++ b/include/IrrCompileConfig.h @@ -122,14 +122,6 @@ #undef _IRR_COMPILE_WITH_JOYSTICK_EVENTS_ #endif -//! Define _IRR_COMPILE_WITH_DIRECTINPUT_JOYSTICK_ if you want to use DirectInput for joystick handling. -/** This only applies to Windows devices, currently only supported under Win32 device. -If not defined, Windows Multimedia library is used, which offers also broad support for joystick devices. */ -#define _IRR_COMPILE_WITH_DIRECTINPUT_JOYSTICK_ -#ifdef NO_IRR_COMPILE_WITH_DIRECTINPUT_JOYSTICK_ -#undef _IRR_COMPILE_WITH_DIRECTINPUT_JOYSTICK_ -#endif - //! Maximum number of texture an SMaterial can have, up to 8 are supported by Irrlicht. #define _IRR_MATERIAL_MAX_TEXTURES_ 4 @@ -151,6 +143,14 @@ headers, e.g. Summer 2004. This is a Microsoft issue, not an Irrlicht one. */ #if defined(_IRR_WINDOWS_API_) && (!defined(__GNUC__) || defined(IRR_COMPILE_WITH_DX9_DEV_PACK)) +//! Define _IRR_COMPILE_WITH_DIRECTINPUT_JOYSTICK_ if you want to use DirectInput for joystick handling. +/** This only applies to Windows devices, currently only supported under Win32 device. +If not defined, Windows Multimedia library is used, which offers also broad support for joystick devices. */ +#define _IRR_COMPILE_WITH_DIRECTINPUT_JOYSTICK_ +#ifdef NO_IRR_COMPILE_WITH_DIRECTINPUT_JOYSTICK_ +#undef _IRR_COMPILE_WITH_DIRECTINPUT_JOYSTICK_ +#endif + //! Only define _IRR_COMPILE_WITH_DIRECT3D_8_ if you have an appropriate DXSDK, e.g. Summer 2004 // #define _IRR_COMPILE_WITH_DIRECT3D_8_ #define _IRR_COMPILE_WITH_DIRECT3D_9_ diff --git a/include/SColor.h b/include/SColor.h index 0291cc34..ad4c1405 100644 --- a/include/SColor.h +++ b/include/SColor.h @@ -540,7 +540,7 @@ namespace video return SColorf (r * mul0 + c1.r * mul1 + c2.r * mul2, g * mul0 + c1.g * mul1 + c2.g * mul2, - g * mul0 + c1.b * mul1 + c2.b * mul2, + b * mul0 + c1.b * mul1 + c2.b * mul2, a * mul0 + c1.a * mul1 + c2.a * mul2); } diff --git a/include/SIrrCreationParameters.h b/include/SIrrCreationParameters.h index a8839488..5afe6def 100644 --- a/include/SIrrCreationParameters.h +++ b/include/SIrrCreationParameters.h @@ -29,6 +29,7 @@ namespace irr Stencilbuffer(false), Vsync(false), AntiAlias(0), + HandleSRGB(false), WithAlphaChannel(false), Doublebuffer(true), IgnoreInput(false), @@ -62,6 +63,7 @@ namespace irr Stencilbuffer = other.Stencilbuffer; Vsync = other.Vsync; AntiAlias = other.AntiAlias; + HandleSRGB = other.HandleSRGB; WithAlphaChannel = other.WithAlphaChannel; Doublebuffer = other.Doublebuffer; IgnoreInput = other.IgnoreInput; @@ -140,6 +142,21 @@ namespace irr Default value: 0 - disabled */ u8 AntiAlias; + //! Flag to enable proper sRGB and linear color handling + /** In most situations, it is desireable to have the color handling in + non-linear sRGB color space, and only do the intermediate color + calculations in linear RGB space. If this flag is enabled, the device and + driver try to assure that all color input and output are color corrected + and only the internal color representation is linear. This means, that + the color output is properly gamma-adjusted to provide the brighter + colors for monitor display. And that blending and lighting give a more + natural look, due to proper conversion from non-linear colors into linear + color space for blend operations. If this flag is enabled, all texture colors + (which are usually in sRGB space) are correctly displayed. However vertex colors + and other explicitly set values have to be manually encoded in linear color space. + Default value: false. */ + bool HandleSRGB; + //! Whether the main framebuffer uses an alpha channel. /** In some situations it might be desireable to get a color buffer with an alpha channel, e.g. when rendering into a diff --git a/include/irrArray.h b/include/irrArray.h index ec6cc9ad..7c302820 100644 --- a/include/irrArray.h +++ b/include/irrArray.h @@ -558,12 +558,12 @@ public: for (i=index+count; i index+count) + if (i-count >= index+count) // not already destructed before loop allocator.destruct(&data[i-count]); allocator.construct(&data[i-count], data[i]); // data[i-count] = data[i]; - if (i >= used-count) + if (i >= used-count) // those which are not overwritten allocator.destruct(&data[i]); } diff --git a/include/irrTypes.h b/include/irrTypes.h index 46c85471..e709ec05 100644 --- a/include/irrTypes.h +++ b/include/irrTypes.h @@ -130,8 +130,10 @@ strings */ #if defined(_IRR_WCHAR_FILESYSTEM) typedef wchar_t fschar_t; + #define _IRR_TEXT(X) L##X #else typedef char fschar_t; + #define _IRR_TEXT(X) X #endif } // end namespace irr diff --git a/include/vector2d.h b/include/vector2d.h index 9367e867..f0965833 100644 --- a/include/vector2d.h +++ b/include/vector2d.h @@ -213,24 +213,23 @@ public: return Y < 0 ? 90 : 270; // don't use getLength here to avoid precision loss with s32 vectors - f64 tmp = Y / sqrt((f64)(X*X + Y*Y)); - if ( tmp > 1.0 ) // avoid floating-point trouble as sqrt(y*y) is occasionally larger y - tmp = 1.0; - tmp = atan( core::squareroot(1 - tmp*tmp) / tmp) * RADTODEG64; + // avoid floating-point trouble as sqrt(y*y) is occasionally larger than y, so clamp + const f64 tmp = core::clamp(Y / sqrt((f64)(X*X + Y*Y)), -1.0, 1.0); + const f64 angle = atan( core::squareroot(1 - tmp*tmp) / tmp) * RADTODEG64; if (X>0 && Y>0) - return tmp + 270; + return angle + 270; else if (X>0 && Y<0) - return tmp + 90; + return angle + 90; else if (X<0 && Y<0) - return 90 - tmp; + return 90 - angle; else if (X<0 && Y>0) - return 270 - tmp; + return 270 - angle; - return tmp; + return angle; } //! Calculates the angle between this vector and another one in degree. diff --git a/source/Irrlicht/CAnimatedMeshHalfLife.cpp b/source/Irrlicht/CAnimatedMeshHalfLife.cpp index 91cff72d..faab78b8 100644 --- a/source/Irrlicht/CAnimatedMeshHalfLife.cpp +++ b/source/Irrlicht/CAnimatedMeshHalfLife.cpp @@ -127,8 +127,8 @@ namespace scene inline void VectorTransform(const vec3_hl in1, const f32 in2[3][4], core::vector3df& out) { out.X = DotProduct(in1, in2[0]) + in2[0][3]; - out.Y = DotProduct(in1, in2[1]) + in2[1][3]; - out.Z = DotProduct(in1, in2[2]) + in2[2][3]; + out.Z = DotProduct(in1, in2[1]) + in2[1][3]; + out.Y = DotProduct(in1, in2[2]) + in2[2][3]; } static f32 BoneTransform[MAXSTUDIOBONES][3][4]; // bone transformation matrix @@ -910,7 +910,7 @@ void STextureAtlas::create(u32 border, E_TEXTURE_CLAMP texmode) // build image core::dimension2d dim = core::dimension2d( wsum, hsum ).getOptimalSize(); - IImage* master = new CImage( format, dim ); + IImage* master = new CImage(format, dim); master->fill(0); video::SColor col[2]; @@ -1020,7 +1020,7 @@ SHalflifeHeader* CAnimatedMeshHalfLife::loadModel(io::IReadFile* file, const io: } } - IImage* image = new CImage( ECF_R8G8B8, core::dimension2d(tex[i].width, tex[i].height) ); + IImage* image = SceneManager->getVideoDriver()->createImage(ECF_R8G8B8, core::dimension2d(tex[i].width, tex[i].height)); CColorConverter::convert8BitTo24Bit(src, (u8*)image->lock(), tex[i].width, tex[i].height, (u8*) palette, 0, false); image->unlock(); diff --git a/source/Irrlicht/CAnimatedMeshHalfLife.h b/source/Irrlicht/CAnimatedMeshHalfLife.h index c80696ca..03dc497a 100644 --- a/source/Irrlicht/CAnimatedMeshHalfLife.h +++ b/source/Irrlicht/CAnimatedMeshHalfLife.h @@ -146,7 +146,9 @@ namespace scene } PACK_STRUCT; #ifndef ZONE_H - typedef void *cache_user_t; + // NOTE: this was a void*, but that crashes on 64bit. + // I have found no mdl format desc, so not sure what it's meant to be, but s32 at least works. + typedef s32 cache_user_t; #endif // demand loaded sequence groups diff --git a/source/Irrlicht/CBlit.h b/source/Irrlicht/CBlit.h index 022a9829..34b8b001 100644 --- a/source/Irrlicht/CBlit.h +++ b/source/Irrlicht/CBlit.h @@ -1233,6 +1233,57 @@ static s32 StretchBlit(eBlitter operation, return 1; } + +// Methods for Software drivers +//! draws a rectangle +static void drawRectangle(video::IImage* img, const core::rect& rect, const video::SColor &color) +{ + Blit(color.getAlpha() == 0xFF ? BLITTER_COLOR : BLITTER_COLOR_ALPHA, + img, 0, &rect.UpperLeftCorner, 0, &rect, color.color); +} + + +//! draws a line from to with color +static void drawLine(video::IImage* img, const core::position2d& from, + const core::position2d& to, const video::SColor &color) +{ + AbsRectangle clip; + GetClip(clip, img); + + core::position2d p[2]; + if (ClipLine( clip, p[0], p[1], from, to)) + { + u32 alpha = extractAlpha(color.color); + + switch(img->getColorFormat()) + { + case video::ECF_A1R5G5B5: + if (alpha == 256) + { + RenderLine16_Decal(img, p[0], p[1], video::A8R8G8B8toA1R5G5B5(color.color)); + } + else + { + RenderLine16_Blend(img, p[0], p[1], video::A8R8G8B8toA1R5G5B5(color.color), alpha >> 3); + } + break; + case video::ECF_A8R8G8B8: + if (alpha == 256) + { + RenderLine32_Decal(img, p[0], p[1], color.color); + } + else + { + RenderLine32_Blend(img, p[0], p[1], color.color, alpha); + } + break; + default: + break; + } + } +} + + } #endif diff --git a/source/Irrlicht/CCameraSceneNode.cpp b/source/Irrlicht/CCameraSceneNode.cpp index 2a545e14..79cfb3eb 100644 --- a/source/Irrlicht/CCameraSceneNode.cpp +++ b/source/Irrlicht/CCameraSceneNode.cpp @@ -301,7 +301,7 @@ void CCameraSceneNode::recalculateViewArea() //! Writes attributes of the scene node. void CCameraSceneNode::serializeAttributes(io::IAttributes* out, io::SAttributeReadWriteOptions* options) const { - ISceneNode::serializeAttributes(out, options); + ICameraSceneNode::serializeAttributes(out, options); out->addVector3d("Target", Target); out->addVector3d("UpVector", UpVector); @@ -310,13 +310,13 @@ void CCameraSceneNode::serializeAttributes(io::IAttributes* out, io::SAttributeR out->addFloat("ZNear", ZNear); out->addFloat("ZFar", ZFar); out->addBool("Binding", TargetAndRotationAreBound); + out->addBool("ReceiveInput", InputReceiverEnabled); } - //! Reads attributes of the scene node. void CCameraSceneNode::deserializeAttributes(io::IAttributes* in, io::SAttributeReadWriteOptions* options) { - ISceneNode::deserializeAttributes(in, options); + ICameraSceneNode::deserializeAttributes(in, options); Target = in->getAttributeAsVector3d("Target"); UpVector = in->getAttributeAsVector3d("UpVector"); @@ -325,6 +325,8 @@ void CCameraSceneNode::deserializeAttributes(io::IAttributes* in, io::SAttribute ZNear = in->getAttributeAsFloat("ZNear"); ZFar = in->getAttributeAsFloat("ZFar"); TargetAndRotationAreBound = in->getAttributeAsBool("Binding"); + if ( in->findAttribute("ReceiveInput") ) + InputReceiverEnabled = in->getAttributeAsBool("InputReceiverEnabled"); recalculateProjectionMatrix(); recalculateViewArea(); @@ -348,6 +350,8 @@ bool CCameraSceneNode::getTargetAndRotationBinding(void) const //! Creates a clone of this scene node and its children. ISceneNode* CCameraSceneNode::clone(ISceneNode* newParent, ISceneManager* newManager) { + ICameraSceneNode::clone(newParent, newManager); + if (!newParent) newParent = Parent; if (!newManager) @@ -356,7 +360,19 @@ ISceneNode* CCameraSceneNode::clone(ISceneNode* newParent, ISceneManager* newMan CCameraSceneNode* nb = new CCameraSceneNode(newParent, newManager, ID, RelativeTranslation, Target); - nb->cloneMembers(this, newManager); + nb->ISceneNode::cloneMembers(this, newManager); + nb->ICameraSceneNode::cloneMembers(this); + + nb->Target = Target; + nb->UpVector = UpVector; + nb->Fovy = Fovy; + nb->Aspect = Aspect; + nb->ZNear = ZNear; + nb->ZFar = ZFar; + nb->ViewArea = ViewArea; + nb->Affector = Affector; + nb->InputReceiverEnabled = InputReceiverEnabled; + nb->TargetAndRotationAreBound = TargetAndRotationAreBound; if ( newParent ) nb->drop(); diff --git a/source/Irrlicht/CColladaFileLoader.cpp b/source/Irrlicht/CColladaFileLoader.cpp index a9c6f04f..f23cd521 100644 --- a/source/Irrlicht/CColladaFileLoader.cpp +++ b/source/Irrlicht/CColladaFileLoader.cpp @@ -2191,7 +2191,7 @@ void CColladaFileLoader::readPolygonSection(io::IXMLReaderUTF8* reader, } else { - for (u32 ind = 0; i+2 < indices.size(); ++ind) + for (u32 ind = 0; ind+2 < indices.size(); ++ind) { mbuffer->Indices.push_back(indices[0]); mbuffer->Indices.push_back(indices[ind+1]); diff --git a/source/Irrlicht/CColladaMeshWriter.cpp b/source/Irrlicht/CColladaMeshWriter.cpp index e2b5ecdc..135cedd8 100644 --- a/source/Irrlicht/CColladaMeshWriter.cpp +++ b/source/Irrlicht/CColladaMeshWriter.cpp @@ -79,7 +79,7 @@ bool CColladaMeshWriter::writeMesh(io::IWriteFile* file, scene::IMesh* mesh, s32 L"version", L"1.4.1"); Writer->writeLineBreak(); - // write asset data + // write asset data Writer->writeElement(L"asset", false); Writer->writeLineBreak(); @@ -165,92 +165,41 @@ bool CColladaMeshWriter::writeMesh(io::IWriteFile* file, scene::IMesh* mesh, s32 io::IAttributes* attributes = VideoDriver->createAttributesFromMaterial( mesh->getMeshBuffer(i)->getMaterial()); - u32 count = attributes->getAttributeCount(); - for (u32 attridx=0; attridxfindAttribute("Emissive"); + if ( attridx >= 0 ) { - core::stringc str = attributes->getAttributeName(attridx); - if (str=="Emissive") - { - Writer->writeElement(L"emission", false); - Writer->writeLineBreak(); - Writer->writeElement(L"color", false); - Writer->writeLineBreak(); + writeColorAttribute(L"emission", attributes, attridx); + } + attridx = attributes->findAttribute("Ambient"); + if ( attridx >= 0 ) + { + writeColorAttribute(L"ambient", attributes, attridx); + } + attridx = attributes->findAttribute("Diffuse"); + if ( attridx >= 0 ) + { + writeColorAttribute(L"diffuse", attributes, attridx); + } + attridx = attributes->findAttribute("Specular"); + if ( attridx >= 0 ) + { + writeColorAttribute(L"specular", attributes, attridx); + } + attridx = attributes->findAttribute("Shininess"); + if ( attridx >= 0 ) + { + Writer->writeElement(L"shininess", false); + Writer->writeLineBreak(); + Writer->writeElement(L"float", false); - str = attributes->getAttributeAsString(attridx); - str.replace(',',' '); - Writer->writeText(core::stringw(str.c_str()).c_str()); + Writer->writeText(core::stringw(attributes->getAttributeAsString(attridx).c_str()).c_str()); - Writer->writeClosingTag(L"color"); - Writer->writeLineBreak(); - Writer->writeClosingTag(L"emission"); - Writer->writeLineBreak(); - } - else - if (str=="Ambient") - { - Writer->writeElement(L"ambient", false); - Writer->writeLineBreak(); - Writer->writeElement(L"color", false); - Writer->writeLineBreak(); - - str = attributes->getAttributeAsString(attridx); - str.replace(',',' '); - Writer->writeText(core::stringw(str.c_str()).c_str()); - - Writer->writeClosingTag(L"color"); - Writer->writeLineBreak(); - Writer->writeClosingTag(L"ambient"); - Writer->writeLineBreak(); - } - else - if (str=="Diffuse") - { - Writer->writeElement(L"diffuse", false); - Writer->writeLineBreak(); - Writer->writeElement(L"color", false); - Writer->writeLineBreak(); - - str = attributes->getAttributeAsString(attridx); - str.replace(',',' '); - Writer->writeText(core::stringw(str.c_str()).c_str()); - - Writer->writeClosingTag(L"color"); - Writer->writeLineBreak(); - Writer->writeClosingTag(L"diffuse"); - Writer->writeLineBreak(); - } - else - if (str=="Specular") - { - Writer->writeElement(L"specular", false); - Writer->writeLineBreak(); - Writer->writeElement(L"color", false); - Writer->writeLineBreak(); - - str = attributes->getAttributeAsString(attridx); - str.replace(',',' '); - Writer->writeText(core::stringw(str.c_str()).c_str()); - - Writer->writeClosingTag(L"color"); - Writer->writeLineBreak(); - Writer->writeClosingTag(L"specular"); - Writer->writeLineBreak(); - } - else - if (str=="Shininess") - { - Writer->writeElement(L"shininess", false); - Writer->writeLineBreak(); - Writer->writeElement(L"float", false); - Writer->writeLineBreak(); - - Writer->writeText(core::stringw(attributes->getAttributeAsString(attridx).c_str()).c_str()); - - Writer->writeClosingTag(L"float"); - Writer->writeLineBreak(); - Writer->writeClosingTag(L"shininess"); - Writer->writeLineBreak(); - } + Writer->writeClosingTag(L"float"); + Writer->writeLineBreak(); + Writer->writeClosingTag(L"shininess"); + Writer->writeLineBreak(); } attributes->drop(); @@ -332,14 +281,7 @@ bool CColladaMeshWriter::writeMesh(io::IWriteFile* file, scene::IMesh* mesh, s32 video::S3DVertex* vtx = (video::S3DVertex*)buffer->getVertices(); for (u32 j=0; jwriteText(str.c_str()); + Writer->writeText(toString(vtx[j].Pos).c_str()); Writer->writeLineBreak(); } } @@ -349,14 +291,7 @@ bool CColladaMeshWriter::writeMesh(io::IWriteFile* file, scene::IMesh* mesh, s32 video::S3DVertex2TCoords* vtx = (video::S3DVertex2TCoords*)buffer->getVertices(); for (u32 j=0; jwriteText(str.c_str()); + Writer->writeText(toString(vtx[j].Pos).c_str()); Writer->writeLineBreak(); } } @@ -366,14 +301,7 @@ bool CColladaMeshWriter::writeMesh(io::IWriteFile* file, scene::IMesh* mesh, s32 video::S3DVertexTangents* vtx = (video::S3DVertexTangents*)buffer->getVertices(); for (u32 j=0; jwriteText(str.c_str()); + Writer->writeText(toString(vtx[j].Pos).c_str()); Writer->writeLineBreak(); } } @@ -439,12 +367,7 @@ bool CColladaMeshWriter::writeMesh(io::IWriteFile* file, scene::IMesh* mesh, s32 video::S3DVertex* vtx = (video::S3DVertex*)buffer->getVertices(); for (u32 j=0; jwriteText(str.c_str()); + Writer->writeText(toString(vtx[j].TCoords).c_str()); Writer->writeLineBreak(); } } @@ -454,12 +377,7 @@ bool CColladaMeshWriter::writeMesh(io::IWriteFile* file, scene::IMesh* mesh, s32 video::S3DVertex2TCoords* vtx = (video::S3DVertex2TCoords*)buffer->getVertices(); for (u32 j=0; jwriteText(str.c_str()); + Writer->writeText(toString(vtx[j].TCoords).c_str()); Writer->writeLineBreak(); } } @@ -469,12 +387,7 @@ bool CColladaMeshWriter::writeMesh(io::IWriteFile* file, scene::IMesh* mesh, s32 video::S3DVertexTangents* vtx = (video::S3DVertexTangents*)buffer->getVertices(); for (u32 j=0; jwriteText(str.c_str()); + Writer->writeText(toString(vtx[j].TCoords).c_str()); Writer->writeLineBreak(); } } @@ -494,9 +407,9 @@ bool CColladaMeshWriter::writeMesh(io::IWriteFile* file, scene::IMesh* mesh, s32 L"count", vertexCountStr.c_str(), L"stride", L"2"); Writer->writeLineBreak(); - Writer->writeElement(L"param", true, L"name", L"U", L"type", L"float", L"flow", L"OUT"); + Writer->writeElement(L"param", true, L"name", L"U", L"type", L"float"); Writer->writeLineBreak(); - Writer->writeElement(L"param", true, L"name", L"V", L"type", L"float", L"flow", L"OUT"); + Writer->writeElement(L"param", true, L"name", L"V", L"type", L"float"); Writer->writeLineBreak(); Writer->writeClosingTag(L"accessor"); @@ -538,14 +451,7 @@ bool CColladaMeshWriter::writeMesh(io::IWriteFile* file, scene::IMesh* mesh, s32 video::S3DVertex* vtx = (video::S3DVertex*)buffer->getVertices(); for (u32 j=0; jwriteText(str.c_str()); + Writer->writeText(toString(vtx[j].Normal).c_str()); Writer->writeLineBreak(); } } @@ -555,14 +461,7 @@ bool CColladaMeshWriter::writeMesh(io::IWriteFile* file, scene::IMesh* mesh, s32 video::S3DVertex2TCoords* vtx = (video::S3DVertex2TCoords*)buffer->getVertices(); for (u32 j=0; jwriteText(str.c_str()); + Writer->writeText(toString(vtx[j].Normal).c_str()); Writer->writeLineBreak(); } } @@ -572,14 +471,7 @@ bool CColladaMeshWriter::writeMesh(io::IWriteFile* file, scene::IMesh* mesh, s32 video::S3DVertexTangents* vtx = (video::S3DVertexTangents*)buffer->getVertices(); for (u32 j=0; jwriteText(str.c_str()); + Writer->writeText(toString(vtx[j].Normal).c_str()); Writer->writeLineBreak(); } } @@ -599,11 +491,11 @@ bool CColladaMeshWriter::writeMesh(io::IWriteFile* file, scene::IMesh* mesh, s32 L"count", vertexCountStr.c_str(), L"stride", L"3"); Writer->writeLineBreak(); - Writer->writeElement(L"param", true, L"name", L"X", L"type", L"float", L"flow", L"OUT"); + Writer->writeElement(L"param", true, L"name", L"X", L"type", L"float"); Writer->writeLineBreak(); - Writer->writeElement(L"param", true, L"name", L"Y", L"type", L"float", L"flow", L"OUT"); + Writer->writeElement(L"param", true, L"name", L"Y", L"type", L"float"); Writer->writeLineBreak(); - Writer->writeElement(L"param", true, L"name", L"Z", L"type", L"float", L"flow", L"OUT"); + Writer->writeElement(L"param", true, L"name", L"Z", L"type", L"float"); Writer->writeLineBreak(); Writer->writeClosingTag(L"accessor"); @@ -649,12 +541,7 @@ bool CColladaMeshWriter::writeMesh(io::IWriteFile* file, scene::IMesh* mesh, s32 video::S3DVertex2TCoords* vtx = (video::S3DVertex2TCoords*)buffer->getVertices(); for (u32 j=0; jwriteText(str.c_str()); + Writer->writeText(toString(vtx[j].TCoords2).c_str()); Writer->writeLineBreak(); } } @@ -677,9 +564,9 @@ bool CColladaMeshWriter::writeMesh(io::IWriteFile* file, scene::IMesh* mesh, s32 L"count", vertexCountStr.c_str(), L"stride", L"2"); Writer->writeLineBreak(); - Writer->writeElement(L"param", true, L"name", L"U", L"type", L"float", L"flow", L"OUT"); + Writer->writeElement(L"param", true, L"name", L"U", L"type", L"float"); Writer->writeLineBreak(); - Writer->writeElement(L"param", true, L"name", L"V", L"type", L"float", L"flow", L"OUT"); + Writer->writeElement(L"param", true, L"name", L"V", L"type", L"float"); Writer->writeLineBreak(); Writer->writeClosingTag(L"accessor"); @@ -715,18 +602,18 @@ bool CColladaMeshWriter::writeMesh(io::IWriteFile* file, scene::IMesh* mesh, s32 const u32 polyCount = buffer->getIndexCount() / 3; core::stringw strPolyCount(polyCount); - core::stringw strMat = "#mat"; + core::stringw strMat = "mat"; strMat += i; Writer->writeElement(L"triangles", false, L"count", strPolyCount.c_str(), L"material", strMat.c_str()); Writer->writeLineBreak(); - Writer->writeElement(L"input", true, L"semantic", L"VERTEX", L"source", L"#mesh-Vtx", L"idx", L"0"); + Writer->writeElement(L"input", true, L"semantic", L"VERTEX", L"source", L"#mesh-Vtx", L"offset", L"0"); Writer->writeLineBreak(); - Writer->writeElement(L"input", true, L"semantic", L"TEXCOORD", L"source", L"#mesh-TexCoord0", L"idx", L"1"); + Writer->writeElement(L"input", true, L"semantic", L"TEXCOORD", L"source", L"#mesh-TexCoord0", L"offset", L"1"); Writer->writeLineBreak(); - Writer->writeElement(L"input", true, L"semantic", L"NORMAL", L"source", L"#mesh-Normal", L"idx", L"2"); + Writer->writeElement(L"input", true, L"semantic", L"NORMAL", L"source", L"#mesh-Normal", L"offset", L"2"); Writer->writeLineBreak(); bool has2ndTexCoords = hasSecondTextureCoordinates(buffer->getVertexType()); @@ -823,6 +710,49 @@ bool CColladaMeshWriter::hasSecondTextureCoordinates(video::E_VERTEX_TYPE type) return type == video::EVT_2TCOORDS; } +irr::core::stringw CColladaMeshWriter::toString(const irr::core::vector3df& vec) const +{ + c8 tmpbuf[255]; + snprintf(tmpbuf, 255, "%f %f %f", vec.X, vec.Y, vec.Z); + core::stringw str = tmpbuf; + + return str; +} + +irr::core::stringw CColladaMeshWriter::toString(const irr::core::vector2df& vec) const +{ + c8 tmpbuf[255]; + snprintf(tmpbuf, 255, "%f %f", vec.X, vec.Y); + core::stringw str = tmpbuf; + + return str; +} + +inline irr::core::stringw CColladaMeshWriter::toString(const irr::video::SColorf& colorf) const +{ + c8 tmpbuf[255]; + snprintf(tmpbuf, 255, "%f %f %f %f", colorf.getRed(), colorf.getGreen(), colorf.getBlue(), colorf.getAlpha()); + core::stringw str = tmpbuf; + + return str; +} + +void CColladaMeshWriter::writeColorAttribute(wchar_t * parentTag, io::IAttributes* attributes, s32 attridx) +{ + Writer->writeElement(parentTag, false); + Writer->writeLineBreak(); + + Writer->writeElement(L"color", false); + + irr::core::stringw str( toString(attributes->getAttributeAsColorf(attridx)) ); + Writer->writeText(str.c_str()); + + Writer->writeClosingTag(L"color"); + Writer->writeLineBreak(); + + Writer->writeClosingTag(parentTag); + Writer->writeLineBreak(); +} } // end namespace } // end namespace diff --git a/source/Irrlicht/CColladaMeshWriter.h b/source/Irrlicht/CColladaMeshWriter.h index 74c0eae6..fe7619a1 100644 --- a/source/Irrlicht/CColladaMeshWriter.h +++ b/source/Irrlicht/CColladaMeshWriter.h @@ -35,9 +35,14 @@ public: //! writes a mesh virtual bool writeMesh(io::IWriteFile* file, scene::IMesh* mesh, s32 flags=EMWF_NONE); + protected: bool hasSecondTextureCoordinates(video::E_VERTEX_TYPE type) const; + inline irr::core::stringw toString(const irr::core::vector3df& vec) const; + inline irr::core::stringw toString(const irr::core::vector2df& vec) const; + inline irr::core::stringw toString(const irr::video::SColorf& colorf) const; + inline void writeColorAttribute(wchar_t * parentTag, io::IAttributes* attributes, s32 attridx); struct SComponentGlobalStartPos { diff --git a/source/Irrlicht/CD3D8Driver.cpp b/source/Irrlicht/CD3D8Driver.cpp index 11495189..81c21f55 100644 --- a/source/Irrlicht/CD3D8Driver.cpp +++ b/source/Irrlicht/CD3D8Driver.cpp @@ -12,7 +12,6 @@ #include "os.h" #include "S3DVertex.h" #include "CD3D8Texture.h" -#include "CImage.h" #include "CD3D8MaterialRenderer.h" #include "CD3D8ShaderMaterialRenderer.h" #include "CD3D8NormalMapRenderer.h" @@ -394,6 +393,7 @@ bool CD3D8Driver::initDriver(const core::dimension2d& screenSize, DriverAttributes->setAttribute("MaxTextures", (s32)MaxTextureUnits); DriverAttributes->setAttribute("MaxSupportedTextures", (s32)Caps.MaxSimultaneousTextures); + DriverAttributes->setAttribute("MaxLights", (s32)Caps.MaxActiveLights); DriverAttributes->setAttribute("MaxAnisotropy", (s32)Caps.MaxAnisotropy); DriverAttributes->setAttribute("MaxUserClipPlanes", (s32)Caps.MaxUserClipPlanes); DriverAttributes->setAttribute("MaxIndices", (s32)Caps.MaxVertexIndex); @@ -607,6 +607,7 @@ bool CD3D8Driver::queryFeature(E_VIDEO_DRIVER_FEATURE feature) const case EVDF_COLOR_MASK: return (Caps.PrimitiveMiscCaps & D3DPMISCCAPS_COLORWRITEENABLE) != 0; case EVDF_BLEND_OPERATIONS: + case EVDF_TEXTURE_MATRIX: return true; default: return false; @@ -790,6 +791,7 @@ bool CD3D8Driver::setRenderTarget(video::ITexture* texture, CurrentRendertargetSize = tex->getSize(); } + Transformation3DChanged = true; if (clearBackBuffer || clearZBuffer) { @@ -1576,7 +1578,7 @@ void CD3D8Driver::setBasicRenderStates(const SMaterial& material, const SMateria if (queryFeature(EVDF_BLEND_OPERATIONS) && (resetAllRenderstates|| lastmaterial.BlendOperation != material.BlendOperation)) { - if (EBO_NONE) + if (material.BlendOperation==EBO_NONE) pID3DDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, FALSE); else { @@ -1854,72 +1856,57 @@ void CD3D8Driver::setRenderStates2DMode(bool alpha, bool texture, bool alphaChan if (OverrideMaterial2DEnabled) { OverrideMaterial2D.Lighting=false; - OverrideMaterial2D.ZBuffer=ECFN_NEVER; - OverrideMaterial2D.ZWriteEnable=false; setBasicRenderStates(OverrideMaterial2D, LastMaterial, false); LastMaterial = OverrideMaterial2D; } + // no alphaChannel without texture + alphaChannel &= texture; + + if (alpha || alphaChannel) + { + pID3DDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE); + pID3DDevice->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA); + pID3DDevice->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA); + } + else + pID3DDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, FALSE); + pID3DDevice->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE); + pID3DDevice->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE); + pID3DDevice->SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_DIFFUSE); if (texture) { setTransform(ETS_TEXTURE_0, core::IdentityMatrix); - if (alphaChannel) + // Due to the transformation change, the previous line would call a reset each frame + // but we can safely reset the variable as it was false before + Transformation3DChanged=false; + } + if (alphaChannel) + { + pID3DDevice->SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE); + + if (alpha) { - pID3DDevice->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE ); - pID3DDevice->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE ); - pID3DDevice->SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_DIFFUSE ); - pID3DDevice->SetTextureStageState (0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE ); - - if (alpha) - { - pID3DDevice->SetTextureStageState (0, D3DTSS_ALPHAOP, D3DTOP_MODULATE ); - pID3DDevice->SetTextureStageState (0, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE ); - } - else - { - pID3DDevice->SetTextureStageState (0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1 ); - } - - pID3DDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE); - pID3DDevice->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA); - pID3DDevice->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA); + pID3DDevice->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_MODULATE); + pID3DDevice->SetTextureStageState(0, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE); } else { - pID3DDevice->SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_DIFFUSE ); - if (alpha) - { - pID3DDevice->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG2); - pID3DDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE); - pID3DDevice->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA); - pID3DDevice->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA ); - } - else - { - pID3DDevice->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE ); - pID3DDevice->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE ); - pID3DDevice->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_DISABLE); - pID3DDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, FALSE); - } + pID3DDevice->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1); } + } else { - pID3DDevice->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE ); - pID3DDevice->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE ); - pID3DDevice->SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_DIFFUSE ); + pID3DDevice->SetTextureStageState(0, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE ); if (alpha) { - pID3DDevice->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1); - pID3DDevice->SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_DIFFUSE ); - pID3DDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE); - pID3DDevice->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA); - pID3DDevice->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA ); + pID3DDevice->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG2); } else { - pID3DDevice->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_DISABLE); - pID3DDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, FALSE); + pID3DDevice->SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE); + pID3DDevice->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_MODULATE); } } @@ -2252,18 +2239,23 @@ void CD3D8Driver::clearZBuffer() //! Returns an image created from the last rendered frame. -IImage* CD3D8Driver::createScreenShot() +IImage* CD3D8Driver::createScreenShot(video::ECOLOR_FORMAT format, video::E_RENDER_TARGET target) { #if defined( _IRR_XBOX_PLATFORM_) return 0; #else - HRESULT hr; + if (target != video::ERT_FRAME_BUFFER) + return 0; // query the screen dimensions of the current adapter D3DDISPLAYMODE displayMode; pID3DDevice->GetDisplayMode(&displayMode); + if (format==video::ECF_UNKNOWN) + format=video::ECF_A8R8G8B8; + // create the image surface to store the front buffer image [always A8R8G8B8] + HRESULT hr; LPDIRECT3DSURFACE8 lpSurface; if (FAILED(hr = pID3DDevice->CreateImageSurface(displayMode.Width, displayMode.Height, D3DFMT_A8R8G8B8, &lpSurface))) return 0; @@ -2308,41 +2300,42 @@ IImage* CD3D8Driver::createScreenShot() shotSize.Height = core::min_( ScreenSize.Height, (u32)(clientRect.bottom-clientRect.top) ); // this could throw, but we aren't going to worry about that case very much - IImage* newImage = new CImage(ECF_A8R8G8B8, shotSize); + IImage* newImage = createImage(format, shotSize); - // d3d pads the image, so we need to copy the correct number of bytes - u32* dP = (u32*)newImage->lock(); - u8 * sP = (u8 *)lockedRect.pBits; - - // If the display mode format doesn't promise anything about the Alpha value - // and it appears that it's not presenting 255, then we should manually - // set each pixel alpha value to 255. - if(D3DFMT_X8R8G8B8 == displayMode.Format && (0xFF000000 != (*dP & 0xFF000000))) + if (newImage) { - for (u32 y = 0; y < shotSize.Height; ++y) + // d3d pads the image, so we need to copy the correct number of bytes + u32* dP = (u32*)newImage->lock(); + u8 * sP = (u8 *)lockedRect.pBits; + + // If the display mode format doesn't promise anything about the Alpha value + // and it appears that it's not presenting 255, then we should manually + // set each pixel alpha value to 255. + if(D3DFMT_X8R8G8B8 == displayMode.Format && (0xFF000000 != (*dP & 0xFF000000))) { - for(u32 x = 0; x < shotSize.Width; ++x) + for (u32 y = 0; y < shotSize.Height; ++y) { - *dP = *((u32*)sP) | 0xFF000000; - dP++; - sP += 4; + for(u32 x = 0; x < shotSize.Width; ++x) + { + newImage->setPixel(x,y,*((u32*)sP) | 0xFF000000); + sP += 4; + } + + sP += lockedRect.Pitch - (4 * shotSize.Width); } - - sP += lockedRect.Pitch - (4 * shotSize.Width); } - } - else - { - for (u32 y = 0; y < shotSize.Height; ++y) + else { - memcpy(dP, sP, shotSize.Width * 4); - - sP += lockedRect.Pitch; - dP += shotSize.Width; + for (u32 y = 0; y < shotSize.Height; ++y) + { + convertColor(sP, video::ECF_A8R8G8B8, shotSize.Width, dP, format); + sP += lockedRect.Pitch; + dP += shotSize.Width; + } } - } - newImage->unlock(); + newImage->unlock(); + } // we can unlock and release the surface lpSurface->UnlockRect(); diff --git a/source/Irrlicht/CD3D8Driver.h b/source/Irrlicht/CD3D8Driver.h index 5b4d64ae..6d5dabbc 100644 --- a/source/Irrlicht/CD3D8Driver.h +++ b/source/Irrlicht/CD3D8Driver.h @@ -199,7 +199,7 @@ namespace video virtual void clearZBuffer(); //! Returns an image created from the last rendered frame. - virtual IImage* createScreenShot(); + virtual IImage* createScreenShot(video::ECOLOR_FORMAT format=video::ECF_UNKNOWN, video::E_RENDER_TARGET target=video::ERT_FRAME_BUFFER); //! Set/unset a clipping plane. //! There are at least 6 clipping planes available for the user to set at will. diff --git a/source/Irrlicht/CD3D9Driver.cpp b/source/Irrlicht/CD3D9Driver.cpp index a0edb304..788a3b6b 100644 --- a/source/Irrlicht/CD3D9Driver.cpp +++ b/source/Irrlicht/CD3D9Driver.cpp @@ -10,7 +10,6 @@ #include "os.h" #include "S3DVertex.h" #include "CD3D9Texture.h" -#include "CImage.h" #include "CD3D9MaterialRenderer.h" #include "CD3D9ShaderMaterialRenderer.h" #include "CD3D9NormalMapRenderer.h" @@ -29,20 +28,17 @@ namespace } //! constructor -CD3D9Driver::CD3D9Driver(const core::dimension2d& screenSize, HWND window, - bool fullscreen, bool stencilbuffer, - io::IFileSystem* io, bool pureSoftware) -: CNullDriver(io, screenSize), CurrentRenderMode(ERM_NONE), +CD3D9Driver::CD3D9Driver(const SIrrlichtCreationParameters& params, io::IFileSystem* io) + : CNullDriver(io, params.WindowSize), CurrentRenderMode(ERM_NONE), ResetRenderStates(true), Transformation3DChanged(false), - StencilBuffer(stencilbuffer), AntiAliasing(0), D3DLibrary(0), pID3D(0), pID3DDevice(0), PrevRenderTarget(0), WindowId(0), SceneSourceRect(0), LastVertexType((video::E_VERTEX_TYPE)-1), VendorID(0), MaxTextureUnits(0), MaxUserClipPlanes(0), MaxMRTs(1), NumSetMRTs(1), - MaxLightDistance(0.f), LastSetLight(-1), Cached2DModeSignature(0), + MaxLightDistance(0.f), LastSetLight(-1), ColorFormat(ECF_A8R8G8B8), DeviceLost(false), - Fullscreen(fullscreen), DriverWasReset(true), OcclusionQuerySupport(false), - AlphaToCoverageSupport(false), DisplayAdapter(0) + DriverWasReset(true), OcclusionQuerySupport(false), + AlphaToCoverageSupport(false), Params(params) { #ifdef _DEBUG setDebugName("CD3D9Driver"); @@ -164,15 +160,8 @@ void CD3D9Driver::createMaterialRenderers() //! initialises the Direct3D API -bool CD3D9Driver::initDriver(const core::dimension2d& screenSize, - HWND hwnd, u32 bits, bool fullScreen, bool pureSoftware, - bool highPrecisionFPU, bool vsync, u8 antiAlias, u32 displayAdapter) +bool CD3D9Driver::initDriver(HWND hwnd, bool pureSoftware) { - HRESULT hr; - Fullscreen = fullScreen; - CurrentDepthBufferSize = screenSize; - DisplayAdapter = displayAdapter; - if (!pID3D) { D3DLibrary = LoadLibrary( __TEXT("d3d9.dll") ); @@ -204,7 +193,7 @@ bool CD3D9Driver::initDriver(const core::dimension2d& screenSize, // print device information D3DADAPTER_IDENTIFIER9 dai; - if (!FAILED(pID3D->GetAdapterIdentifier(DisplayAdapter, 0, &dai))) + if (!FAILED(pID3D->GetAdapterIdentifier(Params.DisplayAdapter, 0, &dai))) { char tmp[512]; @@ -232,8 +221,7 @@ bool CD3D9Driver::initDriver(const core::dimension2d& screenSize, } D3DDISPLAYMODE d3ddm; - hr = pID3D->GetAdapterDisplayMode(DisplayAdapter, &d3ddm); - if (FAILED(hr)) + if (FAILED(pID3D->GetAdapterDisplayMode(Params.DisplayAdapter, &d3ddm))) { os::Printer::log("Error: Could not get Adapter Display mode.", ELL_ERROR); return false; @@ -241,19 +229,19 @@ bool CD3D9Driver::initDriver(const core::dimension2d& screenSize, ZeroMemory(&present, sizeof(present)); - present.BackBufferCount = 1; - present.EnableAutoDepthStencil = TRUE; - if (vsync) + present.BackBufferCount = 1; + present.EnableAutoDepthStencil = TRUE; + if (Params.Vsync) present.PresentationInterval = D3DPRESENT_INTERVAL_ONE; else present.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE; - if (fullScreen) + if (Params.Fullscreen) { - present.BackBufferWidth = screenSize.Width; - present.BackBufferHeight = screenSize.Height; + present.BackBufferWidth = Params.WindowSize.Width; + present.BackBufferHeight = Params.WindowSize.Height; // request 32bit mode if user specified 32 bit, added by Thomas Stuefe - if (bits == 32) + if (Params.Bits == 32) present.BackBufferFormat = D3DFMT_X8R8G8B8; else present.BackBufferFormat = D3DFMT_R5G6B5; @@ -268,7 +256,7 @@ bool CD3D9Driver::initDriver(const core::dimension2d& screenSize, present.Windowed = TRUE; } - UINT adapter = DisplayAdapter; + UINT adapter = Params.DisplayAdapter; D3DDEVTYPE devtype = D3DDEVTYPE_HAL; #ifndef _IRR_D3D_NO_SHADER_DEBUGGING devtype = D3DDEVTYPE_REF; @@ -287,36 +275,35 @@ bool CD3D9Driver::initDriver(const core::dimension2d& screenSize, #endif // enable anti alias if possible and desired - if (antiAlias > 0) + if (Params.AntiAlias > 0) { - if(antiAlias > 16) - antiAlias = 16; + if (Params.AntiAlias > 32) + Params.AntiAlias = 32; DWORD qualityLevels = 0; - while(antiAlias > 0) + while(Params.AntiAlias > 0) { if(SUCCEEDED(pID3D->CheckDeviceMultiSampleType(adapter, - devtype, present.BackBufferFormat, !fullScreen, - (D3DMULTISAMPLE_TYPE)antiAlias, &qualityLevels))) + devtype, present.BackBufferFormat, !Params.Fullscreen, + (D3DMULTISAMPLE_TYPE)Params.AntiAlias, &qualityLevels))) { - present.MultiSampleType = (D3DMULTISAMPLE_TYPE)antiAlias; + present.MultiSampleType = (D3DMULTISAMPLE_TYPE)Params.AntiAlias; present.MultiSampleQuality = qualityLevels-1; present.SwapEffect = D3DSWAPEFFECT_DISCARD; break; } - --antiAlias; + --Params.AntiAlias; } - if(antiAlias==0) + if (Params.AntiAlias==0) { os::Printer::log("Anti aliasing disabled because hardware/driver lacks necessary caps.", ELL_WARNING); } } - AntiAliasing = antiAlias; // check stencil buffer compatibility - if (StencilBuffer) + if (Params.Stencilbuffer) { present.AutoDepthStencilFormat = D3DFMT_D24S8; if(FAILED(pID3D->CheckDeviceFormat(adapter, devtype, @@ -334,7 +321,7 @@ bool CD3D9Driver::initDriver(const core::dimension2d& screenSize, D3DRTYPE_SURFACE, present.AutoDepthStencilFormat))) { os::Printer::log("Device does not support stencilbuffer, disabling stencil buffer.", ELL_WARNING); - StencilBuffer = false; + Params.Stencilbuffer = false; } } } @@ -343,11 +330,11 @@ bool CD3D9Driver::initDriver(const core::dimension2d& screenSize, present.BackBufferFormat, present.BackBufferFormat, present.AutoDepthStencilFormat))) { os::Printer::log("Depth-stencil format is not compatible with display format, disabling stencil buffer.", ELL_WARNING); - StencilBuffer = false; + Params.Stencilbuffer = false; } } // do not use else here to cope with flag change in previous block - if (!StencilBuffer) + if (!Params.Stencilbuffer) { present.AutoDepthStencilFormat = D3DFMT_D32; if(FAILED(pID3D->CheckDeviceFormat(adapter, devtype, @@ -373,18 +360,16 @@ bool CD3D9Driver::initDriver(const core::dimension2d& screenSize, // create device - DWORD fpuPrecision = highPrecisionFPU ? D3DCREATE_FPU_PRESERVE : 0; + DWORD fpuPrecision = Params.HighPrecisionFPU ? D3DCREATE_FPU_PRESERVE : 0; if (pureSoftware) { - hr = pID3D->CreateDevice(DisplayAdapter, D3DDEVTYPE_REF, hwnd, - fpuPrecision | D3DCREATE_SOFTWARE_VERTEXPROCESSING, &present, &pID3DDevice); - - if (FAILED(hr)) + if (FAILED(pID3D->CreateDevice(Params.DisplayAdapter, D3DDEVTYPE_REF, hwnd, + fpuPrecision | D3DCREATE_SOFTWARE_VERTEXPROCESSING, &present, &pID3DDevice))) os::Printer::log("Was not able to create Direct3D9 software device.", ELL_ERROR); } else { - hr = pID3D->CreateDevice(adapter, devtype, hwnd, + HRESULT hr = pID3D->CreateDevice(adapter, devtype, hwnd, fpuPrecision | D3DCREATE_HARDWARE_VERTEXPROCESSING, &present, &pID3DDevice); if(FAILED(hr)) @@ -408,14 +393,16 @@ bool CD3D9Driver::initDriver(const core::dimension2d& screenSize, // get caps pID3DDevice->GetDeviceCaps(&Caps); + os::Printer::log("Currently available Video Memory (kB)", core::stringc(pID3DDevice->GetAvailableTextureMem()/1024).c_str()); + // disable stencilbuffer if necessary - if (StencilBuffer && + if (Params.Stencilbuffer && (!(Caps.StencilCaps & D3DSTENCILCAPS_DECRSAT) || !(Caps.StencilCaps & D3DSTENCILCAPS_INCRSAT) || !(Caps.StencilCaps & D3DSTENCILCAPS_KEEP))) { os::Printer::log("Device not able to use stencil buffer, disabling stencil buffer.", ELL_WARNING); - StencilBuffer = false; + Params.Stencilbuffer = false; } // set default vertex shader @@ -440,19 +427,20 @@ bool CD3D9Driver::initDriver(const core::dimension2d& screenSize, OcclusionQuerySupport=(pID3DDevice->CreateQuery(D3DQUERYTYPE_OCCLUSION, NULL) == S_OK); if (VendorID==0x10DE)//NVidia - AlphaToCoverageSupport = (pID3D->CheckDeviceFormat(DisplayAdapter, D3DDEVTYPE_HAL, + AlphaToCoverageSupport = (pID3D->CheckDeviceFormat(adapter, D3DDEVTYPE_HAL, D3DFMT_X8R8G8B8, 0,D3DRTYPE_SURFACE, (D3DFORMAT)MAKEFOURCC('A', 'T', 'O', 'C')) == S_OK); else if (VendorID==0x1002)//ATI AlphaToCoverageSupport = true; // TODO: Check unknown #if 0 - AlphaToCoverageSupport = (pID3D->CheckDeviceFormat(DisplayAdapter, D3DDEVTYPE_HAL, + AlphaToCoverageSupport = (pID3D->CheckDeviceFormat(adapter, D3DDEVTYPE_HAL, D3DFMT_X8R8G8B8, 0,D3DRTYPE_SURFACE, (D3DFORMAT)MAKEFOURCC('A','2','M','1')) == S_OK); #endif DriverAttributes->setAttribute("MaxTextures", (s32)MaxTextureUnits); DriverAttributes->setAttribute("MaxSupportedTextures", (s32)Caps.MaxSimultaneousTextures); + DriverAttributes->setAttribute("MaxLights", (s32)Caps.MaxActiveLights); DriverAttributes->setAttribute("MaxAnisotropy", (s32)Caps.MaxAnisotropy); DriverAttributes->setAttribute("MaxUserClipPlanes", (s32)Caps.MaxUserClipPlanes); DriverAttributes->setAttribute("MaxMultipleRenderTargets", (s32)Caps.NumSimultaneousRTs); @@ -460,8 +448,8 @@ bool CD3D9Driver::initDriver(const core::dimension2d& screenSize, DriverAttributes->setAttribute("MaxTextureSize", (s32)core::min_(Caps.MaxTextureHeight,Caps.MaxTextureWidth)); DriverAttributes->setAttribute("MaxTextureLODBias", 16); DriverAttributes->setAttribute("Version", 901); - DriverAttributes->setAttribute("ShaderLanguageVersion", (s32)Caps.VertexShaderVersion*100); - DriverAttributes->setAttribute("AntiAlias", AntiAliasing); + DriverAttributes->setAttribute("ShaderLanguageVersion", (s32)(((0x00ff00 & Caps.VertexShaderVersion)>>8)*100 + (Caps.VertexShaderVersion&0xff))); + DriverAttributes->setAttribute("AntiAlias", Params.AntiAlias); // set the renderstates setRenderStates3DMode(); @@ -537,7 +525,7 @@ bool CD3D9Driver::beginScene(bool backBuffer, bool zBuffer, SColor color, if (zBuffer) flags |= D3DCLEAR_ZBUFFER; - if (StencilBuffer) + if (Params.Stencilbuffer) flags |= D3DCLEAR_STENCIL; if (flags) @@ -582,7 +570,10 @@ bool CD3D9Driver::endScene() sourceRectData.bottom = SceneSourceRect->LowerRightCorner.Y; } - hr = pID3DDevice->Present(srcRct, NULL, WindowId, NULL); + IDirect3DSwapChain9* swChain; + hr = pID3DDevice->GetSwapChain(0, &swChain); + DWORD flags = (Params.HandleSRGB && (Caps.Caps3&D3DCAPS3_LINEAR_TO_SRGB_PRESENTATION))?D3DPRESENT_LINEAR_CONTENT:0; + hr = swChain->Present(srcRct, NULL, WindowId, NULL, flags); if (SUCCEEDED(hr)) return true; @@ -630,7 +621,7 @@ bool CD3D9Driver::queryFeature(E_VIDEO_DRIVER_FEATURE feature) const // this but actually don't do this at all. return false; //(Caps.Caps2 & D3DCAPS2_CANAUTOGENMIPMAP) != 0; case EVDF_STENCIL_BUFFER: - return StencilBuffer && Caps.StencilCaps; + return Params.Stencilbuffer && Caps.StencilCaps; case EVDF_VERTEX_SHADER_1_1: return Caps.VertexShaderVersion >= D3DVS_VERSION(1,1); case EVDF_VERTEX_SHADER_2_0: @@ -668,6 +659,7 @@ bool CD3D9Driver::queryFeature(E_VIDEO_DRIVER_FEATURE feature) const case EVDF_POLYGON_OFFSET: return (Caps.RasterCaps & (D3DPRASTERCAPS_DEPTHBIAS|D3DPRASTERCAPS_SLOPESCALEDEPTHBIAS)) != 0; case EVDF_BLEND_OPERATIONS: + case EVDF_TEXTURE_MATRIX: return true; default: return false; @@ -852,6 +844,7 @@ bool CD3D9Driver::setRenderTarget(video::ITexture* texture, os::Printer::log("Error: Could not set new depth buffer.", ELL_ERROR); } } + Transformation3DChanged=true; if (clearBackBuffer || clearZBuffer) { @@ -1628,6 +1621,8 @@ void CD3D9Driver::draw2DImageBatch(const video::ITexture* texture, if (!setActiveTexture(0, const_cast(texture))) return; + setRenderStates2DMode(color.getAlpha()<255, true, useAlphaChannelOfTexture); + const irr::u32 drawCount = core::min_(positions.size(), sourceRects.size()); core::array vtx(drawCount * 4); @@ -1726,8 +1721,6 @@ void CD3D9Driver::draw2DImageBatch(const video::ITexture* texture, const core::rect poss(targetPos, sourceSize); - setRenderStates2DMode(color.getAlpha()<255, true, useAlphaChannelOfTexture); - vtx.push_back(S3DVertex((f32)poss.UpperLeftCorner.X, (f32)poss.UpperLeftCorner.Y, 0.0f, 0.0f, 0.0f, 0.0f, color, tcoords.UpperLeftCorner.X, tcoords.UpperLeftCorner.Y)); @@ -2025,6 +2018,7 @@ bool CD3D9Driver::setRenderStates3DMode() pID3DDevice->SetTransform(D3DTS_PROJECTION, (D3DMATRIX*)((void*)&Matrices[ETS_PROJECTION])); pID3DDevice->SetRenderState(D3DRS_STENCILENABLE, FALSE); + pID3DDevice->SetRenderState(D3DRS_CLIPPING, TRUE); ResetRenderStates = true; } @@ -2096,6 +2090,10 @@ D3DTEXTUREADDRESS CD3D9Driver::getTextureWrapMode(const u8 clamp) void CD3D9Driver::setBasicRenderStates(const SMaterial& material, const SMaterial& lastmaterial, bool resetAllRenderstates) { + // This needs only to be updated onresets + if (Params.HandleSRGB && resetAllRenderstates) + pID3DDevice->SetRenderState(D3DRS_SRGBWRITEENABLE, TRUE); + if (resetAllRenderstates || lastmaterial.AmbientColor != material.AmbientColor || lastmaterial.DiffuseColor != material.DiffuseColor || @@ -2259,7 +2257,7 @@ void CD3D9Driver::setBasicRenderStates(const SMaterial& material, const SMateria if (queryFeature(EVDF_BLEND_OPERATIONS) && (resetAllRenderstates|| lastmaterial.BlendOperation != material.BlendOperation)) { - if (EBO_NONE) + if (material.BlendOperation==EBO_NONE) pID3DDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, FALSE); else { @@ -2334,7 +2332,7 @@ void CD3D9Driver::setBasicRenderStates(const SMaterial& material, const SMateria } // enable antialiasing - if (AntiAliasing) + if (Params.AntiAlias) { if (material.AntiAliasing & (EAAM_SIMPLE|EAAM_QUALITY)) pID3DDevice->SetRenderState(D3DRS_MULTISAMPLEANTIALIAS, TRUE); @@ -2356,6 +2354,9 @@ void CD3D9Driver::setBasicRenderStates(const SMaterial& material, const SMateria // texture address mode for (u32 st=0; stSetSamplerState(st, D3DSAMP_SRGBTEXTURE, TRUE); + if (resetAllRenderstates || lastmaterial.TextureLayer[st].LODBias != material.TextureLayer[st].LODBias) { const float tmp = material.TextureLayer[st].LODBias * 0.125f; @@ -2560,7 +2561,7 @@ void CD3D9Driver::setRenderStates2DMode(bool alpha, bool texture, bool alphaChan // fix everything that is wrongly set by InitMaterial2D default pID3DDevice->SetTextureStageState(1, D3DTSS_COLOROP, D3DTOP_DISABLE); - pID3DDevice->SetRenderState( D3DRS_STENCILENABLE, FALSE ); + pID3DDevice->SetRenderState(D3DRS_STENCILENABLE, FALSE); } pID3DDevice->SetTransform(D3DTS_WORLD, &UnitMatrixD3D9); @@ -2573,90 +2574,67 @@ void CD3D9Driver::setRenderStates2DMode(bool alpha, bool texture, bool alphaChan m.setTranslation(core::vector3df(-1,1,0)); pID3DDevice->SetTransform(D3DTS_PROJECTION, (D3DMATRIX*)((void*)m.pointer())); + pID3DDevice->SetRenderState(D3DRS_CLIPPING, FALSE); + Transformation3DChanged = false; } if (OverrideMaterial2DEnabled) { OverrideMaterial2D.Lighting=false; - OverrideMaterial2D.ZBuffer=ECFN_NEVER; - OverrideMaterial2D.ZWriteEnable=false; setBasicRenderStates(OverrideMaterial2D, LastMaterial, false); LastMaterial = OverrideMaterial2D; } - u32 current2DSignature = 0; - current2DSignature |= alpha ? EC2D_ALPHA : 0; - current2DSignature |= texture ? EC2D_TEXTURE : 0; - current2DSignature |= alphaChannel ? EC2D_ALPHA_CHANNEL : 0; + // no alphaChannel without texture + alphaChannel &= texture; - if(CurrentRenderMode != ERM_2D || current2DSignature != Cached2DModeSignature) + if (alpha || alphaChannel) { - if (texture) + pID3DDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE); + pID3DDevice->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA); + pID3DDevice->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA); + } + else + pID3DDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, FALSE); + pID3DDevice->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE); + pID3DDevice->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE); + pID3DDevice->SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_DIFFUSE); + if (texture) + { + setTransform(ETS_TEXTURE_0, core::IdentityMatrix); + // Due to the transformation change, the previous line would call a reset each frame + // but we can safely reset the variable as it was false before + Transformation3DChanged=false; + } + if (alphaChannel) + { + pID3DDevice->SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE); + + if (alpha) { - setTransform(ETS_TEXTURE_0, core::IdentityMatrix); - if (alphaChannel) - { - pID3DDevice->SetTextureStageState( 0, D3DTSS_COLOROP, D3DTOP_MODULATE ); - pID3DDevice->SetTextureStageState( 0, D3DTSS_COLORARG1, D3DTA_TEXTURE ); - pID3DDevice->SetTextureStageState( 0, D3DTSS_COLORARG2, D3DTA_DIFFUSE ); - pID3DDevice->SetTextureStageState (0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE ); - - if (alpha) - { - pID3DDevice->SetTextureStageState (0, D3DTSS_ALPHAOP, D3DTOP_MODULATE ); - pID3DDevice->SetTextureStageState (0, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE ); - } - else - { - pID3DDevice->SetTextureStageState (0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1 ); - } - - pID3DDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE); - pID3DDevice->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA); - pID3DDevice->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA ); - } - else - { - pID3DDevice->SetTextureStageState( 0, D3DTSS_COLORARG2, D3DTA_DIFFUSE ); - if (alpha) - { - pID3DDevice->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG2); - pID3DDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE); - pID3DDevice->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA); - pID3DDevice->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA ); - } - else - { - pID3DDevice->SetTextureStageState( 0, D3DTSS_COLOROP, D3DTOP_MODULATE ); - pID3DDevice->SetTextureStageState( 0, D3DTSS_COLORARG1, D3DTA_TEXTURE ); - pID3DDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, FALSE); - } - } + pID3DDevice->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_MODULATE); + pID3DDevice->SetTextureStageState(0, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE); } else { - pID3DDevice->SetTextureStageState( 0, D3DTSS_COLOROP, D3DTOP_MODULATE ); - pID3DDevice->SetTextureStageState( 0, D3DTSS_COLORARG1, D3DTA_TEXTURE ); - pID3DDevice->SetTextureStageState( 0, D3DTSS_COLORARG2, D3DTA_DIFFUSE ); - pID3DDevice->SetTextureStageState( 0, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE); - if (alpha) - { - pID3DDevice->SetTextureStageState( 0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG2); - pID3DDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE); - pID3DDevice->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA); - pID3DDevice->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA ); - } - else - { - pID3DDevice->SetTextureStageState( 0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE); - pID3DDevice->SetTextureStageState( 0, D3DTSS_ALPHAOP, D3DTOP_MODULATE); - pID3DDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, FALSE); - } + pID3DDevice->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1); + } + } + else + { + pID3DDevice->SetTextureStageState(0, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE); + if (alpha) + { + pID3DDevice->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG2); + } + else + { + pID3DDevice->SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE); + pID3DDevice->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_MODULATE); } } CurrentRenderMode = ERM_2D; - Cached2DModeSignature = current2DSignature; } @@ -2767,7 +2745,7 @@ const wchar_t* CD3D9Driver::getName() const //! volume. Then, use IVideoDriver::drawStencilShadow() to visualize the shadow. void CD3D9Driver::drawStencilShadowVolume(const core::vector3df* triangles, s32 count, bool zfail) { - if (!StencilBuffer || !count) + if (!Params.Stencilbuffer || !count) return; setRenderStatesStencilShadowMode(zfail); @@ -2809,7 +2787,7 @@ void CD3D9Driver::drawStencilShadowVolume(const core::vector3df* triangles, s32 void CD3D9Driver::drawStencilShadow(bool clearStencilBuffer, video::SColor leftUpEdge, video::SColor rightUpEdge, video::SColor leftDownEdge, video::SColor rightDownEdge) { - if (!StencilBuffer) + if (!Params.Stencilbuffer) return; S3DVertex vtx[4]; @@ -3183,15 +3161,20 @@ void CD3D9Driver::clearZBuffer() //! Returns an image created from the last rendered frame. -IImage* CD3D9Driver::createScreenShot() +IImage* CD3D9Driver::createScreenShot(video::ECOLOR_FORMAT format, video::E_RENDER_TARGET target) { - HRESULT hr; + if (target != video::ERT_FRAME_BUFFER) + return 0; // query the screen dimensions of the current adapter D3DDISPLAYMODE displayMode; pID3DDevice->GetDisplayMode(0, &displayMode); + if (format==video::ECF_UNKNOWN) + format=video::ECF_A8R8G8B8; + // create the image surface to store the front buffer image [always A8R8G8B8] + HRESULT hr; LPDIRECT3DSURFACE9 lpSurface; if (FAILED(hr = pID3DDevice->CreateOffscreenPlainSurface(displayMode.Width, displayMode.Height, D3DFMT_A8R8G8B8, D3DPOOL_SCRATCH, &lpSurface, 0))) return 0; @@ -3236,41 +3219,42 @@ IImage* CD3D9Driver::createScreenShot() shotSize.Height = core::min_( ScreenSize.Height, (u32)(clientRect.bottom-clientRect.top) ); // this could throw, but we aren't going to worry about that case very much - IImage* newImage = new CImage(ECF_A8R8G8B8, shotSize); + IImage* newImage = createImage(format, shotSize); - // d3d pads the image, so we need to copy the correct number of bytes - u32* dP = (u32*)newImage->lock(); - u8 * sP = (u8 *)lockedRect.pBits; - - // If the display mode format doesn't promise anything about the Alpha value - // and it appears that it's not presenting 255, then we should manually - // set each pixel alpha value to 255. - if(D3DFMT_X8R8G8B8 == displayMode.Format && (0xFF000000 != (*dP & 0xFF000000))) + if (newImage) { - for (u32 y = 0; y < shotSize.Height; ++y) + // d3d pads the image, so we need to copy the correct number of bytes + u32* dP = (u32*)newImage->lock(); + u8 * sP = (u8 *)lockedRect.pBits; + + // If the display mode format doesn't promise anything about the Alpha value + // and it appears that it's not presenting 255, then we should manually + // set each pixel alpha value to 255. + if (D3DFMT_X8R8G8B8 == displayMode.Format && (0xFF000000 != (*dP & 0xFF000000))) { - for(u32 x = 0; x < shotSize.Width; ++x) + for (u32 y = 0; y < shotSize.Height; ++y) { - *dP = *((u32*)sP) | 0xFF000000; - dP++; - sP += 4; + for (u32 x = 0; x < shotSize.Width; ++x) + { + newImage->setPixel(x,y,*((u32*)sP) | 0xFF000000); + sP += 4; + } + + sP += lockedRect.Pitch - (4 * shotSize.Width); } - - sP += lockedRect.Pitch - (4 * shotSize.Width); } - } - else - { - for (u32 y = 0; y < shotSize.Height; ++y) + else { - memcpy(dP, sP, shotSize.Width * 4); - - sP += lockedRect.Pitch; - dP += shotSize.Width; + for (u32 y = 0; y < shotSize.Height; ++y) + { + convertColor(sP, video::ECF_A8R8G8B8, shotSize.Width, dP, format); + sP += lockedRect.Pitch; + dP += shotSize.Width; + } } - } - newImage->unlock(); + newImage->unlock(); + } // we can unlock and release the surface lpSurface->UnlockRect(); @@ -3503,9 +3487,8 @@ IVideoDriver* createDirectX9Driver(const SIrrlichtCreationParameters& params, io::IFileSystem* io, HWND window) { const bool pureSoftware = false; - CD3D9Driver* dx9 = new CD3D9Driver(params.WindowSize, window, params.Fullscreen, params.Stencilbuffer, io, pureSoftware); - if (!dx9->initDriver(params.WindowSize, window, params.Bits, params.Fullscreen, pureSoftware, params.HighPrecisionFPU, - params.Vsync, params.AntiAlias, params.DisplayAdapter)) + CD3D9Driver* dx9 = new CD3D9Driver(params, io); + if (!dx9->initDriver(window, pureSoftware)) { dx9->drop(); dx9 = 0; diff --git a/source/Irrlicht/CD3D9Driver.h b/source/Irrlicht/CD3D9Driver.h index 390db1f4..3346496f 100644 --- a/source/Irrlicht/CD3D9Driver.h +++ b/source/Irrlicht/CD3D9Driver.h @@ -15,6 +15,7 @@ #endif #include "CNullDriver.h" +#include "SIrrCreationParameters.h" #include "IMaterialRendererServices.h" #if defined(__BORLANDC__) || defined (__BCPLUSPLUS__) #include "irrMath.h" // needed by borland for sqrtf define @@ -50,8 +51,7 @@ namespace video friend class CD3D9Texture; //! constructor - CD3D9Driver(const core::dimension2d& screenSize, HWND window, bool fullscreen, - bool stencibuffer, io::IFileSystem* io, bool pureSoftware=false); + CD3D9Driver(const SIrrlichtCreationParameters& params, io::IFileSystem* io); //! destructor virtual ~CD3D9Driver(); @@ -191,9 +191,7 @@ namespace video const core::vector3df& end, SColor color = SColor(255,255,255,255)); //! initialises the Direct3D API - bool initDriver(const core::dimension2d& screenSize, HWND hwnd, - u32 bits, bool fullScreen, bool pureSoftware, - bool highPrecisionFPU, bool vsync, u8 antiAlias, u32 displayAdapter); + bool initDriver(HWND hwnd, bool pureSoftware); //! \return Returns the name of the video driver. Example: In case of the DIRECT3D8 //! driver, it would return "Direct3D8.1". @@ -280,7 +278,7 @@ namespace video virtual void clearZBuffer(); //! Returns an image created from the last rendered frame. - virtual IImage* createScreenShot(); + virtual IImage* createScreenShot(video::ECOLOR_FORMAT format=video::ECF_UNKNOWN, video::E_RENDER_TARGET target=video::ERT_FRAME_BUFFER); //! Set/unset a clipping plane. virtual bool setClipPlane(u32 index, const core::plane3df& plane, bool enable=false); @@ -412,8 +410,6 @@ namespace video SMaterial Material, LastMaterial; bool ResetRenderStates; // bool to make all renderstates be reseted if set. bool Transformation3DChanged; - bool StencilBuffer; - u8 AntiAliasing; const ITexture* CurrentTexture[MATERIAL_MAX_TEXTURES]; bool LastTextureMipMapsAvailable[MATERIAL_MAX_TEXTURES]; core::matrix4 Matrices[ETS_COUNT]; // matrizes of the 3d mode we need to restore when we switch back from the 2d mode. @@ -424,13 +420,14 @@ namespace video IDirect3DSurface9* PrevRenderTarget; core::dimension2d CurrentRendertargetSize; - core::dimension2d CurrentDepthBufferSize; HWND WindowId; core::rect* SceneSourceRect; D3DCAPS9 Caps; + SIrrlichtCreationParameters Params; + E_VERTEX_TYPE LastVertexType; SColorf AmbientLight; @@ -454,17 +451,12 @@ namespace video EC2D_ALPHA_CHANNEL = 0x4 }; - u32 Cached2DModeSignature; - ECOLOR_FORMAT ColorFormat; D3DFORMAT D3DColorFormat; bool DeviceLost; - bool Fullscreen; bool DriverWasReset; bool OcclusionQuerySupport; bool AlphaToCoverageSupport; - - u32 DisplayAdapter; }; diff --git a/source/Irrlicht/CD3D9Texture.cpp b/source/Irrlicht/CD3D9Texture.cpp index aff46232..d5d2750e 100644 --- a/source/Irrlicht/CD3D9Texture.cpp +++ b/source/Irrlicht/CD3D9Texture.cpp @@ -312,9 +312,9 @@ bool CD3D9Texture::createTexture(u32 flags, IImage * image) { LPDIRECT3D9 intf = Driver->getExposedVideoData().D3D9.D3D9; D3DDISPLAYMODE d3ddm; - intf->GetAdapterDisplayMode(Driver->DisplayAdapter, &d3ddm); + intf->GetAdapterDisplayMode(Driver->Params.DisplayAdapter, &d3ddm); - if (D3D_OK==intf->CheckDeviceFormat(Driver->DisplayAdapter,D3DDEVTYPE_HAL,d3ddm.Format,D3DUSAGE_AUTOGENMIPMAP,D3DRTYPE_TEXTURE,format)) + if (D3D_OK==intf->CheckDeviceFormat(Driver->Params.DisplayAdapter,D3DDEVTYPE_HAL,d3ddm.Format,D3DUSAGE_AUTOGENMIPMAP,D3DRTYPE_TEXTURE,format)) { usage = D3DUSAGE_AUTOGENMIPMAP; HardwareMipMaps = true; diff --git a/source/Irrlicht/CDMFLoader.cpp b/source/Irrlicht/CDMFLoader.cpp index c8491cb8..cbd93483 100644 --- a/source/Irrlicht/CDMFLoader.cpp +++ b/source/Irrlicht/CDMFLoader.cpp @@ -29,7 +29,6 @@ #include "irrString.h" #include "irrMath.h" #include "dmfsupport.h" -#include "CImage.h" namespace irr { @@ -285,14 +284,14 @@ IAnimatedMesh* CDMFLoader::createMesh(io::IReadFile* file) if (color.getAlpha()!=255 && materiali[i].textureBlend==4) driver->setTextureCreationFlag(video::ETCF_ALWAYS_32_BIT,true); - video::CImage *immagine= new video::CImage(video::ECF_A8R8G8B8, + video::IImage *immagine= driver->createImage(video::ECF_A8R8G8B8, core::dimension2d(8,8)); immagine->fill(color); tex = driver->addTexture("", immagine); immagine->drop(); //to support transparent materials - if(color.getAlpha()!=255 && materiali[i].textureBlend==4) + if (color.getAlpha()!=255 && materiali[i].textureBlend==4) { mat.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL; mat.MaterialTypeParam =(((f32) (color.getAlpha()-1))/255.0f); diff --git a/source/Irrlicht/CFileSystem.cpp b/source/Irrlicht/CFileSystem.cpp index 35fd1ae9..8bdb8a0b 100644 --- a/source/Irrlicht/CFileSystem.cpp +++ b/source/Irrlicht/CFileSystem.cpp @@ -129,7 +129,7 @@ IReadFile* CFileSystem::createMemoryReadFile(void* memory, s32 len, return 0; else return new CMemoryFile(memory, len, fileName, deleteMemoryWhenDropped); -} + } //! Creates an IReadFile interface for reading files inside files @@ -321,9 +321,9 @@ bool CFileSystem::changeArchivePassword(const path& filename, const core::string { // TODO: This should go into a path normalization method // We need to check for directory names with trailing slash and without - const core::stringc absPath = getAbsolutePath(filename); - const core::stringc arcPath = FileArchives[idx]->getFileList()->getPath(); - if ((absPath == arcPath) || ((absPath+"/") == arcPath)) + const path absPath = getAbsolutePath(filename); + const path arcPath = FileArchives[idx]->getFileList()->getPath(); + if ((absPath == arcPath) || ((absPath+_IRR_TEXT("/")) == arcPath)) { if (password.size()) FileArchives[idx]->Password=password; @@ -533,7 +533,8 @@ bool CFileSystem::changeWorkingDirectoryTo(const io::path& newDirectory) if (FileSystemType != FILESYSTEM_NATIVE) { WorkingDirectory[FILESYSTEM_VIRTUAL] = newDirectory; - flattenFilename(WorkingDirectory[FILESYSTEM_VIRTUAL], ""); + // is this empty string constant really intended? + flattenFilename(WorkingDirectory[FILESYSTEM_VIRTUAL], _IRR_TEXT("")); success = 1; } else @@ -591,7 +592,7 @@ io::path CFileSystem::getAbsolutePath(const io::path& filename) const return io::path(fpath); } if (filename[filename.size()-1]=='/') - return io::path(p)+"/"; + return io::path(p)+_IRR_TEXT("/"); else return io::path(p); #else @@ -613,7 +614,7 @@ io::path CFileSystem::getFileDir(const io::path& filename) const if ((u32)lastSlash < filename.size()) return filename.subString(0, lastSlash); else - return "."; + return _IRR_TEXT("."); } @@ -666,7 +667,7 @@ io::path& CFileSystem::flattenFilename(io::path& directory, const io::path& root { subdir = directory.subString(lastpos, pos - lastpos + 1); - if (subdir == "../") + if (subdir == _IRR_TEXT("../")) { if (lastWasRealDir) { @@ -679,11 +680,11 @@ io::path& CFileSystem::flattenFilename(io::path& directory, const io::path& root lastWasRealDir=false; } } - else if (subdir == "/") + else if (subdir == _IRR_TEXT("/")) { dir = root; } - else if (subdir != "./" ) + else if (subdir != _IRR_TEXT("./")) { dir.append(subdir); lastWasRealDir=true; @@ -699,36 +700,36 @@ io::path& CFileSystem::flattenFilename(io::path& directory, const io::path& root //! Get the relative filename, relative to the given directory path CFileSystem::getRelativeFilename(const path& filename, const path& directory) const { - io::path path, file, ext; - core::splitFilename(getAbsolutePath(filename), &path, &file, &ext); + io::path path1, file, ext; + core::splitFilename(getAbsolutePath(filename), &path1, &file, &ext); io::path path2(getAbsolutePath(directory)); core::list list1, list2; - path.split(list1, "/\\", 2); - path2.split(list2, "/\\", 2); + path1.split(list1, _IRR_TEXT("/\\"), 2); + path2.split(list2, _IRR_TEXT("/\\"), 2); u32 i=0; core::list::ConstIterator it1,it2; it1=list1.begin(); it2=list2.begin(); - for (; iaddItem(Path + "..", 0, 0, true, 0); + r->addItem(Path + _IRR_TEXT(".."), 0, 0, true, 0); //! We use the POSIX compliant methods instead of scandir DIR* dirHandle=opendir(Path.c_str()); @@ -835,10 +836,10 @@ IFileList* CFileSystem::createFileList() SFileListEntry e3; //! PWD - r->addItem(Path + ".", 0, 0, true, 0); + r->addItem(Path + _IRR_TEXT("."), 0, 0, true, 0); //! parent - r->addItem(Path + "..", 0, 0, true, 0); + r->addItem(Path + _IRR_TEXT(".."), 0, 0, true, 0); //! merge archives for (u32 i=0; i < FileArchives.size(); ++i) diff --git a/source/Irrlicht/CGUIColorSelectDialog.cpp b/source/Irrlicht/CGUIColorSelectDialog.cpp index cb7e3aed..072b20b5 100644 --- a/source/Irrlicht/CGUIColorSelectDialog.cpp +++ b/source/Irrlicht/CGUIColorSelectDialog.cpp @@ -15,7 +15,6 @@ #include "IGUISpriteBank.h" #include "IFileList.h" #include "os.h" -#include "CImage.h" #include "fast_atof.h" namespace irr @@ -177,7 +176,9 @@ CGUIColorSelectDialog::~CGUIColorSelectDialog() void CGUIColorSelectDialog::buildColorRing( const core::dimension2d & dim, s32 supersample, const video::SColor& borderColor ) { const core::dimension2d d(dim.Width * supersample, dim.Height * supersample); - video::CImage *RawTexture = new video::CImage(video::ECF_A8R8G8B8, d); + video::IVideoDriver* driver = Environment->getVideoDriver(); + + video::IImage *RawTexture = driver->createImage(video::ECF_A8R8G8B8, d); RawTexture->fill ( 0x00808080 ); @@ -270,14 +271,12 @@ void CGUIColorSelectDialog::buildColorRing( const core::dimension2d & dim, if ( supersample > 1 ) { - video::CImage * filter = new video::CImage(video::ECF_A8R8G8B8, dim ); + video::IImage * filter = driver->createImage(video::ECF_A8R8G8B8, dim ); RawTexture->copyToScalingBoxFilter(filter); RawTexture->drop(); RawTexture = filter; } - video::IVideoDriver* driver = Environment->getVideoDriver(); - bool generateMipLevels = driver->getTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS); driver->setTextureCreationFlag( video::ETCF_CREATE_MIP_MAPS, false); diff --git a/source/Irrlicht/CGUIEnvironment.cpp b/source/Irrlicht/CGUIEnvironment.cpp index 6ed59628..a3350347 100644 --- a/source/Irrlicht/CGUIEnvironment.cpp +++ b/source/Irrlicht/CGUIEnvironment.cpp @@ -575,7 +575,11 @@ bool CGUIEnvironment::postEventFromUser(const SEvent& event) break; case EET_KEY_INPUT_EVENT: { - // send focus changing event + if (Focus && Focus->OnEvent(event)) + return true; + + // For keys we handle the event before changing focus to give elements the chance for catching the TAB + // Send focus changing event if (event.EventType == EET_KEY_INPUT_EVENT && event.KeyInput.PressedDown && event.KeyInput.Key == KEY_TAB) @@ -587,11 +591,7 @@ bool CGUIEnvironment::postEventFromUser(const SEvent& event) return true; } } - if (Focus) - { - _IRR_IMPLEMENT_MANAGED_MARSHALLING_BUGFIX; - return Focus->OnEvent(event); - } + } break; default: diff --git a/source/Irrlicht/CGUIFont.cpp b/source/Irrlicht/CGUIFont.cpp index edfeb515..616951cd 100644 --- a/source/Irrlicht/CGUIFont.cpp +++ b/source/Irrlicht/CGUIFont.cpp @@ -11,7 +11,6 @@ #include "IReadFile.h" #include "IVideoDriver.h" #include "IGUISpriteBank.h" -#include "CImage.h" namespace irr { @@ -254,7 +253,7 @@ bool CGUIFont::loadTexture(video::IImage* image, const io::path& name) switch(image->getColorFormat()) { case video::ECF_R5G6B5: - tmpImage = new video::CImage(video::ECF_A1R5G5B5,image->getDimension()); + tmpImage = Driver->createImage(video::ECF_A1R5G5B5,image->getDimension()); image->copyTo(tmpImage); deleteTmpImage=true; break; @@ -262,7 +261,7 @@ bool CGUIFont::loadTexture(video::IImage* image, const io::path& name) case video::ECF_A8R8G8B8: break; case video::ECF_R8G8B8: - tmpImage = new video::CImage(video::ECF_A8R8G8B8,image->getDimension()); + tmpImage = Driver->createImage(video::ECF_A8R8G8B8,image->getDimension()); image->copyTo(tmpImage); deleteTmpImage=true; break; diff --git a/source/Irrlicht/CGUITabControl.cpp b/source/Irrlicht/CGUITabControl.cpp index ec82664f..a6c611fc 100644 --- a/source/Irrlicht/CGUITabControl.cpp +++ b/source/Irrlicht/CGUITabControl.cpp @@ -254,10 +254,6 @@ void CGUITabControl::refreshSprites() //! Adds a tab IGUITab* CGUITabControl::addTab(const wchar_t* caption, s32 id) { - IGUISkin* skin = Environment->getSkin(); - if (!skin) - return 0; - CGUITab* tab = new CGUITab(Tabs.size(), Environment, this, calcTabPos(), id); tab->setText(caption); @@ -315,6 +311,59 @@ void CGUITabControl::addTab(CGUITab* tab) } } +//! Insert the tab at the given index +IGUITab* CGUITabControl::insertTab(s32 idx, const wchar_t* caption, s32 id) +{ + if ( idx < 0 || idx > (s32)Tabs.size() ) // idx == Tabs.size() is indeed ok here as core::array can handle that + return NULL; + + CGUITab* tab = new CGUITab(idx, Environment, this, calcTabPos(), id); + + tab->setText(caption); + tab->setAlignment(EGUIA_UPPERLEFT, EGUIA_LOWERRIGHT, EGUIA_UPPERLEFT, EGUIA_LOWERRIGHT); + tab->setVisible(false); + Tabs.insert(tab, (u32)idx); + + if (ActiveTab == -1) + { + ActiveTab = 0; + tab->setVisible(true); + } + + for ( u32 i=(u32)idx+1; i < Tabs.size(); ++i ) + { + Tabs[i]->setNumber(i); + } + + recalculateScrollBar(); + + return tab; +} + +//! Removes a tab from the tabcontrol +void CGUITabControl::removeTab(s32 idx) +{ + if ( idx < 0 || idx >= (s32)Tabs.size() ) + return; + + Tabs[(u32)idx]->drop(); + Tabs.erase((u32)idx); + for ( u32 i=(u32)idx; i < Tabs.size(); ++i ) + { + Tabs[i]->setNumber(i); + } +} + +//! Clears the tabcontrol removing all tabs +void CGUITabControl::clear() +{ + for (u32 i=0; idrop(); + } + Tabs.clear(); +} //! Returns amount of tabs in the tabcontrol s32 CGUITabControl::getTabCount() const @@ -368,9 +417,15 @@ bool CGUITabControl::OnEvent(const SEvent& event) // todo: dragging tabs around return true; case EMIE_LMOUSE_LEFT_UP: - if (selectTab(core::position2d(event.MouseInput.X, event.MouseInput.Y))) + { + s32 idx = getTabAt(event.MouseInput.X, event.MouseInput.Y); + if ( idx >= 0 ) + { + setActiveTab(idx); return true; + } break; + } default: break; } @@ -402,7 +457,7 @@ void CGUITabControl::scrollRight() recalculateScrollBar(); } -s32 CGUITabControl::calcTabWidth(s32 pos, IGUIFont* font, const wchar_t* text, bool withScrollControl) +s32 CGUITabControl::calcTabWidth(s32 pos, IGUIFont* font, const wchar_t* text, bool withScrollControl) const { if ( !font ) return 0; @@ -477,55 +532,6 @@ bool CGUITabControl::needScrollControl(s32 startIndex, bool withScrollControl) } -bool CGUITabControl::selectTab(core::position2d p) -{ - IGUISkin* skin = Environment->getSkin(); - IGUIFont* font = skin->getFont(); - - core::rect frameRect(AbsoluteRect); - - if ( VerticalAlignment == EGUIA_UPPERLEFT ) - { - frameRect.UpperLeftCorner.Y += 2; - frameRect.LowerRightCorner.Y = frameRect.UpperLeftCorner.Y + TabHeight; - } - else - { - frameRect.UpperLeftCorner.Y = frameRect.LowerRightCorner.Y - TabHeight; - } - - s32 pos = frameRect.UpperLeftCorner.X + 2; - - if (!frameRect.isPointInside(p)) - return false; - - for (s32 i=CurrentScrollTabIndex; i<(s32)Tabs.size(); ++i) - { - // get Text - const wchar_t* text = 0; - if (Tabs[i]) - text = Tabs[i]->getText(); - - // get text length - s32 len = calcTabWidth(pos, font, text, true); - if ( ScrollControl && pos+len > UpButton->getAbsolutePosition().UpperLeftCorner.X - 2 ) - return false; - - frameRect.UpperLeftCorner.X = pos; - frameRect.LowerRightCorner.X = frameRect.UpperLeftCorner.X + len; - - pos += len; - - if (frameRect.isPointInside(p)) - { - setActiveTab(i); - return true; - } - } - return false; -} - - core::rect CGUITabControl::calcTabPos() { core::rect r; @@ -863,6 +869,54 @@ EGUI_ALIGNMENT CGUITabControl::getTabVerticalAlignment() const } +s32 CGUITabControl::getTabAt(s32 xpos, s32 ypos) const +{ + core::position2di p(xpos, ypos); + IGUISkin* skin = Environment->getSkin(); + IGUIFont* font = skin->getFont(); + + core::rect frameRect(AbsoluteRect); + + if ( VerticalAlignment == EGUIA_UPPERLEFT ) + { + frameRect.UpperLeftCorner.Y += 2; + frameRect.LowerRightCorner.Y = frameRect.UpperLeftCorner.Y + TabHeight; + } + else + { + frameRect.UpperLeftCorner.Y = frameRect.LowerRightCorner.Y - TabHeight; + } + + s32 pos = frameRect.UpperLeftCorner.X + 2; + + if (!frameRect.isPointInside(p)) + return -1; + + for (s32 i=CurrentScrollTabIndex; i<(s32)Tabs.size(); ++i) + { + // get Text + const wchar_t* text = 0; + if (Tabs[i]) + text = Tabs[i]->getText(); + + // get text length + s32 len = calcTabWidth(pos, font, text, true); + if ( ScrollControl && pos+len > UpButton->getAbsolutePosition().UpperLeftCorner.X - 2 ) + return -1; + + frameRect.UpperLeftCorner.X = pos; + frameRect.LowerRightCorner.X = frameRect.UpperLeftCorner.X + len; + + pos += len; + + if (frameRect.isPointInside(p)) + { + return i; + } + } + return -1; +} + //! Returns which tab is currently active s32 CGUITabControl::getActiveTab() const { diff --git a/source/Irrlicht/CGUITabControl.h b/source/Irrlicht/CGUITabControl.h index 15be4368..b51d28bd 100644 --- a/source/Irrlicht/CGUITabControl.h +++ b/source/Irrlicht/CGUITabControl.h @@ -97,6 +97,15 @@ namespace gui //! Adds a tab that has already been created virtual void addTab(CGUITab* tab); + //! Insert the tab at the given index + virtual IGUITab* insertTab(s32 idx, const wchar_t* caption, s32 id=-1); + + //! Removes a tab from the tabcontrol + virtual void removeTab(s32 idx); + + //! Clears the tabcontrol removing all tabs + virtual void clear(); + //! Returns amount of tabs in the tabcontrol virtual s32 getTabCount() const; @@ -112,6 +121,9 @@ namespace gui //! Returns which tab is currently active virtual s32 getActiveTab() const; + //! get the the id of the tab at the given absolute coordinates + virtual s32 getTabAt(s32 xpos, s32 ypos) const; + //! called if an event happened. virtual bool OnEvent(const SEvent& event); @@ -156,18 +168,17 @@ namespace gui private: - bool selectTab(core::position2d p); void scrollLeft(); void scrollRight(); bool needScrollControl( s32 startIndex=0, bool withScrollControl=false ); - s32 calcTabWidth(s32 pos, IGUIFont* font, const wchar_t* text, bool withScrollControl ); + s32 calcTabWidth(s32 pos, IGUIFont* font, const wchar_t* text, bool withScrollControl ) const; core::rect calcTabPos(); void recalculateScrollButtonPlacement(); void recalculateScrollBar(); void refreshSprites(); - core::array Tabs; + core::array Tabs; // CGUITab* because we need setNumber (which is certainly not nice) s32 ActiveTab; bool Border; bool FillBackground; diff --git a/source/Irrlicht/CGeometryCreator.cpp b/source/Irrlicht/CGeometryCreator.cpp index 84682bbb..9e2ef238 100644 --- a/source/Irrlicht/CGeometryCreator.cpp +++ b/source/Irrlicht/CGeometryCreator.cpp @@ -8,7 +8,6 @@ #include "SMesh.h" #include "IMesh.h" #include "IVideoDriver.h" -#include "CImage.h" #include "os.h" namespace irr @@ -265,7 +264,7 @@ IMesh* CGeometryCreator::createTerrainMesh(video::IImage* texture, { c8 textureName[64]; // create texture for this block - video::IImage* img = new video::CImage(texture->getColorFormat(), core::dimension2d(core::floor32(blockSize.Width*thRel.X), core::floor32(blockSize.Height*thRel.Y))); + video::IImage* img = driver->createImage(texture->getColorFormat(), core::dimension2d(core::floor32(blockSize.Width*thRel.X), core::floor32(blockSize.Height*thRel.Y))); texture->copyTo(img, core::position2di(0,0), core::recti( core::position2d(core::floor32(processed.X*thRel.X), core::floor32(processed.Y*thRel.Y)), core::dimension2d(core::floor32(blockSize.Width*thRel.X), core::floor32(blockSize.Height*thRel.Y))), 0); diff --git a/source/Irrlicht/CImage.cpp b/source/Irrlicht/CImage.cpp index 4e8b4e4e..24067656 100644 --- a/source/Irrlicht/CImage.cpp +++ b/source/Irrlicht/CImage.cpp @@ -451,55 +451,5 @@ inline SColor CImage::getPixelBox( s32 x, s32 y, s32 fx, s32 fy, s32 bias ) cons } -// Methods for Software drivers, non-virtual and not necessary to copy into other image classes -//! draws a rectangle -void CImage::drawRectangle(const core::rect& rect, const SColor &color) -{ - Blit(color.getAlpha() == 0xFF ? BLITTER_COLOR : BLITTER_COLOR_ALPHA, - this, 0, &rect.UpperLeftCorner, 0, &rect, color.color); -} - - -//! draws a line from to with color -void CImage::drawLine(const core::position2d& from, const core::position2d& to, const SColor &color) -{ - AbsRectangle clip; - GetClip( clip, this ); - - core::position2d p[2]; - - if ( ClipLine( clip, p[0], p[1], from, to ) ) - { - u32 alpha = extractAlpha( color.color ); - - switch ( Format ) - { - case ECF_A1R5G5B5: - if ( alpha == 256 ) - { - RenderLine16_Decal( this, p[0], p[1], video::A8R8G8B8toA1R5G5B5( color.color ) ); - } - else - { - RenderLine16_Blend( this, p[0], p[1], video::A8R8G8B8toA1R5G5B5( color.color ), alpha >> 3 ); - } - break; - case ECF_A8R8G8B8: - if ( alpha == 256 ) - { - RenderLine32_Decal( this, p[0], p[1], color.color ); - } - else - { - RenderLine32_Blend( this, p[0], p[1], color.color, alpha ); - } - break; - default: - break; - } - } -} - - } // end namespace video } // end namespace irr diff --git a/source/Irrlicht/CImage.h b/source/Irrlicht/CImage.h index 6645a29e..3bae6760 100644 --- a/source/Irrlicht/CImage.h +++ b/source/Irrlicht/CImage.h @@ -103,12 +103,6 @@ public: //! fills the surface with given color virtual void fill(const SColor &color); - //! draws a rectangle - void drawRectangle(const core::rect& rect, const SColor &color); - - //! draws a line from to - void drawLine(const core::position2d& from, const core::position2d& to, const SColor &color); - private: //! assumes format and size has been set and creates the rest diff --git a/source/Irrlicht/CImageLoaderDDS.cpp b/source/Irrlicht/CImageLoaderDDS.cpp index 8cd4c429..2a94dc66 100644 --- a/source/Irrlicht/CImageLoaderDDS.cpp +++ b/source/Irrlicht/CImageLoaderDDS.cpp @@ -28,21 +28,21 @@ namespace irr namespace video { +namespace +{ + /*! DDSDecodePixelFormat() determines which pixel format the dds texture is in */ void DDSDecodePixelFormat( ddsBuffer *dds, eDDSPixelFormat *pf ) { - u32 fourCC; - - /* dummy check */ if( dds == NULL || pf == NULL ) return; /* extract fourCC */ - fourCC = dds->pixelFormat.fourCC; + const u32 fourCC = dds->pixelFormat.fourCC; /* test it */ if( fourCC == 0 ) @@ -62,7 +62,6 @@ void DDSDecodePixelFormat( ddsBuffer *dds, eDDSPixelFormat *pf ) } - /*! DDSGetInfo() extracts relevant info from a dds texture, returns 0 on success @@ -93,7 +92,6 @@ s32 DDSGetInfo( ddsBuffer *dds, s32 *width, s32 *height, eDDSPixelFormat *pf ) } - /*! DDSGetColorBlockColors() extracts colors from a dds color block @@ -188,14 +186,13 @@ void DDSGetColorBlockColors( ddsColorBlock *block, ddsColor colors[ 4 ] ) } - /* DDSDecodeColorBlock() decodes a dds color block fixme: make endian-safe */ -static void DDSDecodeColorBlock( u32 *pixel, ddsColorBlock *block, s32 width, u32 colors[ 4 ] ) +void DDSDecodeColorBlock( u32 *pixel, ddsColorBlock *block, s32 width, u32 colors[ 4 ] ) { s32 r, n; u32 bits; @@ -246,13 +243,11 @@ static void DDSDecodeColorBlock( u32 *pixel, ddsColorBlock *block, s32 width, u3 } - /* DDSDecodeAlphaExplicit() decodes a dds explicit alpha block */ - -static void DDSDecodeAlphaExplicit( u32 *pixel, ddsAlphaBlockExplicit *alphaBlock, s32 width, u32 alphaZero ) +void DDSDecodeAlphaExplicit( u32 *pixel, ddsAlphaBlockExplicit *alphaBlock, s32 width, u32 alphaZero ) { s32 row, pix; u16 word; @@ -279,7 +274,6 @@ static void DDSDecodeAlphaExplicit( u32 *pixel, ddsAlphaBlockExplicit *alphaBloc *pixel |= *((u32*) &color); word >>= 4; /* move next bits to lowest 4 */ pixel++; /* move to next pixel in the row */ - } } } @@ -290,16 +284,14 @@ static void DDSDecodeAlphaExplicit( u32 *pixel, ddsAlphaBlockExplicit *alphaBloc DDSDecodeAlpha3BitLinear() decodes interpolated alpha block */ - -static void DDSDecodeAlpha3BitLinear( u32 *pixel, ddsAlphaBlock3BitLinear *alphaBlock, s32 width, u32 alphaZero ) +void DDSDecodeAlpha3BitLinear( u32 *pixel, ddsAlphaBlock3BitLinear *alphaBlock, s32 width, u32 alphaZero ) { - s32 row, pix; - u32 stuff; - u8 bits[ 4 ][ 4 ]; - u16 alphas[ 8 ]; - ddsColor aColors[ 4 ][ 4 ]; - + s32 row, pix; + u32 stuff; + u8 bits[ 4 ][ 4 ]; + u16 alphas[ 8 ]; + ddsColor aColors[ 4 ][ 4 ]; /* get initial alphas */ alphas[ 0 ] = alphaBlock->alpha0; @@ -397,18 +389,16 @@ static void DDSDecodeAlpha3BitLinear( u32 *pixel, ddsAlphaBlock3BitLinear *alpha } - /* DDSDecompressDXT1() decompresses a dxt1 format texture */ s32 DDSDecompressDXT1( ddsBuffer *dds, s32 width, s32 height, u8 *pixels ) { - s32 x, y, xBlocks, yBlocks; - u32 *pixel; - ddsColorBlock *block; - ddsColor colors[ 4 ]; - + s32 x, y, xBlocks, yBlocks; + u32 *pixel; + ddsColorBlock *block; + ddsColor colors[ 4 ]; /* setup */ xBlocks = width / 4; @@ -418,7 +408,7 @@ s32 DDSDecompressDXT1( ddsBuffer *dds, s32 width, s32 height, u8 *pixels ) for( y = 0; y < yBlocks; y++ ) { /* 8 bytes per block */ - block = (ddsColorBlock*) ((u32) dds->data + y * xBlocks * 8); + block = (ddsColorBlock*) (dds->data + y * xBlocks * 8); /* walk x */ for( x = 0; x < xBlocks; x++, block++ ) @@ -434,7 +424,6 @@ s32 DDSDecompressDXT1( ddsBuffer *dds, s32 width, s32 height, u8 *pixels ) } - /* DDSDecompressDXT3() decompresses a dxt3 format texture @@ -442,12 +431,11 @@ decompresses a dxt3 format texture s32 DDSDecompressDXT3( ddsBuffer *dds, s32 width, s32 height, u8 *pixels ) { - s32 x, y, xBlocks, yBlocks; - u32 *pixel, alphaZero; - ddsColorBlock *block; - ddsAlphaBlockExplicit *alphaBlock; - ddsColor colors[ 4 ]; - + s32 x, y, xBlocks, yBlocks; + u32 *pixel, alphaZero; + ddsColorBlock *block; + ddsAlphaBlockExplicit *alphaBlock; + ddsColor colors[ 4 ]; /* setup */ xBlocks = width / 4; @@ -464,7 +452,7 @@ s32 DDSDecompressDXT3( ddsBuffer *dds, s32 width, s32 height, u8 *pixels ) for( y = 0; y < yBlocks; y++ ) { /* 8 bytes per block, 1 block for alpha, 1 block for color */ - block = (ddsColorBlock*) ((u32) dds->data + y * xBlocks * 16); + block = (ddsColorBlock*) (dds->data + y * xBlocks * 16); /* walk x */ for( x = 0; x < xBlocks; x++, block++ ) @@ -490,19 +478,17 @@ s32 DDSDecompressDXT3( ddsBuffer *dds, s32 width, s32 height, u8 *pixels ) } - /* DDSDecompressDXT5() decompresses a dxt5 format texture */ s32 DDSDecompressDXT5( ddsBuffer *dds, s32 width, s32 height, u8 *pixels ) { - s32 x, y, xBlocks, yBlocks; - u32 *pixel, alphaZero; - ddsColorBlock *block; - ddsAlphaBlock3BitLinear *alphaBlock; - ddsColor colors[ 4 ]; - + s32 x, y, xBlocks, yBlocks; + u32 *pixel, alphaZero; + ddsColorBlock *block; + ddsAlphaBlock3BitLinear *alphaBlock; + ddsColor colors[ 4 ]; /* setup */ xBlocks = width / 4; @@ -519,7 +505,7 @@ s32 DDSDecompressDXT5( ddsBuffer *dds, s32 width, s32 height, u8 *pixels ) for( y = 0; y < yBlocks; y++ ) { /* 8 bytes per block, 1 block for alpha, 1 block for color */ - block = (ddsColorBlock*) ((u32) dds->data + y * xBlocks * 16); + block = (ddsColorBlock*) (dds->data + y * xBlocks * 16); /* walk x */ for( x = 0; x < xBlocks; x++, block++ ) @@ -545,62 +531,49 @@ s32 DDSDecompressDXT5( ddsBuffer *dds, s32 width, s32 height, u8 *pixels ) } - /* DDSDecompressDXT2() decompresses a dxt2 format texture (fixme: un-premultiply alpha) */ s32 DDSDecompressDXT2( ddsBuffer *dds, s32 width, s32 height, u8 *pixels ) { - s32 r; - - /* decompress dxt3 first */ - r = DDSDecompressDXT3( dds, width, height, pixels ); + const s32 r = DDSDecompressDXT3( dds, width, height, pixels ); /* return to sender */ return r; } - /* DDSDecompressDXT4() decompresses a dxt4 format texture (fixme: un-premultiply alpha) */ s32 DDSDecompressDXT4( ddsBuffer *dds, s32 width, s32 height, u8 *pixels ) { - s32 r; - - /* decompress dxt5 first */ - r = DDSDecompressDXT5( dds, width, height, pixels ); + const s32 r = DDSDecompressDXT5( dds, width, height, pixels ); /* return to sender */ return r; } - /* DDSDecompressARGB8888() decompresses an argb 8888 format texture */ s32 DDSDecompressARGB8888( ddsBuffer *dds, s32 width, s32 height, u8 *pixels ) { - s32 x, y; - u8 *in, *out; - - /* setup */ - in = dds->data; - out = pixels; + u8* in = dds->data; + u8* out = pixels; /* walk y */ - for( y = 0; y < height; y++ ) + for(s32 y = 0; y < height; y++) { /* walk x */ - for( x = 0; x < width; x++ ) + for(s32 x = 0; x < width; x++) { *out++ = *in++; *out++ = *in++; @@ -614,20 +587,18 @@ s32 DDSDecompressARGB8888( ddsBuffer *dds, s32 width, s32 height, u8 *pixels ) } - /* DDSDecompress() decompresses a dds texture into an rgba image buffer, returns 0 on success */ s32 DDSDecompress( ddsBuffer *dds, u8 *pixels ) { - s32 width, height, r; - eDDSPixelFormat pf; - + s32 width, height; + eDDSPixelFormat pf; /* get dds info */ - r = DDSGetInfo( dds, &width, &height, &pf ); - if( r ) + s32 r = DDSGetInfo( dds, &width, &height, &pf ); + if ( r ) return r; /* decompress */ @@ -669,6 +640,9 @@ s32 DDSDecompress( ddsBuffer *dds, u8 *pixels ) return r; } +} // end anonymous namespace + + //! returns true if the file maybe is able to be loaded by this class //! based on the file extension (e.g. ".tga") bool CImageLoaderDDS::isALoadableFileExtension(const io::path& filename) const @@ -677,8 +651,6 @@ bool CImageLoaderDDS::isALoadableFileExtension(const io::path& filename) const } - - //! returns true if the file maybe is able to be loaded by this class bool CImageLoaderDDS::isALoadableFileFormat(io::IReadFile* file) const { @@ -691,11 +663,10 @@ bool CImageLoaderDDS::isALoadableFileFormat(io::IReadFile* file) const s32 width, height; eDDSPixelFormat pixelFormat; - return 0 == DDSGetInfo( &header, &width, &height, &pixelFormat); + return (0 == DDSGetInfo( &header, &width, &height, &pixelFormat)); } - //! creates a surface from the file IImage* CImageLoaderDDS::loadImage(io::IReadFile* file) const { @@ -727,7 +698,7 @@ IImage* CImageLoaderDDS::loadImage(io::IReadFile* file) const } -//! creates a loader which is able to load tgas +//! creates a loader which is able to load dds images IImageLoader* createImageLoaderDDS() { return new CImageLoaderDDS(); @@ -738,4 +709,3 @@ IImageLoader* createImageLoaderDDS() } // end namespace irr #endif - diff --git a/source/Irrlicht/CImageLoaderDDS.h b/source/Irrlicht/CImageLoaderDDS.h index c085a2ab..aa78ed8a 100644 --- a/source/Irrlicht/CImageLoaderDDS.h +++ b/source/Irrlicht/CImageLoaderDDS.h @@ -9,13 +9,13 @@ #include "IImageLoader.h" - +#define _IRR_COMPILE_WITH_DDS_LOADER_ namespace irr { namespace video { -#if defined(_IRR_COMPILE_WITH_DDS_LOADER_) || defined(_IRR_COMPILE_WITH_DDS_WRITER_) +#if defined(_IRR_COMPILE_WITH_DDS_LOADER_) // byte-align structures #if defined(_MSC_VER) || defined(__BORLANDC__) || defined (__BCPLUSPLUS__) @@ -195,13 +195,6 @@ struct ddsColor } PACK_STRUCT; - -/* public functions */ -s32 DDSGetInfo( ddsBuffer *dds, s32 *width, s32 *height, eDDSPixelFormat *pf ); -s32 DDSDecompress( ddsBuffer *dds, u8 *pixels ); - - - /* endian tomfoolery */ typedef union { @@ -295,7 +288,7 @@ floatSwapUnion; #ifdef _IRR_COMPILE_WITH_DDS_LOADER_ /*! - Surface Loader for targa images + Surface Loader for DDS images */ class CImageLoaderDDS : public IImageLoader { @@ -315,7 +308,7 @@ private: }; -#endif // compiled with loader +#endif // compiled with DDS loader } // end namespace video } // end namespace irr diff --git a/source/Irrlicht/CIrrDeviceFB.cpp b/source/Irrlicht/CIrrDeviceFB.cpp index b680be26..f95338aa 100644 --- a/source/Irrlicht/CIrrDeviceFB.cpp +++ b/source/Irrlicht/CIrrDeviceFB.cpp @@ -56,7 +56,7 @@ CIrrDeviceFB::CIrrDeviceFB(const SIrrlichtCreationParameters& params) linuxversion += " "; linuxversion += FBInfo.machine; - Operator = new COSOperator(linuxversion.c_str()); + Operator = new COSOperator(linuxversion); os::Printer::log(linuxversion.c_str(), ELL_INFORMATION); // create window diff --git a/source/Irrlicht/CIrrDeviceLinux.cpp b/source/Irrlicht/CIrrDeviceLinux.cpp index 885dc450..e0c058e6 100644 --- a/source/Irrlicht/CIrrDeviceLinux.cpp +++ b/source/Irrlicht/CIrrDeviceLinux.cpp @@ -106,7 +106,7 @@ CIrrDeviceLinux::CIrrDeviceLinux(const SIrrlichtCreationParameters& param) linuxversion += " "; linuxversion += LinuxInfo.machine; - Operator = new COSOperator(linuxversion.c_str(), this); + Operator = new COSOperator(linuxversion, this); os::Printer::log(linuxversion.c_str(), ELL_INFORMATION); // create keymap @@ -416,6 +416,11 @@ bool CIrrDeviceLinux::createWindow() GLX_SAMPLE_BUFFERS_SGIS, 1, GLX_SAMPLES_SGIS, CreationParams.AntiAlias, // 18,19 #endif +//#ifdef GL_ARB_framebuffer_sRGB +// GLX_FRAMEBUFFER_SRGB_CAPABLE_ARB, CreationParams.HandleSRGB, +//#elif defined(GL_EXT_framebuffer_sRGB) +// GLX_FRAMEBUFFER_SRGB_CAPABLE_EXT, CreationParams.HandleSRGB, +//#endif GLX_STEREO, CreationParams.Stereobuffer?True:False, None }; @@ -550,7 +555,7 @@ bool CIrrDeviceLinux::createWindow() // attribute array for the draw buffer int visualAttrBuffer[] = { - GLX_RGBA, GL_TRUE, + GLX_RGBA, GLX_USE_GL, GLX_RED_SIZE, 4, GLX_GREEN_SIZE, 4, GLX_BLUE_SIZE, 4, @@ -562,6 +567,11 @@ bool CIrrDeviceLinux::createWindow() // GLX_USE_GL, which is silently ignored by glXChooseVisual CreationParams.Doublebuffer?GLX_DOUBLEBUFFER:GLX_USE_GL, // 14 CreationParams.Stereobuffer?GLX_STEREO:GLX_USE_GL, // 15 +//#ifdef GL_ARB_framebuffer_sRGB +// CreationParams.HandleSRGB?GLX_FRAMEBUFFER_SRGB_CAPABLE_ARB:GLX_USE_GL, +//#elif defined(GL_EXT_framebuffer_sRGB) +// CreationParams.HandleSRGB?GLX_FRAMEBUFFER_SRGB_CAPABLE_EXT:GLX_USE_GL, +//#endif None }; @@ -618,7 +628,7 @@ bool CIrrDeviceLinux::createWindow() } #ifdef _DEBUG else - os::Printer::log("Visual chosen: ", core::stringc(static_cast(visual->visualid)).c_str(), ELL_INFORMATION); + os::Printer::log("Visual chosen: ", core::stringc(static_cast(visual->visualid)).c_str(), ELL_DEBUG); #endif // create color map @@ -851,7 +861,8 @@ bool CIrrDeviceLinux::run() #ifdef _IRR_COMPILE_WITH_X11_ - static_cast(CursorControl)->update(); + if ( CursorControl ) + static_cast(CursorControl)->update(); if ((CreationParams.DriverType != video::EDT_NULL) && display) { diff --git a/source/Irrlicht/CIrrDeviceSDL.cpp b/source/Irrlicht/CIrrDeviceSDL.cpp index eb738ae1..ae640628 100644 --- a/source/Irrlicht/CIrrDeviceSDL.cpp +++ b/source/Irrlicht/CIrrDeviceSDL.cpp @@ -94,7 +94,7 @@ CIrrDeviceSDL::CIrrDeviceSDL(const SIrrlichtCreationParameters& param) sdlversion += "."; sdlversion += Info.version.patch; - Operator = new COSOperator(sdlversion.c_str()); + Operator = new COSOperator(sdlversion); os::Printer::log(sdlversion.c_str(), ELL_INFORMATION); // create keymap diff --git a/source/Irrlicht/CIrrDeviceWin32.cpp b/source/Irrlicht/CIrrDeviceWin32.cpp index 649bff50..efc27399 100644 --- a/source/Irrlicht/CIrrDeviceWin32.cpp +++ b/source/Irrlicht/CIrrDeviceWin32.cpp @@ -26,12 +26,12 @@ #pragma comment(lib, "dinput8.lib") #pragma comment(lib, "dxguid.lib") #endif -#endif #else #ifdef _MSC_VER #pragma comment(lib, "winmm.lib") #endif #endif +#endif namespace irr { @@ -919,7 +919,7 @@ CIrrDeviceWin32::CIrrDeviceWin32(const SIrrlichtCreationParameters& params) // get windows version and create OS operator core::stringc winversion; getWindowsVersion(winversion); - Operator = new COSOperator(winversion.c_str()); + Operator = new COSOperator(winversion); os::Printer::log(winversion.c_str(), ELL_INFORMATION); // get handle to exe file diff --git a/source/Irrlicht/CIrrDeviceWinCE.cpp b/source/Irrlicht/CIrrDeviceWinCE.cpp index cfa06757..1439b289 100644 --- a/source/Irrlicht/CIrrDeviceWinCE.cpp +++ b/source/Irrlicht/CIrrDeviceWinCE.cpp @@ -346,7 +346,7 @@ CIrrDeviceWinCE::CIrrDeviceWinCE(const SIrrlichtCreationParameters& params) core::stringc winversion; getWindowsVersion(winversion); - Operator = new COSOperator(winversion.c_str()); + Operator = new COSOperator(winversion); os::Printer::log(winversion.c_str(), ELL_INFORMATION); HINSTANCE hInstance = GetModuleHandle(0); diff --git a/source/Irrlicht/CMY3DMeshFileLoader.cpp b/source/Irrlicht/CMY3DMeshFileLoader.cpp index ca70d371..5026c582 100644 --- a/source/Irrlicht/CMY3DMeshFileLoader.cpp +++ b/source/Irrlicht/CMY3DMeshFileLoader.cpp @@ -17,8 +17,6 @@ #include "SMeshBuffer.h" #include "IReadFile.h" #include "IAttributes.h" -#include "CImage.h" -#include "CColorConverter.h" #include "CMY3DHelper.h" #include "os.h" diff --git a/source/Irrlicht/CMY3DMeshFileLoader.h b/source/Irrlicht/CMY3DMeshFileLoader.h index 78145636..040b2732 100644 --- a/source/Irrlicht/CMY3DMeshFileLoader.h +++ b/source/Irrlicht/CMY3DMeshFileLoader.h @@ -6,7 +6,6 @@ // I (Nikolaus Gebhardt) did some few changes to this: // - replaced logging calls to their os:: counterparts // - removed some logging calls -// - enabled image dropping of CImage again, because that bug has been fixed now // - removed setTexture path and replaced it with the directory of the mesh // - added EAMT_MY3D file type // - fixed a memory leak when decompressing RLE data. diff --git a/source/Irrlicht/CMeshManipulator.cpp b/source/Irrlicht/CMeshManipulator.cpp index 7b60c8f4..44d2dc26 100644 --- a/source/Irrlicht/CMeshManipulator.cpp +++ b/source/Irrlicht/CMeshManipulator.cpp @@ -48,29 +48,38 @@ void CMeshManipulator::flipSurfaces(scene::IMesh* mesh) const { IMeshBuffer* buffer = mesh->getMeshBuffer(b); const u32 idxcnt = buffer->getIndexCount(); - u16* idx = buffer->getIndices(); - s32 tmp; - - for (u32 i=0; igetIndexType() == video::EIT_16BIT) { - tmp = idx[i+1]; - idx[i+1] = idx[i+2]; - idx[i+2] = tmp; + u16* idx = buffer->getIndices(); + for (u32 i=0; i(buffer->getIndices()); + for (u32 i=0; i +void recalculateNormalsT(IMeshBuffer* buffer, bool smooth, bool angleWeighted) { - if (!buffer) - return; - const u32 vtxcnt = buffer->getVertexCount(); const u32 idxcnt = buffer->getIndexCount(); - const u16* idx = buffer->getIndices(); + const T* idx = reinterpret_cast(buffer->getIndices()); if (!smooth) { @@ -90,7 +99,7 @@ void CMeshManipulator::recalculateNormals(IMeshBuffer* buffer, bool smooth, bool u32 i; for ( i = 0; i!= vtxcnt; ++i ) - buffer->getNormal(i).set( 0.f, 0.f, 0.f ); + buffer->getNormal(i).set(0.f, 0.f, 0.f); for ( i=0; igetNormal(i).normalize(); } } +} + + +//! Recalculates all normals of the mesh buffer. +/** \param buffer: Mesh buffer on which the operation is performed. */ +void CMeshManipulator::recalculateNormals(IMeshBuffer* buffer, bool smooth, bool angleWeighted) const +{ + if (!buffer) + return; + + if (buffer->getIndexType()==video::EIT_16BIT) + recalculateNormalsT(buffer, smooth, angleWeighted); + else + recalculateNormalsT(buffer, smooth, angleWeighted); +} //! Recalculates all normals of the mesh. @@ -126,168 +150,433 @@ void CMeshManipulator::recalculateNormals(scene::IMesh* mesh, bool smooth, bool } -//! Recalculates tangents, requires a tangent mesh +namespace +{ +void calculateTangents( + core::vector3df& normal, + core::vector3df& tangent, + core::vector3df& binormal, + const core::vector3df& vt1, const core::vector3df& vt2, const core::vector3df& vt3, // vertices + const core::vector2df& tc1, const core::vector2df& tc2, const core::vector2df& tc3) // texture coords +{ + // choose one of them: + //#define USE_NVIDIA_GLH_VERSION // use version used by nvidia in glh headers + #define USE_IRR_VERSION + +#ifdef USE_IRR_VERSION + + core::vector3df v1 = vt1 - vt2; + core::vector3df v2 = vt3 - vt1; + normal = v2.crossProduct(v1); + normal.normalize(); + + // binormal + + f32 deltaX1 = tc1.X - tc2.X; + f32 deltaX2 = tc3.X - tc1.X; + binormal = (v1 * deltaX2) - (v2 * deltaX1); + binormal.normalize(); + + // tangent + + f32 deltaY1 = tc1.Y - tc2.Y; + f32 deltaY2 = tc3.Y - tc1.Y; + tangent = (v1 * deltaY2) - (v2 * deltaY1); + tangent.normalize(); + + // adjust + + core::vector3df txb = tangent.crossProduct(binormal); + if (txb.dotProduct(normal) < 0.0f) + { + tangent *= -1.0f; + binormal *= -1.0f; + } + +#endif // USE_IRR_VERSION + +#ifdef USE_NVIDIA_GLH_VERSION + + tangent.set(0,0,0); + binormal.set(0,0,0); + + core::vector3df v1(vt2.X - vt1.X, tc2.X - tc1.X, tc2.Y - tc1.Y); + core::vector3df v2(vt3.X - vt1.X, tc3.X - tc1.X, tc3.Y - tc1.Y); + + core::vector3df txb = v1.crossProduct(v2); + if ( !core::iszero ( txb.X ) ) + { + tangent.X = -txb.Y / txb.X; + binormal.X = -txb.Z / txb.X; + } + + v1.X = vt2.Y - vt1.Y; + v2.X = vt3.Y - vt1.Y; + txb = v1.crossProduct(v2); + + if ( !core::iszero ( txb.X ) ) + { + tangent.Y = -txb.Y / txb.X; + binormal.Y = -txb.Z / txb.X; + } + + v1.X = vt2.Z - vt1.Z; + v2.X = vt3.Z - vt1.Z; + txb = v1.crossProduct(v2); + + if ( !core::iszero ( txb.X ) ) + { + tangent.Z = -txb.Y / txb.X; + binormal.Z = -txb.Z / txb.X; + } + + tangent.normalize(); + binormal.normalize(); + + normal = tangent.crossProduct(binormal); + normal.normalize(); + + binormal = tangent.crossProduct(normal); + binormal.normalize(); + + core::plane3d pl(vt1, vt2, vt3); + + if(normal.dotProduct(pl.Normal) < 0.0f ) + normal *= -1.0f; + +#endif // USE_NVIDIA_GLH_VERSION +} + + +//! Recalculates tangents for a tangent mesh buffer +template +void recalculateTangentsT(IMeshBuffer* buffer, bool recalculateNormals, bool smooth, bool angleWeighted) +{ + if (!buffer || (buffer->getVertexType()!= video::EVT_TANGENTS)) + return; + + const u32 vtxCnt = buffer->getVertexCount(); + const u32 idxCnt = buffer->getIndexCount(); + + T* idx = reinterpret_cast(buffer->getIndices()); + video::S3DVertexTangents* v = + (video::S3DVertexTangents*)buffer->getVertices(); + + if (smooth) + { + u32 i; + + for ( i = 0; i!= vtxCnt; ++i ) + { + if (recalculateNormals) + v[i].Normal.set( 0.f, 0.f, 0.f ); + v[i].Tangent.set( 0.f, 0.f, 0.f ); + v[i].Binormal.set( 0.f, 0.f, 0.f ); + } + + //Each vertex gets the sum of the tangents and binormals from the faces around it + for ( i=0; igetVertexType() == video::EVT_TANGENTS)) + { + if (buffer->getIndexType() == video::EIT_16BIT) + recalculateTangentsT(buffer, recalculateNormals, smooth, angleWeighted); + else + recalculateTangentsT(buffer, recalculateNormals, smooth, angleWeighted); + } +} + + +//! Recalculates tangents for all tangent mesh buffers void CMeshManipulator::recalculateTangents(IMesh* mesh, bool recalculateNormals, bool smooth, bool angleWeighted) const { - if (!mesh || !mesh->getMeshBufferCount() || (mesh->getMeshBuffer(0)->getVertexType()!= video::EVT_TANGENTS)) + if (!mesh) return; const u32 meshBufferCount = mesh->getMeshBufferCount(); for (u32 b=0; bgetMeshBuffer(b); - const u32 vtxCnt = clone->getVertexCount(); - const u32 idxCnt = clone->getIndexCount(); + recalculateTangents(mesh->getMeshBuffer(b), recalculateNormals, smooth, angleWeighted); + } +} - u16* idx = clone->getIndices(); - video::S3DVertexTangents* v = - (video::S3DVertexTangents*)clone->getVertices(); - if (smooth) +namespace +{ +//! Creates a planar texture mapping on the meshbuffer +template +void makePlanarTextureMappingT(scene::IMeshBuffer* buffer, f32 resolution) +{ + u32 idxcnt = buffer->getIndexCount(); + T* idx = reinterpret_cast(buffer->getIndices()); + + for (u32 i=0; igetPosition(idx[i+0]), buffer->getPosition(idx[i+1]), buffer->getPosition(idx[i+2])); + p.Normal.X = fabsf(p.Normal.X); + p.Normal.Y = fabsf(p.Normal.Y); + p.Normal.Z = fabsf(p.Normal.Z); + // calculate planar mapping worldspace coordinates + + if (p.Normal.X > p.Normal.Y && p.Normal.X > p.Normal.Z) { - u32 i; - - for ( i = 0; i!= vtxCnt; ++i ) + for (u32 o=0; o!=3; ++o) { - if (recalculateNormals) - v[i].Normal.set( 0.f, 0.f, 0.f ); - v[i].Tangent.set( 0.f, 0.f, 0.f ); - v[i].Binormal.set( 0.f, 0.f, 0.f ); + buffer->getTCoords(idx[i+o]).X = buffer->getPosition(idx[i+o]).Y * resolution; + buffer->getTCoords(idx[i+o]).Y = buffer->getPosition(idx[i+o]).Z * resolution; } - - //Each vertex gets the sum of the tangents and binormals from the faces around it - for ( i=0; i p.Normal.X && p.Normal.Y > p.Normal.Z) + { + for (u32 o=0; o!=3; ++o) { - // if this triangle is degenerate, skip it! - if (v[idx[i+0]].Pos == v[idx[i+1]].Pos || - v[idx[i+0]].Pos == v[idx[i+2]].Pos || - v[idx[i+1]].Pos == v[idx[i+2]].Pos - /*|| - v[idx[i+0]].TCoords == v[idx[i+1]].TCoords || - v[idx[i+0]].TCoords == v[idx[i+2]].TCoords || - v[idx[i+1]].TCoords == v[idx[i+2]].TCoords */ - ) - continue; - - //Angle-weighted normals look better, but are slightly more CPU intensive to calculate - core::vector3df weight(1.f,1.f,1.f); - if (angleWeighted) - weight = getAngleWeight(v[i+0].Pos,v[i+1].Pos,v[i+2].Pos); - core::vector3df localNormal; - core::vector3df localTangent; - core::vector3df localBinormal; - - calculateTangents( - localNormal, - localTangent, - localBinormal, - v[idx[i+0]].Pos, - v[idx[i+1]].Pos, - v[idx[i+2]].Pos, - v[idx[i+0]].TCoords, - v[idx[i+1]].TCoords, - v[idx[i+2]].TCoords); - - if (recalculateNormals) - v[idx[i+0]].Normal += localNormal * weight.X; - v[idx[i+0]].Tangent += localTangent * weight.X; - v[idx[i+0]].Binormal += localBinormal * weight.X; - - calculateTangents( - localNormal, - localTangent, - localBinormal, - v[idx[i+1]].Pos, - v[idx[i+2]].Pos, - v[idx[i+0]].Pos, - v[idx[i+1]].TCoords, - v[idx[i+2]].TCoords, - v[idx[i+0]].TCoords); - - if (recalculateNormals) - v[idx[i+1]].Normal += localNormal * weight.Y; - v[idx[i+1]].Tangent += localTangent * weight.Y; - v[idx[i+1]].Binormal += localBinormal * weight.Y; - - calculateTangents( - localNormal, - localTangent, - localBinormal, - v[idx[i+2]].Pos, - v[idx[i+0]].Pos, - v[idx[i+1]].Pos, - v[idx[i+2]].TCoords, - v[idx[i+0]].TCoords, - v[idx[i+1]].TCoords); - - if (recalculateNormals) - v[idx[i+2]].Normal += localNormal * weight.Z; - v[idx[i+2]].Tangent += localTangent * weight.Z; - v[idx[i+2]].Binormal += localBinormal * weight.Z; - } - - // Normalize the tangents and binormals - if (recalculateNormals) - { - for ( i = 0; i!= vtxCnt; ++i ) - v[i].Normal.normalize(); - } - for ( i = 0; i!= vtxCnt; ++i ) - { - v[i].Tangent.normalize(); - v[i].Binormal.normalize(); + buffer->getTCoords(idx[i+o]).X = buffer->getPosition(idx[i+o]).X * resolution; + buffer->getTCoords(idx[i+o]).Y = buffer->getPosition(idx[i+o]).Z * resolution; } } else { - core::vector3df localNormal; - for (u32 i=0; igetTCoords(idx[i+o]).X = buffer->getPosition(idx[i+o]).X * resolution; + buffer->getTCoords(idx[i+o]).Y = buffer->getPosition(idx[i+o]).Y * resolution; } } } } +} + + +//! Creates a planar texture mapping on the meshbuffer +void CMeshManipulator::makePlanarTextureMapping(scene::IMeshBuffer* buffer, f32 resolution) const +{ + if (!buffer) + return; + + if (buffer->getIndexType()==video::EIT_16BIT) + makePlanarTextureMappingT(buffer, resolution); + else + makePlanarTextureMappingT(buffer, resolution); +} + + +//! Creates a planar texture mapping on the mesh +void CMeshManipulator::makePlanarTextureMapping(scene::IMesh* mesh, f32 resolution) const +{ + if (!mesh) + return; + + const u32 bcount = mesh->getMeshBufferCount(); + for ( u32 b=0; bgetMeshBuffer(b), resolution); + } +} + + +namespace +{ +//! Creates a planar texture mapping on the meshbuffer +template +void makePlanarTextureMappingT(scene::IMeshBuffer* buffer, f32 resolutionS, f32 resolutionT, u8 axis, const core::vector3df& offset) +{ + u32 idxcnt = buffer->getIndexCount(); + T* idx = reinterpret_cast(buffer->getIndices()); + + for (u32 i=0; igetTCoords(idx[i+o]).X = 0.5f+(buffer->getPosition(idx[i+o]).Z + offset.Z) * resolutionS; + buffer->getTCoords(idx[i+o]).Y = 0.5f-(buffer->getPosition(idx[i+o]).Y + offset.Y) * resolutionT; + } + } + else if (axis==1) + { + for (u32 o=0; o!=3; ++o) + { + buffer->getTCoords(idx[i+o]).X = 0.5f+(buffer->getPosition(idx[i+o]).X + offset.X) * resolutionS; + buffer->getTCoords(idx[i+o]).Y = 1.f-(buffer->getPosition(idx[i+o]).Z + offset.Z) * resolutionT; + } + } + else if (axis==2) + { + for (u32 o=0; o!=3; ++o) + { + buffer->getTCoords(idx[i+o]).X = 0.5f+(buffer->getPosition(idx[i+o]).X + offset.X) * resolutionS; + buffer->getTCoords(idx[i+o]).Y = 0.5f-(buffer->getPosition(idx[i+o]).Y + offset.Y) * resolutionT; + } + } + } +} +} + + +//! Creates a planar texture mapping on the meshbuffer +void CMeshManipulator::makePlanarTextureMapping(scene::IMeshBuffer* buffer, f32 resolutionS, f32 resolutionT, u8 axis, const core::vector3df& offset) const +{ + if (!buffer) + return; + + if (buffer->getIndexType()==video::EIT_16BIT) + makePlanarTextureMappingT(buffer, resolutionS, resolutionT, axis, offset); + else + makePlanarTextureMappingT(buffer, resolutionS, resolutionT, axis, offset); +} + + +//! Creates a planar texture mapping on the mesh +void CMeshManipulator::makePlanarTextureMapping(scene::IMesh* mesh, f32 resolutionS, f32 resolutionT, u8 axis, const core::vector3df& offset) const +{ + if (!mesh) + return; + + const u32 bcount = mesh->getMeshBufferCount(); + for ( u32 b=0; bgetMeshBuffer(b), resolutionS, resolutionT, axis, offset); + } +} //! Clones a static IMesh into a modifyable SMesh. +// not yet 32bit SMesh* CMeshManipulator::createMeshCopy(scene::IMesh* mesh) const { if (!mesh) @@ -365,101 +654,8 @@ SMesh* CMeshManipulator::createMeshCopy(scene::IMesh* mesh) const } -//! Creates a planar texture mapping on the mesh -void CMeshManipulator::makePlanarTextureMapping(scene::IMesh* mesh, f32 resolution=0.01f) const -{ - if (!mesh) - return; - - const u32 bcount = mesh->getMeshBufferCount(); - for ( u32 b=0; bgetMeshBuffer(b), resolution); - } -} - - -//! Creates a planar texture mapping on the meshbuffer -void CMeshManipulator::makePlanarTextureMapping(scene::IMeshBuffer* buffer, f32 resolution) const -{ - u32 idxcnt = buffer->getIndexCount(); - u16* idx = buffer->getIndices(); - - for (u32 i=0; igetPosition(idx[i+0]), buffer->getPosition(idx[i+1]), buffer->getPosition(idx[i+2])); - p.Normal.X = fabsf(p.Normal.X); - p.Normal.Y = fabsf(p.Normal.Y); - p.Normal.Z = fabsf(p.Normal.Z); - // calculate planar mapping worldspace coordinates - - if (p.Normal.X > p.Normal.Y && p.Normal.X > p.Normal.Z) - { - for (u32 o=0; o!=3; ++o) - { - buffer->getTCoords(idx[i+o]).X = buffer->getPosition(idx[i+o]).Y * resolution; - buffer->getTCoords(idx[i+o]).Y = buffer->getPosition(idx[i+o]).Z * resolution; - } - } - else - if (p.Normal.Y > p.Normal.X && p.Normal.Y > p.Normal.Z) - { - for (u32 o=0; o!=3; ++o) - { - buffer->getTCoords(idx[i+o]).X = buffer->getPosition(idx[i+o]).X * resolution; - buffer->getTCoords(idx[i+o]).Y = buffer->getPosition(idx[i+o]).Z * resolution; - } - } - else - { - for (u32 o=0; o!=3; ++o) - { - buffer->getTCoords(idx[i+o]).X = buffer->getPosition(idx[i+o]).X * resolution; - buffer->getTCoords(idx[i+o]).Y = buffer->getPosition(idx[i+o]).Y * resolution; - } - } - } -} - - -//! Creates a planar texture mapping on the meshbuffer -void CMeshManipulator::makePlanarTextureMapping(scene::IMeshBuffer* buffer, f32 resolutionS, f32 resolutionT, u8 axis, const core::vector3df& offset) const -{ - u32 idxcnt = buffer->getIndexCount(); - u16* idx = buffer->getIndices(); - - for (u32 i=0; igetTCoords(idx[i+o]).X = 0.5f+(buffer->getPosition(idx[i+o]).Z + offset.Z) * resolutionS; - buffer->getTCoords(idx[i+o]).Y = 0.5f-(buffer->getPosition(idx[i+o]).Y + offset.Y) * resolutionT; - } - } - else if (axis==1) - { - for (u32 o=0; o!=3; ++o) - { - buffer->getTCoords(idx[i+o]).X = 0.5f+(buffer->getPosition(idx[i+o]).X + offset.X) * resolutionS; - buffer->getTCoords(idx[i+o]).Y = 1.f-(buffer->getPosition(idx[i+o]).Z + offset.Z) * resolutionT; - } - } - else if (axis==2) - { - for (u32 o=0; o!=3; ++o) - { - buffer->getTCoords(idx[i+o]).X = 0.5f+(buffer->getPosition(idx[i+o]).X + offset.X) * resolutionS; - buffer->getTCoords(idx[i+o]).Y = 0.5f-(buffer->getPosition(idx[i+o]).Y + offset.Y) * resolutionT; - } - } - } -} - - //! Creates a copy of the mesh, which will only consist of unique primitives +// not yet 32bit IMesh* CMeshManipulator::createMeshUniquePrimitives(IMesh* mesh) const { if (!mesh) @@ -562,7 +758,9 @@ IMesh* CMeshManipulator::createMeshUniquePrimitives(IMesh* mesh) const return clone; } + //! Creates a copy of a mesh, which will have identical vertices welded together +// not yet 32bit IMesh* CMeshManipulator::createMeshWelded(IMesh *mesh, f32 tolerance) const { SMesh* clone = new SMesh(); @@ -731,6 +929,7 @@ IMesh* CMeshManipulator::createMeshWelded(IMesh *mesh, f32 tolerance) const //! Creates a copy of the mesh, which will only consist of S3DVertexTangents vertices. +// not yet 32bit IMesh* CMeshManipulator::createMeshWithTangents(IMesh* mesh, bool recalculateNormals, bool smooth, bool angleWeighted, bool calculateTangents) const { if (!mesh) @@ -751,7 +950,7 @@ IMesh* CMeshManipulator::createMeshWithTangents(IMesh* mesh, bool recalculateNor buffer->Material = original->getMaterial(); buffer->Vertices.reallocate(idxCnt); - buffer->Indices.set_used(idxCnt); + buffer->Indices.reallocate(idxCnt); core::map vertMap; int vertLocation; @@ -801,7 +1000,7 @@ IMesh* CMeshManipulator::createMeshWithTangents(IMesh* mesh, bool recalculateNor } // create new indices - buffer->Indices[i] = vertLocation; + buffer->Indices.push_back(vertLocation); } buffer->recalculateBoundingBox(); @@ -819,6 +1018,7 @@ IMesh* CMeshManipulator::createMeshWithTangents(IMesh* mesh, bool recalculateNor //! Creates a copy of the mesh, which will only consist of S3DVertex2TCoords vertices. +// not yet 32bit IMesh* CMeshManipulator::createMeshWith2TCoords(IMesh* mesh) const { if (!mesh) @@ -838,7 +1038,7 @@ IMesh* CMeshManipulator::createMeshWith2TCoords(IMesh* mesh) const SMeshBufferLightMap* buffer = new SMeshBufferLightMap(); buffer->Material = original->getMaterial(); buffer->Vertices.reallocate(idxCnt); - buffer->Indices.set_used(idxCnt); + buffer->Indices.reallocate(idxCnt); core::map vertMap; int vertLocation; @@ -888,7 +1088,7 @@ IMesh* CMeshManipulator::createMeshWith2TCoords(IMesh* mesh) const } // create new indices - buffer->Indices[i] = vertLocation; + buffer->Indices.push_back(vertLocation); } buffer->recalculateBoundingBox(); @@ -901,7 +1101,9 @@ IMesh* CMeshManipulator::createMeshWith2TCoords(IMesh* mesh) const return clone; } + //! Creates a copy of the mesh, which will only consist of S3DVertex vertices. +// not yet 32bit IMesh* CMeshManipulator::createMeshWith1TCoords(IMesh* mesh) const { if (!mesh) @@ -913,14 +1115,14 @@ IMesh* CMeshManipulator::createMeshWith1TCoords(IMesh* mesh) const for (u32 b=0; bgetMeshBuffer(b); + IMeshBuffer* original = mesh->getMeshBuffer(b); const u32 idxCnt = original->getIndexCount(); const u16* idx = original->getIndices(); SMeshBuffer* buffer = new SMeshBuffer(); buffer->Material = original->getMaterial(); buffer->Vertices.reallocate(idxCnt); - buffer->Indices.set_used(idxCnt); + buffer->Indices.reallocate(idxCnt); core::map vertMap; int vertLocation; @@ -969,10 +1171,9 @@ IMesh* CMeshManipulator::createMeshWith1TCoords(IMesh* mesh) const } // create new indices - buffer->Indices[i] = vertLocation; + buffer->Indices.push_back(vertLocation); } buffer->recalculateBoundingBox(); - // add new buffer clone->addMeshBuffer(buffer); buffer->drop(); @@ -983,103 +1184,6 @@ IMesh* CMeshManipulator::createMeshWith1TCoords(IMesh* mesh) const } -void CMeshManipulator::calculateTangents( - core::vector3df& normal, - core::vector3df& tangent, - core::vector3df& binormal, - const core::vector3df& vt1, const core::vector3df& vt2, const core::vector3df& vt3, // vertices - const core::vector2df& tc1, const core::vector2df& tc2, const core::vector2df& tc3) // texture coords -{ - // choose one of them: - //#define USE_NVIDIA_GLH_VERSION // use version used by nvidia in glh headers - #define USE_IRR_VERSION - -#ifdef USE_IRR_VERSION - - core::vector3df v1 = vt1 - vt2; - core::vector3df v2 = vt3 - vt1; - normal = v2.crossProduct(v1); - normal.normalize(); - - // binormal - - f32 deltaX1 = tc1.X - tc2.X; - f32 deltaX2 = tc3.X - tc1.X; - binormal = (v1 * deltaX2) - (v2 * deltaX1); - binormal.normalize(); - - // tangent - - f32 deltaY1 = tc1.Y - tc2.Y; - f32 deltaY2 = tc3.Y - tc1.Y; - tangent = (v1 * deltaY2) - (v2 * deltaY1); - tangent.normalize(); - - // adjust - - core::vector3df txb = tangent.crossProduct(binormal); - if (txb.dotProduct(normal) < 0.0f) - { - tangent *= -1.0f; - binormal *= -1.0f; - } - -#endif // USE_IRR_VERSION - -#ifdef USE_NVIDIA_GLH_VERSION - - tangent.set(0,0,0); - binormal.set(0,0,0); - - core::vector3df v1(vt2.X - vt1.X, tc2.X - tc1.X, tc2.Y - tc1.Y); - core::vector3df v2(vt3.X - vt1.X, tc3.X - tc1.X, tc3.Y - tc1.Y); - - core::vector3df txb = v1.crossProduct(v2); - if ( !core::iszero ( txb.X ) ) - { - tangent.X = -txb.Y / txb.X; - binormal.X = -txb.Z / txb.X; - } - - v1.X = vt2.Y - vt1.Y; - v2.X = vt3.Y - vt1.Y; - txb = v1.crossProduct(v2); - - if ( !core::iszero ( txb.X ) ) - { - tangent.Y = -txb.Y / txb.X; - binormal.Y = -txb.Z / txb.X; - } - - v1.X = vt2.Z - vt1.Z; - v2.X = vt3.Z - vt1.Z; - txb = v1.crossProduct(v2); - - if ( !core::iszero ( txb.X ) ) - { - tangent.Z = -txb.Y / txb.X; - binormal.Z = -txb.Z / txb.X; - } - - tangent.normalize(); - binormal.normalize(); - - normal = tangent.crossProduct(binormal); - normal.normalize(); - - binormal = tangent.crossProduct(normal); - binormal.normalize(); - - core::plane3d pl(vt1, vt2, vt3); - - if(normal.dotProduct(pl.Normal) < 0.0f ) - normal *= -1.0f; - -#endif // USE_NVIDIA_GLH_VERSION -} - - - //! Returns amount of polygons in mesh. s32 CMeshManipulator::getPolyCount(scene::IMesh* mesh) const { diff --git a/source/Irrlicht/CMeshManipulator.h b/source/Irrlicht/CMeshManipulator.h index 2cf9a2e1..e13d783d 100644 --- a/source/Irrlicht/CMeshManipulator.h +++ b/source/Irrlicht/CMeshManipulator.h @@ -43,7 +43,7 @@ public: \param resolution: resolution of the planar mapping. This is the value specifying which is the relation between world space and texture coordinate space. */ - virtual void makePlanarTextureMapping(scene::IMesh* mesh, f32 resolution) const; + virtual void makePlanarTextureMapping(scene::IMesh* mesh, f32 resolution=0.001f) const; //! Creates a planar texture mapping on the meshbuffer virtual void makePlanarTextureMapping(scene::IMeshBuffer* meshbuffer, f32 resolution=0.001f) const; @@ -51,6 +51,12 @@ public: //! Creates a planar texture mapping on the meshbuffer void makePlanarTextureMapping(scene::IMeshBuffer* buffer, f32 resolutionS, f32 resolutionT, u8 axis, const core::vector3df& offset) const; + //! Creates a planar texture mapping on the mesh + void makePlanarTextureMapping(scene::IMesh* mesh, f32 resolutionS, f32 resolutionT, u8 axis, const core::vector3df& offset) const; + + //! Recalculates tangents, requires a tangent mesh buffer + virtual void recalculateTangents(IMeshBuffer* buffer, bool recalculateNormals=false, bool smooth=false, bool angleWeighted=false) const; + //! Recalculates tangents, requires a tangent mesh virtual void recalculateTangents(IMesh* mesh, bool recalculateNormals=false, bool smooth=false, bool angleWeighted=false) const; @@ -77,14 +83,6 @@ public: //! create a new AnimatedMesh and adds the mesh to it virtual IAnimatedMesh * createAnimatedMesh(scene::IMesh* mesh,scene::E_ANIMATED_MESH_TYPE type) const; - -private: - - static void calculateTangents(core::vector3df& normal, - core::vector3df& tangent, - core::vector3df& binormal, - const core::vector3df& vt1, const core::vector3df& vt2, const core::vector3df& vt3, - const core::vector2df& tc1, const core::vector2df& tc2, const core::vector2df& tc3); }; } // end namespace scene diff --git a/source/Irrlicht/CNullDriver.cpp b/source/Irrlicht/CNullDriver.cpp index 5473d3bf..6b1962e6 100644 --- a/source/Irrlicht/CNullDriver.cpp +++ b/source/Irrlicht/CNullDriver.cpp @@ -2306,7 +2306,7 @@ scene::IMeshManipulator* CNullDriver::getMeshManipulator() //! Returns an image created from the last rendered frame. -IImage* CNullDriver::createScreenShot() +IImage* CNullDriver::createScreenShot(video::ECOLOR_FORMAT format, video::E_RENDER_TARGET target) { return 0; } diff --git a/source/Irrlicht/CNullDriver.h b/source/Irrlicht/CNullDriver.h index 102523fe..4ef4db6f 100644 --- a/source/Irrlicht/CNullDriver.h +++ b/source/Irrlicht/CNullDriver.h @@ -580,7 +580,7 @@ namespace video virtual void clearZBuffer(); //! Returns an image created from the last rendered frame. - virtual IImage* createScreenShot(); + virtual IImage* createScreenShot(video::ECOLOR_FORMAT format=video::ECF_UNKNOWN, video::E_RENDER_TARGET target=video::ERT_FRAME_BUFFER); //! Writes the provided image to disk file virtual bool writeImageToFile(IImage* image, const io::path& filename, u32 param = 0); diff --git a/source/Irrlicht/COCTLoader.cpp b/source/Irrlicht/COCTLoader.cpp index ca5a8bbd..1080caac 100644 --- a/source/Irrlicht/COCTLoader.cpp +++ b/source/Irrlicht/COCTLoader.cpp @@ -19,7 +19,6 @@ #include "SAnimatedMesh.h" #include "SMeshBufferLightMap.h" #include "irrString.h" -#include "CImage.h" #include "ISceneManager.h" namespace irr @@ -224,7 +223,7 @@ IAnimatedMesh* COCTLoader::createMesh(io::IReadFile* file) bool oldMipMapState = SceneManager->getVideoDriver()->getTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS); SceneManager->getVideoDriver()->setTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS, false); - video::CImage tmpImage(video::ECF_R8G8B8, lmapsize); + video::IImage* tmpImage = SceneManager->getVideoDriver()->createImage(video::ECF_R8G8B8, lmapsize); for (i = 1; i < (header.numLightmaps + 1); ++i) { core::stringc lightmapname = file->getFileName(); @@ -237,7 +236,7 @@ IAnimatedMesh* COCTLoader::createMesh(io::IReadFile* file) { for (u32 y=0; ysetPixel(x, y, video::SColor(255, lm->data[x][y][2], lm->data[x][y][1], @@ -245,8 +244,9 @@ IAnimatedMesh* COCTLoader::createMesh(io::IReadFile* file) } } - lig[i] = SceneManager->getVideoDriver()->addTexture(lightmapname.c_str(), &tmpImage); + lig[i] = SceneManager->getVideoDriver()->addTexture(lightmapname.c_str(), tmpImage); } + tmpImage->drop(); SceneManager->getVideoDriver()->setTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS, oldMipMapState); // Free stuff diff --git a/source/Irrlicht/COSOperator.cpp b/source/Irrlicht/COSOperator.cpp index 8351e5dc..22e49f0e 100644 --- a/source/Irrlicht/COSOperator.cpp +++ b/source/Irrlicht/COSOperator.cpp @@ -29,14 +29,14 @@ namespace irr #if defined(_IRR_COMPILE_WITH_X11_DEVICE_) // constructor linux -COSOperator::COSOperator(const c8* osversion, CIrrDeviceLinux* device) -: IrrDeviceLinux(device) + COSOperator::COSOperator(const core::stringc& osVersion, CIrrDeviceLinux* device) +: OperatingSystem(osVersion), IrrDeviceLinux(device) { } #endif // constructor -COSOperator::COSOperator(const c8* osVersion) : OperatingSystem(osVersion) +COSOperator::COSOperator(const core::stringc& osVersion) : OperatingSystem(osVersion) { #ifdef _DEBUG setDebugName("COSOperator"); @@ -45,9 +45,9 @@ COSOperator::COSOperator(const c8* osVersion) : OperatingSystem(osVersion) //! returns the current operating system version as string. -const wchar_t* COSOperator::getOperationSystemVersion() const +const core::stringc& COSOperator::getOperatingSystemVersion() const { - return OperatingSystem.c_str(); + return OperatingSystem; } diff --git a/source/Irrlicht/COSOperator.h b/source/Irrlicht/COSOperator.h index ec039d70..e8065973 100644 --- a/source/Irrlicht/COSOperator.h +++ b/source/Irrlicht/COSOperator.h @@ -6,8 +6,6 @@ #define __C_OS_OPERATOR_H_INCLUDED__ #include "IOSOperator.h" -#include "irrString.h" -#include "IrrCompileConfig.h" namespace irr { @@ -21,12 +19,12 @@ public: // constructor #if defined(_IRR_COMPILE_WITH_X11_DEVICE_) - COSOperator(const c8* osversion, CIrrDeviceLinux* device); + COSOperator(const core::stringc& osversion, CIrrDeviceLinux* device); #endif - COSOperator(const c8* osversion); + COSOperator(const core::stringc& osversion); //! returns the current operation system version as string. - virtual const wchar_t* getOperationSystemVersion() const; + virtual const core::stringc& getOperatingSystemVersion() const; //! copies text to the clipboard virtual void copyToClipboard(const c8* text) const; @@ -48,7 +46,7 @@ public: private: - core::stringw OperatingSystem; + core::stringc OperatingSystem; #if defined(_IRR_COMPILE_WITH_X11_DEVICE_) CIrrDeviceLinux * IrrDeviceLinux; diff --git a/source/Irrlicht/COpenGLDriver.cpp b/source/Irrlicht/COpenGLDriver.cpp index a15b2daf..8078b30d 100644 --- a/source/Irrlicht/COpenGLDriver.cpp +++ b/source/Irrlicht/COpenGLDriver.cpp @@ -14,7 +14,6 @@ #include "COpenGLSLMaterialRenderer.h" #include "COpenGLNormalMapRenderer.h" #include "COpenGLParallaxMapRenderer.h" -#include "CImage.h" #include "os.h" #ifdef _IRR_COMPILE_WITH_SDL_DEVICE_ @@ -37,8 +36,7 @@ COpenGLDriver::COpenGLDriver(const irr::SIrrlichtCreationParameters& params, CurrentRenderMode(ERM_NONE), ResetRenderStates(true), Transformation3DChanged(true), AntiAlias(params.AntiAlias), RenderTargetTexture(0), CurrentRendertargetSize(0,0), ColorFormat(ECF_R8G8B8), - CurrentTarget(ERT_FRAME_BUFFER), - Doublebuffer(params.Doublebuffer), Stereo(params.Stereobuffer), + CurrentTarget(ERT_FRAME_BUFFER), Params(params), HDc(0), Window(static_cast(params.WindowId)), Win32Device(device), DeviceType(EIDT_WIN32) { @@ -79,7 +77,7 @@ bool COpenGLDriver::changeRenderContext(const SExposedVideoData& videoData, CIrr } //! inits the open gl driver -bool COpenGLDriver::initDriver(irr::SIrrlichtCreationParameters params, CIrrDeviceWin32* device) +bool COpenGLDriver::initDriver(CIrrDeviceWin32* device) { // Create a window to test antialiasing support const fschar_t* ClassName = __TEXT("GLCIrrDeviceWin32"); @@ -105,11 +103,11 @@ bool COpenGLDriver::initDriver(irr::SIrrlichtCreationParameters params, CIrrDevi RECT clientSize; clientSize.top = 0; clientSize.left = 0; - clientSize.right = params.WindowSize.Width; - clientSize.bottom = params.WindowSize.Height; + clientSize.right = Params.WindowSize.Width; + clientSize.bottom = Params.WindowSize.Height; DWORD style = WS_POPUP; - if (!params.Fullscreen) + if (!Params.Fullscreen) style = WS_SYSMENU | WS_BORDER | WS_CAPTION | WS_CLIPCHILDREN | WS_CLIPSIBLINGS; AdjustWindowRect(&clientSize, style, FALSE); @@ -138,17 +136,17 @@ bool COpenGLDriver::initDriver(irr::SIrrlichtCreationParameters params, CIrrDevi 1, // Version Number PFD_DRAW_TO_WINDOW | // Format Must Support Window PFD_SUPPORT_OPENGL | // Format Must Support OpenGL - (params.Doublebuffer?PFD_DOUBLEBUFFER:0) | // Must Support Double Buffering - (params.Stereobuffer?PFD_STEREO:0), // Must Support Stereo Buffer + (Params.Doublebuffer?PFD_DOUBLEBUFFER:0) | // Must Support Double Buffering + (Params.Stereobuffer?PFD_STEREO:0), // Must Support Stereo Buffer PFD_TYPE_RGBA, // Request An RGBA Format - params.Bits, // Select Our Color Depth + Params.Bits, // Select Our Color Depth 0, 0, 0, 0, 0, 0, // Color Bits Ignored 0, // No Alpha Buffer 0, // Shift Bit Ignored 0, // No Accumulation Buffer 0, 0, 0, 0, // Accumulation Bits Ignored - params.ZBufferBits, // Z-Buffer (Depth Buffer) - params.Stencilbuffer ? 1 : 0, // Stencil Buffer Depth + Params.ZBufferBits, // Z-Buffer (Depth Buffer) + Params.Stencilbuffer ? 1 : 0, // Stencil Buffer Depth 0, // No Auxiliary Buffer PFD_MAIN_PLANE, // Main Drawing Layer 0, // Reserved @@ -161,10 +159,10 @@ bool COpenGLDriver::initDriver(irr::SIrrlichtCreationParameters params, CIrrDevi { if (i == 1) { - if (params.Stencilbuffer) + if (Params.Stencilbuffer) { os::Printer::log("Cannot create a GL device with stencil buffer, disabling stencil shadows.", ELL_WARNING); - params.Stencilbuffer = false; + Params.Stencilbuffer = false; pfd.cStencilBits = 0; } else @@ -177,7 +175,7 @@ bool COpenGLDriver::initDriver(irr::SIrrlichtCreationParameters params, CIrrDevi } if (i == 3) { - if (params.Bits!=16) + if (Params.Bits!=16) pfd.cDepthBits = 16; else continue; @@ -186,7 +184,7 @@ bool COpenGLDriver::initDriver(irr::SIrrlichtCreationParameters params, CIrrDevi if (i == 4) { // try single buffer - if (params.Doublebuffer) + if (Params.Doublebuffer) pfd.dwFlags &= ~PFD_DOUBLEBUFFER; else continue; @@ -268,15 +266,15 @@ bool COpenGLDriver::initDriver(irr::SIrrlichtCreationParameters params, CIrrDevi f32 fAttributes[] = {0.0, 0.0}; s32 iAttributes[] = { - WGL_DRAW_TO_WINDOW_ARB,GL_TRUE, - WGL_SUPPORT_OPENGL_ARB,GL_TRUE, + WGL_DRAW_TO_WINDOW_ARB,1, + WGL_SUPPORT_OPENGL_ARB,1, WGL_ACCELERATION_ARB,WGL_FULL_ACCELERATION_ARB, - WGL_COLOR_BITS_ARB,(params.Bits==32) ? 24 : 15, - WGL_ALPHA_BITS_ARB,(params.Bits==32) ? 8 : 1, - WGL_DEPTH_BITS_ARB,params.ZBufferBits, // 10,11 - WGL_STENCIL_BITS_ARB,(params.Stencilbuffer) ? 1 : 0, - WGL_DOUBLE_BUFFER_ARB,(params.Doublebuffer) ? GL_TRUE : GL_FALSE, - WGL_STEREO_ARB,(params.Stereobuffer) ? GL_TRUE : GL_FALSE, + WGL_COLOR_BITS_ARB,(Params.Bits==32) ? 24 : 15, + WGL_ALPHA_BITS_ARB,(Params.Bits==32) ? 8 : 1, + WGL_DEPTH_BITS_ARB,Params.ZBufferBits, // 10,11 + WGL_STENCIL_BITS_ARB,Params.Stencilbuffer ? 1 : 0, + WGL_DOUBLE_BUFFER_ARB,Params.Doublebuffer ? 1 : 0, + WGL_STEREO_ARB,Params.Stereobuffer ? 1 : 0, WGL_PIXEL_TYPE_ARB, WGL_TYPE_RGBA_ARB, #ifdef WGL_ARB_multisample WGL_SAMPLES_ARB,AntiAlias, // 20,21 @@ -288,21 +286,26 @@ bool COpenGLDriver::initDriver(irr::SIrrlichtCreationParameters params, CIrrDevi WGL_SAMPLES_3DFX,AntiAlias, // 20,21 WGL_SAMPLE_BUFFERS_3DFX, 1, #endif -#if 0 #ifdef WGL_ARB_framebuffer_sRGB - WGL_FRAMEBUFFER_SRGB_CAPABLE_ARB, GL_TRUE, + WGL_FRAMEBUFFER_SRGB_CAPABLE_ARB, Params.HandleSRGB ? 1:0, #elif defined(WGL_EXT_framebuffer_sRGB) - WGL_FRAMEBUFFER_SRGB_CAPABLE_EXT, GL_TRUE, + WGL_FRAMEBUFFER_SRGB_CAPABLE_EXT, Params.HandleSRGB ? 1:0, #endif -#endif - 0,0 +// WGL_DEPTH_FLOAT_EXT, 1, + 0,0,0,0 }; + int iAttrSize = sizeof(iAttributes)/sizeof(int); + const bool framebuffer_srgb_supported = ((wglExtensions.find("WGL_ARB_framebuffer_sRGB") != -1) || + (wglExtensions.find("WGL_EXT_framebuffer_sRGB") != -1)); + if (!framebuffer_srgb_supported) + { + memmove(&iAttributes[24],&iAttributes[26],sizeof(int)*(iAttrSize-26)); + iAttrSize -= 2; + } if (!multi_sample_supported) { - iAttributes[20]=0; - iAttributes[21]=0; - iAttributes[22]=0; - iAttributes[23]=0; + memmove(&iAttributes[20],&iAttributes[24],sizeof(int)*(iAttrSize-24)); + iAttrSize -= 4; } s32 rv=0; @@ -313,7 +316,7 @@ bool COpenGLDriver::initDriver(irr::SIrrlichtCreationParameters params, CIrrDevi UINT numFormats=0; const BOOL valid = wglChoosePixelFormat_ARB(HDc,iAttributes,fAttributes,1,&pixelFormat,&numFormats); - if (valid && numFormats>0) + if (valid && numFormats) rv = pixelFormat; else iAttributes[21] -= 1; @@ -350,10 +353,10 @@ bool COpenGLDriver::initDriver(irr::SIrrlichtCreationParameters params, CIrrDevi { if (i == 1) { - if (params.Stencilbuffer) + if (Params.Stencilbuffer) { os::Printer::log("Cannot create a GL device with stencil buffer, disabling stencil shadows.", ELL_WARNING); - params.Stencilbuffer = false; + Params.Stencilbuffer = false; pfd.cStencilBits = 0; } else @@ -366,7 +369,7 @@ bool COpenGLDriver::initDriver(irr::SIrrlichtCreationParameters params, CIrrDevi } if (i == 3) { - if (params.Bits!=16) + if (Params.Bits!=16) pfd.cDepthBits = 16; else continue; @@ -391,6 +394,7 @@ bool COpenGLDriver::initDriver(irr::SIrrlichtCreationParameters params, CIrrDevi os::Printer::log("Cannot set the pixel format.", ELL_ERROR); return false; } + os::Printer::log("Pixel Format", core::stringc(PixelFormat).c_str(), ELL_DEBUG); // create rendering context #ifdef WGL_ARB_create_context @@ -446,9 +450,9 @@ bool COpenGLDriver::initDriver(irr::SIrrlichtCreationParameters params, CIrrDevi ColorFormat = ECF_R5G6B5; } - genericDriverInit(params.WindowSize, params.Stencilbuffer); + genericDriverInit(); - extGlSwapInterval(params.Vsync ? 1 : 0); + extGlSwapInterval(Params.Vsync ? 1 : 0); return true; } @@ -465,14 +469,13 @@ COpenGLDriver::COpenGLDriver(const SIrrlichtCreationParameters& params, CurrentRenderMode(ERM_NONE), ResetRenderStates(true), Transformation3DChanged(true), AntiAlias(params.AntiAlias), RenderTargetTexture(0), CurrentRendertargetSize(0,0), ColorFormat(ECF_R8G8B8), - CurrentTarget(ERT_FRAME_BUFFER), - Doublebuffer(params.Doublebuffer), Stereo(params.Stereobuffer), + CurrentTarget(ERT_FRAME_BUFFER), Params(params), OSXDevice(device), DeviceType(EIDT_OSX) { #ifdef _DEBUG setDebugName("COpenGLDriver"); #endif - genericDriverInit(params.WindowSize, params.Stencilbuffer); + genericDriverInit(); } #endif @@ -488,8 +491,7 @@ COpenGLDriver::COpenGLDriver(const SIrrlichtCreationParameters& params, CurrentRenderMode(ERM_NONE), ResetRenderStates(true), Transformation3DChanged(true), AntiAlias(params.AntiAlias), RenderTargetTexture(0), CurrentRendertargetSize(0,0), ColorFormat(ECF_R8G8B8), - CurrentTarget(ERT_FRAME_BUFFER), - Doublebuffer(params.Doublebuffer), Stereo(params.Stereobuffer), + CurrentTarget(ERT_FRAME_BUFFER), Params(params), X11Device(device), DeviceType(EIDT_X11) { #ifdef _DEBUG @@ -532,18 +534,18 @@ bool COpenGLDriver::changeRenderContext(const SExposedVideoData& videoData, CIrr //! inits the open gl driver -bool COpenGLDriver::initDriver(irr::SIrrlichtCreationParameters params, CIrrDeviceLinux* device) +bool COpenGLDriver::initDriver(CIrrDeviceLinux* device) { ExposedData.OpenGLLinux.X11Context = glXGetCurrentContext(); ExposedData.OpenGLLinux.X11Display = glXGetCurrentDisplay(); - ExposedData.OpenGLLinux.X11Window = (unsigned long)params.WindowId; + ExposedData.OpenGLLinux.X11Window = (unsigned long)Params.WindowId; Drawable = glXGetCurrentDrawable(); X11Display = (Display*)ExposedData.OpenGLLinux.X11Display; - genericDriverInit(params.WindowSize, params.Stencilbuffer); + genericDriverInit(); // set vsync - extGlSwapInterval(params.Vsync ? 1 : 0); + extGlSwapInterval(Params.Vsync ? 1 : 0); return true; } @@ -561,15 +563,14 @@ COpenGLDriver::COpenGLDriver(const SIrrlichtCreationParameters& params, CurrentRenderMode(ERM_NONE), ResetRenderStates(true), Transformation3DChanged(true), AntiAlias(params.AntiAlias), RenderTargetTexture(0), CurrentRendertargetSize(0,0), ColorFormat(ECF_R8G8B8), - CurrentTarget(ERT_FRAME_BUFFER), - Doublebuffer(params.Doublebuffer), Stereo(params.Stereobuffer), + CurrentTarget(ERT_FRAME_BUFFER), Params(params), SDLDevice(device), DeviceType(EIDT_SDL) { #ifdef _DEBUG setDebugName("COpenGLDriver"); #endif - genericDriverInit(params.WindowSize, params.Stencilbuffer); + genericDriverInit(); } #endif // _IRR_COMPILE_WITH_SDL_DEVICE_ @@ -612,7 +613,7 @@ COpenGLDriver::~COpenGLDriver() // METHODS // ----------------------------------------------------------------------- -bool COpenGLDriver::genericDriverInit(const core::dimension2d& screenSize, bool stencilBuffer) +bool COpenGLDriver::genericDriverInit() { Name=L"OpenGL "; Name.append(glGetString(GL_VERSION)); @@ -634,7 +635,7 @@ bool COpenGLDriver::genericDriverInit(const core::dimension2d& screenSize, for (i=0; i& screenSize, os::Printer::log("GLSL not available.", ELL_INFORMATION); DriverAttributes->setAttribute("MaxTextures", MaxTextureUnits); DriverAttributes->setAttribute("MaxSupportedTextures", MaxSupportedTextures); -// DriverAttributes->setAttribute("MaxLights", MaxLights); + DriverAttributes->setAttribute("MaxLights", MaxLights); DriverAttributes->setAttribute("MaxAnisotropy", MaxAnisotropy); DriverAttributes->setAttribute("MaxUserClipPlanes", MaxUserClipPlanes); DriverAttributes->setAttribute("MaxAuxBuffers", MaxAuxBuffers); @@ -662,7 +663,7 @@ bool COpenGLDriver::genericDriverInit(const core::dimension2d& screenSize, glPixelStorei(GL_PACK_ALIGNMENT, 1); // Reset The Current Viewport - glViewport(0, 0, screenSize.Width, screenSize.Height); + glViewport(0, 0, Params.WindowSize.Width, Params.WindowSize.Height); UserClipPlanes.reallocate(MaxUserClipPlanes); for (i=0; i& screenSize, #endif glLightModeli(GL_LIGHT_MODEL_LOCAL_VIEWER, 1); + Params.HandleSRGB &= ((FeatureAvailable[IRR_ARB_framebuffer_sRGB] || FeatureAvailable[IRR_EXT_framebuffer_sRGB]) && + FeatureAvailable[IRR_EXT_texture_sRGB]); +#if defined(GL_ARB_framebuffer_sRGB) + if (Params.HandleSRGB) + glEnable(GL_FRAMEBUFFER_SRGB); +#elif defined(GL_EXT_framebuffer_sRGB) + if (Params.HandleSRGB) + glEnable(GL_FRAMEBUFFER_SRGB_EXT); +#endif + // This is a fast replacement for NORMALIZE_NORMALS // if ((Version>101) || FeatureAvailable[IRR_EXT_rescale_normal]) // glEnable(GL_RESCALE_NORMAL_EXT); @@ -2505,6 +2516,10 @@ void COpenGLDriver::setRenderStates3DMode() glLoadMatrixf(Matrices[ETS_PROJECTION].pointer()); ResetRenderStates = true; +#ifdef GL_EXT_clip_volume_hint + if (FeatureAvailable[IRR_EXT_clip_volume_hint]) + glHint(GL_CLIP_VOLUME_CLIPPING_HINT_EXT, GL_NICEST); +#endif } if (ResetRenderStates || LastMaterial != Material) @@ -2924,7 +2939,7 @@ void COpenGLDriver::setBasicRenderStates(const SMaterial& material, const SMater if (queryFeature(EVDF_BLEND_OPERATIONS) && (resetAllRenderStates|| lastmaterial.BlendOperation != material.BlendOperation)) { - if (EBO_NONE) + if (material.BlendOperation==EBO_NONE) glDisable(GL_BLEND); else { @@ -3159,16 +3174,22 @@ void COpenGLDriver::setRenderStates2DMode(bool alpha, bool texture, bool alphaCh LastMaterial = InitMaterial2D; } glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); +#ifdef GL_EXT_clip_volume_hint + if (FeatureAvailable[IRR_EXT_clip_volume_hint]) + glHint(GL_CLIP_VOLUME_CLIPPING_HINT_EXT, GL_FASTEST); +#endif + } if (OverrideMaterial2DEnabled) { OverrideMaterial2D.Lighting=false; - OverrideMaterial2D.ZBuffer=ECFN_NEVER; - OverrideMaterial2D.ZWriteEnable=false; setBasicRenderStates(OverrideMaterial2D, LastMaterial, false); LastMaterial = OverrideMaterial2D; } + // no alphaChannel without texture + alphaChannel &= texture; + if (alphaChannel || alpha) { glEnable(GL_BLEND); @@ -3509,7 +3530,7 @@ void COpenGLDriver::drawStencilShadowVolume(const core::vector3df* triangles, s3 glVertexPointer(3,GL_FLOAT,sizeof(core::vector3df),&triangles[0]); glStencilMask(~0); glStencilFunc(GL_ALWAYS, 0, ~0); - glPolygonOffset(1.f,1.f); + glPolygonOffset(-1.f,-1.f); glEnable(GL_POLYGON_OFFSET_FILL); GLenum incr = GL_INCR; @@ -3929,16 +3950,16 @@ bool COpenGLDriver::setRenderTarget(video::E_RENDER_TARGET target, bool clearTar return false; } - if (Stereo && (ERT_STEREO_RIGHT_BUFFER == target)) + if (Params.Stereobuffer && (ERT_STEREO_RIGHT_BUFFER == target)) { - if (Doublebuffer) + if (Params.Doublebuffer) glDrawBuffer(GL_BACK_RIGHT); else glDrawBuffer(GL_FRONT_RIGHT); } - else if (Stereo && ERT_STEREO_BOTH_BUFFERS == target) + else if (Params.Stereobuffer && ERT_STEREO_BOTH_BUFFERS == target) { - if (Doublebuffer) + if (Params.Doublebuffer) glDrawBuffer(GL_BACK); else glDrawBuffer(GL_FRONT); @@ -3949,7 +3970,7 @@ bool COpenGLDriver::setRenderTarget(video::E_RENDER_TARGET target, bool clearTar } else { - if (Doublebuffer) + if (Params.Doublebuffer) glDrawBuffer(GL_BACK_LEFT); else glDrawBuffer(GL_FRONT_LEFT); @@ -4016,7 +4037,7 @@ bool COpenGLDriver::setRenderTarget(video::ITexture* texture, bool clearBackBuff RenderTargetTexture = 0; CurrentRendertargetSize = core::dimension2d(0,0); CurrentTarget=ERT_FRAME_BUFFER; - glDrawBuffer(Doublebuffer?GL_BACK_LEFT:GL_FRONT_LEFT); + glDrawBuffer(Params.Doublebuffer?GL_BACK_LEFT:GL_FRONT_LEFT); } // we need to update the matrices due to the rendersize change. Transformation3DChanged=true; @@ -4240,16 +4261,10 @@ void COpenGLDriver::clearZBuffer() //! Returns an image created from the last rendered frame. -IImage* COpenGLDriver::createScreenShot() +IImage* COpenGLDriver::createScreenShot(video::ECOLOR_FORMAT format, video::E_RENDER_TARGET target) { - IImage* newImage = new CImage(ECF_R8G8B8, ScreenSize); - - u8* pixels = static_cast(newImage->lock()); - if (!pixels) - { - newImage->drop(); + if (target==video::ERT_MULTI_RENDER_TEXTURES || target==video::ERT_RENDER_TEXTURE || target==video::ERT_STEREO_BOTH_BUFFERS) return 0; - } // allows to read pixels in top-to-bottom order #ifdef GL_MESA_pack_invert @@ -4257,16 +4272,133 @@ IImage* COpenGLDriver::createScreenShot() glPixelStorei(GL_PACK_INVERT_MESA, GL_TRUE); #endif - // We want to read the front buffer to get the latest render finished. - glReadBuffer(GL_FRONT); - glReadPixels(0, 0, ScreenSize.Width, ScreenSize.Height, GL_RGB, GL_UNSIGNED_BYTE, pixels); - glReadBuffer(GL_BACK); + if (format==video::ECF_UNKNOWN) + format=getColorFormat(); + GLenum fmt; + GLenum type; + switch (format) + { + case ECF_A1R5G5B5: + fmt = GL_BGRA; + type = GL_UNSIGNED_SHORT_1_5_5_5_REV; + break; + case ECF_R5G6B5: + fmt = GL_BGR; + type = GL_UNSIGNED_SHORT_5_6_5_REV; + break; + case ECF_R8G8B8: + fmt = GL_BGR; + type = GL_UNSIGNED_BYTE; + break; + case ECF_A8R8G8B8: + fmt = GL_BGRA; + if (Version > 101) + type = GL_UNSIGNED_INT_8_8_8_8_REV; + else + type = GL_UNSIGNED_BYTE; + break; + case ECF_R16F: + if (FeatureAvailable[IRR_ARB_texture_rg]) + fmt = GL_RED; + else + fmt = GL_LUMINANCE; +#ifdef GL_ARB_half_float_pixel + if (FeatureAvailable[IRR_ARB_half_float_pixel]) + type = GL_HALF_FLOAT_ARB; + else +#endif + { + type = GL_FLOAT; + format = ECF_R32F; + } + break; + case ECF_G16R16F: +#ifdef GL_ARB_texture_rg + if (FeatureAvailable[IRR_ARB_texture_rg]) + fmt = GL_RG; + else +#endif + fmt = GL_LUMINANCE_ALPHA; +#ifdef GL_ARB_half_float_pixel + if (FeatureAvailable[IRR_ARB_half_float_pixel]) + type = GL_HALF_FLOAT_ARB; + else +#endif + { + type = GL_FLOAT; + format = ECF_G32R32F; + } + break; + case ECF_A16B16G16R16F: + fmt = GL_BGRA; +#ifdef GL_ARB_half_float_pixel + if (FeatureAvailable[IRR_ARB_half_float_pixel]) + type = GL_HALF_FLOAT_ARB; + else +#endif + { + type = GL_FLOAT; + format = ECF_A32B32G32R32F; + } + break; + case ECF_R32F: + if (FeatureAvailable[IRR_ARB_texture_rg]) + fmt = GL_RED; + else + fmt = GL_LUMINANCE; + type = GL_FLOAT; + break; + case ECF_G32R32F: +#ifdef GL_ARB_texture_rg + if (FeatureAvailable[IRR_ARB_texture_rg]) + fmt = GL_RG; + else +#endif + fmt = GL_LUMINANCE_ALPHA; + type = GL_FLOAT; + break; + case ECF_A32B32G32R32F: + fmt = GL_BGRA; + type = GL_FLOAT; + break; + default: + fmt = GL_BGRA; + type = GL_UNSIGNED_BYTE; + break; + } + IImage* newImage = createImage(format, ScreenSize); + + u8* pixels = 0; + if (newImage) + pixels = static_cast(newImage->lock()); + if (pixels) + { + GLenum tgt=GL_FRONT; + switch (target) + { + case video::ERT_FRAME_BUFFER: + break; + case video::ERT_STEREO_LEFT_BUFFER: + tgt=GL_FRONT_LEFT; + break; + case video::ERT_STEREO_RIGHT_BUFFER: + tgt=GL_FRONT_RIGHT; + break; + default: + tgt=GL_AUX0+(target-video::ERT_AUX_BUFFER0); + break; + } + glReadBuffer(tgt); + glReadPixels(0, 0, ScreenSize.Width, ScreenSize.Height, fmt, type, pixels); + glReadBuffer(GL_BACK); + } #ifdef GL_MESA_pack_invert if (FeatureAvailable[IRR_MESA_pack_invert]) glPixelStorei(GL_PACK_INVERT_MESA, GL_FALSE); else #endif + if (pixels) { // opengl images are horizontally flipped, so we have to fix that here. const s32 pitch=newImage->getPitch(); @@ -4275,7 +4407,15 @@ IImage* COpenGLDriver::createScreenShot() for (u32 i=0; i < ScreenSize.Height; i += 2) { memcpy(tmpBuffer, pixels, pitch); +// for (u32 j=0; junlock(); - - if (testGLError()) + if (newImage) { - newImage->drop(); - return 0; + newImage->unlock(); + if (testGLError() || !pixels) + { + newImage->drop(); + return 0; + } } - return newImage; } @@ -4421,7 +4562,7 @@ GLenum COpenGLDriver::primitiveTypeToGL(scene::E_PRIMITIVE_TYPE type) const } -GLenum COpenGLDriver::getGLBlend (E_BLEND_FACTOR factor) const +GLenum COpenGLDriver::getGLBlend(E_BLEND_FACTOR factor) const { GLenum r = 0; switch (factor) @@ -4462,7 +4603,7 @@ IVideoDriver* createOpenGLDriver(const SIrrlichtCreationParameters& params, { #ifdef _IRR_COMPILE_WITH_OPENGL_ COpenGLDriver* ogl = new COpenGLDriver(params, io, device); - if (!ogl->initDriver(params, device)) + if (!ogl->initDriver(device)) { ogl->drop(); ogl = 0; @@ -4498,7 +4639,7 @@ IVideoDriver* createOpenGLDriver(const SIrrlichtCreationParameters& params, { #ifdef _IRR_COMPILE_WITH_OPENGL_ COpenGLDriver* ogl = new COpenGLDriver(params, io, device); - if (!ogl->initDriver(params, device)) + if (!ogl->initDriver(device)) { ogl->drop(); ogl = 0; diff --git a/source/Irrlicht/COpenGLDriver.h b/source/Irrlicht/COpenGLDriver.h index 4f1518bc..1e9f506d 100644 --- a/source/Irrlicht/COpenGLDriver.h +++ b/source/Irrlicht/COpenGLDriver.h @@ -33,19 +33,20 @@ namespace video class COpenGLDriver : public CNullDriver, public IMaterialRendererServices, public COpenGLExtensionHandler { + friend class COpenGLTexture; public: #ifdef _IRR_COMPILE_WITH_WINDOWS_DEVICE_ COpenGLDriver(const SIrrlichtCreationParameters& params, io::IFileSystem* io, CIrrDeviceWin32* device); //! inits the windows specific parts of the open gl driver - bool initDriver(SIrrlichtCreationParameters params, CIrrDeviceWin32* device); + bool initDriver(CIrrDeviceWin32* device); bool changeRenderContext(const SExposedVideoData& videoData, CIrrDeviceWin32* device); #endif #ifdef _IRR_COMPILE_WITH_X11_DEVICE_ COpenGLDriver(const SIrrlichtCreationParameters& params, io::IFileSystem* io, CIrrDeviceLinux* device); //! inits the GLX specific parts of the open gl driver - bool initDriver(SIrrlichtCreationParameters params, CIrrDeviceLinux* device); + bool initDriver(CIrrDeviceLinux* device); bool changeRenderContext(const SExposedVideoData& videoData, CIrrDeviceLinux* device); #endif @@ -344,7 +345,7 @@ namespace video virtual void clearZBuffer(); //! Returns an image created from the last rendered frame. - virtual IImage* createScreenShot(); + virtual IImage* createScreenShot(video::ECOLOR_FORMAT format=video::ECF_UNKNOWN, video::E_RENDER_TARGET target=video::ERT_FRAME_BUFFER); //! checks if an OpenGL error has happend and prints it //! for performance reasons only available in debug mode @@ -392,7 +393,7 @@ namespace video void uploadClipPlane(u32 index); //! inits the parts of the open gl driver used on all platforms - bool genericDriverInit(const core::dimension2d& screenSize, bool stencilBuffer); + bool genericDriverInit(); //! returns a device dependent texture from a software surface (IImage) virtual video::ITexture* createDeviceDependentTexture(IImage* surface, const io::path& name, void* mipmapData); @@ -472,8 +473,7 @@ namespace video //! Render target type for render operations E_RENDER_TARGET CurrentTarget; - bool Doublebuffer; - bool Stereo; + SIrrlichtCreationParameters Params; //! All the lights that have been requested; a hardware limited //! number of them will be used at once. diff --git a/source/Irrlicht/COpenGLExtensionHandler.cpp b/source/Irrlicht/COpenGLExtensionHandler.cpp index 463eaaf9..94c22ebb 100644 --- a/source/Irrlicht/COpenGLExtensionHandler.cpp +++ b/source/Irrlicht/COpenGLExtensionHandler.cpp @@ -748,6 +748,8 @@ bool COpenGLExtensionHandler::queryFeature(E_VIDEO_DRIVER_FEATURE feature) const case EVDF_BLEND_OPERATIONS: return (Version>=120) || FeatureAvailable[IRR_EXT_blend_minmax] || FeatureAvailable[IRR_EXT_blend_subtract] || FeatureAvailable[IRR_EXT_blend_logic_op]; + case EVDF_TEXTURE_MATRIX: + return true; default: return false; }; diff --git a/source/Irrlicht/COpenGLExtensionHandler.h b/source/Irrlicht/COpenGLExtensionHandler.h index 4ac56f86..24f6516a 100644 --- a/source/Irrlicht/COpenGLExtensionHandler.h +++ b/source/Irrlicht/COpenGLExtensionHandler.h @@ -66,6 +66,13 @@ #include "glxext.h" #endif #endif + +#ifndef GL_ARB_shader_objects +/* GL types for program/shader text and shader object handles */ +typedef char GLcharARB; +typedef unsigned int GLhandleARB; +#endif + #ifndef GL_VERSION_2_0 /* GL type for program/shader text */ typedef char GLchar; @@ -1041,7 +1048,7 @@ class COpenGLExtensionHandler void extGlDisableIndexed(GLenum target, GLuint index); void extGlBlendFuncIndexed(GLuint buf, GLenum src, GLenum dst); void extGlBlendEquationIndexed(GLuint buf, GLenum mode); - void extGlProgramParameteri(GLuint program, GLenum pname, GLint value); + void extGlProgramParameteri(GLhandleARB program, GLenum pname, GLint value); // occlusion query void extGlGenQueries(GLsizei n, GLuint *ids); @@ -2202,7 +2209,7 @@ inline void COpenGLExtensionHandler::extGlBlendEquationIndexed(GLuint buf, GLenu } -inline void COpenGLExtensionHandler::extGlProgramParameteri(GLuint program, GLenum pname, GLint value) +inline void COpenGLExtensionHandler::extGlProgramParameteri(GLhandleARB program, GLenum pname, GLint value) { #if defined(_IRR_OPENGL_USE_EXTPOINTER_) if (queryFeature(EVDF_GEOMETRY_SHADER)) @@ -2223,6 +2230,7 @@ inline void COpenGLExtensionHandler::extGlProgramParameteri(GLuint program, GLen #endif } + inline void COpenGLExtensionHandler::extGlGenQueries(GLsizei n, GLuint *ids) { #ifdef _IRR_OPENGL_USE_EXTPOINTER_ @@ -2239,6 +2247,7 @@ inline void COpenGLExtensionHandler::extGlGenQueries(GLsizei n, GLuint *ids) #endif } + inline void COpenGLExtensionHandler::extGlDeleteQueries(GLsizei n, const GLuint *ids) { #ifdef _IRR_OPENGL_USE_EXTPOINTER_ diff --git a/source/Irrlicht/COpenGLSLMaterialRenderer.cpp b/source/Irrlicht/COpenGLSLMaterialRenderer.cpp index 4bac925e..cbf5fe55 100644 --- a/source/Irrlicht/COpenGLSLMaterialRenderer.cpp +++ b/source/Irrlicht/COpenGLSLMaterialRenderer.cpp @@ -157,12 +157,12 @@ void COpenGLSLMaterialRenderer::init(s32& outMaterialTypeNr, } else { - Driver->extGlProgramParameteri((GLuint)Program, GL_GEOMETRY_INPUT_TYPE_EXT, Driver->primitiveTypeToGL(inType)); - Driver->extGlProgramParameteri((GLuint)Program, GL_GEOMETRY_OUTPUT_TYPE_EXT, Driver->primitiveTypeToGL(outType)); + Driver->extGlProgramParameteri(Program, GL_GEOMETRY_INPUT_TYPE_EXT, Driver->primitiveTypeToGL(inType)); + Driver->extGlProgramParameteri(Program, GL_GEOMETRY_OUTPUT_TYPE_EXT, Driver->primitiveTypeToGL(outType)); if (verticesOut==0) - Driver->extGlProgramParameteri((GLuint)Program, GL_GEOMETRY_VERTICES_OUT_EXT, Driver->MaxGeometryVerticesOut); + Driver->extGlProgramParameteri(Program, GL_GEOMETRY_VERTICES_OUT_EXT, Driver->MaxGeometryVerticesOut); else - Driver->extGlProgramParameteri((GLuint)Program, GL_GEOMETRY_VERTICES_OUT_EXT, core::min_(verticesOut, Driver->MaxGeometryVerticesOut)); + Driver->extGlProgramParameteri(Program, GL_GEOMETRY_VERTICES_OUT_EXT, core::min_(verticesOut, Driver->MaxGeometryVerticesOut)); } #elif defined(GL_NV_geometry_program4) if (verticesOut==0) diff --git a/source/Irrlicht/COpenGLTexture.cpp b/source/Irrlicht/COpenGLTexture.cpp index f340d514..f467683f 100644 --- a/source/Irrlicht/COpenGLTexture.cpp +++ b/source/Irrlicht/COpenGLTexture.cpp @@ -10,7 +10,6 @@ #include "COpenGLTexture.h" #include "COpenGLDriver.h" #include "os.h" -#include "CImage.h" #include "CColorConverter.h" #include "irrString.h" @@ -39,12 +38,12 @@ COpenGLTexture::COpenGLTexture(IImage* origImage, const io::path& name, void* mi if (ImageSize==TextureSize) { - Image = new CImage(ColorFormat, ImageSize); + Image = Driver->createImage(ColorFormat, ImageSize); origImage->copyTo(Image); } else { - Image = new CImage(ColorFormat, TextureSize); + Image = Driver->createImage(ColorFormat, TextureSize); // scale texture origImage->copyToScaling(Image); } @@ -135,26 +134,31 @@ GLint COpenGLTexture::getOpenGLFormatAndParametersFromColorFormat(ECOLOR_FORMAT filtering = GL_LINEAR; colorformat = GL_RGBA; type = GL_UNSIGNED_BYTE; + GLenum internalformat = GL_RGBA; switch(format) { case ECF_A1R5G5B5: colorformat=GL_BGRA_EXT; type=GL_UNSIGNED_SHORT_1_5_5_5_REV; - return GL_RGBA; + internalformat = GL_RGBA; + break; case ECF_R5G6B5: colorformat=GL_BGR; type=GL_UNSIGNED_SHORT_5_6_5_REV; - return GL_RGB; + internalformat = GL_RGB; + break; case ECF_R8G8B8: colorformat=GL_BGR; type=GL_UNSIGNED_BYTE; - return GL_RGB; + internalformat = GL_RGB; + break; case ECF_A8R8G8B8: colorformat=GL_BGRA_EXT; if (Driver->Version > 101) type=GL_UNSIGNED_INT_8_8_8_8_REV; - return GL_RGBA; + internalformat = GL_RGBA; + break; // Floating Point texture formats. Thanks to Patryk "Nadro" Nadrowski. case ECF_R16F: { @@ -163,11 +167,12 @@ GLint COpenGLTexture::getOpenGLFormatAndParametersFromColorFormat(ECOLOR_FORMAT colorformat = GL_RED; type = GL_FLOAT; - return GL_R16F; + internalformat = GL_R16F; #else - return GL_RGB8; + internalformat = GL_RGB8; #endif } + break; case ECF_G16R16F: { #ifdef GL_ARB_texture_rg @@ -175,11 +180,12 @@ GLint COpenGLTexture::getOpenGLFormatAndParametersFromColorFormat(ECOLOR_FORMAT colorformat = GL_RG; type = GL_FLOAT; - return GL_RG16F; + internalformat = GL_RG16F; #else - return GL_RGB8; + internalformat = GL_RGB8; #endif } + break; case ECF_A16B16G16R16F: { #ifdef GL_ARB_texture_rg @@ -187,11 +193,12 @@ GLint COpenGLTexture::getOpenGLFormatAndParametersFromColorFormat(ECOLOR_FORMAT colorformat = GL_RGBA; type = GL_FLOAT; - return GL_RGBA16F_ARB; + internalformat = GL_RGBA16F_ARB; #else - return GL_RGBA8; + internalformat = GL_RGBA8; #endif } + break; case ECF_R32F: { #ifdef GL_ARB_texture_rg @@ -199,11 +206,12 @@ GLint COpenGLTexture::getOpenGLFormatAndParametersFromColorFormat(ECOLOR_FORMAT colorformat = GL_RED; type = GL_FLOAT; - return GL_R32F; + internalformat = GL_R32F; #else - return GL_RGB8; + internalformat = GL_RGB8; #endif } + break; case ECF_G32R32F: { #ifdef GL_ARB_texture_rg @@ -211,11 +219,12 @@ GLint COpenGLTexture::getOpenGLFormatAndParametersFromColorFormat(ECOLOR_FORMAT colorformat = GL_RG; type = GL_FLOAT; - return GL_RG32F; + internalformat = GL_RG32F; #else - return GL_RGB8; + internalformat = GL_RGB8; #endif } + break; case ECF_A32B32G32R32F: { #ifdef GL_ARB_texture_float @@ -223,17 +232,28 @@ GLint COpenGLTexture::getOpenGLFormatAndParametersFromColorFormat(ECOLOR_FORMAT colorformat = GL_RGBA; type = GL_FLOAT; - return GL_RGBA32F_ARB; + internalformat = GL_RGBA32F_ARB; #else - return GL_RGBA8; + internalformat = GL_RGBA8; #endif } + break; default: { os::Printer::log("Unsupported texture format", ELL_ERROR); - return GL_RGBA8; + internalformat = GL_RGBA8; } } +#if defined(GL_ARB_framebuffer_sRGB) || defined(GL_EXT_framebuffer_sRGB) + if (Driver->Params.HandleSRGB) + { + if (internalformat==GL_RGBA) + internalformat=GL_SRGB_ALPHA_EXT; + else if (internalformat==GL_RGB) + internalformat=GL_SRGB_EXT; + } +#endif + return internalformat; } @@ -360,6 +380,17 @@ void* COpenGLTexture::lock(E_TEXTURE_LOCK_MODE mode, u32 mipmapLevel) IImage* image = (mipmapLevel==0)?Image:MipImage; ReadOnlyLock |= (mode==ETLM_READ_ONLY); MipLevelStored = mipmapLevel; + if (!ReadOnlyLock && mipmapLevel) + { +#ifdef GL_SGIS_generate_mipmap + if (Driver->queryFeature(EVDF_MIP_MAP_AUTO_UPDATE)) + { + // do not automatically generate and update mipmaps + glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_FALSE); + } +#endif + AutomaticMipmapUpdate=false; + } // if data not available or might have changed on GPU download it if (!image || IsRenderTarget) @@ -381,10 +412,10 @@ void* COpenGLTexture::lock(E_TEXTURE_LOCK_MODE mode, u32 mipmapLevel) ++i; } while (i != mipmapLevel); - MipImage = image = new CImage(ECF_A8R8G8B8, core::dimension2du(width,height)); + MipImage = image = Driver->createImage(ECF_A8R8G8B8, core::dimension2du(width,height)); } else - Image = image = new CImage(ECF_A8R8G8B8, ImageSize); + Image = image = Driver->createImage(ECF_A8R8G8B8, ImageSize); ColorFormat = ECF_A8R8G8B8; } if (!image) @@ -401,34 +432,41 @@ void* COpenGLTexture::lock(E_TEXTURE_LOCK_MODE mode, u32 mipmapLevel) glGetIntegerv(GL_TEXTURE_BINDING_2D, &tmpTexture); glBindTexture(GL_TEXTURE_2D, TextureName); + // we need to flip textures vertical + // however, it seems that this does not hold for mipmap + // textures, for unknown reasons. + // allows to read pixels in top-to-bottom order - #ifdef GL_MESA_pack_invert - if (Driver->queryOpenGLFeature(COpenGLExtensionHandler::IRR_MESA_pack_invert)) +#ifdef GL_MESA_pack_invert + if (!mipmapLevel && Driver->queryOpenGLFeature(COpenGLExtensionHandler::IRR_MESA_pack_invert)) glPixelStorei(GL_PACK_INVERT_MESA, GL_TRUE); - #endif +#endif // download GPU data as ARGB8 to pixels; glGetTexImage(GL_TEXTURE_2D, mipmapLevel, GL_BGRA_EXT, GL_UNSIGNED_BYTE, pixels); - #ifdef GL_MESA_pack_invert - if (Driver->queryOpenGLFeature(COpenGLExtensionHandler::IRR_MESA_pack_invert)) - glPixelStorei(GL_PACK_INVERT_MESA, GL_FALSE); - else - #endif + if (!mipmapLevel) { - // opengl images are horizontally flipped, so we have to fix that here. - const s32 pitch=image->getPitch(); - u8* p2 = pixels + (image->getDimension().Height - 1) * pitch; - u8* tmpBuffer = new u8[pitch]; - for (u32 i=0; i < image->getDimension().Height; i += 2) +#ifdef GL_MESA_pack_invert + if (Driver->queryOpenGLFeature(COpenGLExtensionHandler::IRR_MESA_pack_invert)) + glPixelStorei(GL_PACK_INVERT_MESA, GL_FALSE); + else +#endif { - memcpy(tmpBuffer, pixels, pitch); - memcpy(pixels, p2, pitch); - memcpy(p2, tmpBuffer, pitch); - pixels += pitch; - p2 -= pitch; + // opengl images are horizontally flipped, so we have to fix that here. + const s32 pitch=image->getPitch(); + u8* p2 = pixels + (image->getDimension().Height - 1) * pitch; + u8* tmpBuffer = new u8[pitch]; + for (u32 i=0; i < image->getDimension().Height; i += 2) + { + memcpy(tmpBuffer, pixels, pitch); + memcpy(pixels, p2, pitch); + memcpy(p2, tmpBuffer, pitch); + pixels += pitch; + p2 -= pitch; + } + delete [] tmpBuffer; } - delete [] tmpBuffer; } image->unlock(); diff --git a/source/Irrlicht/CSoftwareDriver.cpp b/source/Irrlicht/CSoftwareDriver.cpp index 25a860dc..393415ee 100644 --- a/source/Irrlicht/CSoftwareDriver.cpp +++ b/source/Irrlicht/CSoftwareDriver.cpp @@ -8,6 +8,7 @@ #ifdef _IRR_COMPILE_WITH_SOFTWARE_ #include "CSoftwareTexture.h" +#include "CBlit.h" #include "os.h" #include "S3DVertex.h" @@ -283,7 +284,7 @@ bool CSoftwareDriver::setRenderTarget(video::ITexture* texture, bool clearBackBu ZBuffer->clear(); if (clearBackBuffer) - ((video::CImage*)RenderTargetSurface)->fill( color ); + RenderTargetSurface->fill(color); } return true; @@ -820,7 +821,7 @@ void CSoftwareDriver::draw2DLine(const core::position2d& start, const core::position2d& end, SColor color) { - RenderTargetSurface->drawLine(start, end, color ); + drawLine(RenderTargetSurface, start, end, color ); } @@ -844,14 +845,14 @@ void CSoftwareDriver::draw2DRectangle(SColor color, const core::rect& pos, if(!p.isValid()) return; - RenderTargetSurface->drawRectangle(p, color); + drawRectangle(RenderTargetSurface, p, color); } else { if(!pos.isValid()) return; - RenderTargetSurface->drawRectangle(pos, color); + drawRectangle(RenderTargetSurface, pos, color); } } @@ -903,7 +904,7 @@ ITexture* CSoftwareDriver::addRenderTargetTexture(const core::dimension2d& const io::path& name, const ECOLOR_FORMAT format) { - CImage* img = new CImage(video::ECF_A1R5G5B5, size); + IImage* img = createImage(video::ECF_A1R5G5B5, size); ITexture* tex = new CSoftwareTexture(img, name, true); img->drop(); addTexture(tex); @@ -921,11 +922,14 @@ void CSoftwareDriver::clearZBuffer() //! Returns an image created from the last rendered frame. -IImage* CSoftwareDriver::createScreenShot() +IImage* CSoftwareDriver::createScreenShot(video::ECOLOR_FORMAT format, video::E_RENDER_TARGET target) { + if (target != video::ERT_FRAME_BUFFER) + return 0; + if (BackBuffer) { - CImage* tmp = new CImage(BackBuffer->getColorFormat(), BackBuffer->getDimension()); + IImage* tmp = createImage(BackBuffer->getColorFormat(), BackBuffer->getDimension()); BackBuffer->copyTo(tmp); return tmp; } diff --git a/source/Irrlicht/CSoftwareDriver.h b/source/Irrlicht/CSoftwareDriver.h index 12d98f12..d1930044 100644 --- a/source/Irrlicht/CSoftwareDriver.h +++ b/source/Irrlicht/CSoftwareDriver.h @@ -111,7 +111,7 @@ namespace video virtual void clearZBuffer(); //! Returns an image created from the last rendered frame. - virtual IImage* createScreenShot(); + virtual IImage* createScreenShot(video::ECOLOR_FORMAT format=video::ECF_UNKNOWN, video::E_RENDER_TARGET target=video::ERT_FRAME_BUFFER); //! Returns the maximum amount of primitives (mostly vertices) which //! the device is able to render with one drawIndexedTriangleList diff --git a/source/Irrlicht/CSoftwareDriver2.cpp b/source/Irrlicht/CSoftwareDriver2.cpp index 6d31752d..dd4986b2 100644 --- a/source/Irrlicht/CSoftwareDriver2.cpp +++ b/source/Irrlicht/CSoftwareDriver2.cpp @@ -359,8 +359,9 @@ CBurningVideoDriver::CBurningVideoDriver(const irr::SIrrlichtCreationParameters& DriverAttributes->setAttribute("MaxTextures", 2); DriverAttributes->setAttribute("MaxIndices", 1<<16); DriverAttributes->setAttribute("MaxTextureSize", 1024); + DriverAttributes->setAttribute("MaxLights", glsl::gl_MaxLights); DriverAttributes->setAttribute("MaxTextureLODBias", 16.f); - DriverAttributes->setAttribute("Version", 45); + DriverAttributes->setAttribute("Version", 47); // create triangle renderers @@ -2275,7 +2276,7 @@ void CBurningVideoDriver::draw2DLine(const core::position2d& start, const core::position2d& end, SColor color) { - BackBuffer->drawLine(start, end, color ); + drawLine(BackBuffer, start, end, color ); } @@ -2299,14 +2300,14 @@ void CBurningVideoDriver::draw2DRectangle(SColor color, const core::rect& p if(!p.isValid()) return; - BackBuffer->drawRectangle(p, color); + drawRectangle(BackBuffer, p, color); } else { if(!pos.isValid()) return; - BackBuffer->drawRectangle(pos, color); + drawRectangle(BackBuffer, pos, color); } } @@ -2564,7 +2565,7 @@ const core::matrix4& CBurningVideoDriver::getTransform(E_TRANSFORMATION_STATE st ITexture* CBurningVideoDriver::addRenderTargetTexture(const core::dimension2d& size, const io::path& name, const ECOLOR_FORMAT format) { - CImage* img = new CImage(BURNINGSHADER_COLOR_FORMAT, size); + IImage* img = createImage(BURNINGSHADER_COLOR_FORMAT, size); ITexture* tex = new CSoftwareTexture2(img, name, CSoftwareTexture2::IS_RENDERTARGET ); img->drop(); addTexture(tex); @@ -2582,17 +2583,19 @@ void CBurningVideoDriver::clearZBuffer() //! Returns an image created from the last rendered frame. -IImage* CBurningVideoDriver::createScreenShot() +IImage* CBurningVideoDriver::createScreenShot(video::ECOLOR_FORMAT format, video::E_RENDER_TARGET target) { + if (target != video::ERT_FRAME_BUFFER) + return 0; + if (BackBuffer) { - CImage* tmp = new CImage(BackBuffer->getColorFormat(), BackBuffer->getDimension()); + IImage* tmp = createImage(BackBuffer->getColorFormat(), BackBuffer->getDimension()); BackBuffer->copyTo(tmp); return tmp; } else return 0; - } diff --git a/source/Irrlicht/CSoftwareDriver2.h b/source/Irrlicht/CSoftwareDriver2.h index 5f9d8898..7a182346 100644 --- a/source/Irrlicht/CSoftwareDriver2.h +++ b/source/Irrlicht/CSoftwareDriver2.h @@ -136,7 +136,7 @@ namespace video virtual void clearZBuffer(); //! Returns an image created from the last rendered frame. - virtual IImage* createScreenShot(); + virtual IImage* createScreenShot(video::ECOLOR_FORMAT format=video::ECF_UNKNOWN, video::E_RENDER_TARGET target=video::ERT_FRAME_BUFFER); //! Returns the maximum amount of primitives (mostly vertices) which //! the device is able to render with one drawIndexedTriangleList diff --git a/source/Irrlicht/CTerrainSceneNode.cpp b/source/Irrlicht/CTerrainSceneNode.cpp index 1597efe2..82c2ec75 100644 --- a/source/Irrlicht/CTerrainSceneNode.cpp +++ b/source/Irrlicht/CTerrainSceneNode.cpp @@ -565,23 +565,23 @@ namespace scene { if (!IsVisible || !SceneManager->getActiveCamera()) return; - - preRenderLODCalculations(); - preRenderIndicesCalculations(); + + SceneManager->registerNodeForRendering(this); + + preRenderCalculationsIfNeeded(); + + // Do Not call ISceneNode::OnRegisterSceneNode(), this node should have no children (luke: is this comment still true, as ISceneNode::OnRegisterSceneNode() is called?) + ISceneNode::OnRegisterSceneNode(); ForceRecalculation = false; } - - - void CTerrainSceneNode::preRenderLODCalculations() - { - scene::ICameraSceneNode * camera = SceneManager->getActiveCamera(); + + void CTerrainSceneNode::preRenderCalculationsIfNeeded() + { + scene::ICameraSceneNode * camera = SceneManager->getActiveCamera(); if(!camera) return; - - SceneManager->registerNodeForRendering(this); - // Do Not call ISceneNode::OnRegisterSceneNode(), this node should have no children - + // Determine the camera rotation, based on the camera direction. const core::vector3df cameraPosition = camera->getAbsolutePosition(); const core::vector3df cameraRotation = core::line3d(cameraPosition, camera->getTarget()).getVector().getHorizontalAngle(); @@ -607,13 +607,31 @@ namespace scene } } } - + + //we need to redo calculations... + OldCameraPosition = cameraPosition; OldCameraRotation = cameraRotation; OldCameraUp = cameraUp; OldCameraFOV = CameraFOV; - - const SViewFrustum* frustum = SceneManager->getActiveCamera()->getViewFrustum(); + + + preRenderLODCalculations(); + preRenderIndicesCalculations(); + + + } + + void CTerrainSceneNode::preRenderLODCalculations() + { + scene::ICameraSceneNode * camera = SceneManager->getActiveCamera(); + + if(!camera) + return; + + const core::vector3df cameraPosition = camera->getAbsolutePosition(); + + const SViewFrustum* frustum = camera->getViewFrustum(); // Determine each patches LOD based on distance from camera (and whether or not they are in // the view frustum). diff --git a/source/Irrlicht/CTerrainSceneNode.h b/source/Irrlicht/CTerrainSceneNode.h index a8c2c466..a2c7e37e 100644 --- a/source/Irrlicht/CTerrainSceneNode.h +++ b/source/Irrlicht/CTerrainSceneNode.h @@ -258,7 +258,10 @@ namespace scene core::aabbox3df BoundingBox; core::array LODDistanceThreshold; }; - + + + virtual void preRenderCalculationsIfNeeded(); + virtual void preRenderLODCalculations(); virtual void preRenderIndicesCalculations(); diff --git a/source/Irrlicht/Irrlicht.aps b/source/Irrlicht/Irrlicht.aps index 601d2949af2f35ce815721291080cdce282aae6c..1c5c2c9307f40c3a185cdaec5ad50f356969b9c3 100644 GIT binary patch literal 61604 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 BAG81f literal 34548 zcmc(IdAMXrRp+VhhGrMRk!2LB8zpX<1#} z6jWC;Rb2v#qoB^`E;=srspBvX`pqaVHis%{pPq>ScWIvn`18K|ju`y<>&##1 zmhU7QKK1nI?q^@Q>)geA&KF9zUEr zIy!UDx${VE-+uc1rArsipMB;%r!U`q<teoK&&pG)^`czXdy8SYK3PomO3GRpJy7DqdO9s;BLjC@Ef5~$ z6K1PLJ!$d-f3U=t2h-AK=h7cSWb1V1j_09Tszyg{py!^ysDHYKj+|VsPnL^iRnOMd zY+O@DkDw#fXt-Xkid8kFn{LjI(J?|UwC2cqIv%da)ufyiQ#7Pp(2ZHEbt65NjtmEj z<+_+uJG1qq9IvSLJRq$J-Qq#3dJZI4$X9#FqT1QzT-@*peT|0=>eZ^A0_-X9X&zY2 z4uFgm@^pg?>cyyB*ow4JlL^L;uA{+pOdLfG zn+c5#w5sdLs+uoROT1G6y9O9f4hCHJ7HTn}s!6Sj)q1*zJPPXodxrm%_DCT;ASVp6 zTa8M*!TF>ZmI`e3CNweNvbwEYj}GwC@J9K0tsHXcke zpL#jRXys?)e%B8ZkJs5u0vCLY_Dr^7GBK4OPw&-Q5y*$@q`Pk=F%wqJbF14PQGo75#ues#m zq+Y5TWOzlt?$VdVNt4{B-|&;yg?il?Ue3!MGDnB4m*r}YXKXyYORo^%qQW;W&@Fmp zfa+Y^^r|3zJ*x1^)!@roJ$iKr&lly%%C@*quW|U^d{nGVBMN%01GsJLVLd-+S~8&5 z3An_vIxQ+SI?$Q7|S)5 z(VJtet+AZm5@Q{Wwdt)f*40>t{#T4`X{<~CJI1y(wng{GSWjcy^tKr5Yph3ak1@2G zM@H7C-%5a{XA1i51ZcWuK);g!P2UXZcN3uLoDuzA0yMo-((flg(>-H)hYwB)`-aE# zP6d^tYPB9t%0f+AS>B>Q4AQaF(W5EP=#K=3F{t_^&*@zvc~1|ayiI@XQpcE#HMv9Y zcFA*8M-TG6OYaHN*OO{?LTB2dKMBF*yc|}Asn|BX*WrtDUKXYjJ$j!9m@Pw@<$Zd; zLzd+PpQ&qTK_777-fUFsY7FRuE_qSfmyCnOvur;P@8%%u@Jo?mHk>h-EosXe4w##!YJ@*UH zU4GB&3M_tZj@9%2K4ZSXindr)rFlSyu5Sn^c;Keu923t2j+n)NZPp8XuV&$EquvjT zU~F7?MdOFJQTZ>331&ysM&%zKq5Kq`w@qJ|K(QQZxI>SK;M?kYTB&xov5Y=4iJQUK z#?t>qs@=Q@uKDaR(9=*4&3cqW_Iqw-vptLq{Dj z;4_MPN4=~|H#8lk`T|{8O|kvNU{yc6>fRU-i&Gxal==kq9PB-CICO)lk8XXEMC@|V z$0)f;0{o%zJMLqE9Fu?@8+{DwCo|xmo^xBf<$5{fqZk*06C!<;1g5p#pN;X@TRec9 z-Q>#Yt38mL+JJ5P8VPcR_57=!*rBIMn5!+}ET z9$~h=TQqktAJkQ;Sw^?o(=d$7?KAK`+@?z_;vLx7%YV5i9=JR^7!UM`v z!*afY^cUVrF_8nyp3qnZf&?!f#W-w>ri^TL0^15{3 zcym55)HANSsxH>p(+;g?IM=DkdECM}`?U^P?bi;;>FXS#N@PqQNs+NlYYD4k1QRg2 z_=pKchrV7iP$jdj>Q5VtE}cm;bOBrR4FN1hSZABg1}Kiw%^T|U==K0!)y*jB^y!>K zabD!j*A$JbRwW;~$FMiidB;%eF|Ugn%jj7SD~7A;B#ugKp)+_+&vy7wFUDpsiIUrN z!J)iJ=t4VmmrI6ma!>6o!SB*V2aFf>ZR#irV2kc{0KPx<74Q-OY@43rFm+_(=4q@) zmmH?gd8+Xm?bBt4s+`4Azy;kCz~(F{I0L%kIJ~D5*pR-_Vd~-7s;#--P0dI2Tt}E< zS>v~cJrr-HbPY>-zGI9kY*qv~rf+iKpju6fxy1V|`eui#EtWPEi!%Bdy})slA;K#n zr*CnvIzezbdVl87E`6Ir7pt*X_!hm;fojJpYqm`eRrkE;M^_rKE^{7@80b9qvCq+_Yt0Cp|y^e%+jf-IhS0%%m@qG?;&lxUo(1Nwq zKB+_BFK9e&u%}zA?UXL3OFtk8O?|Py!&Y2Pa+SwS;H$Q6E9gHu*4}JU?x5Z3AcBVs=tn}( zd&H1_)Zrsc6)-kq)l?mzTRfs4^BDsWFr~4#LNiMGamRpVaLH#F^+{&fzWzjr;nncr zc${JF{mBqS#%ks;8oC+S z>i(CIG&oR30FHV22tV_SNnI?+cqF;7A7|E;oN|&Xc z28+nAMljlj#^ba0wr^}9`8B~+9=f&ODyXusLwLEvO|N6p!l8SnO|Ijtm(eR62irS* zP#BC$HC1Q5oL=cr^S!ApM21bmt1P6qqL}04!#nh9i(`FjCNgYVtFbA)ORq^$^c<#} zutl$R6f@~IhF?^Fn_lN|_Y_I#(d$zbOiwtN=+i*f>(d)jB;NdBz({66znNk>dk1PY zpf@_sP6-=|+^XxA4e3pe1JikmGom*;j%hsx!?0NB5!@^3Eso%tuU8%YoH4yMqF~EW zsR>q5u;aMbQS4`_IGBhvC8M`Fig}P?EMZO8l$_o!D7>`I?Cf8(=(ht58})Lb{Ey?T z$mn+hm^*A-!>*{xE^_+afWU*XnGuRM{a%3cvWh+Gfm-QxAszbtfMjNbQ8}xWzY|sI z(mMi@?GU>+&x$R2XTVwD?8Ht7wa?KjPO(jY5U}9ta8p##qyH1&+@@t&vPBm)>C+zu z_%7c2x>CFGtSIP@0(c2rTNROeK<^4*tkWkNcu0R7z&wFA^CxD=cL#V`k9ECDdXIyO z1*WCuJr7#+-T;&Dd63cj0+{E|dN$D$!XT&j2RKGoJ#Kn_(54Rr1U0j|5<2w3fPhZ5 z*4c43=+cK`d}n84cfw-%eP zm$y#rY_FzZZbFVJ|KmPI8^L%E!cKml2bt~A94o*ld~!LRuMSY^WN(VQ9on{oLfiC7 zpEs_`35K)ky7jbzc@8U>u0V%A<#Vgaq};)7ie0ao(xrd&De4Bq9FxkT-iL>GJjM#9 zzu6qe%uoAdm`}{7h{>A`e7aEB$Rl2&xE}qpPjmevhii^S<6nFt_cxxss#khvnfD6% zj8CaYgJHP0&9Pv7)~E6HjO3z__lER2pSzsFL%)j&Gg@Wx-iYq=$vpF8tx`v}nYtW; zmsb*9V+`ne(3pYav&MA9XQ8Rs;uRP*W}?oqlw9L;(7$VQf{&|*bL_P~U3JgN9Ix%x zJvfH-i|glfolnD)=k>B;V+zi5x_tEo=lwR_&*$>Ir$$zye{c@Izt2_;S}kyj!nw^n zzDw8pByQBWwBxzIMGx@tYKB3FQL|ced(`0TZ_@*PlIfKZ9BVis>NQnUd-NcmYA);W zpoU2_)s8+r*w3OrKW`b$abA9i&l=SAiD|Jo5f~G7H3sz1!`L`v!!BvMXh;vU+~ry~ z$si;8LW|5XT(EJT?ZDuVlNnwgjD@%1PLV$O|)$X zbN^wSV-b0b&%)cn6pJrJ8yj=1B9HSKSR?o;>dG5Wk%JCB-e>6fZg1w8{KHEVx z!mdVj+o(&&0)js*MqBjcfM5;+>Wrfc*`}`uI9y0I#gq)Yo&8isJ$j0xVeTI)^Z%ee zwPw2nepIFLJ&6r8LCPuHK$@ESY2&tdBrvBBKd|d%g@6l7Q#^k#TVD{;&HZ#qaSr&AQC1K&gdX1NX z>!vLlxMXbsR+mtRE_J|bBMwM>b!g<0l@W?9TFS`AuHT&yR1~%}!Jfz7-`$9SZ_%J_ zsv^{w@$*)Po^cJkqv4UC;5JpwH=p2<&1kBU_p93mg&I>xY}3pp8s8ieJ5<|5_~NT2 z??8~;rMY|Psu=K+$hT=V+M-)6I%4-X8%9Ty9cs6>@#xzfrrK$doX$x^`5V}}r#!Zt zwM}<8f_AQ(12dx?I`2^ZLX?A*u`WGJV8*k~z!p8*0m{&8FMTk&O&1)>9s*@eR?X?wSL(O>(fPtDO&&v!EAhqZh^kz%%$Q|#wCZUP2>pEf3148 z-JvTM9IAJLSyJ7DXt(;?+w^<~PK%ux8xR=i(Kk6j`G4Wf#+X%LpT60FyXuw)d%=+p zc99o2=%fc_^eqmW)g#1VjA54O};4OM_3RTyGxae*A zp%jkgVx>y&(GQ1s4R>afzfV7!!fk$7ntv>X>pp?0`Nukj87s!a(a1)>?}&`vw*ef6(KfIksW-I_LU)YP{MDr&-uFasu08+hb>jX=oY;? zM281(e*xO2*M!i{0`ml*9=$e%5Q?$8vUs0f7vdwF0(2`efM1_NZA&qP-;lyhYcYy{ zGlbQ{d4=JM;~K~tLjphDwHj`cH>DV+<#3R^Ie_(eWbepZ9I`xseRrx}JVsZRZPQyF z$T`#w1Wt#H?iD~aT(N5m={dd4rJDs3$!&VOOV;;7k=mi(vZ=64qIAA%z;+VcPH*RV zcbndkz?IF1;U2v+0nbfkn9-*{und@*>;sJNmN$eY8uD&O(IcMk(s1N~!aMYy5Xa7H zzs3P@t|LWINSFR3B<;^eywBRv2eA^P^1TU)J=&BQn(qs7cJAw2Xm~`A-k-u<0e$*F zNKm&J%tba7QP2lNMl}mK1Nu(2URSSHA zf0bY~Eyf4<*9m+VbBOwsbs=5)n~($}vN8&|lD6ohAt@+koBnnq!?ZYfcX*}(myy*uO_YzBl@@l*(oZaU4}})K>R7RQJFW z`stWqE%qf2CI1w|`c{h0-=Tkw3HH{DrgZ6FVv4yPiZ*P~XB@0&rTLtr$bj|evk7c1 zE=rZShyFRo;f`K6*$cWahWy+J6uq{I3^G0F3P7x167|a%(KQiEuLm~FXpC#bbgd)l zPhRbL)*KfB99N{%xUx@_I6mFa;n+A0YA2@Cy0k_^6Rp`Oy~&_g2zA7J^iuV!@VVG)CE;?8rp|3*@N|B2+4V1M!(SfqP>_4gFbY3O5{&;C5l8oWJTo_0N;n*kB}iN| z<(<(J#Gpq{PO!`*aiC+&c@`S<=_?K)ax}m}<1WQ+i=HCc@JOMl&7I?d{Ur7VP&pXT z&4-Y6lZSL%a%N>YvXu;1+T}d&vQ#as-FkDLl&+2UC<{abc%+7j_-qzl{G*E2c&` z|A@}0Jm(F!jnYnodkOztz@MD=9;95xv#%iSEO@68w;$q{kS;mLkq;Ubs@`+3??zPh zB|PgcRr}M3#OJa(oparRXB?%Is*YE{x`3aSNV|gCoCkgsJ=~(>`2RFmL;TLQzJRjs zKq*|`i};gUeY>jP8KgfCrE{JBb!+lmRX14^ez&aa;g7GIk8nD_^K1Ao_$5aY#+UpN z155sh5e1t-Mxm|+(R*$QpL0BV&d1pGAY;tIs75smNKn6s%0wbGG3Yu_@QJd`EoWLa zLMS1d7WEH{a)p?dj{X7i7#um%)ju6z)!Wj4oz=9h|EA+^din=t67Tchb*Q`V&=JYP zp@Y;zRY%aD4i7~`0NN+%{jT-~i6_P0pa=}TK`}vkgJM*8g9PZCAp<~|A%VE|88?p< zO%YAxBBhx!D3q`98Z)Ip0yCvR0yCvR0yCvR^zlfTDI-jnDI=^gQ$~n2-obbkVa`k$ zLDEbafzDtDgHVJzGi3x>Go%{Tc6{lU2WHABt7oQ6qPC^5k|c3wri|dwOc@a(Gi8$D z%#qyAnQ>L zfySd60*&8PEwgqG3OuS!rVZXWfv3H34I%A~Yv3X7r$rvyG-^DyY0!FXlc@FBCc(yI zn?$Y0HVHN!+cauCwrS9MY?El?aSh&)?J4VVg*ze2A9!q&xXllfdlGLvwh<~kwhfl_ zs0OI?s5a0BZ&3MJdwoT!v8lo1qIL?F+yuI_yI0Un zs2}KNC$Gy^*UbtxR{=5GdW|UmAFLKmF-erBx6J}I8-J6P!=rVP<`^K-1I47MP!o}^ zv#XWIHm!+@K~;;eZE#h3ib;zJYHe0h-PkCBa59DV4KPObS3Nk&(fxr6CoK?kOgP=}t(?tON>7HUz1lr4U-0`?QCvaae$p@rjl0ag`Bmv@V*eE86WE|j3 zvVD26Vv?qtbRplu@qiu{C?TTnG$F)4mD<3ceB1T1-cp&vmU zzPM!wNpZ^%(@G*r+%kluxMhe5amx^bi;Ww};+7#K#Vs|KQhX9ftoS5APw`2l^96Y^ zWw7FtK%ImVD#?mZ0*e%%B&MUlki`yLRrf&g$;%)Wp9J75K50PvC9!fE=|z@k@({cJ zutbvw7_)?uXz~C%7+InTfF5n39+L-1JtiN(HBBeEBt||LHx2ZdyqsJ+DYzsCKIi2+ zN?5%qSJUQeWj!XZ+(3`X!$LhK0fu@^0!r#J37mJ*ywS$kR(ebVvW_YZsrO^s5a}_A zY@r^LfNY6Uk4eDXZ7h{$`$y?92|T68Bw(q>)FdCK$JCI55Gp@bpFKrp;midbydX>aw{-(cQDjr z61X<>*`2D=V-hG&;kr<7!tnK&1mD+V5IDEui`D2cMIgFL ziY<9BUl+~UB}HuQa3fNwLFvm^o*q;5pp+g{M3Q<;5h(STBG7&`JcY9!Q$ChIXp z1gXaqf%cPSJ*EicXY1#BZ`TYUJ2|-715pjKkbu{yugJYkBlW}g>zvpLZXt`>n1T{| zOdCl;KuIin&C`;4OmSfiO%x~g4A4GE=rM_649r)*(|Syjo6=(vxaovgk4bP6dQ1W} z-&>={)Ib~bm>Rt4Har}c!-O7FlH%(z35uC?8?&;~V-mP)qNI3Z-cfv&C2!n25^sJG z{_g8BC7>k#KLDdeQ}m_k;l#}wk+CaK31;(;Dh2nTvhAspy2g)mQ`QjaObg&vcDB0Z)MlkXW9 zr$dG)nf{{SJ7h%*x0ic>qw6&AaSESJ*EIp z>oEl+W7kOPF$L7mM~^AUvO$k2U|mIz=}>l{$JB5mJ*EcvJoK0XZm7o;z{W1dn*|%O z#Cl8t3vUZktf$8mFtA31dQ1TWw?&~p7eoTF9#g|h=`n?1pvM%#y#5DzOd$mu(pZlv zBw-f!^_W6}x4X)r#}pD$dQ2gQ3rXlP37U6FEzo1yMDz5RHjz9%CPCuux-#1Ej16mO zpvQDoW~0Y+RVH6~fFhFDbLpq^{Rc2TyI7jp=rLXO>5U%KAxx*oBuMPDRriEs$gXx{ zSvQHk7afH0iy}0h3geGRaTtG!!X`xSBM#$_@F0voiMs;Cg1ppH2D%y*bm2wnP|zR8mk(Vz4v}S0k~PTk=6(hVhFh$S{79EF<;3Q4xpni(GLSzW~{m zlMdtGNK$>Iz7T!k$$qJRQT8$>e@W7g#xk)gI@`IXIE-IBjqyYne}W;y_!F=n#-AWq zXNhVIRK^k%8OASQJx-c9eDAe3m>)#%O$K2YzbM*5es1q|n;!JS`0ZA>>0d_uGQ0`E zl#vMI7j;R7@o&JJFn)ondpGP0R6UF^{t)vn?Wi#RhOse>Kh2O~{4s;iv@(o8X7Eu; z8V@q3(P7{A95rTAg|9t(BRhhsmC-{VNb5{L17U{in}#vgKQ qO zsB53rSEM{>Q5e5hz{W6skF_a`-y?Bju<_-=t2D&RfoFPT5xk9I{9ZxKLqT2`zeg}; zNATh>{uD6^<4=*eADzFYL>PaHWhMzfjQ=oV7)md8S@X5}XMEAr&0+jr(J(_vvU=mt zH1;@$hw*z;c|_kt0|?~%+4-Vny0qM9W{KQbJ5e1*SU zcwzir8JolSJ=*3levfuY7(aO8-ypnMwVLR`i2mr{{}rO%PvQ3p|IGUk|3v&A;@%+# zj`(}z!!H@vf9c%iyRTe2d+s;_*uNOq;rVs6w#@&8^B2!vxN`fsqc@(p<9R6T_%m;O z=z~zswO1{R8Gcu30x+dIbJ z`Pb{N`@7FU=71i+0|LM7pHEXi5kk7JBO7~el4yd2BPtS~^#d*Rm?eAI>&0M4o6Lk)qWct7V9PT)Nf zdF``CDvqDk0?wnPd+__+D2;!`Ww3t1QSiC0Tx0#Lr_lXHc_)#23CaMjoJ$B??;?_Y z3uQi)9ttkEj};a!;ZN2ha5WE~c^pq<#RdMgqDLd|1o`e%xw!SuLA$x;cPb?X)wQdQwFz*>tp+df6_gASiQ!oKeZ0S?NCWLi?+EI zeg6G;3q1sN@yiK&GlSwnPiZwAS2cYO+Iscy zHnPTpdq>w__bb=^2!^h&o8U&Q{hNK(F6}Fw1^bN?IX#VcJ)Q7RbQ3wwz4*!h_E-bt z-a>L>4G`Au=nNz$*6sjIH=*(FS+Ujw>omOBOLj^+-Ln=ctgC0W$gVJVMra%@>xbWsmwpwlg9;6Fj diff --git a/source/Irrlicht/Irrlicht9.0.vcproj b/source/Irrlicht/Irrlicht9.0.vcproj index 05d2315f..14d0fd40 100644 --- a/source/Irrlicht/Irrlicht9.0.vcproj +++ b/source/Irrlicht/Irrlicht9.0.vcproj @@ -79,7 +79,7 @@ Name="VCLinkerTool" UseLibraryDependencyInputs="true" AdditionalOptions="/MACHINE:I386" - AdditionalDependencies="kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib opengl32.lib" + AdditionalDependencies="kernel32.lib user32.lib gdi32.lib opengl32.lib" OutputFile="..\..\bin\Win32-visualstudio\Irrlicht.dll" LinkIncremental="2" SuppressStartupBanner="true" @@ -179,7 +179,7 @@ + + diff --git a/source/Irrlicht/MacOSX/CIrrDeviceMacOSX.mm b/source/Irrlicht/MacOSX/CIrrDeviceMacOSX.mm index 58c7c332..c3b92a81 100644 --- a/source/Irrlicht/MacOSX/CIrrDeviceMacOSX.mm +++ b/source/Irrlicht/MacOSX/CIrrDeviceMacOSX.mm @@ -632,7 +632,7 @@ bool CIrrDeviceMacOSX::createWindow() CGLSetParameter(CGLContext,kCGLCPSwapInterval,&newSwapInterval); if (IsSoftwareRenderer && CreationParams.DriverType != video::EDT_NULL) { - long order = -1; // below window + GLint order = -1; // below window CGLSetParameter(CGLContext, kCGLCPSurfaceOrder, &order); } } @@ -677,7 +677,7 @@ void CIrrDeviceMacOSX::createDriver() case video::EDT_BURNINGSVIDEO: #ifdef _IRR_COMPILE_WITH_BURNINGSVIDEO_ - VideoDriver = video::createSoftwareDriver2(CreationParams.WindowSize, CreationParams.Fullscreen, FileSystem, this); + VideoDriver = video::createBurningVideoDriver(CreationParams, FileSystem, this); IsSoftwareRenderer = true; #else os::Printer::log("Burning's video driver was not compiled in.", ELL_ERROR); diff --git a/source/Irrlicht/MacOSX/OSXClipboard.mm b/source/Irrlicht/MacOSX/OSXClipboard.mm index 71369f92..1428c41b 100644 --- a/source/Irrlicht/MacOSX/OSXClipboard.mm +++ b/source/Irrlicht/MacOSX/OSXClipboard.mm @@ -6,12 +6,12 @@ #include "OSXClipboard.h" #import -void OSXCopyToClipboard(const char *text) +void OSXCopyToClipboard(const char *text) { - NSString *str; - NSPasteboard *board; + NSString *str; + NSPasteboard *board; - if (text != NULL && strlen(text) > 0) + if ((text != NULL) && (strlen(text) > 0)) { str = [NSString stringWithCString:text encoding:NSWindowsCP1252StringEncoding]; board = [NSPasteboard generalPasteboard]; @@ -20,15 +20,17 @@ void OSXCopyToClipboard(const char *text) } } -char* OSXCopyFromClipboard() +char* OSXCopyFromClipboard() { - NSString *str; - NSPasteboard *board; - char *result; - + NSString* str; + NSPasteboard* board; + char* result; + result = NULL; board = [NSPasteboard generalPasteboard]; str = [board stringForType:NSStringPboardType]; - if (str != nil) result = (char*)[str cStringUsingEncoding:NSWindowsCP1252StringEncoding]; + if (str != nil) + result = (char*)[str cStringUsingEncoding:NSWindowsCP1252StringEncoding]; return (result); } + diff --git a/tests/2dmaterial.cpp b/tests/2dmaterial.cpp index f50f323c..eac04cd3 100644 --- a/tests/2dmaterial.cpp +++ b/tests/2dmaterial.cpp @@ -645,6 +645,16 @@ static bool draw2DImage4c(video::E_DRIVER_TYPE type) video::IVideoDriver* driver = device->getVideoDriver(); + if (!driver->queryFeature(video::EVDF_BILINEAR_FILTER)) + { + device->closeDevice(); + device->run(); + device->drop(); + return true; + } + + logTestString("Testing driver %ls\n", driver->getName()); + driver->setTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS,true); driver->setTextureCreationFlag(video::ETCF_OPTIMIZED_FOR_QUALITY,true); @@ -739,6 +749,16 @@ static bool addBlend2d(video::E_DRIVER_TYPE type) video::IVideoDriver* driver = device->getVideoDriver(); scene::ISceneManager* smgr = device->getSceneManager(); + if (!driver->queryFeature(video::EVDF_BILINEAR_FILTER)) + { + device->closeDevice(); + device->run(); + device->drop(); + return true; + } + + logTestString("Testing driver %ls\n", driver->getName()); + scene::IAnimatedMesh* mesh = smgr->getMesh("../media/sydney.md2"); if (!mesh) { @@ -816,6 +836,16 @@ static bool moreFilterTests(video::E_DRIVER_TYPE type) video::IVideoDriver* driver = device->getVideoDriver(); gui::IGUIEnvironment* gui = device->getGUIEnvironment(); + if (!driver->queryFeature(video::EVDF_BILINEAR_FILTER)) + { + device->closeDevice(); + device->run(); + device->drop(); + return true; + } + + logTestString("Testing driver %ls\n", driver->getName()); + driver->setTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS, false); video::ITexture* tex = driver->getTexture("../media/irrlichtlogo.jpg"); gui::IGUIImage* image = gui->addImage(core::recti(0,0,64,64)); @@ -857,20 +887,9 @@ static bool moreFilterTests(video::E_DRIVER_TYPE type) bool twodmaterial() { - bool result = addBlend2d(video::EDT_OPENGL); - result &= addBlend2d(video::EDT_DIRECT3D9); - result &= addBlend2d(video::EDT_DIRECT3D8); - result &= addBlend2d(video::EDT_BURNINGSVIDEO); - - result &= moreFilterTests(video::EDT_OPENGL); - result &= moreFilterTests(video::EDT_DIRECT3D9); - result &= moreFilterTests(video::EDT_DIRECT3D8); - result &= moreFilterTests(video::EDT_BURNINGSVIDEO); - - result &= draw2DImage4c(video::EDT_OPENGL); - result &= draw2DImage4c(video::EDT_DIRECT3D9); - result &= draw2DImage4c(video::EDT_DIRECT3D8); - result &= draw2DImage4c(video::EDT_BURNINGSVIDEO); - + bool result = true; + TestWithAllDrivers(addBlend2d); + TestWithAllDrivers(moreFilterTests); + TestWithAllDrivers(draw2DImage4c); return result; } diff --git a/tests/anti-aliasing.cpp b/tests/anti-aliasing.cpp index 687f17d9..fb8f9886 100644 --- a/tests/anti-aliasing.cpp +++ b/tests/anti-aliasing.cpp @@ -25,6 +25,8 @@ static bool testLineRendering(video::E_DRIVER_TYPE type) return true; } + logTestString("Testing driver %ls\n", driver->getName()); + scene::ISceneManager* smgr = device->getSceneManager(); scene::IAnimatedMesh* mesh = smgr->getMesh("../media/sydney.md2"); @@ -62,7 +64,7 @@ static bool testLineRendering(video::E_DRIVER_TYPE type) bool antiAliasing() { - bool result = testLineRendering(video::EDT_OPENGL); - result &= testLineRendering(video::EDT_DIRECT3D9); + bool result = true; + TestWithAllHWDrivers(testLineRendering); return result; } diff --git a/tests/createImage.cpp b/tests/createImage.cpp index afde8577..3f82e39b 100644 --- a/tests/createImage.cpp +++ b/tests/createImage.cpp @@ -2,11 +2,11 @@ using namespace irr; -static bool testImageCreation(video::E_DRIVER_TYPE driverType) +static bool testImageCreation() { // create device - IrrlichtDevice *device = createDevice(driverType, core::dimension2d(160,120)); + IrrlichtDevice *device = createDevice(video::EDT_SOFTWARE, core::dimension2d(160,120)); if (device == 0) return true; // could not create selected driver. @@ -37,6 +37,6 @@ static bool testImageCreation(video::E_DRIVER_TYPE driverType) bool createImage() { - bool result = testImageCreation(video::EDT_SOFTWARE); + bool result = testImageCreation(); return result; } diff --git a/tests/draw2DImage.cpp b/tests/draw2DImage.cpp index 70cff392..6e57a5f5 100644 --- a/tests/draw2DImage.cpp +++ b/tests/draw2DImage.cpp @@ -16,6 +16,15 @@ bool testWithRenderTarget(video::E_DRIVER_TYPE driverType) video::IVideoDriver* driver = device->getVideoDriver(); + if (!driver->queryFeature(video::EVDF_RENDER_TO_TARGET)) + { + device->closeDevice(); + device->run(); + device->drop(); + return true; + } + logTestString("Testing driver %ls\n", driver->getName()); + video::ITexture* RenderTarget=driver->addRenderTargetTexture(core::dimension2d(64,64), "BASEMAP"); video::ITexture *tex=driver->getTexture("../media/water.jpg"); @@ -53,13 +62,56 @@ bool testWithPNG(video::E_DRIVER_TYPE driverType) video::IVideoDriver* driver = device->getVideoDriver(); + logTestString("Testing driver %ls\n", driver->getName()); + video::ITexture *tex=driver->getTexture("media/RedbrushAlpha-0.25.png"); driver->beginScene(true, true, video::SColor(255,40,40,255));//Backbuffer background is blue driver->draw2DImage(tex, core::recti(0,0,160,120), core::recti(0,0,256,256), 0, 0, true); driver->endScene(); - bool result = takeScreenshotAndCompareAgainstReference(driver, "-draw2DImagePNG.png"); + bool result = takeScreenshotAndCompareAgainstReference(driver, "-draw2DImagePNG.png", 98.f); + + device->closeDevice(); + device->run(); + device->drop(); + + return result; +} + +// draws an image and checks if the written example equals the original image +bool testExactPlacement(video::E_DRIVER_TYPE driverType) +{ + // create device + + IrrlichtDevice *device = createDevice(video::EDT_DIRECT3D9, core::dimension2d(160,120), 32); + + if (device == 0) + return true; // could not create selected driver. + + video::IVideoDriver* driver = device->getVideoDriver(); + + if (driver->getColorFormat() != video::ECF_A8R8G8B8 || !driver->queryFeature(video::EVDF_RENDER_TO_TARGET)) + { + device->closeDevice(); + device->run(); + device->drop(); + return true; + } + logTestString("Testing driver %ls\n", driver->getName()); + + video::ITexture* rt=driver->addRenderTargetTexture(core::dimension2d(32,32), "rt1"); + video::ITexture *tex=driver->getTexture("../media/fireball.bmp"); + + driver->beginScene(true, true, video::SColor(255,40,40,255));//Backbuffer background is blue + driver->setRenderTarget(rt); + driver->draw2DImage(tex, core::recti(0,0,32,32), core::recti(0,0,64,64)); + driver->setRenderTarget(0); + driver->endScene(); + + video::IImage* img = driver->createImage(rt, core::vector2di(), rt->getSize()); + driver->writeImageToFile(img, "results/fireball.png"); + bool result = binaryCompareFiles("media/fireball.png", "results/fireball.png"); device->closeDevice(); device->run(); @@ -72,14 +124,9 @@ bool testWithPNG(video::E_DRIVER_TYPE driverType) bool draw2DImage() { - bool result = testWithRenderTarget(video::EDT_DIRECT3D9); - result &= testWithRenderTarget(video::EDT_DIRECT3D8); - result &= testWithRenderTarget(video::EDT_OPENGL); - result &= testWithRenderTarget(video::EDT_BURNINGSVIDEO); - result &= testWithRenderTarget(video::EDT_SOFTWARE); - - result &= testWithPNG(video::EDT_DIRECT3D9); - result &= testWithPNG(video::EDT_OPENGL); - + bool result = true; + TestWithAllDrivers(testWithRenderTarget); + TestWithAllDrivers(testExactPlacement); + TestWithAllHWDrivers(testWithPNG); return result; } diff --git a/tests/drawPixel.cpp b/tests/drawPixel.cpp index b8456d54..649d1358 100644 --- a/tests/drawPixel.cpp +++ b/tests/drawPixel.cpp @@ -24,6 +24,8 @@ static bool lineRender(E_DRIVER_TYPE driverType) IVideoDriver* driver = device->getVideoDriver(); ISceneManager * smgr = device->getSceneManager(); + logTestString("Testing driver %ls\n", driver->getName()); + // Draw a cube background so that we can check that the pixels' alpha is working. ISceneNode * cube = smgr->addCubeSceneNode(50.f, 0, -1, vector3df(0, 0, 60)); cube->setMaterialTexture(0, driver->getTexture("../media/wall.bmp")); @@ -64,6 +66,8 @@ static bool pixelAccuracy(E_DRIVER_TYPE driverType) IVideoDriver* driver = device->getVideoDriver(); + logTestString("Testing driver %ls\n", driver->getName()); + device->getSceneManager()->addCameraSceneNode(); driver->beginScene(true, true, SColor(255,100,101,140)); @@ -108,30 +112,10 @@ static bool pixelAccuracy(E_DRIVER_TYPE driverType) bool drawPixel(void) { - bool passed = true; + bool result = true; - logTestString("Check OpenGL driver\n"); - passed &= lineRender(EDT_OPENGL); - logTestString("Check Software driver\n"); - passed &= lineRender(EDT_SOFTWARE); - logTestString("Check Burning's Video driver\n"); - passed &= lineRender(EDT_BURNINGSVIDEO); - logTestString("Check Direct3D9 driver\n"); - passed &= lineRender(EDT_DIRECT3D9); - logTestString("Check Direct3D8 driver\n"); - passed &= lineRender(EDT_DIRECT3D8); + TestWithAllDrivers(lineRender); + TestWithAllDrivers(pixelAccuracy); - logTestString("Check OpenGL driver\n"); - passed &= pixelAccuracy(EDT_OPENGL); - logTestString("Check Software driver\n"); - passed &= pixelAccuracy(EDT_SOFTWARE); - logTestString("Check Burning's Video driver\n"); - passed &= pixelAccuracy(EDT_BURNINGSVIDEO); - logTestString("Check Direct3D9 driver\n"); - passed &= pixelAccuracy(EDT_DIRECT3D9); - logTestString("Check Direct3D8 driver\n"); - passed &= pixelAccuracy(EDT_DIRECT3D8); - - return passed; + return result; } - diff --git a/tests/drawRectOutline.cpp b/tests/drawRectOutline.cpp index 29e0926b..8db16925 100644 --- a/tests/drawRectOutline.cpp +++ b/tests/drawRectOutline.cpp @@ -11,6 +11,8 @@ bool testWithDriver(video::E_DRIVER_TYPE driverType) video::IVideoDriver* driver = device->getVideoDriver(); + logTestString("Testing driver %ls\n", driver->getName()); + driver->beginScene(true, true, video::SColor(255,100,101,140)); core::recti r; @@ -40,10 +42,7 @@ bool testWithDriver(video::E_DRIVER_TYPE driverType) bool drawRectOutline(void) { // TODO: Only OpenGL supports thick lines - bool result = testWithDriver(video::EDT_BURNINGSVIDEO); - result &= testWithDriver(video::EDT_DIRECT3D8); - result &= testWithDriver(video::EDT_DIRECT3D9); - result &= testWithDriver(video::EDT_OPENGL); - result &= testWithDriver(video::EDT_SOFTWARE); + bool result = true; + TestWithAllDrivers(testWithDriver); return result; } diff --git a/tests/guiDisabledMenu.cpp b/tests/guiDisabledMenu.cpp index d505c8d6..fba5a882 100644 --- a/tests/guiDisabledMenu.cpp +++ b/tests/guiDisabledMenu.cpp @@ -14,7 +14,7 @@ using namespace gui; bool guiDisabledMenu(void) { - IrrlichtDevice *device = createDevice( video::EDT_OPENGL, + IrrlichtDevice *device = createDevice( video::EDT_BURNINGSVIDEO, dimension2d(160, 40), 32); assert(device); if (!device) diff --git a/tests/ioScene.cpp b/tests/ioScene.cpp index 1f68de93..3996cc18 100644 --- a/tests/ioScene.cpp +++ b/tests/ioScene.cpp @@ -94,7 +94,10 @@ static bool loadScene(void) { smgr->drawAll(); driver->endScene(); - result = takeScreenshotAndCompareAgainstReference(driver, "-loadScene.png", 98.91f); + // we need to be very sloppy, because the animators will produce a different + // start depending on the actual loading time. 97% seems to be safe, as removing + // an object produces values around 95% + result = takeScreenshotAndCompareAgainstReference(driver, "-loadScene.png", 97.4f); if (!result) logTestString("Rendering the loaded scene failed.\n"); } diff --git a/tests/irrArray.cpp b/tests/irrArray.cpp index 4c10b843..176ba9b2 100644 --- a/tests/irrArray.cpp +++ b/tests/irrArray.cpp @@ -8,6 +8,60 @@ using namespace irr; using namespace core; +core::map countReferences; + +struct SDummy +{ + SDummy(int a) : x(a) + { + countReferences.insert(x,1); + } + + SDummy() : x(0) + { + countReferences.insert(x,1); + } + + SDummy(const SDummy& other) + { + x = other.x; + countReferences[x] = countReferences[x] + 1; + } + + ~SDummy() + { + countReferences[x] = countReferences[x] - 1; + } + + int x; +}; + +static bool testErase() +{ + { + core::array aaa; + aaa.push_back(SDummy(0)); + aaa.push_back(SDummy(1)); + aaa.push_back(SDummy(2)); + aaa.push_back(SDummy(3)); + aaa.push_back(SDummy(4)); + aaa.push_back(SDummy(5)); + + aaa.erase(0,2); + } + + for ( core::map::Iterator it = countReferences.getIterator(); !it.atEnd(); it++ ) + { + if ( it->getValue() != 0 ) + { + logTestString("testErase: wrong count for %d, it's: %d\n", it->getKey(), it->getValue()); + return false; + } + } + return true; +} + + struct VarArray { core::array < int, core::irrAllocatorFast > MyArray; @@ -69,6 +123,7 @@ bool testIrrArray(void) crashTestFastAlloc(); allExpected &= testSelfAssignment(); allExpected &= testSwap(); + allExpected &= testErase(); if(allExpected) logTestString("\nAll tests passed\n"); diff --git a/tests/lightMaps.cpp b/tests/lightMaps.cpp index 6c86de14..8d968fe4 100644 --- a/tests/lightMaps.cpp +++ b/tests/lightMaps.cpp @@ -20,6 +20,15 @@ static bool runTestWithDriver(E_DRIVER_TYPE driverType) IVideoDriver* driver = device->getVideoDriver(); ISceneManager * smgr = device->getSceneManager(); + logTestString("Testing driver %ls\n", driver->getName()); + if (driver->getDriverAttributes().getAttributeAsInt("MaxTextures")<2) + { + device->closeDevice(); + device->run(); + device->drop(); + return true; + } + bool result = true; bool added = device->getFileSystem()->addFileArchive("../media/map-20kdm2.pk3"); assert(added); @@ -55,13 +64,8 @@ static bool runTestWithDriver(E_DRIVER_TYPE driverType) bool lightMaps(void) { - bool passed = true; - - passed &= runTestWithDriver(EDT_OPENGL); - passed &= runTestWithDriver(EDT_BURNINGSVIDEO); - passed &= runTestWithDriver(EDT_DIRECT3D9); - passed &= runTestWithDriver(EDT_DIRECT3D8); - - return passed; + bool result = true; + TestWithAllDrivers(runTestWithDriver); + return result; } diff --git a/tests/lights.cpp b/tests/lights.cpp index 1c6c04c1..be934143 100644 --- a/tests/lights.cpp +++ b/tests/lights.cpp @@ -10,34 +10,46 @@ static bool testLightTypes(video::E_DRIVER_TYPE driverType) IrrlichtDevice *device = createDevice (driverType, core::dimension2d(128,128)); if (!device) return true; // No error if device does not exist -// device->getSceneManager()->setAmbientLight(video::SColorf(0.3f,0.3f,0.3f)); - scene::ICameraSceneNode* cam = device->getSceneManager()->addCameraSceneNode(); + + video::IVideoDriver* driver = device->getVideoDriver(); + scene::ISceneManager* smgr = device->getSceneManager(); + if (!driver->getDriverAttributes().getAttributeAsInt("MaxLights")) + { + device->closeDevice(); + device->run(); + device->drop(); + return true; + } + logTestString("Testing driver %ls\n", driver->getName()); + +// smgr->setAmbientLight(video::SColorf(0.3f,0.3f,0.3f)); + scene::ICameraSceneNode* cam = smgr->addCameraSceneNode(); cam->setPosition(core::vector3df(0,200,0)); cam->setTarget(core::vector3df()); - device->getSceneManager()->addAnimatedMeshSceneNode(device->getSceneManager()->addHillPlaneMesh("plane", core::dimension2df(4,4), core::dimension2du(128,128))); - scene::ILightSceneNode* light1 = device->getSceneManager()->addLightSceneNode(0, core::vector3df(-100,30,-100)); + smgr->addAnimatedMeshSceneNode(device->getSceneManager()->addHillPlaneMesh("plane", core::dimension2df(4,4), core::dimension2du(128,128))); + scene::ILightSceneNode* light1 = smgr->addLightSceneNode(0, core::vector3df(-100,30,-100)); light1->setLightType(video::ELT_POINT); light1->setRadius(100.f); light1->getLightData().DiffuseColor.set(0,1,1); -// device->getSceneManager()->addCubeSceneNode(10, light1)->setMaterialFlag(video::EMF_LIGHTING, false); - scene::ILightSceneNode* light2 = device->getSceneManager()->addLightSceneNode(0, core::vector3df(100,30,100)); +// smgr->addCubeSceneNode(10, light1)->setMaterialFlag(video::EMF_LIGHTING, false); + scene::ILightSceneNode* light2 = smgr->addLightSceneNode(0, core::vector3df(100,30,100)); light2->setRotation(core::vector3df(90,0,0)); light2->setLightType(video::ELT_SPOT); light2->setRadius(100.f); light2->getLightData().DiffuseColor.set(1,0,0); light2->getLightData().InnerCone=10.f; light2->getLightData().OuterCone=30.f; -// device->getSceneManager()->addCubeSceneNode(10, light2)->setMaterialFlag(video::EMF_LIGHTING, false); - scene::ILightSceneNode* light3 = device->getSceneManager()->addLightSceneNode(); +// smgr->addCubeSceneNode(10, light2)->setMaterialFlag(video::EMF_LIGHTING, false); + scene::ILightSceneNode* light3 = smgr->addLightSceneNode(); light3->setRotation(core::vector3df(15,0,0)); light3->setLightType(video::ELT_DIRECTIONAL); light1->getLightData().DiffuseColor.set(0,1,0); - device->getVideoDriver()->beginScene (true, true, 0); - device->getSceneManager()->drawAll(); - device->getVideoDriver()->endScene(); + driver->beginScene (true, true, 0); + smgr->drawAll(); + driver->endScene(); - const bool result = takeScreenshotAndCompareAgainstReference(device->getVideoDriver(), "-lightType.png", 99.91f); + const bool result = takeScreenshotAndCompareAgainstReference(driver, "-lightType.png", 99.91f); device->closeDevice(); device->run(); @@ -48,14 +60,8 @@ static bool testLightTypes(video::E_DRIVER_TYPE driverType) bool lights(void) { - bool passed = true; - - passed &= testLightTypes(video::EDT_OPENGL); + bool result = true; // no lights in sw renderer -// passed &= testLightTypes(video::EDT_SOFTWARE); - passed &= testLightTypes(video::EDT_BURNINGSVIDEO); - passed &= testLightTypes(video::EDT_DIRECT3D9); - passed &= testLightTypes(video::EDT_DIRECT3D8); - - return passed; + TestWithAllDrivers(testLightTypes); + return result; } diff --git a/tests/main.cpp b/tests/main.cpp index 9c3c22b9..93400bf7 100644 --- a/tests/main.cpp +++ b/tests/main.cpp @@ -92,6 +92,8 @@ int main(int argumentCount, char * arguments[]) TEST(createImage); TEST(cursorSetVisible); TEST(flyCircleAnimator); + TEST(guiDisabledMenu); + TEST(makeColorKeyTexture); TEST(md2Animation); TEST(meshTransform); TEST(skinnedMesh); @@ -100,10 +102,9 @@ int main(int argumentCount, char * arguments[]) TEST(ioScene); // all driver checks TEST(videoDriver); + TEST(screenshot); TEST(drawPixel); TEST(drawRectOutline); - TEST(guiDisabledMenu); - TEST(makeColorKeyTexture); TEST(material); TEST(renderTargetTexture); TEST(textureFeatures); @@ -138,9 +139,9 @@ int main(int argumentCount, char * arguments[]) { for (unsigned int i=0; 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 diff --git a/tests/media/Direct3D 9.0-renderMipmap.png b/tests/media/Direct3D 9.0-renderMipmap.png new file mode 100644 index 0000000000000000000000000000000000000000..86e4fda680642596f10379bfe5fd76100ef851b4 GIT binary patch literal 917 zcmeAS@N?(olHy`uVBq!ia0vp^3xK$Sg9%9fI*@0EaktG3V{A{n^5WGRHou zf3r@pFi2?QU}ctjb-{7NY{^Yre0+IUW_MW>6}WUg4Hw_NWAu}KhoyJRKgOt4E9M9+ zbUMNz>F4o~yU^o`&}1`v5BuxO&vzG0`8N9r^ZRFdCu6hLe&IRvdd^pdO%Yr^evKzJ z4J=vJmLGT`GGh+YDC%2o+-K+nfeLLq}%SO&WtLpeC zv(9$h$r!G&-#Q@qfK~zb&ZD+JzHTp&`}p?L^!ZsnRXlMwoh2T=`nl<=!Z!N-!*mV;n@BD z<==0y@nxAZNI$-w6MFp5$J-_6OPO+*!UN{M?VCJDWnby$N3x%ME7;!)_()cK)~wok zcJcKkT8o+2ul4^PqyAvZ+eNZ{_n(_}ec)@AExG)GZ}s}+b<;xDvoDzLb=Dy9(wt2P zCV${#SR`=Q~pxn88}($%Ru3 zMHS?tCcjkZVBh}l{NLQ`DUXyjKXA-Ff14q0%DbNEg6|hQE;n(m;eNa5b%nu&}vm1k_tDnm{r-UW|k<6Qx literal 0 HcmV?d00001 diff --git a/tests/media/Irrlicht Software Driver 1.0-projMat.png b/tests/media/Irrlicht Software Driver 1.0-projMat.png index b39703729e0b78360cb6063c6512131f9496aab5..4cb549d8d04ac79f52a49912299369feb1b5aa10 100644 GIT binary patch delta 102 zcmbQie3of~im`~Ni(^Q|oVOP@3N{!Buoyc3x1G&*V#C?KDRo7kt=SlW;LpLW)dzw; xf1UN`=-z6E`$7{J*i2ln3q#^E2_Que82C4>Vm$HpdFe$E*VEO{Wt~$(69C2NFn-65L+ntok zc)VkIX4$UW{gNn_*Z=*QBlUajjWZJ$M#&^F06_u+zjp`6qqi9EaktG3V`E?ILAEfwre> zSsR_1#b^DJVrJ%@tK6c?cUtaX!GeY7Cn)Rin3xtvh-uuudR1zxN&k^=@BeFuXSrQ_ z@pYxCu)3Gn?6ckqJN{UdGoQHbbnrodfrSVUA8WHSY0N(+&rhcsZ|PXJv%dDPb#V2G zGZ65`|sD)tWvA9*JX^%ZJV4qR$qNJ`?ljgj(g1q7bX;FNX+?Hz2SV--wUPI z>W9mIFY#x8n?E`Gap>*0!5@p}x`&^wElq5B@b%Zi8R>IoPgo(9o>9f=F=cv2?v`VD ze6rEE-%g%0t3bS}*(kiDPJ7wxok^=shjzJNwY8`c-Tm*^9dDUD$vbDCxw`F_NO^YX zp3|0dGv+<(S^n`snbPiWirHPUihQee9@ITcN|}A+Hs>kFDen))Zl9fZoh2Y(lkbO_ z?Q3Ebm&x>M9(*z-^?_ScMdGiWF=p+Dqknl$`~0NF>h8zQFS?hObK^S+qK&G-{+A$NcOL(}l= z<;D&LJs~$cu9aGI=E*+wGh6t&tV-wG@7kCHhyVYWcKOaBKP|~W6GU@QGQCS*?cx)E z<<9Yh18fDI-n9zLt!6!q-CKBeN^(=&bg^V}x1Uvi4=BuYdi!ki>4TMF>lt$Ngzj+lA``Nj8&%Y#XfKar}rhfhV_uc6*CE5Bd4Ze>9_o0yzot zMtsfxqxDnja`)`~e`<~(kDud*_x=2}I_pggrypNpA?edQ>AuOa^B1aTY2VLu^Sypa z%g4W1_r34R^O;#ZdWZkD9R8f7SZbp$_HnhJ=jRt9!5!z4`?`5fy@}7e6?xy()1O~W zsrB`$%TvzZSv);MGjREl@LRmglXphW*IsC0IJa;L+iaU<%9|BZ!@PWNFAMgWeOBeQ zFK2Cb!$N_B&wdqNeCD!YR<($~zUNZq zvykc1Uy|ID(&sKZ({Q3?#rxgD(_f~1@?JM5;_$nJX>1kDX;(l diff --git a/tests/media/OpenGL-guiDisabledMenu.png b/tests/media/OpenGL-guiDisabledMenu.png deleted file mode 100644 index 425a582ef9ea2adbfef18f010bf0e873100425bc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 742 zcmV001Zm0ssI2_D*Kc00085Nkl zq?}A9y$+uGx~_8%_eVUD@)3zg6_E+a0`m?LqucFT zmIVO1u44%1e?IIhmklBlq`_ctJRSu{?Q}Zx`CM?&?<9Gd^cT<>EDU;+RdVtX80aBO z6mq-WzFaPXqqbVD*=!~_=yxJbr&HUu^G>4xIF3`qiIQ#GKAlc^r}61*w_C??^3LKN zWT4w&sUd+&4hdXxG7z|4uLHp0aESZk&NNXn94+4{B1tQ-qA0uFjv4#?KHQYy+YCif z#Jn3p#9FNu*^xpc13;QDcb?~|s_J>3=Xn6YY*qq*qA08nYYK104BnHq-W`NT5%z^q zQ4}7!=u`k8;R|~buIs9*%53=kG1zro-pZ=1WFA#jBdy%#B`1BJufXSp?@`2GIMQCC zHBECI$8j8Hz`ef%?mBtN*O!m_a70dYL07$T(h9uUY#NOQGs0Cirlx7U31Z?!wvGGq z5|}4k0;JJsv|KLpPUBN(Hk<49I_~#EaktG3V{A^ZvI>WsZMT ze_|bWYCCh`Y~PeyS64;H=yV>*IVogVbeY**c>TL)^&1zzdv$+-dA+P+Q9)MyHC6KEGX_|LM&4-?_h+K7Q;yKXI3pGk?O` zrw1O%M9g9CE>0-q(>d38)N+FbyV!Hu2rjz7EYt8PhIfbmakaSzYBuVBamiJ2c+k5u zIs5T>M$Ly??hUt|`ZWXrSKDOA;T!G!-J`_ngfF3m4le_^v%+0;8{KVB00>F@5{ zJ7ed(nOk|JWA9jdOkoX++R9I}Eb|1+En;hbm@SSsto>a@ZW|UvKnxO81ifd4Eoqw_WboXXOyoe*gQ1H{0KT{`op5 zw&Kq37v=i-j4#h!_gD7&>-GMs?Vf87IRt*ltQMb1fF@0To|^6Y5mzN2CO+vWN8IOk>M6lDGGy3qcWN zLr;CrzTbx*$Jc+5Eo|Olba}7eb=zHLC)DmNdAm;8)bfM!(=P4(!2a9I-q-T=COmK|t^U6D{tlt4X&+3O#aFG{t?Te${)@ebHU7tN*8RF2 zTORw}k&)@V*yqhh|2VF*n9gW*^UV{x3A^4gtMFYs%5vk*?f-c;+DG=kFrZoXz|4o= a&1Kzw-}c)(bq}zNVDNPHb6Mw<&;$S_IugqO literal 0 HcmV?d00001 diff --git a/tests/media/OpenGL-terrainSceneNode-1.png b/tests/media/OpenGL-terrainSceneNode-1.png deleted file mode 100644 index 7d1974f6b8b66aa0da69b3ecb9b1235e4ff27553..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 37019 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 literal 0 HcmV?d00001 diff --git a/tests/media/ter1.png b/tests/media/ter1.png new file mode 100644 index 0000000000000000000000000000000000000000..0675b27514d4710d8d5b2b82813dc020c72035fe GIT binary patch literal 41095 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�YS%yM$owdblCcN8;I)` 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�n*b3? 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, 120), 32); + IrrlichtDevice *device = createDevice(video::EDT_NULL, core::dimension2d(160, 120), 32); assert(device); if (!device) return false; - ISceneManager * smgr = device->getSceneManager(); - - IAnimatedMesh* mesh = smgr->getMesh("../media/ninja.b3d"); + scene::ISceneManager * smgr = device->getSceneManager(); + scene::IAnimatedMesh* mesh = smgr->getMesh("../media/ninja.b3d"); assert(mesh); bool result = (mesh != 0); diff --git a/tests/mrt.cpp b/tests/mrt.cpp index 0c0cbf9f..6d2cda04 100644 --- a/tests/mrt.cpp +++ b/tests/mrt.cpp @@ -17,6 +17,8 @@ static bool testWithDriver(video::E_DRIVER_TYPE driverType) // if (driver->getDriverAttributes().getAttributeAsInt("ShaderLanguageVersion")<=100) // return true; + logTestString("Testing driver %ls\n", driver->getName()); + const char* const ps1="struct PS_INPUT\n {\n float4 Position : POSITION0;\n };\n\n struct PS_OUTPUT\n {\n float4 Color : COLOR0;\n float4 Normal : COLOR1;\n float4 Depth : COLOR2;\n };\n PS_OUTPUT pixelMain( PS_INPUT Input )\n {\n PS_OUTPUT Output;\n Output.Color = float4(1.0,1.0,1.0,1.0);\n Output.Normal = float4(0.0,1.0,0.0,1.0);\n Output.Depth = float4(0.0,0.0,1.0,1.0);\n return Output;\n }"; const char* const ps2="void main(void)\n {\n gl_FragData[0] = vec4(1.0,1.0,1.0,1.0);\n gl_FragData[1] = vec4(0.0,1.0,0.0,1.0);\n gl_FragData[2] = vec4(0.0,0.0,1.0,1.0);\n }"; @@ -95,10 +97,9 @@ static bool testWithDriver(video::E_DRIVER_TYPE driverType) bool mrt(void) { - bool passed = true; + bool result = true; - passed &= testWithDriver(video::EDT_OPENGL); - passed &= testWithDriver(video::EDT_DIRECT3D9); + TestWithAllHWDrivers(testWithDriver); - return passed; + return result; } diff --git a/tests/projectionMatrix.cpp b/tests/projectionMatrix.cpp index 5abcb2ba..457ac63f 100644 --- a/tests/projectionMatrix.cpp +++ b/tests/projectionMatrix.cpp @@ -19,6 +19,8 @@ static bool runTestWithDriver(E_DRIVER_TYPE driverType) IVideoDriver* driver = device->getVideoDriver(); + logTestString("Testing driver %ls\n", driver->getName()); + bool result = true; driver->beginScene(true, false, SColor(255,0,0,0)); @@ -73,14 +75,10 @@ static bool runTestWithDriver(E_DRIVER_TYPE driverType) bool projectionMatrix(void) { - bool passed = true; + bool result = true; // TODO: Seems that software driver does not handle this projection matrix -// passed &= runTestWithDriver(EDT_SOFTWARE); - passed &= runTestWithDriver(EDT_BURNINGSVIDEO); - passed &= runTestWithDriver(EDT_DIRECT3D9); - passed &= runTestWithDriver(EDT_DIRECT3D8); - passed &= runTestWithDriver(EDT_OPENGL); + TestWithAllDrivers(runTestWithDriver); - return passed; + return result; } diff --git a/tests/renderTargetTexture.cpp b/tests/renderTargetTexture.cpp index b275b284..871472a2 100644 --- a/tests/renderTargetTexture.cpp +++ b/tests/renderTargetTexture.cpp @@ -13,10 +13,18 @@ static bool testWith2DImage(video::E_DRIVER_TYPE driverType) if (!device) return true; // No error if device does not exist - device->setWindowCaption (L"Irrlicht - RTT Bug report"); video::IVideoDriver *driver = device->getVideoDriver (); scene::ISceneManager *smgr = device->getSceneManager (); + if (!driver->queryFeature(video::EVDF_RENDER_TO_TARGET)) + { + device->closeDevice(); + device->run(); + device->drop(); + return true; + } + logTestString("Testing driver %ls\n", driver->getName()); + video::ITexture *image = driver->getTexture ("../media/irrlichtlogo2.png"); video::ITexture *RTT_texture = driver->addRenderTargetTexture (core::dimension2d < u32 > (128, 128)); @@ -101,6 +109,8 @@ bool rttAndZBuffer(video::E_DRIVER_TYPE driverType) IrrlichtDevice* nullDevice = createDevice(video::EDT_NULL); cp.WindowSize = nullDevice->getVideoModeList()->getDesktopResolution(); + nullDevice->closeDevice(); + nullDevice->run(); nullDevice->drop(); cp.WindowSize -= core::dimension2d(100, 100); @@ -113,7 +123,14 @@ bool rttAndZBuffer(video::E_DRIVER_TYPE driverType) scene::ISceneManager* sm = device->getSceneManager(); if (!vd->queryFeature(video::EVDF_RENDER_TO_TARGET)) + { + device->closeDevice(); + device->run(); + device->drop(); return true; + } + + logTestString("Testing driver %ls\n", vd->getName()); video::ITexture* rt = vd->addRenderTargetTexture(cp.WindowSize, "rt", video::ECF_A32B32G32R32F); video::S3DVertex vertices[4]; @@ -196,10 +213,24 @@ bool rttAndText(video::E_DRIVER_TYPE driverType) video::IVideoDriver* driver = device->getVideoDriver(); gui::IGUIEnvironment* guienv = device->getGUIEnvironment(); + if (!driver->queryFeature(video::EVDF_RENDER_TO_TARGET)) + { + device->closeDevice(); + device->run(); + device->drop(); + return true; + } + logTestString("Testing driver %ls\n", driver->getName()); + //RTT video::ITexture* rt = driver->addRenderTargetTexture(core::dimension2d(256, 256), "rt"); if (!rt) + { + device->closeDevice(); + device->run(); + device->drop(); return false; + } driver->beginScene(true, true, video::SColor(255,255, 255, 255)); driver->setRenderTarget(rt, true, true, video::SColor(255,255,0,255)); @@ -245,7 +276,8 @@ bool rttAndText(video::E_DRIVER_TYPE driverType) static void Render(IrrlichtDevice* device, video::ITexture* rt, core::vector3df& pos1, core::vector3df& pos2, scene::IAnimatedMesh* sphereMesh, core::vector3df& pos3, core::vector3df& pos4) { - device->getVideoDriver()->setRenderTarget(rt); + video::IVideoDriver* driver = device->getVideoDriver(); + driver->setRenderTarget(rt); device->getSceneManager()->drawAll(); video::SMaterial mat; @@ -257,27 +289,27 @@ static void Render(IrrlichtDevice* device, video::ITexture* rt, core::vector3df& core::matrix4 m; m.setTranslation(pos1); - device->getVideoDriver()->setTransform(video::ETS_WORLD, m); - device->getVideoDriver()->setMaterial(mat); - device->getVideoDriver()->drawMeshBuffer(sphereMesh->getMeshBuffer(0)); + driver->setTransform(video::ETS_WORLD, m); + driver->setMaterial(mat); + driver->drawMeshBuffer(sphereMesh->getMeshBuffer(0)); m.setTranslation(pos2); mat.Shininess=0.f; - device->getVideoDriver()->setTransform(video::ETS_WORLD, m); - device->getVideoDriver()->setMaterial(mat); - device->getVideoDriver()->drawMeshBuffer(sphereMesh->getMeshBuffer(0)); + driver->setTransform(video::ETS_WORLD, m); + driver->setMaterial(mat); + driver->drawMeshBuffer(sphereMesh->getMeshBuffer(0)); m.setTranslation(pos3); mat.Shininess=8.f; - device->getVideoDriver()->setTransform(video::ETS_WORLD, m); - device->getVideoDriver()->setMaterial(mat); - device->getVideoDriver()->drawMeshBuffer(sphereMesh->getMeshBuffer(0)); + driver->setTransform(video::ETS_WORLD, m); + driver->setMaterial(mat); + driver->drawMeshBuffer(sphereMesh->getMeshBuffer(0)); m.setTranslation(pos4); mat.Shininess=0.f; - device->getVideoDriver()->setTransform(video::ETS_WORLD, m); - device->getVideoDriver()->setMaterial(mat); - device->getVideoDriver()->drawMeshBuffer(sphereMesh->getMeshBuffer(0)); + driver->setTransform(video::ETS_WORLD, m); + driver->setMaterial(mat); + driver->drawMeshBuffer(sphereMesh->getMeshBuffer(0)); } bool rttAndAntiAliasing(video::E_DRIVER_TYPE driverType) @@ -293,6 +325,16 @@ bool rttAndAntiAliasing(video::E_DRIVER_TYPE driverType) return true; video::IVideoDriver* driver = device->getVideoDriver(); + if ((driver->getDriverAttributes().getAttributeAsInt("AntiAlias")<2) || + (!driver->queryFeature(video::EVDF_RENDER_TO_TARGET))) + { + device->closeDevice(); + device->run(); + device->drop(); + return true; + } + + logTestString("Testing driver %ls\n", driver->getName()); // sphere mesh scene::IAnimatedMesh* sphereMesh = device->getSceneManager()->addSphereMesh("atom", 1, 32, 32); @@ -320,11 +362,11 @@ bool rttAndAntiAliasing(video::E_DRIVER_TYPE driverType) video::ITexture* rt1 = device->getVideoDriver()->addRenderTargetTexture(dim_txt, "rt1", device->getColorFormat()); video::ITexture* rt2 = device->getVideoDriver()->addRenderTargetTexture(dim_txt, "rt2", device->getColorFormat()); - video::ITexture* rt3 = device->getVideoDriver()->addRenderTargetTexture(dim_txt, "rt3", video::ECF_A8R8G8B8);//device->getColorFormat()); + video::ITexture* rt3 = device->getVideoDriver()->addRenderTargetTexture(dim_txt, "rt3", video::ECF_A8R8G8B8); video::ITexture* rt4 = device->getVideoDriver()->addRenderTargetTexture(dim_txt, "rt4", device->getColorFormat()); device->getSceneManager()->setActiveCamera(cam); - device->getVideoDriver()->beginScene(); //true, true, SColor(0, 30, 40, 60)); + device->getVideoDriver()->beginScene(); #if 1 st->setText(L"Texture Rendering"); Render(device, rt1, pos1, pos2, sphereMesh, pos3, pos4); @@ -365,6 +407,8 @@ bool rttFormats(video::E_DRIVER_TYPE driverType) video::IVideoDriver* driver = device->getVideoDriver(); + logTestString("Testing driver %ls\n", driver->getName()); + video::ITexture* tex = 0; { @@ -517,41 +561,19 @@ bool rttFormats(video::E_DRIVER_TYPE driverType) bool renderTargetTexture(void) { - bool passed = true; + bool result = true; - passed &= testWith2DImage(video::EDT_OPENGL); - passed &= testWith2DImage(video::EDT_SOFTWARE); - passed &= testWith2DImage(video::EDT_BURNINGSVIDEO); - passed &= testWith2DImage(video::EDT_DIRECT3D9); - passed &= testWith2DImage(video::EDT_DIRECT3D8); + TestWithAllDrivers(testWith2DImage); #if 0 - passed &= rttAndZBuffer(video::EDT_OPENGL); - passed &= rttAndZBuffer(video::EDT_SOFTWARE); - passed &= rttAndZBuffer(video::EDT_BURNINGSVIDEO); - passed &= rttAndZBuffer(video::EDT_DIRECT3D9); - passed &= rttAndZBuffer(video::EDT_DIRECT3D8); + TestWithAllDrivers(rttAndZBuffer); #endif - passed &= rttAndAntiAliasing(video::EDT_OPENGL); -// passed &= rttAndAntiAliasing(video::EDT_SOFTWARE); - passed &= rttAndAntiAliasing(video::EDT_BURNINGSVIDEO); - passed &= rttAndAntiAliasing(video::EDT_DIRECT3D9); - passed &= rttAndAntiAliasing(video::EDT_DIRECT3D8); - - passed &= rttAndText(video::EDT_OPENGL); - passed &= rttAndText(video::EDT_DIRECT3D9); - passed &= rttAndText(video::EDT_DIRECT3D8); - passed &= rttAndText(video::EDT_BURNINGSVIDEO); - passed &= rttAndText(video::EDT_SOFTWARE); + TestWithAllDrivers(rttAndAntiAliasing); + TestWithAllDrivers(rttAndText); logTestString("Test RTT format support\n"); - logTestString("OpenGL:\n"); - passed &= rttFormats(video::EDT_OPENGL); - logTestString("D3D9:\n"); - passed &= rttFormats(video::EDT_DIRECT3D9); - logTestString("D3D8:\n"); - passed &= rttFormats(video::EDT_DIRECT3D8); + TestWithAllHWDrivers(rttFormats); - return passed; + return result; } diff --git a/tests/sceneCollisionManager.cpp b/tests/sceneCollisionManager.cpp index 15b5ac6a..58aa0b2c 100644 --- a/tests/sceneCollisionManager.cpp +++ b/tests/sceneCollisionManager.cpp @@ -456,7 +456,7 @@ static bool compareGetSceneNodeFromRayBBWithBBIntersectsWithLine(IrrlichtDevice /** Test functionality of the sceneCollisionManager */ bool sceneCollisionManager(void) { - IrrlichtDevice * device = irr::createDevice(video::EDT_OPENGL, dimension2d(160, 120)); + IrrlichtDevice * device = irr::createDevice(video::EDT_NULL, dimension2d(160, 120)); assert(device); if(!device) return false; diff --git a/tests/screenshot.cpp b/tests/screenshot.cpp new file mode 100644 index 00000000..6c701f15 --- /dev/null +++ b/tests/screenshot.cpp @@ -0,0 +1,63 @@ +// Copyright (C) 2008-2011 Colin MacDonald +// No rights reserved: this software is in the public domain. + +#include "testUtils.h" + +using namespace irr; + +// Tests screenshots features +bool testShots(video::E_DRIVER_TYPE type) +{ + IrrlichtDevice *device = createDevice(type, core::dimension2d(160, 120), 32); + if (!device) + return true; + + video::IVideoDriver* driver = device->getVideoDriver(); + scene::ISceneManager * smgr = device->getSceneManager(); + + logTestString("Testing driver %ls\n", driver->getName()); + + scene::IAnimatedMesh* mesh = smgr->getMesh("../media/sydney.md2"); + scene::IAnimatedMeshSceneNode* node; + + if (!mesh) + return false; + node = smgr->addAnimatedMeshSceneNode(mesh); + if (!node) + return false; + node->setPosition(core::vector3df(20, 0, 30)); + node->setMaterialFlag(video::EMF_LIGHTING, false); + node->setMaterialTexture(0, driver->getTexture("../media/sydney.bmp")); + node->setLoopMode(false); + + (void)smgr->addCameraSceneNode(); + + node->setMD2Animation(scene::EMAT_DEATH_FALLBACK); + node->setCurrentFrame((f32)(node->getEndFrame())); + node->setAnimationSpeed(0); + + device->run(); + driver->beginScene(true, true, video::SColor(255, 255, 255, 0)); + smgr->drawAll(); + driver->endScene(); + + for (u32 i=0; icreateScreenShot((video::ECOLOR_FORMAT)i); + logTestString("Color Format %d %ssupported\n", i, (img && img->getColorFormat() == i)?"":"un"); + if (img) + img->drop(); + } + device->closeDevice(); + device->run(); + device->drop(); + + return true; +} + +bool screenshot() +{ + bool result = true; + TestWithAllHWDrivers(testShots); + return result; +} diff --git a/tests/terrainSceneNode.cpp b/tests/terrainSceneNode.cpp index a29661a3..1056c4b3 100644 --- a/tests/terrainSceneNode.cpp +++ b/tests/terrainSceneNode.cpp @@ -3,18 +3,23 @@ using namespace irr; using namespace core; -using namespace scene; -using namespace video; -bool terrainSceneNode(void) +namespace +{ + +// test camera changes with terrain scene node recalculation +bool terrainRecalc(void) { IrrlichtDevice *device = - createDevice(video::EDT_OPENGL, dimension2du(160, 120), 32); + createDevice(video::EDT_BURNINGSVIDEO, dimension2du(160, 120), 32); - IVideoDriver* driver = device->getVideoDriver(); - ISceneManager* smgr = device->getSceneManager(); + if (!device) + return true; - ITerrainSceneNode* terrain = smgr->addTerrainSceneNode( + video::IVideoDriver* driver = device->getVideoDriver(); + scene::ISceneManager* smgr = device->getSceneManager(); + + scene::ITerrainSceneNode* terrain = smgr->addTerrainSceneNode( "../media/terrain-heightmap.bmp"); terrain->setScale(core::vector3df(40.f, .1f, 40.f)); @@ -22,7 +27,7 @@ bool terrainSceneNode(void) terrain->setMaterialTexture(0, driver->getTexture("../media/terrain-texture.jpg")); terrain->setDebugDataVisible(scene::EDS_FULL); - ICameraSceneNode* camera = smgr->addCameraSceneNode(); + scene::ICameraSceneNode* camera = smgr->addCameraSceneNode(); const core::vector3df center(terrain->getBoundingBox().getCenter()); camera->setTarget(center); @@ -69,3 +74,54 @@ bool terrainSceneNode(void) device->drop(); return result; } + +bool terrainGaps() +{ + IrrlichtDevice* device = createDevice(video::EDT_BURNINGSVIDEO, dimension2d(160, 120)); + if (!device) + return true; + + video::IVideoDriver * irrVideo = device->getVideoDriver(); + scene::ISceneManager* irrScene = device->getSceneManager(); + + // Add camera + scene::ICameraSceneNode* camera = irrScene->addCameraSceneNode(); + camera->setPosition(vector3df(20000, 500, 12800)); + camera->setTarget(vector3df(16800, 0, 12800)); + camera->setFarValue(42000.0f); + + // Add terrain scene node + for (u32 i = 0; i < 2; i++) + { + const char* name="media/ter1.png"; + scene::ITerrainSceneNode* terrain = irrScene->addTerrainSceneNode( + name, 0, -1, + vector3df((f32)(256*50), 0.f, (f32)(i*256*50)),// position 12800(==imgsize*scale) + vector3df(0.f, 0.f, 0.f), // rotation + vector3df(50.f, 15.0f, 50.f) // scale 50 15 50 + ); + + terrain->setMaterialFlag(video::EMF_LIGHTING, false); + terrain->setMaterialFlag(video::EMF_WIREFRAME, !terrain->getMaterial(0).Wireframe); + } + + irrVideo->beginScene(true, true, video::SColor(0,150,150,150)); + irrScene->drawAll(); + irrVideo->endScene(); + + bool result = takeScreenshotAndCompareAgainstReference(irrVideo, "-terrainGap.png"); + + device->closeDevice(); + device->run(); + device->drop(); + return true; +} + +} + +bool terrainSceneNode() +{ + bool result = terrainRecalc(); + result &= terrainGaps(); + return result; +} \ No newline at end of file diff --git a/tests/testQuaternion.cpp b/tests/testQuaternion.cpp index 74489f42..6e3cd47b 100644 --- a/tests/testQuaternion.cpp +++ b/tests/testQuaternion.cpp @@ -21,8 +21,10 @@ inline bool compareQ(const core::vector3df& v, const core::vector3df& turn=core: q.toEuler(v2); v2*=core::RADTODEG; + v2=v2.rotationToDirection(turn); - if (!v3.equals(v2.rotationToDirection(turn), 0.002f)) + // this yields pretty far values sometimes, so don't be too picky + if (!v3.equals(v2, 0.0035f)) { logTestString("Inequality: %f,%f,%f != %f,%f,%f\n", v.X,v.Y,v.Z, v2.X,v2.Y,v2.Z); return false; diff --git a/tests/testUtils.h b/tests/testUtils.h index a800ff49..973d47e8 100644 --- a/tests/testUtils.h +++ b/tests/testUtils.h @@ -5,9 +5,18 @@ #include "irrlicht.h" #include +#define TestWithAllDrivers(X, ...) \ + logTestString("Running test " #X "\n"); \ + for (u32 i=1; i(static_cast(-1.53080559e-16), static_cast(2.49999523)); + ref = core::vector2d(-1.53080559e-16, 2.49999523); + if (!equals(tmp.getAngle(),ref.getAngle())) + { + logTestString("\nERROR: angle %.16f != angle %.16f\n", + tmp.getAngle(), ref.getAngle()); + return false; + } core::vector2d zeroZero(0, 0); core::vector2d oneOne(1, 1); diff --git a/tests/testXML.cpp b/tests/testXML.cpp index 0e9f4194..dfab9b43 100644 --- a/tests/testXML.cpp +++ b/tests/testXML.cpp @@ -1,169 +1,169 @@ -// Copyright (C) 2009-2011 Christian Stehno -// No rights reserved: this software is in the public domain. - -#include "testUtils.h" - -using namespace irr; -using namespace core; - -bool simple_xml( irr::io::IFileSystem * fs ) -{ - io::IXMLReaderUTF8* reader = fs->createXMLReaderUTF8("media/test.xml"); - if (!reader) - { - logTestString("Could not create XML reader.\n"); - return false; - } - - const core::stringc expected[] = { - "a", "b", "c" - }; - - bool retVal = true; - u32 i=0; - while(reader->read()) - { - if (reader->getNodeType() == io::EXN_ELEMENT) - { - if (expected[i++] != reader->getNodeName()) - { - logTestString("Did not find expected string in XML element name.\n"); - retVal = false; - break; - } - } - } - - reader->drop(); - return retVal; -} - -// CDATA should return everything between "![CDATA[" and "]]>" as it's in the file -bool cdata( irr::io::IFileSystem * fs ) -{ - io::IXMLReaderUTF8* reader = fs->createXMLReaderUTF8("media/cdata.xml"); - if (!reader) - { - logTestString("Could not create XML reader.\n"); - return false; - } - - const core::stringc textNode("text"); - core::array< core::stringc > compareStrings; - compareStrings.push_back("simple"); - compareStrings.push_back(""); - compareStrings.push_back("] ]> "); - compareStrings.push_back("]\n]> "); - compareStrings.push_back("\nNewlines\n\tand tabs\n\t\tgogogo"); - compareStrings.push_back("&&#@#$%*()@#$%*()#$%*("); - compareStrings.push_back("& & && &&& &a &ü &ä &ö &&#"); - - bool result = true; - size_t count = 0; - while(reader->read()) - { - if (reader->getNodeType() == io::EXN_ELEMENT) - { - if ( core::stringc(reader->getNodeName()) == textNode ) - { - while(reader->read()) - { - if (reader->getNodeType() == io::EXN_CDATA) - { - core::stringc data = reader->getNodeData(); - core::stringc name = reader->getNodeName(); - if ( count == compareStrings.size() ) - { - logTestString("too many cdata elements for reading in %s:%d\n", __FILE__, __LINE__); - } - else if ( count < compareStrings.size() ) - { - core::stringc cmpString(compareStrings[count]); - - // some (unused) variables to ease debugging - // const c8* dataRaw = data.c_str(); - // const c8* cmpRaw = cmpString.c_str(); - if ( cmpString != data ) - { - result = false; - logTestString("cdata read failed for string %d in %s:%d\n", count, __FILE__, __LINE__); - } - } - ++count; - } - if ( reader->getNodeType() == io::EXN_ELEMENT_END ) - { - break; - } - } - } - } - } - - reader->drop(); - return result; -} - -bool attributeValues(irr::io::IFileSystem * fs) -{ - io::IXMLReaderUTF8* reader = fs->createXMLReaderUTF8("media/attributes.xml"); - if (!reader) - { - logTestString("Could not create XML reader.\n"); - return false; - } - - bool result = true; - bool hasNode = false; - while (reader->read()) - { - if (io::EXN_ELEMENT == reader->getNodeType() ) - { - if ( core::stringc(reader->getNodeName()) == core::stringc("element_position") ) - { - hasNode = true; - int id1 = reader->getAttributeValueAsInt("id1"); - if ( id1 != 152722522 ) - { - logTestString("id1 is %d in %s:%d\n", id1, __FILE__, __LINE__); - result = false; - } - int id2 = reader->getAttributeValueAsInt("id2"); - result &= id2 == 3; - int x = reader->getAttributeValueAsInt("x"); - result &= x == 301; - int y = reader->getAttributeValueAsInt("y"); - result &= y == 118; - } - } - } - - if ( !hasNode ) - { - logTestString("missing node in xml in %s:%d\n", __FILE__, __LINE__); - return false; - } - - reader->drop(); - return result; -} - -/** Tests for XML handling */ -bool testXML(void) -{ - IrrlichtDevice *device = createDevice(video::EDT_NULL, dimension2du(400, 200)); - - bool result = true; - - logTestString("Test simple XML reader features.\n"); - result &= simple_xml(device->getFileSystem()); - logTestString("Test XML reader CDATA support.\n"); - result &= cdata(device->getFileSystem()); - logTestString("Test XML reader attribute support.\n"); - result &= attributeValues(device->getFileSystem()); - +// Copyright (C) 2009-2011 Christian Stehno +// No rights reserved: this software is in the public domain. + +#include "testUtils.h" + +using namespace irr; +using namespace core; + +bool simple_xml( irr::io::IFileSystem * fs ) +{ + io::IXMLReaderUTF8* reader = fs->createXMLReaderUTF8("media/test.xml"); + if (!reader) + { + logTestString("Could not create XML reader.\n"); + return false; + } + + const core::stringc expected[] = { + "a", "b", "c" + }; + + bool retVal = true; + u32 i=0; + while(reader->read()) + { + if (reader->getNodeType() == io::EXN_ELEMENT) + { + if (expected[i++] != reader->getNodeName()) + { + logTestString("Did not find expected string in XML element name.\n"); + retVal = false; + break; + } + } + } + + reader->drop(); + return retVal; +} + +// CDATA should return everything between "![CDATA[" and "]]>" as it's in the file +bool cdata( irr::io::IFileSystem * fs ) +{ + io::IXMLReaderUTF8* reader = fs->createXMLReaderUTF8("media/cdata.xml"); + if (!reader) + { + logTestString("Could not create XML reader.\n"); + return false; + } + + const core::stringc textNode("text"); + core::array< core::stringc > compareStrings; + compareStrings.push_back("simple"); + compareStrings.push_back(""); + compareStrings.push_back("] ]> "); + compareStrings.push_back("]\n]> "); + compareStrings.push_back("\nNewlines\n\tand tabs\n\t\tgogogo"); + compareStrings.push_back("&&#@#$%*()@#$%*()#$%*("); + compareStrings.push_back("& & && &&& &a &ü &ä &ö &&#"); + + bool result = true; + size_t count = 0; + while(reader->read()) + { + if (reader->getNodeType() == io::EXN_ELEMENT) + { + if ( core::stringc(reader->getNodeName()) == textNode ) + { + while(reader->read()) + { + if (reader->getNodeType() == io::EXN_CDATA) + { + core::stringc data = reader->getNodeData(); + core::stringc name = reader->getNodeName(); + if ( count == compareStrings.size() ) + { + logTestString("too many cdata elements for reading in %s:%d\n", __FILE__, __LINE__); + } + else if ( count < compareStrings.size() ) + { + core::stringc cmpString(compareStrings[count]); + + // some (unused) variables to ease debugging + // const c8* dataRaw = data.c_str(); + // const c8* cmpRaw = cmpString.c_str(); + if ( cmpString != data ) + { + result = false; + logTestString("cdata read failed for string %d in %s:%d\n", count, __FILE__, __LINE__); + } + } + ++count; + } + if ( reader->getNodeType() == io::EXN_ELEMENT_END ) + { + break; + } + } + } + } + } + + reader->drop(); + return result; +} + +bool attributeValues(irr::io::IFileSystem * fs) +{ + io::IXMLReaderUTF8* reader = fs->createXMLReaderUTF8("media/attributes.xml"); + if (!reader) + { + logTestString("Could not create XML reader.\n"); + return false; + } + + bool result = true; + bool hasNode = false; + while (reader->read()) + { + if (io::EXN_ELEMENT == reader->getNodeType() ) + { + if ( core::stringc(reader->getNodeName()) == core::stringc("element_position") ) + { + hasNode = true; + int id1 = reader->getAttributeValueAsInt("id1"); + if ( id1 != 152722522 ) + { + logTestString("id1 is %d in %s:%d\n", id1, __FILE__, __LINE__); + result = false; + } + int id2 = reader->getAttributeValueAsInt("id2"); + result &= id2 == 3; + int x = reader->getAttributeValueAsInt("x"); + result &= x == 301; + int y = reader->getAttributeValueAsInt("y"); + result &= y == 118; + } + } + } + + if ( !hasNode ) + { + logTestString("missing node in xml in %s:%d\n", __FILE__, __LINE__); + return false; + } + + reader->drop(); + return result; +} + +/** Tests for XML handling */ +bool testXML(void) +{ + IrrlichtDevice *device = createDevice(video::EDT_NULL, dimension2du(400, 200)); + + bool result = true; + + logTestString("Test simple XML reader features.\n"); + result &= simple_xml(device->getFileSystem()); + logTestString("Test XML reader CDATA support.\n"); + result &= cdata(device->getFileSystem()); + logTestString("Test XML reader attribute support.\n"); + result &= attributeValues(device->getFileSystem()); + device->closeDevice(); device->run(); device->drop(); - return result; -} + return result; +} diff --git a/tests/tests.cbp b/tests/tests.cbp index 4a3f9616..d43de861 100644 --- a/tests/tests.cbp +++ b/tests/tests.cbp @@ -83,6 +83,7 @@ + diff --git a/tests/tests_vc10.vcxproj b/tests/tests_vc10.vcxproj index 8a712114..2e9927ef 100644 --- a/tests/tests_vc10.vcxproj +++ b/tests/tests_vc10.vcxproj @@ -125,6 +125,7 @@ + diff --git a/tests/tests_vc8.vcproj b/tests/tests_vc8.vcproj index 88594559..b883066e 100644 --- a/tests/tests_vc8.vcproj +++ b/tests/tests_vc8.vcproj @@ -348,6 +348,10 @@ RelativePath=".\sceneNodeAnimator.cpp" > + + diff --git a/tests/tests_vc9.vcproj b/tests/tests_vc9.vcproj index f520287b..6b1341fe 100644 --- a/tests/tests_vc9.vcproj +++ b/tests/tests_vc9.vcproj @@ -347,6 +347,10 @@ RelativePath=".\sceneNodeAnimator.cpp" > + + diff --git a/tests/textureFeatures.cpp b/tests/textureFeatures.cpp index b84a9e63..cb50dff7 100644 --- a/tests/textureFeatures.cpp +++ b/tests/textureFeatures.cpp @@ -6,17 +6,105 @@ using namespace irr; using namespace core; -//! Tests locking miplevels -static bool lockAllMipLevels(video::E_DRIVER_TYPE driverType) +namespace +{ +//! check miplevels by visual test +bool renderMipLevels(video::E_DRIVER_TYPE driverType) { - bool result=true; - IrrlichtDevice *device = createDevice( driverType, dimension2d(160, 120), 32); if (!device) - return result; // Treat a failure to create a driver as benign; this saves a lot of #ifdefs + return true; // Treat a failure to create a driver as benign; this saves a lot of #ifdefs video::IVideoDriver* driver = device->getVideoDriver(); scene::ISceneManager * smgr = device->getSceneManager(); + if (!driver->queryFeature(video::EVDF_MIP_MAP)) + { + device->closeDevice(); + device->run(); + device->drop(); + return true; + } + + logTestString("Testing driver %ls\n", driver->getName()); + + scene::ISceneNode* n = smgr->addCubeSceneNode(); + scene::ISceneNode* n2 = smgr->addCubeSceneNode(10, 0, -1, core::vector3df(20,0,30), core::vector3df(0,45,0)); + + // we use a main texture with blue on top and red below + // and mipmap with pink on top and cyan below + if (n && n2) + { + // create the texture and miplevels with distinct colors + u32 texData[16*16]; + for (u32 i=0; i<16*16; ++i) + texData[i]=(i<8*16?0xff0000ff:0xffff0000); + video::IImage* image = driver->createImageFromData(video::ECF_A8R8G8B8, core::dimension2du(16,16), texData, false); + u32 mipdata[8*16]; + u32 index=0; + for (u32 j=8; j>0; j/=2) + { + for (u32 i=0; iaddTexture("miptest", image, mipdata); + if (!tex) + // is probably an error in the mipdata handling + return false; + else + { + n->setMaterialFlag(video::EMF_LIGHTING, false); + n->setMaterialTexture(0, tex); + n2->setMaterialFlag(video::EMF_LIGHTING, false); + n2->setMaterialTexture(0, tex); + } + image->drop(); + } + + (void)smgr->addCameraSceneNode(0, core::vector3df(10,0,-30)); + + driver->beginScene(true, true, video::SColor(255,100,101,140)); + smgr->drawAll(); + driver->endScene(); + + bool result = takeScreenshotAndCompareAgainstReference(driver, "-renderMipmap.png"); + + if (!result) + logTestString("mipmap render failed.\n", driver->getName()); + else + logTestString("Passed\n"); + + device->closeDevice(); + device->run(); + device->drop(); + + return result; +} + + +//! Tests locking miplevels +bool lockAllMipLevels(video::E_DRIVER_TYPE driverType) +{ + IrrlichtDevice *device = createDevice( driverType, dimension2d(160, 120), 32); + if (!device) + return true; // Treat a failure to create a driver as benign; this saves a lot of #ifdefs + + video::IVideoDriver* driver = device->getVideoDriver(); + scene::ISceneManager * smgr = device->getSceneManager(); + + if (!driver->queryFeature(video::EVDF_MIP_MAP)) + { + device->closeDevice(); + device->run(); + device->drop(); + return true; + } + + logTestString("Testing driver %ls\n", driver->getName()); scene::ISceneNode* n = smgr->addCubeSceneNode(); @@ -25,7 +113,7 @@ static bool lockAllMipLevels(video::E_DRIVER_TYPE driverType) // create the texture and miplevels with distinct colors u32 texData[16*16]; for (u32 i=0; i<16*16; ++i) - texData[i]=0xff0000ff; + texData[i]=0xff0000ff-i; video::IImage* image = driver->createImageFromData(video::ECF_A8R8G8B8, core::dimension2du(16,16), texData, false); u32 mipdata[8*16]; u32 index=0; @@ -35,7 +123,7 @@ static bool lockAllMipLevels(video::E_DRIVER_TYPE driverType) for (u32 i=0; ifindTexture("miptest"); video::SColor* bits = (video::SColor*)tex->lock(video::ETLM_READ_ONLY, 0); - result &= (bits[0].color==0xff0000ff); + bool result = (bits[0].color==0xff0000ff); tex->unlock(); bits = (video::SColor*)tex->lock(video::ETLM_READ_ONLY, 1); result &= (bits[0].color==0x00ff00ff); @@ -70,9 +158,41 @@ static bool lockAllMipLevels(video::E_DRIVER_TYPE driverType) bits = (video::SColor*)tex->lock(video::ETLM_READ_ONLY, 4); result &= (bits[0].color==0x001212ff); tex->unlock(); - + if (!result) - logTestString("mipmap lock with driver %ls failed.\n", driver->getName()); + logTestString("mipmap lock after init with driver %ls failed.\n", driver->getName()); + + // test with updating a lower level, and reading upper and lower + bits = (video::SColor*)tex->lock(video::ETLM_READ_WRITE, 3); + bits[0]=0xff00ff00; + bits[1]=0xff00ff00; + tex->unlock(); + bits = (video::SColor*)tex->lock(video::ETLM_READ_WRITE, 4); + result &= (bits[0].color==0x001212ff); + tex->unlock(); + bits = (video::SColor*)tex->lock(video::ETLM_READ_ONLY, 3); + result &= ((bits[0].color==0xff00ff00)&&(bits[2].color==0xc2c200fe)); + tex->unlock(); + + if (!result) + logTestString("mipmap lock after mipmap write with driver %ls failed.\n", driver->getName()); + + // now test locking level 0 + bits = (video::SColor*)tex->lock(video::ETLM_READ_WRITE, 0); + bits[0]=0xff00ff00; + bits[1]=0xff00ff00; + tex->unlock(); + bits = (video::SColor*)tex->lock(video::ETLM_READ_WRITE, 4); + result &= (bits[0].color==0x001212ff); + tex->unlock(); + bits = (video::SColor*)tex->lock(video::ETLM_READ_ONLY, 0); + result &= ((bits[0].color==0xff00ff00)&&(bits[2].color==0xff0000fd)); + tex->unlock(); + + if (!result) + logTestString("mipmap lock at level 0 after mipmap write with driver %ls failed.\n", driver->getName()); + else + logTestString("Passed\n"); device->closeDevice(); device->run(); @@ -82,14 +202,101 @@ static bool lockAllMipLevels(video::E_DRIVER_TYPE driverType) } +//! Tests locking miplevels after texture was created with auto mipmap update +bool lockWithAutoMipmap(video::E_DRIVER_TYPE driverType) +{ + IrrlichtDevice *device = createDevice( driverType, dimension2d(160, 120), 32); + if (!device) + return true; // Treat a failure to create a driver as benign; this saves a lot of #ifdefs + + video::IVideoDriver* driver = device->getVideoDriver(); + scene::ISceneManager * smgr = device->getSceneManager(); + + if (!driver->queryFeature(video::EVDF_MIP_MAP)) + { + device->closeDevice(); + device->run(); + device->drop(); + return true; + } + + logTestString("Testing driver %ls\n", driver->getName()); + + scene::ISceneNode* n = smgr->addCubeSceneNode(); + + if (n) + { + // create the texture + u32 texData[16*16]; + for (u32 i=0; i<16*16; ++i) + texData[i]=0xff0000ff-i; + video::IImage* image = driver->createImageFromData(video::ECF_A8R8G8B8, core::dimension2du(16,16), texData, false); + + video::ITexture* tex = driver->addTexture("miptest", image); + if (!tex) + return false; + else + n->setMaterialTexture(0, tex); + image->drop(); + } + (void)smgr->addCameraSceneNode(); + + driver->beginScene(true, true, video::SColor(255,100,101,140)); + smgr->drawAll(); + driver->endScene(); + + video::ITexture* tex = driver->findTexture("miptest"); + video::SColor* bits = (video::SColor*)tex->lock(video::ETLM_READ_ONLY, 0); + bool result = (bits[0].color==0xff0000ff); + tex->unlock(); + if (!result) + logTestString("mipmap lock after init with driver %ls failed.\n", driver->getName()); + + // test with updating a lower level, and reading upper and lower + bits = (video::SColor*)tex->lock(video::ETLM_READ_WRITE, 3); + bits[0]=0xff00ff00; + bits[1]=0xff00ff00; + tex->unlock(); + // lock another texture just to invalidate caches in the driver + bits = (video::SColor*)tex->lock(video::ETLM_READ_WRITE, 4); + tex->unlock(); + bits = (video::SColor*)tex->lock(video::ETLM_READ_ONLY, 3); + result &= ((bits[0].color==0xff00ff00)&&(bits[2].color!=0xff00ff00)); + tex->unlock(); + + if (!result) + logTestString("mipmap lock after mipmap write with driver %ls failed.\n", driver->getName()); + + // now test locking level 0 + bits = (video::SColor*)tex->lock(video::ETLM_READ_WRITE, 0); + bits[0]=0x00ff00ff; + bits[1]=0x00ff00ff; + tex->unlock(); + bits = (video::SColor*)tex->lock(video::ETLM_READ_ONLY, 3); + result &= ((bits[0].color==0xff00ff00)&&(bits[2].color!=0xff00ff00)); + tex->unlock(); + + if (!result) + logTestString("mipmap lock at level 0 after mipmap write with driver %ls failed.\n", driver->getName()); + else + logTestString("Passed\n"); + + device->closeDevice(); + device->run(); + device->drop(); + + return result; +} +} + + bool textureFeatures(void) { - bool passed = true; + bool result = true; - passed &= lockAllMipLevels(video::EDT_OPENGL); - passed &= lockAllMipLevels(video::EDT_BURNINGSVIDEO); - passed &= lockAllMipLevels(video::EDT_DIRECT3D9); - passed &= lockAllMipLevels(video::EDT_DIRECT3D8); + TestWithAllDrivers(renderMipLevels); + TestWithAllDrivers(lockAllMipLevels); + TestWithAllDrivers(lockWithAutoMipmap); - return passed; + return result; } diff --git a/tests/textureRenderStates.cpp b/tests/textureRenderStates.cpp index 28f2164b..549561fd 100644 --- a/tests/textureRenderStates.cpp +++ b/tests/textureRenderStates.cpp @@ -14,6 +14,9 @@ static bool manyTextures(video::E_DRIVER_TYPE driverType) video::IVideoDriver* driver = device->getVideoDriver(); scene::ISceneManager * smgr = device->getSceneManager(); + + logTestString("Testing driver %ls\n", driver->getName()); + (void)smgr->addCameraSceneNode(); scene::SMeshBufferLightMap* mesh = new scene::SMeshBufferLightMap; @@ -73,6 +76,8 @@ static bool renderAndLoad(video::E_DRIVER_TYPE driverType) video::IVideoDriver* driver = device->getVideoDriver(); scene::ISceneManager * smgr = device->getSceneManager(); + logTestString("Testing driver %ls\n", driver->getName()); + video::ITexture* tex1 = driver->getTexture("../media/wall.bmp"); (void)smgr->addCameraSceneNode(); @@ -108,6 +113,8 @@ static bool renderAndRemove(video::E_DRIVER_TYPE driverType) video::IVideoDriver* driver = device->getVideoDriver(); scene::ISceneManager * smgr = device->getSceneManager(); + logTestString("Testing driver %ls\n", driver->getName()); + driver->beginScene (true, true, video::SColor(255, 0, 255, 0)); smgr->drawAll(); driver->endScene(); @@ -155,6 +162,16 @@ static bool testTextureMatrixInMixedScenes(video::E_DRIVER_TYPE driverType) scene::ISceneManager* sceneManager = device->getSceneManager(); gui::IGUIEnvironment* gui = device->getGUIEnvironment(); + if (!driver->queryFeature(video::EVDF_TEXTURE_MATRIX)) + { + device->closeDevice(); + device->run(); + device->drop(); + return true; + } + + logTestString("Testing driver %ls\n", driver->getName()); + scene::ICameraSceneNode* camera = sceneManager->addCameraSceneNode(); camera->setPosition(vector3df(0,10,0)); @@ -200,6 +217,16 @@ static bool textureMatrix(video::E_DRIVER_TYPE driverType) video::IVideoDriver* driver = device->getVideoDriver(); scene::ISceneManager* sceneManager = device->getSceneManager(); + if (!driver->queryFeature(video::EVDF_TEXTURE_MATRIX)) + { + device->closeDevice(); + device->run(); + device->drop(); + return true; + } + + logTestString("Testing driver %ls\n", driver->getName()); + scene::ICameraSceneNode* camera = sceneManager->addCameraSceneNode(); camera->setPosition(vector3df(0,0,-10)); @@ -248,35 +275,13 @@ static bool textureMatrix(video::E_DRIVER_TYPE driverType) bool textureRenderStates(void) { - bool passed = true; + bool result = true; - passed &= renderAndLoad(video::EDT_OPENGL); - passed &= renderAndLoad(video::EDT_SOFTWARE); - passed &= renderAndLoad(video::EDT_BURNINGSVIDEO); - passed &= renderAndLoad(video::EDT_DIRECT3D9); - passed &= renderAndLoad(video::EDT_DIRECT3D8); + TestWithAllDrivers(renderAndLoad); + TestWithAllDrivers(renderAndRemove); + TestWithAllDrivers(testTextureMatrixInMixedScenes); + TestWithAllDrivers(manyTextures); + TestWithAllDrivers(textureMatrix); - passed &= renderAndRemove(video::EDT_OPENGL); - passed &= renderAndRemove(video::EDT_SOFTWARE); - passed &= renderAndRemove(video::EDT_BURNINGSVIDEO); - passed &= renderAndRemove(video::EDT_DIRECT3D9); - passed &= renderAndRemove(video::EDT_DIRECT3D8); - - passed &= testTextureMatrixInMixedScenes(video::EDT_OPENGL); - passed &= testTextureMatrixInMixedScenes(video::EDT_SOFTWARE); - passed &= testTextureMatrixInMixedScenes(video::EDT_BURNINGSVIDEO); - passed &= testTextureMatrixInMixedScenes(video::EDT_DIRECT3D9); - passed &= testTextureMatrixInMixedScenes(video::EDT_DIRECT3D8); - - passed &= manyTextures(video::EDT_OPENGL); - passed &= manyTextures(video::EDT_SOFTWARE); - passed &= manyTextures(video::EDT_BURNINGSVIDEO); - passed &= manyTextures(video::EDT_DIRECT3D9); - passed &= manyTextures(video::EDT_DIRECT3D8); - - passed &= textureMatrix(video::EDT_OPENGL); - passed &= textureMatrix(video::EDT_DIRECT3D9); - - return passed; + return result; } - diff --git a/tests/transparentMaterials.cpp b/tests/transparentMaterials.cpp index eadff957..9d2457c4 100644 --- a/tests/transparentMaterials.cpp +++ b/tests/transparentMaterials.cpp @@ -19,6 +19,16 @@ bool testTransparentAlphaChannelRef(video::E_DRIVER_TYPE driverType) video::IVideoDriver* driver = device->getVideoDriver(); scene::ISceneManager* smgr = device->getSceneManager(); + if (driver->getColorFormat() != video::ECF_A8R8G8B8) + { + device->closeDevice(); + device->run(); + device->drop(); + return true; + } + + logTestString("Testing driver %ls\n", driver->getName()); + driver->setTextureCreationFlag(video::ETCF_ALWAYS_32_BIT, true); ISceneNode * backCube = smgr->addCubeSceneNode(); @@ -59,6 +69,16 @@ bool testTransparentAlphaChannel(video::E_DRIVER_TYPE driverType) video::IVideoDriver* driver = device->getVideoDriver(); scene::ISceneManager* smgr = device->getSceneManager(); + if (driver->getColorFormat() != video::ECF_A8R8G8B8) + { + device->closeDevice(); + device->run(); + device->drop(); + return true; + } + + logTestString("Testing driver %ls\n", driver->getName()); + driver->setTextureCreationFlag(video::ETCF_ALWAYS_32_BIT, true); ISceneNode * backCube = smgr->addCubeSceneNode(); @@ -99,6 +119,16 @@ bool testTransparentVertexAlpha(video::E_DRIVER_TYPE driverType) video::IVideoDriver* driver = device->getVideoDriver(); scene::ISceneManager* smgr = device->getSceneManager(); + if (driver->getColorFormat() != video::ECF_A8R8G8B8) + { + device->closeDevice(); + device->run(); + device->drop(); + return true; + } + + logTestString("Testing driver %ls\n", driver->getName()); + driver->setTextureCreationFlag(video::ETCF_ALWAYS_32_BIT, true); ISceneNode * backCube = smgr->addCubeSceneNode(); @@ -146,6 +176,16 @@ bool testTransparentReflection2Layer(video::E_DRIVER_TYPE driverType) video::IVideoDriver* driver = device->getVideoDriver(); scene::ISceneManager* smgr = device->getSceneManager(); + if (driver->getColorFormat() != video::ECF_A8R8G8B8) + { + device->closeDevice(); + device->run(); + device->drop(); + return true; + } + + logTestString("Testing driver %ls\n", driver->getName()); + driver->setTextureCreationFlag(video::ETCF_ALWAYS_32_BIT, true); ISceneNode * backCube = smgr->addCubeSceneNode(); @@ -196,6 +236,16 @@ bool testTransparentAddColor(video::E_DRIVER_TYPE driverType) video::IVideoDriver* driver = device->getVideoDriver(); scene::ISceneManager* smgr = device->getSceneManager(); + if (driver->getColorFormat() != video::ECF_A8R8G8B8) + { + device->closeDevice(); + device->run(); + device->drop(); + return true; + } + + logTestString("Testing driver %ls\n", driver->getName()); + driver->setTextureCreationFlag(video::ETCF_ALWAYS_32_BIT, true); ISceneNode * backCube = smgr->addCubeSceneNode(); @@ -235,6 +285,16 @@ bool testTransparentVertexAlphaMore(E_DRIVER_TYPE driverType) IVideoDriver* driver = device->getVideoDriver(); ISceneManager* smgr = device->getSceneManager(); + if (driver->getColorFormat() != video::ECF_A8R8G8B8) + { + device->closeDevice(); + device->run(); + device->drop(); + return true; + } + + logTestString("Testing driver %ls\n", driver->getName()); + IAnimatedMesh* mesh = smgr->getMesh("../media/sydney.md2"); IAnimatedMeshSceneNode* node = smgr->addAnimatedMeshSceneNode( mesh ); IMeshSceneNode* cube = smgr->addCubeSceneNode(10.0f,0,-1,vector3df(-5,3,-15)); @@ -279,37 +339,21 @@ bool testTransparentVertexAlphaMore(E_DRIVER_TYPE driverType) bool transparentMaterials(void) { - bool result = testTransparentAlphaChannel(EDT_DIRECT3D9); - result &= testTransparentAlphaChannel(EDT_OPENGL); - result &= testTransparentAlphaChannel(EDT_BURNINGSVIDEO); - - result &= testTransparentAlphaChannelRef(EDT_DIRECT3D9); - result &= testTransparentAlphaChannelRef(EDT_OPENGL); + bool result = true; + TestWithAllDrivers(testTransparentAlphaChannel); // FIXME Rogerborg 8-January-2011. Burning's video currently produces unexpected results, // blending using the full alpha value instead of using a boolean mask. This test is being // added now anyway to help verify the fix when it's done; it should just require an // update of the reference image. - result &= testTransparentAlphaChannelRef(EDT_BURNINGSVIDEO); - - result &= testTransparentVertexAlpha(EDT_DIRECT3D9); - result &= testTransparentVertexAlpha(EDT_OPENGL); + TestWithAllDrivers(testTransparentAlphaChannelRef); // This type seems to be broken as well for Burning's video. - result &= testTransparentVertexAlpha(EDT_BURNINGSVIDEO); - - result &= testTransparentAddColor(EDT_DIRECT3D9); - result &= testTransparentAddColor(EDT_OPENGL); - result &= testTransparentAddColor(EDT_BURNINGSVIDEO); - + TestWithAllDrivers(testTransparentVertexAlpha); + TestWithAllDrivers(testTransparentAddColor); // TODO: this simply does not work in OpenGL due to the sphere map // at least it creates different results, and also varies across drivers -// result &= testTransparentReflection2Layer(EDT_OPENGL); - result &= testTransparentReflection2Layer(EDT_DIRECT3D9); - result &= testTransparentReflection2Layer(EDT_BURNINGSVIDEO); - - result &= testTransparentVertexAlphaMore(EDT_DIRECT3D9); - result &= testTransparentVertexAlphaMore(EDT_OPENGL); + TestWithAllDrivers(testTransparentReflection2Layer); // This type seems to be broken as well for Burning's video. - result &= testTransparentVertexAlphaMore(EDT_BURNINGSVIDEO); + TestWithAllDrivers(testTransparentVertexAlphaMore); return result; } diff --git a/tests/videoDriver.cpp b/tests/videoDriver.cpp index 4e9b88b7..1711c9d7 100644 --- a/tests/videoDriver.cpp +++ b/tests/videoDriver.cpp @@ -28,7 +28,7 @@ bool testVideoDriver(video::E_DRIVER_TYPE driverType) logTestString("MaxTextureSize: %d\n", driver->getDriverAttributes().getAttributeAsInt("MaxTextureSize")); logTestString("MaxGeometryVerticesOut: %d\n", driver->getDriverAttributes().getAttributeAsInt("MaxGeometryVerticesOut")); logTestString("Version: %d\n", driver->getDriverAttributes().getAttributeAsInt("Version")); - logTestString("ShaderLanguageVersion: %d\n", driver->getDriverAttributes().getAttributeAsInt("ShaderLanguageVersion")); + logTestString("ShaderLanguageVersion: %d\n\n", driver->getDriverAttributes().getAttributeAsInt("ShaderLanguageVersion")); device->closeDevice(); device->run(); @@ -38,11 +38,7 @@ bool testVideoDriver(video::E_DRIVER_TYPE driverType) bool videoDriver() { - bool result = testVideoDriver(video::EDT_OPENGL); - result &= testVideoDriver(video::EDT_DIRECT3D9); - result &= testVideoDriver(video::EDT_DIRECT3D8); - result &= testVideoDriver(video::EDT_BURNINGSVIDEO); - result &= testVideoDriver(video::EDT_SOFTWARE); - result &= testVideoDriver(video::EDT_NULL); + bool result = true; + TestWithAllDrivers(testVideoDriver); return result; } diff --git a/tests/viewPort.cpp b/tests/viewPort.cpp index 18d88fdf..83f74927 100644 --- a/tests/viewPort.cpp +++ b/tests/viewPort.cpp @@ -29,6 +29,8 @@ static bool viewPortText(E_DRIVER_TYPE driverType) IGUIEnvironment* env = smgr->getGUIEnvironment(); env->addCheckBox(true, core::recti(10,60,28,82)); + logTestString("Testing driver %ls\n", driver->getName()); + IBillboardSceneNode * bnode = smgr->addBillboardSceneNode(0,dimension2d(32,32),core::vector3df(0,0,10)); bnode->setMaterialFlag(video::EMF_LIGHTING, false); bnode->setMaterialType(video::EMT_TRANSPARENT_ADD_COLOR); @@ -63,21 +65,12 @@ static bool viewPortText(E_DRIVER_TYPE driverType) bool viewPort(void) { - bool passed = true; + bool result = true; - logTestString("Check OpenGL driver\n"); - passed &= viewPortText(EDT_OPENGL); // TODO: software driver and burnings don't use view port for // 2d rendering, so result is pretty wrong. - logTestString("Check Software driver\n"); - passed &= viewPortText(EDT_SOFTWARE); - logTestString("Check Burning's Video driver\n"); - passed &= viewPortText(EDT_BURNINGSVIDEO); - logTestString("Check Direct3D9 driver\n"); - passed &= viewPortText(EDT_DIRECT3D9); - logTestString("Check Direct3D8 driver\n"); - passed &= viewPortText(EDT_DIRECT3D8); + TestWithAllDrivers(viewPortText); - return passed; + return result; } diff --git a/tests/writeImageToFile.cpp b/tests/writeImageToFile.cpp index 5d35dcb8..f0f7da22 100644 --- a/tests/writeImageToFile.cpp +++ b/tests/writeImageToFile.cpp @@ -53,8 +53,8 @@ bool writeImageToFile(void) const char * referenceFilename = 0; video::ECOLOR_FORMAT format; - irr::video::IImage * screenshot = driver->createScreenShot(); - if(!screenshot) + irr::video::IImage * screenshot = driver->createScreenShot(video::ECF_R8G8B8); + if (!screenshot) { logTestString("Failed to take screenshot\n"); assert(false); @@ -62,14 +62,14 @@ bool writeImageToFile(void) } format = screenshot->getColorFormat(); - if(format != video::ECF_R8G8B8) + if (format != video::ECF_R8G8B8) { irr::video::IImage * fixedScreenshot = driver->createImage(video::ECF_R8G8B8, screenshot->getDimension()); screenshot->copyTo(fixedScreenshot); screenshot->drop(); screenshot = 0; - if(!fixedScreenshot) + if (!fixedScreenshot) { logTestString("Failed to convert screenshot to ECF_A8R8G8B8\n"); assert(false); @@ -82,7 +82,7 @@ bool writeImageToFile(void) buffer = new c8[BUFFER_SIZE]; writtenFilename = "results/Burning's Video-writeImageToFile.png"; memoryFile = device->getFileSystem()->createMemoryWriteFile(buffer, BUFFER_SIZE, writtenFilename, false); - if(!driver->writeImageToFile(screenshot, memoryFile)) + if (!driver->writeImageToFile(screenshot, memoryFile)) { logTestString("Failed to write png to memory file\n"); assert(false); @@ -90,14 +90,14 @@ bool writeImageToFile(void) } writtenFile = device->getFileSystem()->createAndWriteFile(memoryFile->getFileName()); - if(!writtenFile) + if (!writtenFile) { logTestString("Can't open %s for writing.\n", writtenFilename); assert(false); goto cleanup; } - if(memoryFile->getPos() != writtenFile->write(buffer, memoryFile->getPos())) + if (memoryFile->getPos() != writtenFile->write(buffer, memoryFile->getPos())) { logTestString("Error while writing to %s.\n", writtenFilename); assert(false); @@ -108,7 +108,7 @@ bool writeImageToFile(void) writtenFile = 0; referenceFilename = "media/Burning's Video-drawPixel.png"; - if(!binaryCompareFiles(writtenFilename, referenceFilename)) + if (!binaryCompareFiles(writtenFilename, referenceFilename)) { logTestString("File written from memory is not the same as the reference file. %s:%d\n" , __FILE__, __LINE__); // assert(false);