// Copyright (C) 2014 Jérôme Leclercq // This file is part of the "Nazara Engine - Renderer module" // For conditions of distribution and use, see copyright notice in Config.hpp #include #include #include #include #include #include #include #include #include namespace { std::unordered_map s_samplers; nzUInt8 s_maxAnisotropyLevel; bool s_useAnisotropicFilter; } NzTextureSampler::NzTextureSampler() : m_filterMode(nzSamplerFilter_Default), m_wrapMode(nzSamplerWrap_Default), m_anisotropicLevel(0), m_mipmaps(true), m_samplerId(0) { } nzUInt8 NzTextureSampler::GetAnisotropicLevel() const { return m_anisotropicLevel; } nzSamplerFilter NzTextureSampler::GetFilterMode() const { return m_filterMode; } nzSamplerWrap NzTextureSampler::GetWrapMode() const { return m_wrapMode; } void NzTextureSampler::SetAnisotropyLevel(nzUInt8 anisotropyLevel) { #ifdef NAZARA_DEBUG if (!NzRenderer::IsInitialized()) { NazaraError("Renderer module must be initialized"); return; } #endif if (m_anisotropicLevel != anisotropyLevel) { if (anisotropyLevel > s_maxAnisotropyLevel) { NazaraWarning("Anisotropy level is over maximum anisotropy level (" + NzString::Number(anisotropyLevel) + " > " + NzString::Number(s_maxAnisotropyLevel)); anisotropyLevel = s_maxAnisotropyLevel; } m_anisotropicLevel = anisotropyLevel; m_samplerId = 0; } } void NzTextureSampler::SetFilterMode(nzSamplerFilter filterMode) { if (m_filterMode != filterMode) { m_filterMode = filterMode; m_samplerId = 0; } } void NzTextureSampler::SetWrapMode(nzSamplerWrap wrapMode) { if (m_wrapMode != wrapMode) { m_wrapMode = wrapMode; m_samplerId = 0; } } nzUInt8 NzTextureSampler::GetDefaultAnisotropicLevel() { return s_defaultAnisotropyLevel; } nzSamplerFilter NzTextureSampler::GetDefaultFilterMode() { return s_defaultFilterMode; } nzSamplerWrap NzTextureSampler::GetDefaultWrapMode() { return s_defaultWrapMode; } void NzTextureSampler::SetDefaultAnisotropyLevel(nzUInt8 anisotropyLevel) { #if NAZARA_RENDERER_SAFE if (anisotropyLevel == 0) { NazaraError("Default anisotropy level mode cannot be set to default value (0)"); return; } #endif #ifdef NAZARA_DEBUG if (!NzRenderer::IsInitialized()) { NazaraError("Renderer module must be initialized"); return; } #endif if (anisotropyLevel > s_maxAnisotropyLevel) { NazaraWarning("Anisotropy level is over maximum anisotropy level (" + NzString::Number(anisotropyLevel) + " > " + NzString::Number(s_maxAnisotropyLevel)); anisotropyLevel = s_maxAnisotropyLevel; } s_defaultAnisotropyLevel = anisotropyLevel; if (s_useAnisotropicFilter) { for (const std::pair& pair : s_samplers) { if (((pair.first >> 5) & 0xFF) == 0) glSamplerParameterf(pair.second, GL_TEXTURE_MAX_ANISOTROPY_EXT, static_cast(anisotropyLevel)); } } } void NzTextureSampler::SetDefaultFilterMode(nzSamplerFilter filterMode) { #if NAZARA_RENDERER_SAFE if (filterMode == nzSamplerFilter_Default) { NazaraError("Default filter mode cannot be set to default enum value (nzSamplerFilter_Default)"); return; } #endif s_defaultFilterMode = filterMode; for (const std::pair& pair : s_samplers) { if (((pair.first >> 1) & 0x3) == nzSamplerFilter_Default) { bool mipmaps = pair.first & 0x1; switch (filterMode) { case nzSamplerFilter_Bilinear: if (mipmaps) glSamplerParameteri(pair.second, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST); else glSamplerParameteri(pair.second, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glSamplerParameteri(pair.second, GL_TEXTURE_MAG_FILTER, GL_LINEAR); break; case nzSamplerFilter_Nearest: if (mipmaps) glSamplerParameteri(pair.second, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST); else glSamplerParameteri(pair.second, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glSamplerParameteri(pair.second, GL_TEXTURE_MAG_FILTER, GL_NEAREST); break; case nzSamplerFilter_Trilinear: if (mipmaps) glSamplerParameteri(pair.second, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); else glSamplerParameteri(pair.second, GL_TEXTURE_MIN_FILTER, GL_LINEAR); // Filtrage bilinéaire glSamplerParameteri(pair.second, GL_TEXTURE_MAG_FILTER, GL_LINEAR); break; default: NazaraError("Texture filter not handled (0x" + NzString::Number(filterMode, 16) + ')'); break; } } } } void NzTextureSampler::SetDefaultWrapMode(nzSamplerWrap wrapMode) { #if NAZARA_RENDERER_SAFE if (wrapMode == nzSamplerWrap_Default) { NazaraError("Default wrap mode cannot be set to default enum value (nzSamplerWrap_Default)"); return; } #endif s_defaultWrapMode = wrapMode; GLenum wrapEnum = NzOpenGL::SamplerWrapMode[wrapMode]; for (const std::pair& pair : s_samplers) { if (((pair.first >> 3) & 0x3) == nzSamplerWrap_Default) { glSamplerParameteri(pair.second, GL_TEXTURE_WRAP_R, wrapEnum); glSamplerParameteri(pair.second, GL_TEXTURE_WRAP_T, wrapEnum); glSamplerParameteri(pair.second, GL_TEXTURE_WRAP_S, wrapEnum); } } } void NzTextureSampler::Apply(const NzTexture* texture) const { nzImageType type = texture->GetType(); GLenum target = NzOpenGL::TextureTarget[type]; NzOpenGL::BindTexture(type, texture->GetOpenGLID()); if (s_useAnisotropicFilter) { nzUInt8 anisotropyLevel = (m_anisotropicLevel == 0) ? s_defaultAnisotropyLevel : m_anisotropicLevel; glTexParameterf(target, GL_TEXTURE_MAX_ANISOTROPY_EXT, static_cast(anisotropyLevel)); } nzSamplerFilter filterMode = (m_filterMode == nzSamplerFilter_Default) ? s_defaultFilterMode : m_filterMode; switch (filterMode) { case nzSamplerFilter_Bilinear: if (m_mipmaps) 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 nzSamplerFilter_Nearest: if (m_mipmaps) 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 nzSamplerFilter_Trilinear: if (m_mipmaps) glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); else glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_LINEAR); // Filtrage bilinéaire glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_LINEAR); break; default: NazaraError("Texture filter not handled (0x" + NzString::Number(filterMode, 16) + ')'); break; } GLenum wrapMode = NzOpenGL::SamplerWrapMode[(m_wrapMode == nzSamplerWrap_Default) ? s_defaultWrapMode : m_wrapMode]; switch (type) { // Notez l'absence de "break" ici case nzImageType_3D: glTexParameteri(target, GL_TEXTURE_WRAP_R, wrapMode); case nzImageType_2D: case nzImageType_2D_Array: case nzImageType_Cubemap: glTexParameteri(target, GL_TEXTURE_WRAP_T, wrapMode); case nzImageType_1D: case nzImageType_1D_Array: glTexParameteri(target, GL_TEXTURE_WRAP_S, wrapMode); break; } } void NzTextureSampler::Bind(unsigned int unit) const { static_assert(nzSamplerFilter_Max < 0x4, "Maximum sampler filter mode takes more than 2 bits"); static_assert(nzSamplerWrap_Max < 0x4, "Maximum sampler wrap mode takes more than 2 bits"); if (!m_samplerId) UpdateSamplerId(); NzOpenGL::BindSampler(unit, m_samplerId); } unsigned int NzTextureSampler::GetOpenGLID() const { if (!m_samplerId) UpdateSamplerId(); return m_samplerId; } void NzTextureSampler::UpdateSamplerId() const { nzUInt32 key = (m_mipmaps << 0) | // 1 bit (m_filterMode << 1) | // 2 bits (m_wrapMode << 3) | // 2 bits (m_anisotropicLevel << 5); // 8 bits auto it = s_samplers.find(key); if (it == s_samplers.end()) { GLuint sampler; glGenSamplers(1, &sampler); if (s_useAnisotropicFilter) { nzUInt8 anisotropyLevel = (m_anisotropicLevel == 0) ? s_defaultAnisotropyLevel : m_anisotropicLevel; glSamplerParameterf(sampler, GL_TEXTURE_MAX_ANISOTROPY_EXT, static_cast(anisotropyLevel)); } nzSamplerFilter filterMode = (m_filterMode == nzSamplerFilter_Default) ? s_defaultFilterMode : m_filterMode; switch (filterMode) { case nzSamplerFilter_Bilinear: glSamplerParameteri(sampler, GL_TEXTURE_MIN_FILTER, (m_mipmaps) ? GL_LINEAR_MIPMAP_NEAREST : GL_LINEAR); glSamplerParameteri(sampler, GL_TEXTURE_MAG_FILTER, GL_LINEAR); break; case nzSamplerFilter_Nearest: glSamplerParameteri(sampler, GL_TEXTURE_MIN_FILTER, (m_mipmaps) ? GL_NEAREST_MIPMAP_NEAREST : GL_NEAREST); glSamplerParameteri(sampler, GL_TEXTURE_MAG_FILTER, GL_NEAREST); break; case nzSamplerFilter_Trilinear: // Équivalent au filtrage bilinéaire si les mipmaps sont absentes glSamplerParameteri(sampler, GL_TEXTURE_MIN_FILTER, (m_mipmaps) ? GL_LINEAR_MIPMAP_LINEAR : GL_LINEAR); glSamplerParameteri(sampler, GL_TEXTURE_MAG_FILTER, GL_LINEAR); break; default: NazaraError("Texture filter not handled (0x" + NzString::Number(filterMode, 16) + ')'); break; } GLenum wrapMode = NzOpenGL::SamplerWrapMode[(m_wrapMode == nzSamplerWrap_Default) ? s_defaultWrapMode : m_wrapMode]; glSamplerParameteri(sampler, GL_TEXTURE_WRAP_R, wrapMode); glSamplerParameteri(sampler, GL_TEXTURE_WRAP_T, wrapMode); glSamplerParameteri(sampler, GL_TEXTURE_WRAP_S, wrapMode); s_samplers[key] = sampler; m_samplerId = sampler; } else m_samplerId = it->second; } bool NzTextureSampler::UseMipmaps(bool mipmaps) { if (m_mipmaps != mipmaps) { m_mipmaps = mipmaps; m_samplerId = 0; return true; // Renvoie true si la valeur a été changée (Donc s'il faut réappliquer le sampler) } else return false; } bool NzTextureSampler::Initialize() { s_maxAnisotropyLevel = NzRenderer::GetMaxAnisotropyLevel(); s_useAnisotropicFilter = NzOpenGL::IsSupported(nzOpenGLExtension_AnisotropicFilter); return true; } void NzTextureSampler::Uninitialize() { if (!s_samplers.empty()) { NzContext::EnsureContext(); for (const std::pair& pair : s_samplers) NzOpenGL::DeleteSampler(pair.second); s_samplers.clear(); } } nzUInt8 NzTextureSampler::s_defaultAnisotropyLevel = 1; nzSamplerFilter NzTextureSampler::s_defaultFilterMode = nzSamplerFilter_Trilinear; nzSamplerWrap NzTextureSampler::s_defaultWrapMode = nzSamplerWrap_Repeat;