diff --git a/include/Nazara/Renderer/OpenGL.hpp b/include/Nazara/Renderer/OpenGL.hpp index 04623054d..cd4c1a4d2 100644 --- a/include/Nazara/Renderer/OpenGL.hpp +++ b/include/Nazara/Renderer/OpenGL.hpp @@ -38,6 +38,7 @@ class NAZARA_API NzOpenGL FP64, FrameBufferObject, Texture3D, + TextureCompression_s3tc, TextureStorage, VertexArrayObject, @@ -120,6 +121,8 @@ NAZARA_API extern PFNGLGETSHADERSOURCEPROC glGetShaderSource; NAZARA_API extern PFNGLGETSTRINGPROC glGetString; NAZARA_API extern PFNGLGETSTRINGIPROC glGetStringi; NAZARA_API extern PFNGLGETTEXIMAGEPROC glGetTexImage; +NAZARA_API extern PFNGLGETTEXLEVELPARAMETERFVPROC glGetTexLevelParameterfv; +NAZARA_API extern PFNGLGETTEXLEVELPARAMETERIVPROC glGetTexLevelParameteriv; NAZARA_API extern PFNGLGETTEXPARAMETERFVPROC glGetTexParameterfv; NAZARA_API extern PFNGLGETTEXPARAMETERIVPROC glGetTexParameteriv; NAZARA_API extern PFNGLGETUNIFORMLOCATIONPROC glGetUniformLocation; diff --git a/include/Nazara/Renderer/Shader.hpp b/include/Nazara/Renderer/Shader.hpp index f143d213a..79d0402de 100644 --- a/include/Nazara/Renderer/Shader.hpp +++ b/include/Nazara/Renderer/Shader.hpp @@ -32,6 +32,7 @@ enum nzShaderType class NzRenderer; class NzShaderImpl; +class NzTexture; class NAZARA_API NzShader : public NzResource, NzNonCopyable { @@ -65,6 +66,7 @@ class NAZARA_API NzShader : public NzResource, NzNonCopyable bool SendInteger(const NzString& name, int value); bool SendMatrix(const NzString& name, const NzMatrix4d& matrix); bool SendMatrix(const NzString& name, const NzMatrix4f& matrix); + bool SendTexture(const NzString& name, NzTexture* texture); void Unlock(); diff --git a/include/Nazara/Renderer/Texture.hpp b/include/Nazara/Renderer/Texture.hpp new file mode 100644 index 000000000..f038a86e9 --- /dev/null +++ b/include/Nazara/Renderer/Texture.hpp @@ -0,0 +1,98 @@ +// Copyright (C) 2012 Jérôme Leclercq +// This file is part of the "Nazara Engine". +// For conditions of distribution and use, see copyright notice in Config.hpp + +#pragma once + +#ifndef NAZARA_TEXTURE_HPP +#define NAZARA_TEXTURE_HPP + +#include +#include +#include +#include + +enum nzTextureFilter +{ + nzTextureFilter_Bilinear, + nzTextureFilter_Nearest, + nzTextureFilter_Trilinear, + nzTextureFilter_Unknown +}; + +enum nzTextureWrap +{ + nzTextureWrap_Clamp, + nzTextureWrap_Repeat, + nzTextureWrap_Unknown +}; + +class NzRenderWindow; ///TODO: Screenshot +struct NzTextureImpl; + +class NAZARA_API NzTexture : public NzResource, NzNonCopyable +{ + friend class NzShader; + + public: + NzTexture(); + explicit NzTexture(const NzImage& image); + ~NzTexture(); + + bool Bind(); + + bool Create(nzImageType type, nzPixelFormat format, unsigned int width, unsigned int height, unsigned int depth = 1, nzUInt8 levelCount = 1, bool lock = false); + void Destroy(); + + bool Download(NzImage* image) const; + + bool EnableMipmapping(bool enable); + + unsigned int GetAnisotropyLevel() const; + nzUInt8 GetBPP() const; + unsigned int GetDepth() const; + nzTextureFilter GetFilterMode() const; + nzPixelFormat GetFormat() const; + unsigned int GetHeight() const; + nzImageType GetType() const; + unsigned int GetWidth() const; + nzTextureWrap GetWrapMode() const; + + bool IsCompressed() const; + bool IsCubemap() const; + bool IsValid() const; + + bool LoadFromFile(const NzString& filePath, const NzImageParams& params = NzImageParams()); + bool LoadFromImage(const NzImage& image); + bool LoadFromMemory(const void* data, std::size_t size, const NzImageParams& params = NzImageParams()); + bool LoadFromStream(NzInputStream& stream, const NzImageParams& params = NzImageParams()); + + bool Lock(); + + bool SetAnisotropyLevel(unsigned int anistropyLevel); + bool SetFilterMode(nzTextureFilter filter); + bool SetMipmapRange(nzUInt8 minLevel, nzUInt8 maxLevel); + bool SetWrapMode(nzTextureWrap wrap); + + bool Update(const NzImage& image, nzUInt8 level = 0); + bool Update(const NzImage& image, const NzRectui& rect, unsigned int z = 0, nzUInt8 level = 0); + //bool Update(const NzImage& image, const NzCubeui& cube, nzUInt8 level = 0); + bool Update(const nzUInt8* pixels, nzUInt8 level = 0); + bool Update(const nzUInt8* pixels, const NzRectui& rect, unsigned int z = 0, nzUInt8 level = 0); + //bool Update(const nzUInt8* pixels, const NzCubeui& cube, nzUInt8 level = 0); + bool UpdateFace(nzCubemapFace face, const NzImage& image, nzUInt8 level = 0); + bool UpdateFace(nzCubemapFace face, const NzImage& image, const NzRectui& rect, nzUInt8 level = 0); + bool UpdateFace(nzCubemapFace face, const nzUInt8* pixels, nzUInt8 level = 0); + bool UpdateFace(nzCubemapFace face, const nzUInt8* pixels, const NzRectui& rect, nzUInt8 level = 0); + + void Unlock(); + + static unsigned int GetValidSize(unsigned int size); + static bool IsFormatSupported(nzPixelFormat format); + static bool IsTypeSupported(nzImageType type); + + private: + NzTextureImpl* m_impl; +}; + +#endif // NAZARA_TEXTURE_HPP diff --git a/include/Nazara/Utility/Image.hpp b/include/Nazara/Utility/Image.hpp index 272c1c7b6..359cce0a9 100644 --- a/include/Nazara/Utility/Image.hpp +++ b/include/Nazara/Utility/Image.hpp @@ -31,18 +31,22 @@ enum nzImageType nzImageType_1D, nzImageType_2D, nzImageType_3D, - nzImageType_Cubemap + nzImageType_Cubemap, + + nzImageType_Count }; struct NzImageParams { // GCC 4.7 je te veux NzImageParams() : - loadFormat(nzPixelFormat_Undefined) + loadFormat(nzPixelFormat_Undefined), + levelCount(0) { } nzPixelFormat loadFormat; + nzUInt8 levelCount; bool IsValid() const { diff --git a/include/Nazara/Utility/PixelFormat.hpp b/include/Nazara/Utility/PixelFormat.hpp index dc8b95263..4a9d45eef 100644 --- a/include/Nazara/Utility/PixelFormat.hpp +++ b/include/Nazara/Utility/PixelFormat.hpp @@ -53,6 +53,8 @@ class NzPixelFormat static nzUInt8 GetBPP(nzPixelFormat format); + static bool HasAlpha(nzPixelFormat format); + static bool IsCompressed(nzPixelFormat format); static bool IsConversionSupported(nzPixelFormat srcFormat, nzPixelFormat dstFormat); static bool IsValid(nzPixelFormat format); @@ -65,7 +67,7 @@ class NzPixelFormat static bool Initialize(); static void Uninitialize(); - static ConvertFunction s_convertFunctions[nzPixelFormat_Count][nzPixelFormat_Count]; + static NAZARA_API ConvertFunction s_convertFunctions[nzPixelFormat_Count][nzPixelFormat_Count]; }; #include diff --git a/include/Nazara/Utility/PixelFormat.inl b/include/Nazara/Utility/PixelFormat.inl index b4bcf99e7..4779b6a6b 100644 --- a/include/Nazara/Utility/PixelFormat.inl +++ b/include/Nazara/Utility/PixelFormat.inl @@ -139,6 +139,24 @@ inline nzUInt8 NzPixelFormat::GetBPP(nzPixelFormat format) return 0; } +inline bool NzPixelFormat::HasAlpha(nzPixelFormat format) +{ + switch (format) + { + case nzPixelFormat_BGRA8: + case nzPixelFormat_DXT3: + case nzPixelFormat_DXT5: + case nzPixelFormat_LA8: + case nzPixelFormat_RGB5A1: + case nzPixelFormat_RGBA4: + case nzPixelFormat_RGBA8: + return true; + + default: + return false; + } +} + inline bool NzPixelFormat::IsCompressed(nzPixelFormat format) { switch (format) diff --git a/src/Nazara/Renderer/Context.cpp b/src/Nazara/Renderer/Context.cpp index 880aa3580..dfd885ee3 100644 --- a/src/Nazara/Renderer/Context.cpp +++ b/src/Nazara/Renderer/Context.cpp @@ -31,7 +31,8 @@ namespace NzStringStream ss; ss << "OpenGL debug message (ID: 0x" << NzString::Number(id, 16) << "):\n"; - ss << "-Source: "; + ss << "Sent by context: " << userParam; + ss << "\n-Source: "; switch (source) { case GL_DEBUG_SOURCE_API_ARB: @@ -121,8 +122,7 @@ namespace } ss << '\n'; - ss << "Message: " << message; - ss << "\n\nSent by context: " << userParam; + ss << "Message: " << message << '\n'; NazaraNotice(ss); } diff --git a/src/Nazara/Renderer/GLSLShader.cpp b/src/Nazara/Renderer/GLSLShader.cpp index af9403f89..4ddc12fad 100644 --- a/src/Nazara/Renderer/GLSLShader.cpp +++ b/src/Nazara/Renderer/GLSLShader.cpp @@ -6,6 +6,8 @@ #include #include #include +#include +#include #include #include @@ -26,6 +28,9 @@ namespace GL_GEOMETRY_SHADER, // nzShaderType_Geometry GL_VERTEX_SHADER // nzShaderType_Vertex }; + + GLuint lockedPrevious = 0; + nzUInt8 lockedLevel = 0; } NzGLSLShader::NzGLSLShader(NzShader* parent) : @@ -39,9 +44,24 @@ NzGLSLShader::~NzGLSLShader() bool NzGLSLShader::Bind() { + #if NAZARA_RENDERER_SAFE + if (lockedLevel > 0) + { + NazaraError("Cannot bind shader while a shader is locked"); + return false; + } + #endif + glUseProgram(m_program); - return true; ///FIXME: Comment détecter une erreur d'OpenGL sans ralentir le programme ? + for (auto it = m_textures.begin(); it != m_textures.end(); ++it) + { + glActiveTexture(GL_TEXTURE0 + it->second.unit); + if (!it->second.texture->Bind()) + NazaraWarning("Failed to bind texture"); + } + + return true; } bool NzGLSLShader::Compile() @@ -106,6 +126,8 @@ bool NzGLSLShader::Create() for (int i = 0; i < nzShaderType_Count; ++i) m_shaders[i] = 0; + m_textureFreeID = 0; + return true; } @@ -218,16 +240,16 @@ bool NzGLSLShader::Load(nzShaderType type, const NzString& source) } } -bool NzGLSLShader::Lock() const +bool NzGLSLShader::Lock() { - if (m_lockedLevel++ == 0) + if (lockedLevel++ == 0) { GLint previous; glGetIntegerv(GL_CURRENT_PROGRAM, &previous); - m_lockedPrevious = previous; + lockedPrevious = previous; - if (m_lockedPrevious != m_program) + if (lockedPrevious != m_program) glUseProgram(m_program); } @@ -288,19 +310,72 @@ bool NzGLSLShader::SendMatrix(const NzString& name, const NzMatrix4f& matrix) return true; } +bool NzGLSLShader::SendTexture(const NzString& name, NzTexture* texture) +{ + static const unsigned int maxUnits = NazaraRenderer->GetMaxTextureUnits(); + + unsigned int unitUsed = m_textures.size(); + if (unitUsed >= maxUnits) + { + NazaraError("Unable to use texture \"" + name + "\" for shader: all available texture units are used"); + return false; + } + + // À partir d'ici nous savons qu'il y a au moins un identifiant de texture libre + GLint location = GetUniformLocation(name); + if (location == -1) + { + NazaraError("Parameter name \"" + name + "\" not found in shader"); + return false; + } + + nzUInt8 unit; + if (unitUsed == 0) + // Pas d'unité utilisée, la tâche est simple + unit = 0; + else + { + auto it = m_textures.rbegin(); // Itérateur vers la fin de la map + unit = it->second.unit; + if (unit == maxUnits-1) + { + // Il y a une place libre, mais pas à la fin + for (; it != m_textures.rend(); ++it) + { + if (unit - it->second.unit > 1) // Si l'espace entre les indices est supérieur à 1, alors il y a une place libre + { + unit--; + break; + } + } + } + else + // Il y a une place libre à la fin + unit++; + } + + m_textures[location] = TextureSlot{unit, texture}; + + Lock(); + glUniform1i(location, unit); + Unlock(); + + return true; +} + void NzGLSLShader::Unbind() { glUseProgram(0); } -void NzGLSLShader::Unlock() const +void NzGLSLShader::Unlock() { - if (m_lockedLevel == 0) + if (lockedLevel == 0) { NazaraWarning("Unlock called on non-locked texture"); return; } - if (--m_lockedLevel == 0 && m_lockedPrevious != m_program) - glUseProgram(m_lockedPrevious); + if (--lockedLevel == 0 && lockedPrevious != m_program) + glUseProgram(lockedPrevious); } diff --git a/src/Nazara/Renderer/GLSLShader.hpp b/src/Nazara/Renderer/GLSLShader.hpp index 653955999..2d0287652 100644 --- a/src/Nazara/Renderer/GLSLShader.hpp +++ b/src/Nazara/Renderer/GLSLShader.hpp @@ -34,7 +34,7 @@ class NzGLSLShader : public NzShaderImpl bool IsLoaded(nzShaderType type) const; bool Load(nzShaderType type, const NzString& source); - bool Lock() const; + bool Lock(); bool SendBoolean(const NzString& name, bool value); bool SendDouble(const NzString& name, double value); @@ -42,16 +42,23 @@ class NzGLSLShader : public NzShaderImpl bool SendInteger(const NzString& name, int value); bool SendMatrix(const NzString& name, const NzMatrix4d& matrix); bool SendMatrix(const NzString& name, const NzMatrix4f& matrix); + bool SendTexture(const NzString& name, NzTexture* texture); void Unbind(); - void Unlock() const; + void Unlock(); private: + struct TextureSlot + { + nzUInt8 unit; + NzTexture* texture; + }; + mutable std::map m_idCache; - mutable GLuint m_lockedPrevious; + std::map m_textures; GLuint m_program; GLuint m_shaders[nzShaderType_Count]; - mutable nzUInt8 m_lockedLevel; + nzUInt8 m_textureFreeID; NzShader* m_parent; NzString m_log; }; diff --git a/src/Nazara/Renderer/OcclusionQuery.cpp b/src/Nazara/Renderer/OcclusionQuery.cpp index f87bca264..5ce46f719 100644 --- a/src/Nazara/Renderer/OcclusionQuery.cpp +++ b/src/Nazara/Renderer/OcclusionQuery.cpp @@ -37,7 +37,10 @@ m_id(0) NzOcclusionQuery::~NzOcclusionQuery() { if (m_id) - glDeleteQueries(1, reinterpret_cast(&m_id)); + { + GLuint query = static_cast(m_id); + glDeleteQueries(1, &query); + } } void NzOcclusionQuery::Begin() diff --git a/src/Nazara/Renderer/OpenGL.cpp b/src/Nazara/Renderer/OpenGL.cpp index 86ea4ae1f..610e8c891 100644 --- a/src/Nazara/Renderer/OpenGL.cpp +++ b/src/Nazara/Renderer/OpenGL.cpp @@ -255,6 +255,8 @@ bool NzOpenGL::Initialize() glGetShaderiv = reinterpret_cast(LoadEntry("glGetShaderiv")); glGetShaderSource = reinterpret_cast(LoadEntry("glGetShaderSource")); glGetTexImage = reinterpret_cast(LoadEntry("glGetTexImage")); + glGetTexLevelParameterfv = reinterpret_cast(LoadEntry("glGetTexLevelParameterfv")); + glGetTexLevelParameteriv = reinterpret_cast(LoadEntry("glGetTexLevelParameteriv")); glGetTexParameterfv = reinterpret_cast(LoadEntry("glGetTexParameterfv")); glGetTexParameteriv = reinterpret_cast(LoadEntry("glGetTexParameteriv")); glGetUniformLocation = reinterpret_cast(LoadEntry("glGetUniformLocation")); @@ -445,6 +447,9 @@ bool NzOpenGL::Initialize() } } + // TextureCompression_s3tc + openGLextensions[NzOpenGL::TextureCompression_s3tc] = IsSupported("GL_EXT_texture_compression_s3tc"); + // VertexArrayObject if (openGLversion >= 420 || IsSupported("GL_ARB_texture_storage")) { @@ -587,6 +592,8 @@ PFNGLGETSHADERSOURCEPROC glGetShaderSource = nullptr; PFNGLGETSTRINGPROC glGetString = nullptr; PFNGLGETSTRINGIPROC glGetStringi = nullptr; PFNGLGETTEXIMAGEPROC glGetTexImage = nullptr; +PFNGLGETTEXLEVELPARAMETERFVPROC glGetTexLevelParameterfv = nullptr; +PFNGLGETTEXLEVELPARAMETERIVPROC glGetTexLevelParameteriv = nullptr; PFNGLGETTEXPARAMETERFVPROC glGetTexParameterfv = nullptr; PFNGLGETTEXPARAMETERIVPROC glGetTexParameteriv = nullptr; PFNGLGETUNIFORMLOCATIONPROC glGetUniformLocation = nullptr; diff --git a/src/Nazara/Renderer/RenderWindow.cpp b/src/Nazara/Renderer/RenderWindow.cpp index 38be6c833..6ce72547d 100644 --- a/src/Nazara/Renderer/RenderWindow.cpp +++ b/src/Nazara/Renderer/RenderWindow.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include namespace @@ -25,12 +26,28 @@ NzRenderWindow::NzRenderWindow(NzVideoMode mode, const NzString& title, nzUInt32 m_context(nullptr) { Create(mode, title, style, parameters); + + #ifdef NAZARA_DEBUG + if (!m_impl) + { + NazaraError("Failed to create render window"); + throw std::runtime_error("Constructor failed"); + } + #endif } NzRenderWindow::NzRenderWindow(NzWindowHandle handle, const NzContextParameters& parameters) : m_context(nullptr) { Create(handle, parameters); + + #ifdef NAZARA_DEBUG + if (!m_impl) + { + NazaraError("Failed to create render window"); + throw std::runtime_error("Constructor failed"); + } + #endif } NzRenderWindow::~NzRenderWindow() diff --git a/src/Nazara/Renderer/Renderer.cpp b/src/Nazara/Renderer/Renderer.cpp index f545ad7cb..05627ef61 100644 --- a/src/Nazara/Renderer/Renderer.cpp +++ b/src/Nazara/Renderer/Renderer.cpp @@ -30,7 +30,8 @@ namespace 4 // nzElementUsage_TexCoord }; - const GLenum openglPrimitive[] = { + const GLenum openglPrimitive[] = + { GL_LINES, // nzPrimitiveType_LineList, GL_LINE_STRIP, // nzPrimitiveType_LineStrip, GL_POINTS, // nzPrimitiveType_PointList, @@ -310,6 +311,12 @@ bool NzRenderer::SetShader(NzShader* shader) if (shader == m_shader) return true; + if (m_shader) + { + m_shader->m_impl->Unbind(); + m_shader = nullptr; + } + if (shader) { #if NAZARA_RENDERER_SAFE @@ -328,11 +335,6 @@ bool NzRenderer::SetShader(NzShader* shader) m_shader = shader; } - else if (m_shader) - { - m_shader->m_impl->Unbind(); - m_shader = nullptr; - } return true; } @@ -408,7 +410,7 @@ void NzRenderer::Uninitialize() // Libération des VAOs for (auto it = m_vaos.begin(); it != m_vaos.end(); ++it) { - GLuint vao = it->second; + GLuint vao = static_cast(it->second); glDeleteVertexArrays(1, &vao); } diff --git a/src/Nazara/Renderer/Shader.cpp b/src/Nazara/Renderer/Shader.cpp index 0afb45782..6d642626b 100644 --- a/src/Nazara/Renderer/Shader.cpp +++ b/src/Nazara/Renderer/Shader.cpp @@ -371,6 +371,19 @@ bool NzShader::SendMatrix(const NzString& name, const NzMatrix4f& matrix) return m_impl->SendMatrix(name, matrix); } +bool NzShader::SendTexture(const NzString& name, NzTexture* texture) +{ + #if NAZARA_RENDERER_SAFE + if (!m_impl) + { + NazaraError("Shader not created"); + return false; + } + #endif + + return m_impl->SendTexture(name, texture); +} + void NzShader::Unlock() { #if NAZARA_RENDERER_SAFE diff --git a/src/Nazara/Renderer/ShaderImpl.hpp b/src/Nazara/Renderer/ShaderImpl.hpp index b5b1316ec..330b9dccf 100644 --- a/src/Nazara/Renderer/ShaderImpl.hpp +++ b/src/Nazara/Renderer/ShaderImpl.hpp @@ -10,6 +10,7 @@ #include class NzRenderer; +class NzTexture; class NzVertexBuffer; class NzVertexDeclaration; @@ -36,7 +37,7 @@ class NzShaderImpl virtual bool Load(nzShaderType type, const NzString& source) = 0; - virtual bool Lock() const = 0; + virtual bool Lock() = 0; virtual bool SendBoolean(const NzString& name, bool value) = 0; virtual bool SendDouble(const NzString& name, double value) = 0; @@ -44,9 +45,10 @@ class NzShaderImpl virtual bool SendInteger(const NzString& name, int value) = 0; virtual bool SendMatrix(const NzString& name, const NzMatrix4d& matrix) = 0; virtual bool SendMatrix(const NzString& name, const NzMatrix4f& matrix) = 0; + virtual bool SendTexture(const NzString& name, NzTexture* texture) = 0; virtual void Unbind() = 0; - virtual void Unlock() const = 0; + virtual void Unlock() = 0; }; #endif // NAZARA_SHADERIMPL_HPP diff --git a/src/Nazara/Renderer/Texture.cpp b/src/Nazara/Renderer/Texture.cpp new file mode 100644 index 000000000..36dad7d8f --- /dev/null +++ b/src/Nazara/Renderer/Texture.cpp @@ -0,0 +1,1485 @@ +// Copyright (C) 2012 Jérôme Leclercq +// This file is part of the "Nazara Engine". +// For conditions of distribution and use, see copyright notice in Config.hpp + +#include +#include +#include +#include +#include +#include + +struct NzTextureImpl +{ + // GCC 4.7 !!!!!! + NzTextureImpl() : + mipmapping(false), + mipmapsUpdated(false) + { + } + + GLuint id; + nzImageType type; + nzPixelFormat format; + nzUInt8 levelCount; + bool mipmapping; + bool mipmapsUpdated; + unsigned int depth; + unsigned int height; + unsigned int width; +}; + +namespace +{ + GLenum cubemapFace[] = + { + GL_TEXTURE_CUBE_MAP_POSITIVE_X, // nzCubemapFace_PositiveX + GL_TEXTURE_CUBE_MAP_NEGATIVE_X, // nzCubemapFace_NegativeX + GL_TEXTURE_CUBE_MAP_POSITIVE_Y, // nzCubemapFace_PositiveY + GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, // nzCubemapFace_NegativeY + GL_TEXTURE_CUBE_MAP_POSITIVE_Z, // nzCubemapFace_PositiveZ + GL_TEXTURE_CUBE_MAP_NEGATIVE_Z // nzCubemapFace_NegativeZ + }; + + GLenum openglTarget[] = + { + GL_TEXTURE_1D, // nzImageType_1D + GL_TEXTURE_2D, // nzImageType_2D + GL_TEXTURE_3D, // nzImageType_3D + GL_TEXTURE_CUBE_MAP // nzImageType_Cubemap + }; + + GLenum openglTargetBinding[] = + { + GL_TEXTURE_BINDING_1D, // nzImageType_1D + GL_TEXTURE_BINDING_2D, // nzImageType_2D + GL_TEXTURE_BINDING_3D, // nzImageType_3D + GL_TEXTURE_BINDING_CUBE_MAP // nzImageType_Cubemap + }; + + struct OpenGLFormat + { + GLint internalFormat; + GLenum dataFormat; + GLenum dataType; + }; + + bool GetOpenGLFormat(nzPixelFormat pixelFormat, OpenGLFormat* format) + { + switch (pixelFormat) + { + case nzPixelFormat_BGR8: + format->dataFormat = GL_BGR; + format->dataType = GL_UNSIGNED_BYTE; + format->internalFormat = GL_RGB8; + break; + + case nzPixelFormat_BGRA8: + format->dataFormat = GL_BGRA; + format->dataType = GL_UNSIGNED_BYTE; + format->internalFormat = GL_RGBA8; + break; + + case nzPixelFormat_DXT1: + format->dataFormat = GL_RGB; + format->dataType = GL_UNSIGNED_BYTE; + format->internalFormat = GL_COMPRESSED_RGB_S3TC_DXT1_EXT; + break; + + case nzPixelFormat_DXT3: + format->dataFormat = GL_RGBA; + format->dataType = GL_UNSIGNED_BYTE; + format->internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT; + break; + + case nzPixelFormat_DXT5: + format->dataFormat = GL_RGBA; + format->dataType = GL_UNSIGNED_BYTE; + format->internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; + break; + + case nzPixelFormat_L8: + case nzPixelFormat_LA8: + NazaraError("Pixel format not supported"); + return false; + + case nzPixelFormat_RGB5A1: + format->dataFormat = GL_RGBA; + format->dataType = GL_UNSIGNED_SHORT_5_5_5_1; + format->internalFormat = GL_RGB5_A1; + break; + + case nzPixelFormat_RGB8: + format->dataFormat = GL_RGB; + format->dataType = GL_UNSIGNED_BYTE; + format->internalFormat = GL_RGB8; + break; + + case nzPixelFormat_RGBA4: + format->dataFormat = GL_RGBA; + format->dataType = GL_UNSIGNED_SHORT_4_4_4_4; + format->internalFormat = GL_RGBA4; + break; + + case nzPixelFormat_RGBA8: + format->dataFormat = GL_RGBA; + format->dataType = GL_UNSIGNED_BYTE; + format->internalFormat = GL_RGBA8; + break; + + default: + NazaraError("Pixel format not handled"); + return false; + } + + return true; + } + + bool CreateTexture(NzTextureImpl* impl, bool proxy) + { + OpenGLFormat openGLFormat; + if (!GetOpenGLFormat(impl->format, &openGLFormat)) + { + NazaraError("Failed to get OpenGL format"); + return false; + } + + static const bool texStorageSupported = NzOpenGL::IsSupported(NzOpenGL::TextureStorage); + + GLenum target; + switch (impl->type) + { + case nzImageType_1D: + { + target = (proxy) ? GL_TEXTURE_1D : GL_PROXY_TEXTURE_1D; + + if (texStorageSupported) + glTexStorage1D(target, impl->levelCount, openGLFormat.internalFormat, impl->width); + else + { + unsigned int w = impl->width; + for (nzUInt8 level = 0; level < impl->levelCount; ++level) + { + glTexImage1D(target, level, openGLFormat.internalFormat, w, 0, openGLFormat.dataFormat, openGLFormat.dataType, nullptr); + w = std::max(w/2, 1U); + } + } + break; + } + + case nzImageType_2D: + { + target = (proxy) ? GL_TEXTURE_2D : GL_PROXY_TEXTURE_2D; + + if (texStorageSupported) + glTexStorage2D(target, impl->levelCount, openGLFormat.internalFormat, impl->width, impl->height); + else + { + unsigned int w = impl->width; + unsigned int h = impl->height; + for (nzUInt8 level = 0; level < impl->levelCount; ++level) + { + glTexImage2D(target, level, openGLFormat.internalFormat, w, h, 0, openGLFormat.dataFormat, openGLFormat.dataType, nullptr); + w = std::max(w/2, 1U); + h = std::max(h/2, 1U); + } + } + break; + } + + case nzImageType_3D: + { + target = (proxy) ? GL_TEXTURE_3D : GL_PROXY_TEXTURE_3D; + + if (texStorageSupported) + glTexStorage3D(target, impl->levelCount, openGLFormat.internalFormat, impl->width, impl->height, impl->depth); + else + { + unsigned int w = impl->width; + unsigned int h = impl->height; + unsigned int d = impl->depth; + for (nzUInt8 level = 0; level < impl->levelCount; ++level) + { + glTexImage3D(target, level, openGLFormat.internalFormat, w, h, d, 0, openGLFormat.dataFormat, openGLFormat.dataType, nullptr); + w = std::max(w/2, 1U); + h = std::max(h/2, 1U); + d = std::max(d/2, 1U); + } + } + break; + } + + case nzImageType_Cubemap: + { + target = (proxy) ? GL_TEXTURE_CUBE_MAP : GL_PROXY_TEXTURE_CUBE_MAP; + + if (texStorageSupported) + glTexStorage2D(target, impl->levelCount, openGLFormat.internalFormat, impl->width, impl->height); + else + { + unsigned int size = impl->width; // Les cubemaps ont une longueur et largeur identique + for (nzUInt8 level = 0; level < impl->levelCount; ++level) + { + for (GLenum face : cubemapFace) + glTexImage2D(face, level, openGLFormat.internalFormat, size, size, 0, openGLFormat.dataFormat, openGLFormat.dataType, nullptr); + + size = std::max(size/2, 1U); + } + } + break; + } + + default: + NazaraInternalError("Image type not handled"); + return false; + } + + if (proxy) + { + GLint internalFormat; + glGetTexLevelParameteriv(target, 0, GL_TEXTURE_INTERNAL_FORMAT, &internalFormat); + if (internalFormat == 0) + return false; + } + + return true; + } + + static unsigned short lockedLevel[nzImageType_Count] = {0}; + static GLuint lockedPrevious[nzImageType_Count] = {0}; + + void LockTexture(NzTextureImpl* impl) + { + if (lockedLevel[impl->type]++ == 0) + { + GLint previous; + glGetIntegerv(openglTargetBinding[impl->type], &previous); + + lockedPrevious[impl->type] = static_cast(previous); + + if (lockedPrevious[impl->type] != impl->id) + glBindTexture(openglTarget[impl->type], impl->id); + } + } + + void UnlockTexture(NzTextureImpl* impl) + { + #if NAZARA_RENDERER_SAFE + if (lockedLevel[impl->type] == 0) + { + NazaraError("Unlock called on non-locked texture"); + return; + } + #endif + + if (--lockedLevel[impl->type] == 0 && lockedPrevious[impl->type] != impl->id) + glBindTexture(openglTarget[impl->type], lockedPrevious[impl->type]); + } +} + +NzTexture::NzTexture() : +m_impl(nullptr) +{ +} + +NzTexture::NzTexture(const NzImage& image) : +m_impl(nullptr) +{ + LoadFromImage(image); + + #ifdef NAZARA_DEBUG + if (!m_impl) + { + NazaraError("Failed to create texture"); + throw std::runtime_error("Constructor failed"); + } + #endif +} + +NzTexture::~NzTexture() +{ + Destroy(); +} + +bool NzTexture::Bind() +{ + #if NAZARA_RENDERER_SAFE + if (lockedLevel[m_impl->type] > 0) + { + NazaraError("Cannot bind texture while a texture is locked"); + return false; + } + #endif + + glBindTexture(openglTarget[m_impl->type], m_impl->id); + + return true; +} + +bool NzTexture::Create(nzImageType type, nzPixelFormat format, unsigned int width, unsigned int height, unsigned int depth, nzUInt8 levelCount, bool lock) +{ + Destroy(); + + if (width == 0 || height == 0 || depth == 0) + return true; + + #if NAZARA_RENDERER_SAFE + if (!IsTypeSupported(type)) + { + NazaraError("Texture's type not supported"); + return false; + } + + if (!NzPixelFormat::IsValid(format)) + { + NazaraError("Invalid pixel format"); + return false; + } + + if (!IsFormatSupported(format)) + { + NazaraError("Texture's format not supported"); + return false; + } + + switch (type) + { + case nzImageType_1D: + if (height > 1) + { + NazaraError("1D textures must be 1 height"); + return false; + } + + if (depth > 1) + { + NazaraError("1D textures must be 1 depth"); + return false; + } + break; + + case nzImageType_2D: + if (depth > 1) + { + NazaraError("2D textures must be 1 depth"); + return false; + } + break; + + case nzImageType_3D: + break; + + case nzImageType_Cubemap: + if (depth > 1) + { + NazaraError("Cubemaps must be 1 depth"); + return false; + } + + if (width != height) + { + NazaraError("Cubemaps must have square dimensions"); + return false; + } + break; + + default: + NazaraInternalError("Image type not handled"); + return false; + } + #endif + + levelCount = std::min(levelCount, NzImage::GetMaxLevel(width, height, depth)); + + NzTextureImpl* impl = new NzTextureImpl; + glGenTextures(1, &impl->id); + + impl->depth = GetValidSize(depth); + impl->format = format; + impl->height = GetValidSize(height); + impl->levelCount = levelCount; + impl->type = type; + impl->width = GetValidSize(width); + + LockTexture(impl); + + // Vérification du support par la carte graphique + if (!CreateTexture(impl, true)) + { + NazaraError("Texture's parameters not supported by driver"); + UnlockTexture(impl); + glDeleteTextures(1, &impl->id); + delete impl; + + return false; + } + + // Création de la texture + if (!CreateTexture(impl, false)) + { + NazaraError("Failed to create texture"); + UnlockTexture(impl); + glDeleteTextures(1, &impl->id); + delete impl; + + return false; + } + + m_impl = impl; + + // Paramètres par défaut + SetFilterMode(nzTextureFilter_Nearest); + SetMipmapRange(0, m_impl->levelCount); + SetWrapMode(nzTextureWrap_Repeat); + + if (!lock) + UnlockTexture(impl); + + return true; +} + +void NzTexture::Destroy() +{ + if (m_impl) + { + glDeleteTextures(1, &m_impl->id); + delete m_impl; + m_impl = nullptr; + } +} + +bool NzTexture::Download(NzImage* image) const +{ + #if NAZARA_RENDERER_SAFE + if (!IsValid()) + { + NazaraError("Texture must be valid"); + return false; + } + + if (!image) + { + NazaraError("Cannot download to a null image"); + return false; + } + #endif + + OpenGLFormat format; + if (!GetOpenGLFormat(m_impl->format, &format)) + { + NazaraError("Failed to get OpenGL format"); + return false; + } + + if (!image->Create(m_impl->type, m_impl->format, m_impl->width, m_impl->height, m_impl->depth, m_impl->levelCount)) + { + NazaraError("Failed to create image"); + return false; + } + + LockTexture(m_impl); + + // Téléchargement... + for (nzUInt8 level = 0; level < m_impl->levelCount; ++level) + glGetTexImage(openglTarget[m_impl->type], level, format.dataFormat, format.dataType, image->GetPixels(level)); + + UnlockTexture(m_impl); + + return true; +} + +bool NzTexture::EnableMipmapping(bool enable) +{ + #if NAZARA_RENDERER_SAFE + if (!IsValid()) + { + NazaraError("Texture must be valid"); + return false; + } + #endif + + if (!glGenerateMipmap) + { + NazaraError("Mipmapping not supported"); + return false; + } + + LockTexture(m_impl); + + if (!m_impl->mipmapping && enable) + glGenerateMipmap(openglTarget[m_impl->type]); + + m_impl->mipmapping = enable; + + UnlockTexture(m_impl); + + return true; +} + +unsigned int NzTexture::GetAnisotropyLevel() const +{ + #if NAZARA_RENDERER_SAFE + if (!IsValid()) + { + NazaraError("Texture must be valid"); + return 1; + } + + if (!NzOpenGL::IsSupported(NzOpenGL::AnisotropicFilter)) + { + NazaraError("Anisotropic filter not supported"); + return 1; + } + #endif + + LockTexture(m_impl); + + GLfloat anisotropyLevel; + glGetTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, &anisotropyLevel); + + UnlockTexture(m_impl); + + return static_cast(anisotropyLevel); +} + +nzUInt8 NzTexture::GetBPP() const +{ + #if NAZARA_RENDERER_SAFE + if (!IsValid()) + { + NazaraError("Texture must be valid"); + return 0; + } + #endif + + return NzPixelFormat::GetBPP(m_impl->format); +} + +unsigned int NzTexture::GetDepth() const +{ + #if NAZARA_RENDERER_SAFE + if (!IsValid()) + { + NazaraError("Texture must be valid"); + return 0; + } + #endif + + return m_impl->depth; +} + +nzTextureFilter NzTexture::GetFilterMode() const +{ + #if NAZARA_RENDERER_SAFE + if (!IsValid()) + { + NazaraError("Texture must be valid"); + return nzTextureFilter_Unknown; + } + #endif + + LockTexture(m_impl); + + GLint value; + glGetTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, &value); + + UnlockTexture(m_impl); + + GLenum filterMode = static_cast(value); + switch (filterMode) + { + case GL_LINEAR: + case GL_LINEAR_MIPMAP_NEAREST: + return nzTextureFilter_Bilinear; + + case GL_NEAREST: + case GL_NEAREST_MIPMAP_NEAREST: + return nzTextureFilter_Nearest; + + case GL_LINEAR_MIPMAP_LINEAR: + return nzTextureFilter_Trilinear; + + default: + NazaraInternalError("OpenGL filter mode not handled (0x" + NzString::Number(filterMode, 16) + ')'); + return nzTextureFilter_Unknown; + } +} + +nzPixelFormat NzTexture::GetFormat() const +{ + #if NAZARA_RENDERER_SAFE + if (!IsValid()) + { + NazaraError("Texture must be valid"); + return nzPixelFormat_Undefined; + } + #endif + + return m_impl->format; +} + +unsigned int NzTexture::GetHeight() const +{ + #if NAZARA_RENDERER_SAFE + if (!IsValid()) + { + NazaraError("Texture must be valid"); + return 0; + } + #endif + + return m_impl->height; +} + +nzImageType NzTexture::GetType() const +{ + #if NAZARA_RENDERER_SAFE + if (!IsValid()) + { + NazaraError("Texture must be valid"); + return nzImageType_2D; + } + #endif + + return m_impl->type; +} + +unsigned int NzTexture::GetWidth() const +{ + #if NAZARA_RENDERER_SAFE + if (!IsValid()) + { + NazaraError("Texture must be valid"); + return 0; + } + #endif + + return m_impl->width; +} + +nzTextureWrap NzTexture::GetWrapMode() const +{ + #if NAZARA_RENDERER_SAFE + if (!IsValid()) + { + NazaraError("Texture must be valid"); + return nzTextureWrap_Unknown; + } + #endif + + LockTexture(m_impl); + + GLint value; + glGetTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, &value); + + UnlockTexture(m_impl); + + GLenum wrapMode = static_cast(value); + switch (wrapMode) + { + case GL_CLAMP_TO_EDGE: + return nzTextureWrap_Clamp; + + case GL_REPEAT: + return nzTextureWrap_Repeat; + + default: + NazaraInternalError("OpenGL wrap mode not handled (0x" + NzString::Number(wrapMode, 16) + ')'); + return nzTextureWrap_Unknown; + } +} + +bool NzTexture::IsCompressed() const +{ + #if NAZARA_RENDERER_SAFE + if (!IsValid()) + { + NazaraError("Texture must be valid"); + return false; + } + #endif + + return NzPixelFormat::IsCompressed(m_impl->format); +} + +bool NzTexture::IsCubemap() const +{ + #if NAZARA_RENDERER_SAFE + if (!IsValid()) + { + NazaraError("Texture must be valid"); + return false; + } + #endif + + return m_impl->type == nzImageType_Cubemap; +} + +bool NzTexture::IsValid() const +{ + return m_impl != nullptr; +} + +bool NzTexture::LoadFromFile(const NzString& filePath, const NzImageParams& params) +{ + NzImage image; + if (!image.LoadFromFile(filePath, params)) + { + NazaraError("Failed to load image"); + return false; + } + + return LoadFromImage(image); +} + +bool NzTexture::LoadFromImage(const NzImage& image) +{ + #if NAZARA_RENDERER_SAFE + if (!image.IsValid()) + { + NazaraError("Image must be valid"); + return false; + } + #endif + + // Vive le Copy-On-Write + NzImage newImage(image); + + nzPixelFormat format = newImage.GetFormat(); + if (!IsFormatSupported(format)) + { + nzPixelFormat newFormat = (NzPixelFormat::HasAlpha(format)) ? nzPixelFormat_BGRA8 : nzPixelFormat_BGR8; + + NazaraWarning("Format not supported, trying to convert it to " + NzPixelFormat::ToString(newFormat) + "..."); + + if (NzPixelFormat::IsConversionSupported(format, newFormat)) + { + if (newImage.Convert(newFormat)) + { + NazaraWarning("Conversion succeed"); + format = newFormat; + } + else + { + NazaraError("Conversion failed"); + return false; + } + } + else + { + NazaraError("Conversion not supported"); + return false; + } + } + + nzImageType type = newImage.GetType(); + nzUInt8 levelCount = newImage.GetLevelCount(); + if (!Create(type, format, newImage.GetWidth(), newImage.GetHeight(), newImage.GetDepth(), levelCount, true)) + { + NazaraError("Failed to create texture"); + return false; + } + + for (nzUInt8 level = 0; level < levelCount; ++level) + { + if (!Update(newImage.GetConstPixels(level), level)) + { + NazaraError("Failed to update texture"); + Destroy(); + + return false; + } + } + + UnlockTexture(m_impl); + + return true; +} + +bool NzTexture::LoadFromMemory(const void* data, std::size_t size, const NzImageParams& params) +{ + NzImage image; + if (!image.LoadFromMemory(data, size, params)) + { + NazaraError("Failed to load image"); + return false; + } + + return LoadFromImage(image); +} + +bool NzTexture::LoadFromStream(NzInputStream& stream, const NzImageParams& params) +{ + NzImage image; + if (!image.LoadFromStream(stream, params)) + { + NazaraError("Failed to load image"); + return false; + } + + return LoadFromImage(image); +} + +bool NzTexture::Lock() +{ + #if NAZARA_RENDERER_SAFE + if (!IsValid()) + { + NazaraError("Texture must be valid"); + return false; + } + #endif + + LockTexture(m_impl); + + return true; +} + +bool NzTexture::SetAnisotropyLevel(unsigned int anistropyLevel) +{ + #if NAZARA_RENDERER_SAFE + if (!IsValid()) + { + NazaraError("Texture must be valid"); + return false; + } + + if (!NzOpenGL::IsSupported(NzOpenGL::AnisotropicFilter)) + { + NazaraError("Anisotropic filter not supported"); + return false; + } + #endif + + LockTexture(m_impl); + + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, static_cast(anistropyLevel)); + + UnlockTexture(m_impl); + + return true; +} + +bool NzTexture::SetFilterMode(nzTextureFilter filter) +{ + #if NAZARA_RENDERER_SAFE + if (!IsValid()) + { + NazaraError("Texture must be valid"); + return false; + } + + if (filter == nzTextureFilter_Trilinear && m_impl->levelCount == 1) + { + NazaraError("Trilinear filter set wihout mipmaps"); + return false; + } + #endif + + LockTexture(m_impl); + + GLenum target = openglTarget[m_impl->type]; + switch (filter) + { + case nzTextureFilter_Bilinear: + if (m_impl->levelCount > 1) + glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST); + else + glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + + glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + break; + + case nzTextureFilter_Nearest: + if (m_impl->levelCount > 1) + glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST); + else + glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + + glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + break; + + case nzTextureFilter_Trilinear: + glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + break; + + default: + NazaraError("Texture filter not handled (0x" + NzString::Number(filter, 16) + ')'); + } + + UnlockTexture(m_impl); + + return true; +} + +bool NzTexture::SetMipmapRange(nzUInt8 minLevel, nzUInt8 maxLevel) +{ + #if NAZARA_RENDERER_SAFE + if (!IsValid()) + { + NazaraError("Texture must be valid"); + return false; + } + + if (minLevel >= m_impl->levelCount) + { + NazaraError("Minimum level cannot be greater or equal than level count (" + NzString::Number(minLevel) + " >= " + NzString::Number(m_impl->levelCount) + ')'); + return false; + } + + if (maxLevel < minLevel) + { + NazaraError("Minimum level cannot be greater than maximum level (" + NzString::Number(minLevel) + " < " + NzString::Number(maxLevel) + ')'); + return false; + } + #endif + + LockTexture(m_impl); + glTexParameteri(openglTarget[m_impl->type], GL_TEXTURE_BASE_LEVEL, minLevel); + glTexParameteri(openglTarget[m_impl->type], GL_TEXTURE_MAX_LEVEL, std::min(m_impl->levelCount, maxLevel)); + UnlockTexture(m_impl); + + return true; +} + +bool NzTexture::SetWrapMode(nzTextureWrap wrap) +{ + #if NAZARA_RENDERER_SAFE + if (!IsValid()) + { + NazaraError("Texture must be valid"); + return false; + } + #endif + + GLenum wrapMode; + switch (wrap) + { + case nzTextureWrap_Clamp: + wrapMode = GL_CLAMP_TO_EDGE; + break; + + case nzTextureWrap_Repeat: + wrapMode = GL_REPEAT; + break; + + default: + NazaraError("Texture wrap mode not handled (0x" + NzString::Number(wrap, 16) + ')'); + return false; + } + + LockTexture(m_impl); + + GLenum target = openglTarget[m_impl->type]; + switch (m_impl->type) + { + // Notez l'absence de "break" ici + case nzImageType_3D: + glTexParameteri(target, GL_TEXTURE_WRAP_R, wrapMode); + case nzImageType_2D: + case nzImageType_Cubemap: + glTexParameteri(target, GL_TEXTURE_WRAP_T, wrapMode); + case nzImageType_1D: + glTexParameteri(target, GL_TEXTURE_WRAP_S, wrapMode); + break; + + default: + break; + } + + UnlockTexture(m_impl); + + return true; +} + +bool NzTexture::Update(const NzImage& image, nzUInt8 level) +{ + #if NAZARA_RENDERER_SAFE + if (!image.IsValid()) + { + NazaraError("Image must be valid"); + return false; + } + + if (image.GetFormat() != m_impl->format) + { + NazaraError("Image format does not match texture format"); + return false; + } + #endif + + return Update(image.GetConstPixels(level), level); +} + +bool NzTexture::Update(const NzImage& image, const NzRectui& rect, unsigned int z, nzUInt8 level) +{ + #if NAZARA_RENDERER_SAFE + if (!image.IsValid()) + { + NazaraError("Image must be valid"); + return false; + } + + if (image.GetFormat() != m_impl->format) + { + NazaraError("Image format does not match texture format"); + return false; + } + #endif + + return Update(image.GetConstPixels(level), rect, z, level); +} +/* +bool NzTexture::Update(const NzImage& image, const NzCubeui& cube, nzUInt8 level) +{ + #if NAZARA_RENDERER_SAFE + if (!image.IsValid()) + { + NazaraError("Image must be valid"); + return false; + } + + if (image.GetFormat() != m_impl->format) + { + NazaraError("Image format does not match texture format"); + return false; + } + #endif + + return Update(image.GetConstPixels(level), cube, level); +} +*/ +bool NzTexture::Update(const nzUInt8* pixels, nzUInt8 level) +{ + #if NAZARA_RENDERER_SAFE + if (!IsValid()) + { + NazaraError("Texture must be valid"); + return false; + } + + if (m_impl->type == nzImageType_Cubemap) + { + NazaraError("Update is not designed for cubemaps, use UpdateFace instead"); + return false; + } + + if (!pixels) + { + NazaraError("Invalid pixel source"); + return false; + } + + if (level >= m_impl->levelCount) + { + NazaraError("Level out of bounds (" + NzString::Number(level) + " >= " + NzString::Number(m_impl->levelCount) + ')'); + return false; + } + #endif + + OpenGLFormat format; + if (!GetOpenGLFormat(m_impl->format, &format)) + { + NazaraError("Failed to get OpenGL format"); + return false; + } + + LockTexture(m_impl); + + switch (m_impl->type) + { + case nzImageType_1D: + glTexSubImage1D(GL_TEXTURE_1D, level, 0, std::max(m_impl->width >> level, 1U), format.dataFormat, format.dataType, pixels); + break; + + case nzImageType_2D: + glTexSubImage2D(GL_TEXTURE_2D, level, 0, 0, std::max(m_impl->width >> level, 1U), std::max(m_impl->height >> level, 1U), format.dataFormat, format.dataType, pixels); + break; + + case nzImageType_3D: + glTexSubImage3D(GL_TEXTURE_3D, level, 0, 0, 0, std::max(m_impl->width >> level, 1U), std::max(m_impl->height >> level, 1U), std::max(m_impl->depth >> level, 1U), format.dataFormat, format.dataType, pixels); + break; + + default: + NazaraInternalError("Image type not handled (0x" + NzString::Number(m_impl->type, 16) + ')'); + } + + UnlockTexture(m_impl); + + return true; +} + +bool NzTexture::Update(const nzUInt8* pixels, const NzRectui& rect, unsigned int z, nzUInt8 level) +{ + #if NAZARA_RENDERER_SAFE + if (!IsValid()) + { + NazaraError("Texture must be valid"); + return false; + } + + if (m_impl->type == nzImageType_Cubemap) + { + NazaraError("Update is not designed for cubemaps, use UpdateFace instead"); + return false; + } + + if (!pixels) + { + NazaraError("Invalid pixel source"); + return false; + } + + if (!rect.IsValid()) + { + NazaraError("Invalid rectangle"); + return false; + } + + if (rect.x+rect.width > std::max(m_impl->width >> level, 1U) || rect.y+rect.height > std::max(m_impl->height >> level, 1U)) + { + NazaraError("Rectangle dimensions are out of bounds"); + return false; + } + + if (z >= std::max(m_impl->depth >> level, 1U)) + { + NazaraError("Z value exceeds depth (" + NzString::Number(z) + " >= (" + NzString::Number(m_impl->depth) + ')'); + return false; + } + + if (level >= m_impl->levelCount) + { + NazaraError("Level out of bounds (" + NzString::Number(level) + " >= " + NzString::Number(m_impl->levelCount) + ')'); + return false; + } + #endif + + OpenGLFormat format; + if (!GetOpenGLFormat(m_impl->format, &format)) + { + NazaraError("Failed to get OpenGL format"); + return false; + } + + LockTexture(m_impl); + + switch (m_impl->type) + { + case nzImageType_1D: + glTexSubImage1D(GL_TEXTURE_1D, level, rect.x, rect.width, format.dataFormat, format.dataType, pixels); + break; + + case nzImageType_2D: + glTexSubImage2D(GL_TEXTURE_2D, level, rect.x, rect.y, rect.width, rect.height, format.dataFormat, format.dataType, pixels); + break; + + case nzImageType_3D: + glTexSubImage3D(GL_TEXTURE_3D, level, rect.x, rect.y, z, rect.width, rect.height, 1, format.dataFormat, format.dataType, pixels); + break; + + default: + NazaraInternalError("Image type not handled (0x" + NzString::Number(m_impl->type, 16) + ')'); + } + + UnlockTexture(m_impl); + + return true; +} +/* +bool NzTexture::Update(const nzUInt8* pixels, const NzCubeui& cube, nzUInt8 level = 0) +{ + #if NAZARA_RENDERER_SAFE + if (!IsValid()) + { + NazaraError("Texture must be valid"); + return false; + } + + if (m_impl->type == nzImageType_Cubemap) + { + NazaraError("Update is not designed for cubemaps, use UpdateFace instead"); + return false; + } + + if (!pixels) + { + NazaraError("Invalid pixel source"); + return false; + } + + if (!cube.IsValid()) + { + NazaraError("Invalid rectangle"); + return false; + } + + if (cube.x+cube.width > std::max(m_impl->width >> level, 1U) || + cube.y+cube.height > std::max(m_impl->height >> level, 1U) || + cube.z+cube.depth > std::max(m_impl->depth >> level, 1U)) + { + NazaraError("Cube dimensions are out of bounds"); + return false; + } + + if (level >= m_impl->levelCount) + { + NazaraError("Level out of bounds (" + NzString::Number(level) + " >= " + NzString::Number(m_impl->levelCount) + ')'); + return false; + } + #endif + + OpenGLFormat format; + if (!GetOpenGLFormat(m_impl->format, &format)) + { + NazaraError("Failed to get OpenGL format"); + return false; + } + + LockTexture(m_impl); + + switch (m_impl->type) + { + case nzImageType_1D: + glTexSubImage1D(GL_TEXTURE_1D, level, cube.x, cube.width, format->dataFormat, format->dataType, pixels); + break; + + case nzImageType_2D: + glTexSubImage1D(GL_TEXTURE_2D, level, cube.x, cube.y, cube.width, cube.height, format->dataFormat, format->dataType, pixels); + break; + + case nzImageType_3D: + glTexSubImage1D(GL_TEXTURE_3D, level, cube.x, cube.y, cube.z, cube.width, cube.height, cube.depth, format->dataFormat, format->dataType, pixels); + break; + + default: + NazaraInternalError("Image type not handled (0x" + NzString::Number(m_impl->type, 16) + ')'); + } + + UnlockTexture(m_impl); + + return true; +} +*/ +bool NzTexture::UpdateFace(nzCubemapFace face, const NzImage& image, nzUInt8 level) +{ + #if NAZARA_RENDERER_SAFE + if (!image.IsValid()) + { + NazaraError("Image must be valid"); + return false; + } + + if (image.GetFormat() != m_impl->format) + { + NazaraError("Image format does not match texture format"); + return false; + } + #endif + + return UpdateFace(face, image.GetConstPixels(level), level); +} + +bool NzTexture::UpdateFace(nzCubemapFace face, const NzImage& image, const NzRectui& rect, nzUInt8 level) +{ + #if NAZARA_RENDERER_SAFE + if (!image.IsValid()) + { + NazaraError("Image must be valid"); + return false; + } + + if (image.GetFormat() != m_impl->format) + { + NazaraError("Image format does not match texture format"); + return false; + } + #endif + + return UpdateFace(face, image.GetConstPixels(level), rect, level); +} + +bool NzTexture::UpdateFace(nzCubemapFace face, const nzUInt8* pixels, nzUInt8 level) +{ + #if NAZARA_RENDERER_SAFE + if (!IsValid()) + { + NazaraError("Texture must be valid"); + return false; + } + + if (m_impl->type != nzImageType_Cubemap) + { + NazaraError("UpdateFace is designed for cubemaps, use Update instead"); + return false; + } + + if (!pixels) + { + NazaraError("Invalid pixel source"); + return false; + } + + if (level >= m_impl->levelCount) + { + NazaraError("Level out of bounds (" + NzString::Number(level) + " >= " + NzString::Number(m_impl->levelCount) + ')'); + return false; + } + #endif + + OpenGLFormat format; + if (!GetOpenGLFormat(m_impl->format, &format)) + { + NazaraError("Failed to get OpenGL format"); + return false; + } + + LockTexture(m_impl); + + glTexSubImage2D(cubemapFace[face], level, 0, 0, m_impl->width, m_impl->height, format.dataFormat, format.dataType, pixels); + + UnlockTexture(m_impl); + + return true; +} + +bool NzTexture::UpdateFace(nzCubemapFace face, const nzUInt8* pixels, const NzRectui& rect, nzUInt8 level) +{ + #if NAZARA_RENDERER_SAFE + if (!IsValid()) + { + NazaraError("Texture must be valid"); + return false; + } + + if (m_impl->type != nzImageType_Cubemap) + { + NazaraError("UpdateFace is designed for cubemaps, use Update instead"); + return false; + } + + if (!pixels) + { + NazaraError("Invalid pixel source"); + return false; + } + + if (!rect.IsValid()) + { + NazaraError("Invalid rectangle"); + return false; + } + + if (rect.x+rect.width > std::max(m_impl->width >> level, 1U) || rect.y+rect.height > std::max(m_impl->height >> level, 1U)) + { + NazaraError("Rectangle dimensions are out of bounds"); + return false; + } + + if (level >= m_impl->levelCount) + { + NazaraError("Level out of bounds (" + NzString::Number(level) + " >= " + NzString::Number(m_impl->levelCount) + ')'); + return false; + } + #endif + + OpenGLFormat format; + if (!GetOpenGLFormat(m_impl->format, &format)) + { + NazaraError("Failed to get OpenGL format"); + return false; + } + + LockTexture(m_impl); + + glTexSubImage2D(cubemapFace[face], level, rect.x, rect.y, rect.width, rect.height, format.dataFormat, format.dataType, pixels); + + UnlockTexture(m_impl); + + return true; +} + +void NzTexture::Unlock() +{ + #if NAZARA_RENDERER_SAFE + if (!IsValid()) + { + NazaraError("Texture must be valid"); + return; + } + #endif + + UnlockTexture(m_impl); +} + +unsigned int NzTexture::GetValidSize(unsigned int size) +{ + if (NazaraRenderer->HasCapability(nzRendererCap_TextureNPOT)) + return size; + else + { + unsigned int pot = 1; + while (pot < size) + pot <<= 1; + + return pot; + } +} + +bool NzTexture::IsFormatSupported(nzPixelFormat format) +{ + #if NAZARA_RENDERER_SAFE + if (!NzPixelFormat::IsValid(format)) + { + NazaraError("Invalid pixel format"); + return nzPixelFormat_Undefined; + } + #endif + + switch (format) + { + // Formats de base + case nzPixelFormat_BGR8: + case nzPixelFormat_BGRA8: + case nzPixelFormat_RGB8: + case nzPixelFormat_RGBA8: + return true; + + // Packed formats supportés depuis OpenGL 1.2 + case nzPixelFormat_RGB5A1: + case nzPixelFormat_RGBA4: + return true; + + // Dépréciés depuis OpenGL 3 (FIXME: Il doit bien exister des remplaçants ..) + case nzPixelFormat_L8: + case nzPixelFormat_LA8: + return false; + + case nzPixelFormat_DXT1: + case nzPixelFormat_DXT3: + case nzPixelFormat_DXT5: + { + static const bool supported = NzOpenGL::IsSupported(NzOpenGL::TextureCompression_s3tc); + return supported; + } + + default: + return false; + } +} + +bool NzTexture::IsTypeSupported(nzImageType type) +{ + switch (type) + { + case nzImageType_1D: + case nzImageType_2D: + case nzImageType_3D: + case nzImageType_Cubemap: + return true; // Tous supportés nativement dans OpenGL 2 + + default: + return false; + } +} diff --git a/src/Nazara/Utility/Image.cpp b/src/Nazara/Utility/Image.cpp index 5e76b5ce3..b9cb4d8dd 100644 --- a/src/Nazara/Utility/Image.cpp +++ b/src/Nazara/Utility/Image.cpp @@ -160,6 +160,9 @@ bool NzImage::Create(nzImageType type, nzPixelFormat format, unsigned int width, { ReleaseImage(); + if (width == 0 || height == 0 || depth == 0) + return true; + #if NAZARA_UTILITY_SAFE if (!NzPixelFormat::IsValid(format)) { @@ -191,6 +194,9 @@ bool NzImage::Create(nzImageType type, nzPixelFormat format, unsigned int width, } break; + case nzImageType_3D: + break; + case nzImageType_Cubemap: if (depth > 1) { @@ -206,13 +212,11 @@ bool NzImage::Create(nzImageType type, nzPixelFormat format, unsigned int width, break; default: - break; + NazaraInternalError("Image type not handled"); + return false; } #endif - if (width == 0 || height == 0 || depth == 0) - return true; - levelCount = std::min(levelCount, GetMaxLevel(width, height, depth)); nzUInt8** levels = new nzUInt8*[levelCount]; @@ -279,7 +283,7 @@ unsigned int NzImage::GetDepth(nzUInt8 level) const } #endif - return std::max(m_sharedImage->depth/(1 << level), 1U); + return std::max(m_sharedImage->depth >> level, 1U); } nzPixelFormat NzImage::GetFormat() const @@ -297,7 +301,7 @@ unsigned int NzImage::GetHeight(nzUInt8 level) const } #endif - return std::max(m_sharedImage->height/(1 << level), 1U); + return std::max(m_sharedImage->height >> level, 1U); } nzUInt8 NzImage::GetLevelCount() const @@ -446,9 +450,9 @@ unsigned int NzImage::GetSize(nzUInt8 level) const } #endif - return (std::max(m_sharedImage->width/(1 << level), 1U)) * - (std::max(m_sharedImage->height/(1 << level), 1U)) * - ((m_sharedImage->type == nzImageType_Cubemap) ? 6 : std::min(m_sharedImage->depth/(1 << level), 1U)) * + return (std::max(m_sharedImage->width >> level, 1U)) * + (std::max(m_sharedImage->height >> level, 1U)) * + ((m_sharedImage->type == nzImageType_Cubemap) ? 6 : std::max(m_sharedImage->depth >> level, 1U)) * NzPixelFormat::GetBPP(m_sharedImage->format); } @@ -467,7 +471,7 @@ unsigned int NzImage::GetWidth(nzUInt8 level) const } #endif - return std::max(m_sharedImage->width/(1 << level), 1U); + return std::max(m_sharedImage->width >> level, 1U); } bool NzImage::IsCompressed() const @@ -523,22 +527,23 @@ bool NzImage::SetLevelCount(nzUInt8 levelCount) EnsureOwnership(); + nzUInt8 oldLevelCount = m_sharedImage->levelCount; + nzUInt8 maxLevelCount = std::max(levelCount, oldLevelCount); + m_sharedImage->levelCount = levelCount; // Pour faire fonctionner GetSize + nzUInt8** pixels = new nzUInt8*[levelCount]; - for (unsigned int i = 0; i < levelCount; ++i) + for (unsigned int i = 0; i < maxLevelCount; ++i) { - if (i < m_sharedImage->levelCount) + if (i < oldLevelCount) pixels[i] = m_sharedImage->pixels[i]; + else if (i < levelCount) + pixels[i] = new nzUInt8[GetSize(i)]; else - { - unsigned int size = GetSize(i); - pixels[i] = new nzUInt8[size]; - std::memcpy(pixels[i], m_sharedImage->pixels[i], size); - } + delete[] m_sharedImage->pixels[i]; } delete[] m_sharedImage->pixels; - m_sharedImage->levelCount = levelCount; m_sharedImage->pixels = pixels; return true; @@ -702,23 +707,23 @@ bool NzImage::Update(const nzUInt8* pixels, const NzRectui& rect, unsigned int z return false; } - if (level >= m_sharedImage->levelCount) - { - NazaraError("Level out of bounds (" + NzString::Number(level) + " >= " + NzString::Number(m_sharedImage->levelCount) + ')'); - return false; - } - - if (rect.x+rect.width > (m_sharedImage->width << level) || rect.y+rect.height > (m_sharedImage->height) << level) + if (rect.x+rect.width > std::max(m_sharedImage->width >> level, 1U) || rect.y+rect.height > std::max(m_sharedImage->height >> level, 1U)) { NazaraError("Rectangle dimensions are out of bounds"); return false; } - if (z >= (m_sharedImage->depth << level)) + if (z >= std::max(m_sharedImage->depth >> level, 1U)) { NazaraError("Z value exceeds depth (" + NzString::Number(z) + " >= (" + NzString::Number(m_sharedImage->depth) + ')'); return false; } + + if (level >= m_sharedImage->levelCount) + { + NazaraError("Level out of bounds (" + NzString::Number(level) + " >= " + NzString::Number(m_sharedImage->levelCount) + ')'); + return false; + } #endif EnsureOwnership(); @@ -749,7 +754,7 @@ bool NzImage::UpdateFace(nzCubemapFace face, const nzUInt8* pixels, nzUInt8 leve if (m_sharedImage->type != nzImageType_Cubemap) { - NazaraError("Update is designed for cubemaps, use Update instead"); + NazaraError("UpdateFace is designed for cubemaps, use Update instead"); return false; } @@ -785,7 +790,7 @@ bool NzImage::UpdateFace(nzCubemapFace face, const nzUInt8* pixels, const NzRect if (m_sharedImage->type != nzImageType_Cubemap) { - NazaraError("Update is designed for cubemaps, use Update instead"); + NazaraError("UpdateFace is designed for cubemaps, use Update instead"); return false; } @@ -801,15 +806,15 @@ bool NzImage::UpdateFace(nzCubemapFace face, const nzUInt8* pixels, const NzRect return false; } - if (level >= m_sharedImage->levelCount) + if (rect.x+rect.width > std::max(m_sharedImage->width >> level, 1U) || rect.y+rect.height > std::max(m_sharedImage->height >> level, 1U)) { - NazaraError("Level out of bounds (" + NzString::Number(level) + " >= " + NzString::Number(m_sharedImage->levelCount) + ')'); + NazaraError("Rectangle dimensions are out of bounds"); return false; } - if (rect.x+rect.width > (m_sharedImage->width << level) || rect.y+rect.height > (m_sharedImage->height) << level) + if (level >= m_sharedImage->levelCount) { - NazaraError("Rectangle dimensions are out of bounds"); + NazaraError("Level out of bounds (" + NzString::Number(level) + " >= " + NzString::Number(m_sharedImage->levelCount) + ')'); return false; } #endif @@ -861,7 +866,7 @@ nzUInt8 NzImage::GetMaxLevel(unsigned int width, unsigned int height, unsigned i unsigned int heightLevel = std::log(height)/l2; unsigned int depthLevel = std::log(depth)/l2; - return std::max(std::max(widthLevel, heightLevel), depthLevel); + return std::max(std::max(std::max(widthLevel, heightLevel), depthLevel), 1U); } void NzImage::RegisterFileLoader(const NzString& extensions, LoadFileFunction loadFile) diff --git a/src/Nazara/Utility/Loaders/PCX.cpp b/src/Nazara/Utility/Loaders/PCX.cpp index 0eab2c186..9460ef396 100644 --- a/src/Nazara/Utility/Loaders/PCX.cpp +++ b/src/Nazara/Utility/Loaders/PCX.cpp @@ -93,7 +93,7 @@ namespace unsigned int width = header.xmax - header.xmin+1; unsigned int height = header.ymax - header.ymin+1; - if (!resource->Create(nzImageType_2D, nzPixelFormat_RGB8, width, height)) + if (!resource->Create(nzImageType_2D, nzPixelFormat_RGB8, width, height, 1, (parameters.levelCount > 0) ? parameters.levelCount : 1)) { NazaraError("Failed to create image"); return false; diff --git a/src/Nazara/Utility/Loaders/STB.cpp b/src/Nazara/Utility/Loaders/STB.cpp index e6b42ba6f..ff0e3fbea 100644 --- a/src/Nazara/Utility/Loaders/STB.cpp +++ b/src/Nazara/Utility/Loaders/STB.cpp @@ -111,7 +111,7 @@ namespace if (format == nzPixelFormat_Undefined) format = formats[bpp-1]; - if (!resource->Create(nzImageType_2D, format, width, height)) + if (!resource->Create(nzImageType_2D, format, width, height, 1, (parameters.levelCount > 0) ? parameters.levelCount : 1)) { NazaraError("Failed to create image"); stbi_image_free(ptr);