Index: Bindings/C++/Horde3D.h =================================================================== --- Bindings/C++/Horde3D.h (revision 121) +++ Bindings/C++/Horde3D.h (working copy) @@ -90,6 +90,8 @@ lights are visualized using their screen space bounding box. (Values: 0, 1; Default: 0) DumpFailedShaders - Enables or disables storing of shader code that failed to compile in a text file; this can be useful in combination with the line numbers given back by the shader compiler. (Values: 0, 1; Default: 0) + DisableStreaming - Disables all streaming resources, instead they will be treated as non-streaming resources; only affects + resources created after setting the option. (Values: 0, 1; Default: 0) */ enum List { @@ -104,7 +106,8 @@ SampleCount, WireframeMode, DebugViewMode, - DumpFailedShaders + DumpFailedShaders, + DisableStreaming }; }; @@ -164,12 +167,14 @@ NoQuery - Excludes resource from being listed by queryUnloadedResource function. NoTexCompression - Disables texture compression for Texture resource. NoTexMipmaps - Disables generation of mipmaps for Texture resources. + Stream - Enables data to be streamed to the resource. */ enum Flags { NoQuery = 1, NoTexCompression = 2, - NoTexMipmaps = 4 + NoTexMipmaps = 4, + Stream = 8 }; }; @@ -872,6 +877,34 @@ */ DLL int removeResource( ResHandle res ); + /* Function: isResourceStreaming + Checks if a resource is streaming. + + This function checks if the specified resource is streaming. + + Parameters: + res - handle to the resource to be checked + + Returns: + true if resource is streaming, otherwise or in case of failure false + */ + DLL bool isResourceStreaming( ResHandle res ); + + /* Function: getResourceStreamingDetails + Gets the details needed to load the streaming resource with new data. + + This function gets the details needed to load the streaming resource with new data. + + Parameters: + res - handle to the resource + size - this will be set to the requested size of the new data to be loaded + offset - this will be set to from where in the file the data should be loaded + + Returns: + true in case of success, otherwise false + */ + DLL bool getResourceStreamingDetails( ResHandle res, int &size, int &offset ); + /* Function: isResourceLoaded Checks if a resource is loaded. @@ -1061,6 +1094,23 @@ */ DLL bool updateResourceData( ResHandle res, int param, const void *data, int size ); + /* Function: uploadResourceStreamData + Uploads streaming data to a resource. + + This function uploads streaming data to a resource. The details of the data to be uploaded should be retrieved + from the GetResourceStreaming function. + + Parameters: + res - handle to the resource for which the data will be uploaded to + data - pointer to the new data + size - size of the new data block + doneReading - this will be true if the resource is done reading, in which case a file handle or similar can be closed + + Returns: + true in case of success, otherwise false + */ + DLL bool uploadResourceStreamData( ResHandle res, const void *data, int size, bool &doneReading); + /* Function: queryUnloadedResource Returns handle to an unloaded resource. @@ -1075,6 +1125,21 @@ handle to an unloaded resource or 0 */ DLL ResHandle queryUnloadedResource( int index ); + + /* Function: queryUnloadedStreamingResource + Returns handle to an streaming resource in need of new data. + + This function looks for a streaming resource that requires new data and returns its handle. + If there are no streaming resources in need of data or the zero based index specified is greater + than the number of the currently streaming resources in need of data, 0 is returned. + + Parameters: + index - index of streaming resource within the internal list of streaming resources in need of data (starting with 0) + + Returns: + handle to an unloaded resource or 0 + */ + DLL ResHandle queryUnloadedStreamingResource( int index ); /* Function: releaseUnusedResources Frees resources that are no longer used. Index: Source/Horde3DEngine/egCom.cpp =================================================================== --- Source/Horde3DEngine/egCom.cpp (revision 121) +++ Source/Horde3DEngine/egCom.cpp (working copy) @@ -47,6 +47,7 @@ wireframeMode = false; debugViewMode = false; dumpFailedShaders = false; + disableStreaming = false; } @@ -78,6 +79,8 @@ return debugViewMode ? 1.0f : 0.0f; case EngineOptions::DumpFailedShaders: return dumpFailedShaders ? 1.0f : 0.0f; + case EngineOptions::DisableStreaming: + return disableStreaming ? 1.0f : 0.0f; default: return Math::NaN; } @@ -144,6 +147,9 @@ case EngineOptions::DumpFailedShaders: dumpFailedShaders = (value != 0); return true; + case EngineOptions::DisableStreaming: + disableStreaming = (value != 0); + return true; default: return false; } Index: Source/Horde3DEngine/egCom.h =================================================================== --- Source/Horde3DEngine/egCom.h (revision 121) +++ Source/Horde3DEngine/egCom.h (working copy) @@ -54,7 +54,8 @@ SampleCount, WireframeMode, DebugViewMode, - DumpFailedShaders + DumpFailedShaders, + DisableStreaming }; }; @@ -76,6 +77,7 @@ bool wireframeMode; bool debugViewMode; bool dumpFailedShaders; + bool disableStreaming; EngineConfig(); Index: Source/Horde3DEngine/egMain.cpp =================================================================== --- Source/Horde3DEngine/egMain.cpp (revision 121) +++ Source/Horde3DEngine/egMain.cpp (working copy) @@ -309,6 +309,32 @@ } + DLLEXP bool isResourceStreaming( ResHandle res ) + { + Resource *resObj = Modules::resMan().resolveResHandle( res ); + if( resObj == 0x0 ) + { + Modules::log().writeDebugInfo( "Invalid resource handle %i in isResourceStreaming", res ); + return false; + } + + return resObj->isStreaming(); + } + + + DLLEXP bool getResourceStreamingDetails( ResHandle res, int &size, int &offset ) + { + Resource *resObj = Modules::resMan().resolveResHandle( res ); + if( resObj == 0x0 ) + { + Modules::log().writeDebugInfo( "Invalid resource handle %i in getResourceStreamingDetails", res ); + return false; + } + + return resObj->getStreamingDetails( size, offset ); + } + + DLLEXP bool isResourceLoaded( ResHandle res ) { Resource *resObj = Modules::resMan().resolveResHandle( res ); @@ -468,6 +494,21 @@ } return resObj->updateData( param, data, size ); } + + + DLLEXP bool uploadResourceStreamData( ResHandle res, const void *data, int size, bool &doneReading) + { + if( data == 0x0 ) return false; + + Resource *resObj = Modules::resMan().resolveResHandle( res ); + if( resObj == 0x0 ) + { + Modules::log().writeDebugInfo( "Invalid resource handle %i in uploadResourceStreamData", res ); + return false; + } + + return resObj->uploadStreamData( data, size, doneReading ); + } DLLEXP ResHandle queryUnloadedResource( int index ) @@ -476,6 +517,12 @@ } + DLLEXP ResHandle queryUnloadedStreamingResource( int index ) + { + return Modules::resMan().queryUnloadedStreamingResource( index ); + } + + DLLEXP void releaseUnusedResources() { Modules::resMan().releaseUnusedResources(); Index: Source/Horde3DEngine/egMaterial.cpp =================================================================== --- Source/Horde3DEngine/egMaterial.cpp (revision 121) +++ Source/Horde3DEngine/egMaterial.cpp (working copy) @@ -166,6 +166,10 @@ _stricmp( node1.getAttribute( "mipmaps", "1" ), "0" ) == 0 ) flags |= ResourceFlags::NoTexMipmaps; + if( _stricmp( node1.getAttribute( "stream", "false" ), "true" ) == 0 || + _stricmp( node1.getAttribute( "stream", "0" ), "1" ) == 0 ) + flags |= ResourceFlags::Stream; + texMap = Modules::resMan().addResource( ResourceTypes::Texture, node1.getAttribute( "map" ), flags, false ); Index: Source/Horde3DEngine/egResource.cpp =================================================================== --- Source/Horde3DEngine/egResource.cpp (revision 121) +++ Source/Horde3DEngine/egResource.cpp (working copy) @@ -373,7 +373,7 @@ for( uint32 i = 0; i < _resources.size(); ++i ) { - if( _resources[i] != 0x0 && !_resources[i]->_loaded && !_resources[i]->_noQuery ) + if( _resources[i] != 0x0 && !_resources[i]->_loaded && !_resources[i]->_noQuery && !_resources[i]->isStreaming() ) { if( j == index ) return _resources[i]->_handle; else ++j; @@ -384,6 +384,25 @@ } +ResHandle ResourceManager::queryUnloadedStreamingResource( int index ) +{ + int j = 0; + + for( uint32 i = 0; i < _resources.size(); ++i ) + { + if( _resources[i] != 0x0 && _resources[i]->isStreaming() && !_resources[i]->_loaded && !_resources[i]->_noQuery ) + { + if( j == index ) + return _resources[i]->_handle; + else + ++j; + } + } + + return 0; +} + + void ResourceManager::releaseUnusedResources() { vector< uint32 > killList; Index: Source/Horde3DEngine/egResource.h =================================================================== --- Source/Horde3DEngine/egResource.h (revision 121) +++ Source/Horde3DEngine/egResource.h (working copy) @@ -58,7 +58,8 @@ { NoQuery = 1, NoTexCompression = 2, - NoTexMipmaps = 4 + NoTexMipmaps = 4, + Stream = 8 }; }; @@ -99,11 +100,14 @@ virtual const void *getData( int param ); virtual bool updateData( int param, const void *data, int size ); + virtual bool uploadStreamData( const void *data, int size, bool &doneReading ) { return false; } int &getType() { return _type; } int getFlags() { return _flags; } const std::string &getName() { return _name; } ResHandle getHandle() { return _handle; } + virtual bool isStreaming() { return false; } + virtual bool getStreamingDetails( int &size, int &offset ) { return false; } bool isLoaded() { return _loaded; } void addRef() { ++_refCount; } void subRef() { --_refCount; } @@ -189,6 +193,7 @@ int removeResource( ResHandle handle, bool userCall ); void clear(); ResHandle queryUnloadedResource( int index ); + ResHandle queryUnloadedStreamingResource( int index ); void releaseUnusedResources(); Resource *resolveResHandle( ResHandle handle ) Index: Source/Horde3DEngine/egTextures.cpp =================================================================== --- Source/Horde3DEngine/egTextures.cpp (revision 121) +++ Source/Horde3DEngine/egTextures.cpp (working copy) @@ -154,11 +154,26 @@ _texFormat = TextureFormats::RGBA8; _width = 0; _height = 0; _hasMipMaps = true; + _mipCount = 1; if( _texType = TextureTypes::TexCube ) _texObject = defTexCubeObject; else _texObject = defTex2DObject; + + if( (_flags & ResourceFlags::Stream) == ResourceFlags::Stream && !Modules::config().disableStreaming ) + { + _streaming = true; + // Start with requesting the dds header, which is 128bytes in the beginning of the file + _streamSize = 128; + _streamOffset = 0; + } + else + _streaming = false; + + _loadedHeader = false; + _mipsLoaded = 0; + _slicesLoaded = 0; } @@ -175,6 +190,13 @@ Modules::renderer().unloadTexture( _texObject, _texType ); } + for( size_t i = 0; i < _mipLevels.size(); ++i ) + { + if( _mipLevels[i].data != 0x0 ) + delete[] _mipLevels[i].data; + } + + _mipLevels.clear(); _texObject = 0; } @@ -191,109 +213,119 @@ } -bool TextureResource::load( const char *data, int size ) +bool TextureResource::parseDDSHeader( const void *data ) { - if( !Resource::load( data, size ) ) return false; - if( !Modules::config().loadTextures ) return true; + ASSERT_STATIC( sizeof( DDSHeader ) == 128 ); - // Check if image is a dds - if( size > 128 && *((uint32 *)data) == FOURCC( 'D', 'D', 'S', ' ' ) ) + memcpy( &ddsHeader, data, 128 ); + + // Check header + // There are some flags that are required to be set for every dds but we don't check them + if( ddsHeader.dwSize != 124 ) { - // Load dds - ASSERT_STATIC( sizeof( DDSHeader ) == 128 ); + return raiseError( "Invalid DDS header" ); + } - memcpy( &ddsHeader, data, 128 ); - - // Check header - // There are some flags that are required to be set for every dds but we don't check them - if( ddsHeader.dwSize != 124 ) - { - return raiseError( "Invalid DDS header" ); - } + // Store properties + _width = ddsHeader.dwWidth; + _height = ddsHeader.dwHeight; + _texFormat = (TextureFormats::List)-1; + _texObject = 0; + _mipCount = ddsHeader.dwFlags & DDSD_MIPMAPCOUNT ? ddsHeader.dwMipMapCount : 1; + _hasMipMaps = _mipCount > 1 ? true : false; - // Store properties - _width = ddsHeader.dwWidth; - _height = ddsHeader.dwWidth; - _texFormat = (TextureFormats::List)-1; - _texObject = 0; - int mipCount = ddsHeader.dwFlags & DDSD_MIPMAPCOUNT ? ddsHeader.dwMipMapCount : 1; - _hasMipMaps = mipCount > 1 ? true : false; + // Get texture type + if( ddsHeader.caps.dwCaps2 == 0 ) + { + _texType = TextureTypes::Tex2D; + } + else if( ddsHeader.caps.dwCaps2 & DDSCAPS2_CUBEMAP ) + { + if( !(ddsHeader.caps.dwCaps2 & DDSCAPS2_CM_COMPLETE) ) + raiseError( "DDS cubemap does not contain all cube sides" ); - // Get texture type - if( ddsHeader.caps.dwCaps2 == 0 ) + _texType = TextureTypes::TexCube; + } + else + { + return raiseError( "Unsupported DDS texture type" ); + } + + // Get pixel format + if( ddsHeader.pixFormat.dwFlags & DDPF_FOURCC ) + { + switch( ddsHeader.pixFormat.dwFourCC ) { - _texType = TextureTypes::Tex2D; + case FOURCC( 'D', 'X', 'T', '1' ): + _texFormat = TextureFormats::DXT1; + break; + case FOURCC( 'D', 'X', 'T', '3' ): + _texFormat = TextureFormats::DXT3; + break; + case FOURCC( 'D', 'X', 'T', '5' ): + _texFormat = TextureFormats::DXT5; + break; + case D3DFMT_A16B16G16R16F: + _texFormat = TextureFormats::RGBA16F; + break; + case D3DFMT_A32B32G32R32F: + _texFormat = TextureFormats::RGBA32F; + break; } - else if( ddsHeader.caps.dwCaps2 & DDSCAPS2_CUBEMAP ) - { - if( !(ddsHeader.caps.dwCaps2 & DDSCAPS2_CM_COMPLETE) ) - raiseError( "DDS cubemap does not contain all cube sides" ); - _texType = TextureTypes::TexCube; - } + } + else if( ddsHeader.pixFormat.dwFlags & DDPF_RGB ) + { + const int L_BGR = 0; + const int L_RGB = 1; + int layout = -1; + + if( ddsHeader.pixFormat.dwRBitMask == 0x00ff0000 && + ddsHeader.pixFormat.dwGBitMask == 0x0000ff00 && + ddsHeader.pixFormat.dwBBitMask == 0x000000ff ) layout = L_BGR; else + if( ddsHeader.pixFormat.dwRBitMask == 0x00ff0000 && + ddsHeader.pixFormat.dwGBitMask == 0x0000ff00 && + ddsHeader.pixFormat.dwBBitMask == 0x000000ff ) layout = L_RGB; + + if( layout == L_BGR || layout == L_RGB ) { - return raiseError( "Unsupported DDS texture type" ); - } - - // Get pixel format - if( ddsHeader.pixFormat.dwFlags & DDPF_FOURCC ) - { - switch( ddsHeader.pixFormat.dwFourCC ) + if( ddsHeader.pixFormat.dwRGBBitCount == 24 ) { - case FOURCC( 'D', 'X', 'T', '1' ): - _texFormat = TextureFormats::DXT1; - break; - case FOURCC( 'D', 'X', 'T', '3' ): - _texFormat = TextureFormats::DXT3; - break; - case FOURCC( 'D', 'X', 'T', '5' ): - _texFormat = TextureFormats::DXT5; - break; - case D3DFMT_A16B16G16R16F: - _texFormat = TextureFormats::RGBA16F; - break; - case D3DFMT_A32B32G32R32F: - _texFormat = TextureFormats::RGBA32F; - break; + _texFormat = layout == L_BGR ? TextureFormats::BGR8 : TextureFormats::RGB8; } - } - else if( ddsHeader.pixFormat.dwFlags & DDPF_RGB ) - { - const int L_BGR = 0; - const int L_RGB = 1; - int layout = -1; - - if( ddsHeader.pixFormat.dwRBitMask == 0x00ff0000 && - ddsHeader.pixFormat.dwGBitMask == 0x0000ff00 && - ddsHeader.pixFormat.dwBBitMask == 0x000000ff ) layout = L_BGR; - else - if( ddsHeader.pixFormat.dwRBitMask == 0x00ff0000 && - ddsHeader.pixFormat.dwGBitMask == 0x0000ff00 && - ddsHeader.pixFormat.dwBBitMask == 0x000000ff ) layout = L_RGB; - - if( layout == L_BGR || layout == L_RGB ) + else if( ddsHeader.pixFormat.dwRGBBitCount == 32 ) { - if( ddsHeader.pixFormat.dwRGBBitCount == 24 ) + if( !(ddsHeader.pixFormat.dwFlags & DDPF_ALPHAPIXELS) || + ddsHeader.pixFormat.dwABitMask == 0x00000000 ) { - _texFormat = layout == L_BGR ? TextureFormats::BGR8 : TextureFormats::RGB8; + _texFormat = layout == L_BGR ? TextureFormats::BGRX8 : TextureFormats::RGBX8; } - else if( ddsHeader.pixFormat.dwRGBBitCount == 32 ) - { - if( !(ddsHeader.pixFormat.dwFlags & DDPF_ALPHAPIXELS) || - ddsHeader.pixFormat.dwABitMask == 0x00000000 ) - { - _texFormat = layout == L_BGR ? TextureFormats::BGRX8 : TextureFormats::RGBX8; - } - else - { - _texFormat = layout == L_BGR ? TextureFormats::BGRA8 : TextureFormats::RGBA8; - } + else + { + _texFormat = layout == L_BGR ? TextureFormats::BGRA8 : TextureFormats::RGBA8; } } } + } - if( (int)_texFormat < 0 ) return raiseError( "Unsupported DDS pixel format" ); + if( (int)_texFormat < 0 ) return raiseError( "Unsupported DDS pixel format" ); + return true; +} + + +bool TextureResource::load( const char *data, int size ) +{ + if( !Resource::load( data, size ) ) return false; + if( !Modules::config().loadTextures ) return true; + + // Check if image is a dds + if( size > 128 && *((uint32 *)data) == FOURCC( 'D', 'D', 'S', ' ' ) ) + { + // Parse the dds header + if( !parseDDSHeader( data ) ) + return false; + // Upload texture subresources int numSlices = _texType == TextureTypes::TexCube ? 6 : 1; char *pixels = (char *)(data + 128); @@ -302,7 +334,7 @@ { int width = _width, height = _height; - for( int j = 0; j < mipCount; ++j ) + for( int j = 0; j < _mipCount; ++j ) { size_t mipSize = Modules::renderer().calcTexSize( _texFormat, width, height ); if( pixels + mipSize > (char*)data + size ) @@ -371,6 +403,144 @@ } +bool TextureResource::uploadStreamData( const void *data, int size, bool &doneReading ) +{ + if( !_streaming ) return false; + + if( !Modules::config().loadTextures ) + { + _loaded = true; + doneReading = true; + return true; + } + + if( !_loadedHeader ) + { + // Check if image is a dds + if( size == 128 && *((uint32 *)data) == FOURCC( 'D', 'D', 'S', ' ' ) ) + { + if( !parseDDSHeader( data ) ) + { + _loaded = true; + doneReading = true; + return false; + } + + _streamOffset = 128; + int width = _width, height = _height; + + for( int i = 0; i < _mipCount; ++i ) + { + size_t mipSize = Modules::renderer().calcTexSize( _texFormat, width, height ); + _mipLevels.push_back( MipLevel( 0x0, mipSize, width, height ) ); + + if( i < _mipCount - 1 ) + _streamOffset += (int)mipSize; + + width >>= 1; + height >>= 1; + } + + _streamSize = (int)_mipLevels[_mipCount - 1].sliceSize; + _loadedHeader = true; + doneReading = false; + return true; + } + else + { + _loaded = true; + doneReading = true; + return raiseError( "Trying to stream a non-DDS texture" ); + } + } + else + { + int mipIndex = _mipCount - _mipsLoaded - 1; + + if( mipIndex < 0 ) + return false; + + if( size != _mipLevels[mipIndex].sliceSize ) + { + _loaded = true; + doneReading = true; + return raiseError( "Invalid mipmap size" ); + } + + int numSlices = _texType == TextureTypes::TexCube ? 6 : 1; + + if( _slicesLoaded == 0 ) + _mipLevels[mipIndex].data = new char[_mipLevels[mipIndex].sliceSize * numSlices]; + + memcpy( _mipLevels[mipIndex].data + ( _mipLevels[mipIndex].sliceSize * _slicesLoaded ), data, size ); + _slicesLoaded++; + + if( _slicesLoaded == numSlices ) + { + int index = 0; + char *pixels; + + for( int i = mipIndex; i < _mipCount; ++i ) + { + pixels = _mipLevels[i].data; + + for( int j = 0; j < numSlices; ++j ) + { + _texObject = Modules::renderer().uploadTexture( _texType, pixels, _mipLevels[i].width, _mipLevels[i].height, + _texFormat, j, index, false, false, _texObject ); + + pixels += _mipLevels[i].sliceSize; + } + + index++; + } + + if( _texObject == 0 ) + { + _loaded = true; + doneReading = true; + return raiseError( "Failed to upload texture map" ); + } + + // Loaded all the mipmap's slices, start over again with the next one's + _mipsLoaded++; + _slicesLoaded = 0; + mipIndex--; + } + + if( _mipsLoaded == _mipCount ) + { + for( size_t i = 0; i < _mipLevels.size(); ++i ) + { + if( _mipLevels[i].data != 0x0 ) + delete[] _mipLevels[i].data; + } + + _mipLevels.clear(); + _loaded = true; + doneReading = true; + } + else + { + _streamSize = (int)_mipLevels[mipIndex].sliceSize; + _streamOffset = 128; + + for( int i = 0; i < _mipCount; ++i ) + { + _streamOffset += (int)_mipLevels[i].sliceSize * _slicesLoaded; + + if( i < mipIndex ) + _streamOffset += (int)_mipLevels[i].sliceSize; + } + + doneReading = false; + } + + return true; + } +} + + float *TextureResource::downloadImageData() { if( _texType != TextureTypes::Tex2D ) return 0x0; @@ -396,3 +566,14 @@ return Resource::getParami( param ); } } + +bool TextureResource::getStreamingDetails( int &size, int &offset ) +{ + if( !_streaming ) + return false; + + size = _streamSize; + offset = _streamOffset; + + return true; +} Index: Source/Horde3DEngine/egTextures.h =================================================================== --- Source/Horde3DEngine/egTextures.h (revision 121) +++ Source/Horde3DEngine/egTextures.h (working copy) @@ -50,18 +50,50 @@ // ================================================================================================= +struct MipLevel +{ + char *data; + size_t sliceSize; + int width; + int height; + + MipLevel() + { + } + + MipLevel( char *data, size_t sliceSize, int width, int height ) : + data( data ), + sliceSize( sliceSize ), + width( width ), + height( height ) + { + } +}; + +// ================================================================================================= + class TextureResource : public Resource { protected: - RenderBuffer *_rendBuf; // Used when texture is renderable - TextureTypes::List _texType; - TextureFormats::List _texFormat; - int _width, _height; - uint32 _texObject; - bool _hasMipMaps; + RenderBuffer *_rendBuf; // Used when texture is renderable + TextureTypes::List _texType; + TextureFormats::List _texFormat; + int _width, _height; + uint32 _texObject; + bool _hasMipMaps; + int _mipCount; + bool _streaming; + bool _loadedHeader; + std::vector< MipLevel > _mipLevels; + int _mipsLoaded; + int _slicesLoaded; + int _streamSize; + int _streamOffset; + bool raiseError( const std::string &msg ); + bool parseDDSHeader( const void *data ); public: @@ -82,6 +114,7 @@ void release(); bool load( const char *data, int size ); bool updateData( int param, const void *data, int size ); + bool uploadStreamData( const void *data, int size, bool &doneReading ); float *downloadImageData(); int getParami( int param ); @@ -93,6 +126,8 @@ uint32 getHeight() const { return _height; } uint32 getTexObject() { return _texObject; } bool hasMipMaps() { return _hasMipMaps; } + bool isStreaming() { return _streaming; } + bool getStreamingDetails( int &size, int &offset ); friend class ResourceManager; };