diff --git a/MobileGL/MG_Backend/BackendObject.cpp b/MobileGL/MG_Backend/BackendObject.cpp index 33840df7..b46dc5b7 100644 --- a/MobileGL/MG_Backend/BackendObject.cpp +++ b/MobileGL/MG_Backend/BackendObject.cpp @@ -19,6 +19,36 @@ namespace MobileGL::MG_Backend { } } // namespace + void FormatCapabilityCache::Clear() { + for (auto& row : FullCaps) { + row.fill(FormatCapabilityFlags{}); + } + for (auto& row : CaveatCaps) { + row.fill(FormatCapabilityFlags{}); + } + for (auto& row : SampleCounts) { + for (auto& counts : row) { + counts.clear(); + } + } + } + + Bool HasFormatCapability(FormatCapabilityFlags caps, FormatCapability capability) { + return static_cast(caps & capability); + } + + SizeT GetFormatCapabilityTargetIndex(TextureTarget target) { + if (target == TextureTarget::Unknown || static_cast(target) < 0 || + static_cast(target) >= kFormatCapabilityTextureTargetCount) { + return kFormatCapabilityTargetCount; + } + return static_cast(target); + } + + SizeT GetRenderbufferFormatCapabilityTargetIndex() { + return kFormatCapabilityRenderbufferTargetIndex; + } + Bool BackendObject::InitializeEGLDisplay(EGLDisplay dpy, EGLint* major, EGLint* minor) { const std::lock_guard lock(m_eglStateMutex); if (dpy == EGL_NO_DISPLAY) { @@ -177,6 +207,14 @@ namespace MobileGL::MG_Backend { m_windowHandle = handle; } + const FormatCapabilityCache& BackendObject::GetFormatCapabilities() const { + return m_formatCapabilities; + } + + FormatCapabilityCache& BackendObject::MutableFormatCapabilities() { + return m_formatCapabilities; + } + Bool BackendObject::InitPbufferSurface(EGLint width, EGLint height) { (void)width; (void)height; diff --git a/MobileGL/MG_Backend/BackendObject.h b/MobileGL/MG_Backend/BackendObject.h index 430c4a69..ed1d5f50 100644 --- a/MobileGL/MG_Backend/BackendObject.h +++ b/MobileGL/MG_Backend/BackendObject.h @@ -24,6 +24,53 @@ namespace MobileGL { }; namespace MG_Backend { + enum class FormatCapability : Uint64 { + Creatable = 1ull << 0, + + Sampled = 1ull << 1, + LinearFilter = 1ull << 2, + GenerateMipmap = 1ull << 3, + TextureGather = 1ull << 4, + TextureShadow = 1ull << 5, + + FramebufferRenderable = 1ull << 6, + FramebufferLayered = 1ull << 7, + MultisampleTexture = 1ull << 8, + MultisampleRenderbuffer = 1ull << 9, + + ColorAttachment = 1ull << 10, + DepthAttachment = 1ull << 11, + StencilAttachment = 1ull << 12, + + TextureBuffer = 1ull << 13 + }; + + using FormatCapabilityFlags = Flags; + + inline constexpr SizeT kFormatCapabilityTextureTargetCount = + static_cast(TextureTarget::TextureTargetCount); + inline constexpr SizeT kFormatCapabilityRenderbufferTargetIndex = kFormatCapabilityTextureTargetCount; + inline constexpr SizeT kFormatCapabilityTargetCount = kFormatCapabilityTextureTargetCount + 1; + inline constexpr SizeT kFormatCapabilityFormatCount = + static_cast(TextureInternalFormat::TextureInternalFormatCount); + + using FormatCapabilityTable = + Array, kFormatCapabilityTargetCount>; + using FormatSampleCountTable = + Array, kFormatCapabilityFormatCount>, kFormatCapabilityTargetCount>; + + struct FormatCapabilityCache { + FormatCapabilityTable FullCaps{}; + FormatCapabilityTable CaveatCaps{}; + FormatSampleCountTable SampleCounts{}; + + void Clear(); + }; + + Bool HasFormatCapability(FormatCapabilityFlags caps, FormatCapability capability); + SizeT GetFormatCapabilityTargetIndex(TextureTarget target); + SizeT GetRenderbufferFormatCapabilityTargetIndex(); + struct GLFunctionsTable { void (*DrawArrays)(GLenum mode, GLint first, GLsizei count); void (*DrawElements)(GLenum mode, GLsizei count, GLenum type, const void* indices); @@ -206,6 +253,7 @@ namespace MobileGL { virtual String GetBackendAPIVersionString() const = 0; virtual const GlobalBackendFunctionsTable& GetBackendFunctions() const = 0; virtual const DynamicBackendParameters& GetDynamicParameters() const = 0; + const FormatCapabilityCache& GetFormatCapabilities() const; virtual BackendType GetBackendType() const = 0; protected: @@ -217,8 +265,10 @@ namespace MobileGL { void ResetEGLRuntimeState(); virtual Bool InitPbufferSurface(EGLint width, EGLint height); + FormatCapabilityCache& MutableFormatCapabilities(); mutable std::recursive_mutex m_eglStateMutex; + FormatCapabilityCache m_formatCapabilities; WindowHandle m_windowHandle; EGLDisplay m_eglDisplay = EGL_NO_DISPLAY; Bool m_eglDisplayInitialized = false; diff --git a/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp b/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp index 473cc6d1..4eb63fe4 100644 --- a/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp +++ b/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp @@ -9,7 +9,11 @@ #include "BackendObject_DirectGLES.h" #include "MG_Backend/BackendObject.h" #include +#include #include +#include +#include +#include #include namespace MobileGL::MG_Backend::DirectGLES { @@ -17,6 +21,379 @@ namespace MobileGL::MG_Backend::DirectGLES { Bool IsReleaseCurrentRequest(EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx) { return dpy == EGL_NO_DISPLAY && draw == EGL_NO_SURFACE && read == EGL_NO_SURFACE && ctx == EGL_NO_CONTEXT; } + + void ClearGLErrors(const MG_External::GLESFunctionsTable& gl) { + if (!gl.glGetError) return; + while (gl.glGetError() != GL_NO_ERROR) { + } + } + + Bool CheckNoGLError(const MG_External::GLESFunctionsTable& gl) { + return !gl.glGetError || gl.glGetError() == GL_NO_ERROR; + } + + GLenum GetTextureBindingQuery(TextureTarget target) { + switch (target) { + case TextureTarget::Texture2D: + return GL_TEXTURE_BINDING_2D; + case TextureTarget::Texture3D: + return GL_TEXTURE_BINDING_3D; + case TextureTarget::TextureCubeMap: + return GL_TEXTURE_BINDING_CUBE_MAP; + case TextureTarget::Texture2DArray: + return GL_TEXTURE_BINDING_2D_ARRAY; + case TextureTarget::TextureCubeMapArray: + return GL_TEXTURE_BINDING_CUBE_MAP_ARRAY; + case TextureTarget::Texture2DMultisample: + return GL_TEXTURE_BINDING_2D_MULTISAMPLE; + case TextureTarget::Texture2DMultisampleArray: + return GL_TEXTURE_BINDING_2D_MULTISAMPLE_ARRAY; + default: + return GL_UNKNOWN_MGL; + } + } + + Bool IsGLESProbeTextureTarget(TextureTarget target) { + switch (target) { + case TextureTarget::Texture2D: + case TextureTarget::Texture3D: + case TextureTarget::TextureCubeMap: + case TextureTarget::Texture2DArray: + case TextureTarget::TextureCubeMapArray: + case TextureTarget::Texture2DMultisample: + case TextureTarget::Texture2DMultisampleArray: + return true; + default: + return false; + } + } + + Bool IsGLESProbeMultisampleTarget(TextureTarget target) { + return target == TextureTarget::Texture2DMultisample || + target == TextureTarget::Texture2DMultisampleArray; + } + + GLenum GetFramebufferAttachment(TextureInternalFormat format) { + const Bool isDepth = MG_Util::IsDepthFormatInternalFormat(format); + const Bool isStencil = MG_Util::IsStencilFormatInternalFormat(format); + if (isDepth && isStencil) return GL_DEPTH_STENCIL_ATTACHMENT; + if (isDepth) return GL_DEPTH_ATTACHMENT; + if (isStencil) return GL_STENCIL_ATTACHMENT; + return GL_COLOR_ATTACHMENT0; + } + + FormatCapabilityFlags GetAttachmentCaps(TextureInternalFormat format) { + FormatCapabilityFlags caps = FormatCapability::FramebufferRenderable; + const Bool isDepth = MG_Util::IsDepthFormatInternalFormat(format); + const Bool isStencil = MG_Util::IsStencilFormatInternalFormat(format); + if (!isDepth && !isStencil) { + caps |= FormatCapability::ColorAttachment; + } + if (isDepth) { + caps |= FormatCapability::DepthAttachment; + } + if (isStencil) { + caps |= FormatCapability::StencilAttachment; + } + return caps; + } + + Bool IsDepthOnlyFormat(TextureInternalFormat format) { + return MG_Util::IsDepthFormatInternalFormat(format) && !MG_Util::IsStencilFormatInternalFormat(format); + } + + Bool IsFilterableFormat(TextureInternalFormat format) { + const GLenum glFormat = MG_Util::ConvertTextureInternalFormatToGLEnum(format); + GLenum normalizedInternalFormat = glFormat; + GLenum imageFormat = GL_RGBA; + GLenum imageType = GL_UNSIGNED_BYTE; + MG_Util::TextureFormatProcessor::NormalizePixelFormat( + glFormat, PixelFormatNormalizeOptionBit::None, &normalizedInternalFormat, &imageFormat, &imageType); + return imageFormat != GL_RED_INTEGER && imageFormat != GL_RG_INTEGER && imageFormat != GL_RGB_INTEGER && + imageFormat != GL_RGBA_INTEGER && !MG_Util::IsDepthFormatInternalFormat(format) && + !MG_Util::IsStencilFormatInternalFormat(format); + } + + FormatCapabilityFlags GetTextureFeatureCaps(TextureInternalFormat format, TextureTarget target) { + FormatCapabilityFlags caps = FormatCapability::Creatable | FormatCapability::Sampled; + if (IsFilterableFormat(format)) { + caps |= FormatCapability::LinearFilter; + } + if (!IsGLESProbeMultisampleTarget(target) && !MG_Util::IsStencilFormatInternalFormat(format)) { + caps |= FormatCapability::GenerateMipmap; + } + if (IsDepthOnlyFormat(format)) { + caps |= FormatCapability::TextureShadow; + } + if (IsGLESProbeMultisampleTarget(target)) { + caps |= FormatCapability::MultisampleTexture; + } + return caps; + } + + FormatCapabilityFlags GetRenderbufferFeatureCaps(TextureInternalFormat format) { + return FormatCapabilityFlags(FormatCapability::Creatable) | GetAttachmentCaps(format) | + FormatCapability::MultisampleRenderbuffer; + } + + Bool ProbeFramebufferCompletenessForTexture(const MG_External::GLESFunctionsTable& gl, + TextureTarget target, + GLuint texture, + TextureInternalFormat format) { + GLuint framebuffer = 0; + GLint prevFramebuffer = 0; + if (!gl.glGenFramebuffers || !gl.glBindFramebuffer || !gl.glCheckFramebufferStatus || + !gl.glDeleteFramebuffers) { + return false; + } + + gl.glGetIntegerv(GL_FRAMEBUFFER_BINDING, &prevFramebuffer); + gl.glGenFramebuffers(1, &framebuffer); + gl.glBindFramebuffer(GL_FRAMEBUFFER, framebuffer); + + const GLenum attachment = GetFramebufferAttachment(format); + switch (target) { + case TextureTarget::Texture2D: + gl.glFramebufferTexture2D(GL_FRAMEBUFFER, attachment, GL_TEXTURE_2D, texture, 0); + break; + case TextureTarget::TextureCubeMap: + gl.glFramebufferTexture2D(GL_FRAMEBUFFER, attachment, GL_TEXTURE_CUBE_MAP_POSITIVE_X, texture, 0); + break; + case TextureTarget::Texture3D: + case TextureTarget::Texture2DArray: + case TextureTarget::TextureCubeMapArray: + case TextureTarget::Texture2DMultisampleArray: + if (!gl.glFramebufferTextureLayer) { + gl.glBindFramebuffer(GL_FRAMEBUFFER, static_cast(prevFramebuffer)); + gl.glDeleteFramebuffers(1, &framebuffer); + return false; + } + gl.glFramebufferTextureLayer(GL_FRAMEBUFFER, attachment, texture, 0, 0); + break; + case TextureTarget::Texture2DMultisample: + gl.glFramebufferTexture2D(GL_FRAMEBUFFER, attachment, GL_TEXTURE_2D_MULTISAMPLE, texture, 0); + break; + default: + gl.glBindFramebuffer(GL_FRAMEBUFFER, static_cast(prevFramebuffer)); + gl.glDeleteFramebuffers(1, &framebuffer); + return false; + } + + const Bool complete = gl.glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE; + gl.glBindFramebuffer(GL_FRAMEBUFFER, static_cast(prevFramebuffer)); + gl.glDeleteFramebuffers(1, &framebuffer); + return complete; + } + + Bool ProbeFramebufferCompletenessForRenderbuffer(const MG_External::GLESFunctionsTable& gl, + GLuint renderbuffer, + TextureInternalFormat format) { + GLuint framebuffer = 0; + GLint prevFramebuffer = 0; + if (!gl.glGenFramebuffers || !gl.glBindFramebuffer || !gl.glFramebufferRenderbuffer || + !gl.glCheckFramebufferStatus || !gl.glDeleteFramebuffers) { + return false; + } + + gl.glGetIntegerv(GL_FRAMEBUFFER_BINDING, &prevFramebuffer); + gl.glGenFramebuffers(1, &framebuffer); + gl.glBindFramebuffer(GL_FRAMEBUFFER, framebuffer); + gl.glFramebufferRenderbuffer(GL_FRAMEBUFFER, GetFramebufferAttachment(format), GL_RENDERBUFFER, + renderbuffer); + const Bool complete = gl.glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE; + gl.glBindFramebuffer(GL_FRAMEBUFFER, static_cast(prevFramebuffer)); + gl.glDeleteFramebuffers(1, &framebuffer); + return complete; + } + + Bool ProbeTexture(const MG_External::GLESFunctionsTable& gl, TextureTarget target, GLenum internalFormat, + GLenum imageFormat, GLenum imageType, TextureInternalFormat logicalFormat, + Bool* outRenderable) { + if (!IsGLESProbeTextureTarget(target) || !gl.glGenTextures || !gl.glBindTexture || !gl.glDeleteTextures) { + return false; + } + + const GLenum glTarget = MG_Util::ConvertTextureTargetToGLEnum(target); + const GLenum bindingQuery = GetTextureBindingQuery(target); + if (glTarget == GL_UNKNOWN_MGL || bindingQuery == GL_UNKNOWN_MGL) { + return false; + } + + GLint previousBinding = 0; + gl.glGetIntegerv(bindingQuery, &previousBinding); + GLuint texture = 0; + gl.glGenTextures(1, &texture); + gl.glBindTexture(glTarget, texture); + ClearGLErrors(gl); + + const Bool isMultisample = IsGLESProbeMultisampleTarget(target); + if (isMultisample) { + if (target == TextureTarget::Texture2DMultisample && gl.glTexStorage2DMultisample) { + gl.glTexStorage2DMultisample(glTarget, 1, internalFormat, 1, 1, GL_TRUE); + } else if (target == TextureTarget::Texture2DMultisampleArray && gl.glTexStorage3DMultisample) { + gl.glTexStorage3DMultisample(glTarget, 1, internalFormat, 1, 1, 1, GL_TRUE); + } else { + gl.glBindTexture(glTarget, static_cast(previousBinding)); + gl.glDeleteTextures(1, &texture); + return false; + } + } else { + if (gl.glTexParameteri) { + gl.glTexParameteri(glTarget, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + gl.glTexParameteri(glTarget, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + } + switch (target) { + case TextureTarget::Texture2D: + gl.glTexImage2D(glTarget, 0, static_cast(internalFormat), 2, 2, 0, imageFormat, imageType, + nullptr); + break; + case TextureTarget::TextureCubeMap: + for (GLenum face = GL_TEXTURE_CUBE_MAP_POSITIVE_X; face <= GL_TEXTURE_CUBE_MAP_NEGATIVE_Z; ++face) { + gl.glTexImage2D(face, 0, static_cast(internalFormat), 2, 2, 0, imageFormat, imageType, + nullptr); + } + break; + case TextureTarget::Texture3D: + gl.glTexImage3D(glTarget, 0, static_cast(internalFormat), 2, 2, 2, 0, imageFormat, + imageType, nullptr); + break; + case TextureTarget::Texture2DArray: + gl.glTexImage3D(glTarget, 0, static_cast(internalFormat), 2, 2, 1, 0, imageFormat, + imageType, nullptr); + break; + case TextureTarget::TextureCubeMapArray: + gl.glTexImage3D(glTarget, 0, static_cast(internalFormat), 2, 2, 6, 0, imageFormat, + imageType, nullptr); + break; + default: + break; + } + } + + const Bool created = CheckNoGLError(gl); + Bool renderable = false; + if (created) { + renderable = ProbeFramebufferCompletenessForTexture(gl, target, texture, logicalFormat); + } + if (outRenderable) { + *outRenderable = renderable; + } + gl.glBindTexture(glTarget, static_cast(previousBinding)); + gl.glDeleteTextures(1, &texture); + ClearGLErrors(gl); + return created; + } + + Bool ProbeRenderbuffer(const MG_External::GLESFunctionsTable& gl, + GLenum internalFormat, + TextureInternalFormat logicalFormat, + Bool multisample, + Int samples) { + if (!gl.glGenRenderbuffers || !gl.glBindRenderbuffer || !gl.glDeleteRenderbuffers) { + return false; + } + + GLint prevRenderbuffer = 0; + gl.glGetIntegerv(GL_RENDERBUFFER_BINDING, &prevRenderbuffer); + GLuint renderbuffer = 0; + gl.glGenRenderbuffers(1, &renderbuffer); + gl.glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer); + ClearGLErrors(gl); + if (multisample) { + if (!gl.glRenderbufferStorageMultisample) { + gl.glBindRenderbuffer(GL_RENDERBUFFER, static_cast(prevRenderbuffer)); + gl.glDeleteRenderbuffers(1, &renderbuffer); + return false; + } + gl.glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, internalFormat, 1, 1); + } else { + gl.glRenderbufferStorage(GL_RENDERBUFFER, internalFormat, 1, 1); + } + const Bool created = CheckNoGLError(gl); + const Bool complete = created && ProbeFramebufferCompletenessForRenderbuffer(gl, renderbuffer, logicalFormat); + gl.glBindRenderbuffer(GL_RENDERBUFFER, static_cast(prevRenderbuffer)); + gl.glDeleteRenderbuffers(1, &renderbuffer); + ClearGLErrors(gl); + return complete; + } + + Vector ProbeRenderbufferSampleCounts(const MG_External::GLESFunctionsTable& gl, + GLenum internalFormat, + TextureInternalFormat logicalFormat, + Int maxSamples) { + Vector sampleCounts; + for (Int samples = std::max(maxSamples, 1); samples > 1; samples >>= 1) { + if (ProbeRenderbuffer(gl, internalFormat, logicalFormat, true, samples)) { + sampleCounts.push_back(samples); + } + } + sampleCounts.push_back(1); + return sampleCounts; + } + + void ProbeGLESFormatCapabilities(const MG_External::GLESFunctionsTable& gl, + FormatCapabilityCache& cache, + const DynamicBackendParameters& dynamicParameters) { + cache.Clear(); + for (SizeT formatIndex = 0; formatIndex < kFormatCapabilityFormatCount; ++formatIndex) { + const auto logicalFormat = static_cast(formatIndex); + GLenum requestedInternalFormat = MG_Util::ConvertTextureInternalFormatToGLEnum(logicalFormat); + if (requestedInternalFormat == GL_UNKNOWN_MGL) { + continue; + } + + GLenum backendInternalFormat = requestedInternalFormat; + GLenum imageFormat = GL_RGBA; + GLenum imageType = GL_UNSIGNED_BYTE; + TextureImpl::GenerateTextureFormatInfo(logicalFormat, &backendInternalFormat, &imageFormat, + &imageType); + const Bool isCaveat = backendInternalFormat != requestedInternalFormat; + + for (SizeT targetIndex = 0; targetIndex < kFormatCapabilityTextureTargetCount; ++targetIndex) { + const auto target = static_cast(targetIndex); + Bool renderable = false; + if (!ProbeTexture(gl, target, backendInternalFormat, imageFormat, imageType, logicalFormat, + &renderable)) { + continue; + } + + FormatCapabilityFlags caps = GetTextureFeatureCaps(logicalFormat, target); + if (renderable) { + caps |= GetAttachmentCaps(logicalFormat); + if (target == TextureTarget::Texture3D || target == TextureTarget::Texture2DArray || + target == TextureTarget::TextureCubeMapArray || + target == TextureTarget::Texture2DMultisampleArray) { + caps |= FormatCapability::FramebufferLayered; + } + } + auto& table = isCaveat ? cache.CaveatCaps : cache.FullCaps; + table[targetIndex][formatIndex] |= caps; + + if (IsGLESProbeMultisampleTarget(target)) { + cache.SampleCounts[targetIndex][formatIndex] = {1}; + } + } + + const SizeT renderbufferTargetIndex = GetRenderbufferFormatCapabilityTargetIndex(); + if (ProbeRenderbuffer(gl, backendInternalFormat, logicalFormat, false, 1)) { + auto& table = isCaveat ? cache.CaveatCaps : cache.FullCaps; + table[renderbufferTargetIndex][formatIndex] |= GetRenderbufferFeatureCaps(logicalFormat); + + const Bool isDepth = MG_Util::IsDepthFormatInternalFormat(logicalFormat); + const Bool isStencil = MG_Util::IsStencilFormatInternalFormat(logicalFormat); + const Bool isInteger = imageFormat == GL_RED_INTEGER || imageFormat == GL_RG_INTEGER || + imageFormat == GL_RGB_INTEGER || imageFormat == GL_RGBA_INTEGER; + Int maxSamples = dynamicParameters.MaxColorTextureSamples; + if (isDepth || isStencil) { + maxSamples = dynamicParameters.MaxDepthTextureSamples; + } else if (isInteger) { + maxSamples = dynamicParameters.MaxIntegerSamples; + } + cache.SampleCounts[renderbufferTargetIndex][formatIndex] = + ProbeRenderbufferSampleCounts(gl, backendInternalFormat, logicalFormat, maxSamples); + } + } + } } // namespace BackendObject_DirectGLES::~BackendObject_DirectGLES() { @@ -53,6 +430,7 @@ namespace MobileGL::MG_Backend::DirectGLES { } DirectGLES::SetGLESCapabilities(m_GLESCapabilities); UpdateDynamicBackendParameters(); + ProbeGLESFormatCapabilities(m_GLESFunctions, MutableFormatCapabilities(), m_dynamicParameters); return true; } diff --git a/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.cpp b/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.cpp index 71745561..d863a510 100644 --- a/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.cpp @@ -11,12 +11,253 @@ #include "DirectVulkan.h" #include "MG_State/GLState/FramebufferState/FramebufferObject.h" #include "MG_State/GLState/TextureState/TextureState.h" +#include "MG_Util/Classifiers/TextureEnumClassifier.h" +#include "MG_Util/Converters/MGToGL/TextureEnumConverter.h" +#include "MG_Util/Converters/MGToVk/TextureEnumConverter.h" +#include "MG_Util/Texture/TextureFormatProcessor.h" namespace MobileGL::MG_Backend::DirectVulkan { namespace { Bool IsReleaseCurrentRequest(EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx) { return dpy == EGL_NO_DISPLAY && draw == EGL_NO_SURFACE && read == EGL_NO_SURFACE && ctx == EGL_NO_CONTEXT; } + + Bool IsFormatIndexValid(TextureInternalFormat format) { + return format != TextureInternalFormat::Unknown && static_cast(format) >= 0 && + static_cast(format) < kFormatCapabilityFormatCount; + } + + Bool IsLayeredTarget(TextureTarget target) { + return target == TextureTarget::Texture3D || target == TextureTarget::Texture1DArray || + target == TextureTarget::Texture2DArray || target == TextureTarget::TextureCubeMap || + target == TextureTarget::TextureCubeMapArray || + target == TextureTarget::Texture2DMultisampleArray; + } + + Bool IsMultisampleTarget(TextureTarget target) { + return target == TextureTarget::Texture2DMultisample || + target == TextureTarget::Texture2DMultisampleArray; + } + + Bool IsTextureBufferTarget(TextureTarget target) { + return target == TextureTarget::TextureBuffer; + } + + Bool IsIntegerInternalFormat(TextureInternalFormat format) { + const GLenum glFormat = MG_Util::ConvertTextureInternalFormatToGLEnum(format); + GLenum normalizedInternalFormat = glFormat; + GLenum imageFormat = GL_RGBA; + GLenum imageType = GL_UNSIGNED_BYTE; + MG_Util::TextureFormatProcessor::NormalizePixelFormat( + glFormat, PixelFormatNormalizeOptionBit::None, &normalizedInternalFormat, &imageFormat, &imageType); + return imageFormat == GL_RED_INTEGER || imageFormat == GL_RG_INTEGER || imageFormat == GL_RGB_INTEGER || + imageFormat == GL_RGBA_INTEGER; + } + + FormatCapabilityFlags GetAttachmentCaps(TextureInternalFormat format) { + FormatCapabilityFlags caps = FormatCapability::FramebufferRenderable; + const Bool isDepth = MG_Util::IsDepthFormatInternalFormat(format); + const Bool isStencil = MG_Util::IsStencilFormatInternalFormat(format); + if (!isDepth && !isStencil) { + caps |= FormatCapability::ColorAttachment; + } + if (isDepth) { + caps |= FormatCapability::DepthAttachment; + } + if (isStencil) { + caps |= FormatCapability::StencilAttachment; + } + return caps; + } + + FormatCapabilityFlags BuildVulkanCaps(TextureInternalFormat logicalFormat, + TextureTarget target, + VkFormatFeatureFlags features) { + FormatCapabilityFlags caps; + const Bool isDepth = MG_Util::IsDepthFormatInternalFormat(logicalFormat); + const Bool isStencil = MG_Util::IsStencilFormatInternalFormat(logicalFormat); + const Bool isInteger = IsIntegerInternalFormat(logicalFormat); + + if (IsTextureBufferTarget(target)) { + if ((features & VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT) != 0) { + caps |= FormatCapability::Creatable; + caps |= FormatCapability::Sampled; + caps |= FormatCapability::TextureBuffer; + } + return caps; + } + + const Bool sampled = (features & VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT) != 0; + const Bool linearFilter = (features & VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT) != 0; + const Bool colorRenderable = (features & VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT) != 0; + const Bool depthStencilRenderable = + (features & VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT) != 0; + const Bool renderable = (isDepth || isStencil) ? depthStencilRenderable : colorRenderable; + + if (sampled || renderable) { + caps |= FormatCapability::Creatable; + } + if (sampled) { + caps |= FormatCapability::Sampled; + if (linearFilter && !isInteger && !isStencil) { + caps |= FormatCapability::LinearFilter; + } + if (!isStencil && (features & VK_FORMAT_FEATURE_BLIT_SRC_BIT) != 0 && + (features & VK_FORMAT_FEATURE_BLIT_DST_BIT) != 0) { + caps |= FormatCapability::GenerateMipmap; + } + if (!isInteger && !isDepth && !isStencil) { + caps |= FormatCapability::TextureGather; + } + if (isDepth && !isStencil) { + caps |= FormatCapability::TextureShadow; + } + } + if (renderable) { + caps |= GetAttachmentCaps(logicalFormat); + if (IsLayeredTarget(target)) { + caps |= FormatCapability::FramebufferLayered; + } + } + if (IsMultisampleTarget(target)) { + caps |= FormatCapability::MultisampleTexture; + } + return caps; + } + + Optional ResolveVulkanFallbackFormat(TextureInternalFormat format) { + switch (format) { + case TextureInternalFormat::RGB: + case TextureInternalFormat::RGB8: + return VK_FORMAT_R8G8B8A8_UNORM; + case TextureInternalFormat::SRGB8: + return VK_FORMAT_R8G8B8A8_SRGB; + case TextureInternalFormat::RGB8Snorm: + return VK_FORMAT_R8G8B8A8_SNORM; + case TextureInternalFormat::RGB16: + return VK_FORMAT_R16G16B16A16_UNORM; + case TextureInternalFormat::RGB16Snorm: + return VK_FORMAT_R16G16B16A16_SNORM; + case TextureInternalFormat::RGB16F: + return VK_FORMAT_R16G16B16A16_SFLOAT; + case TextureInternalFormat::RGB32F: + return VK_FORMAT_R32G32B32A32_SFLOAT; + case TextureInternalFormat::RGB8I: + return VK_FORMAT_R8G8B8A8_SINT; + case TextureInternalFormat::RGB8UI: + return VK_FORMAT_R8G8B8A8_UINT; + case TextureInternalFormat::RGB16I: + return VK_FORMAT_R16G16B16A16_SINT; + case TextureInternalFormat::RGB16UI: + return VK_FORMAT_R16G16B16A16_UINT; + case TextureInternalFormat::RGB32I: + return VK_FORMAT_R32G32B32A32_SINT; + case TextureInternalFormat::RGB32UI: + return VK_FORMAT_R32G32B32A32_UINT; + default: + return Nullopt; + } + } + + Vector BuildSampleCounts(Int maxSamples) { + Vector counts; + for (Int samples = std::max(maxSamples, 1); samples > 1; samples >>= 1) { + counts.push_back(samples); + } + counts.push_back(1); + return counts; + } + + void FillVulkanFormatCapabilities(VkPhysicalDevice physicalDevice, + const DynamicBackendParameters& dynamicParameters, + FormatCapabilityCache& cache) { + cache.Clear(); + if (physicalDevice == VK_NULL_HANDLE) { + return; + } + + for (SizeT formatIndex = 0; formatIndex < kFormatCapabilityFormatCount; ++formatIndex) { + const auto logicalFormat = static_cast(formatIndex); + if (!IsFormatIndexValid(logicalFormat)) { + continue; + } + + VkFormat nativeFormat = MG_Util::ConvertTextureInternalFormatToVkEnum(logicalFormat); + VkFormat fallbackFormat = ResolveVulkanFallbackFormat(logicalFormat).value_or(VK_FORMAT_UNDEFINED); + + VkFormatProperties nativeProperties{}; + if (nativeFormat != VK_FORMAT_UNDEFINED) { + vkGetPhysicalDeviceFormatProperties(physicalDevice, nativeFormat, &nativeProperties); + } + + VkFormatProperties fallbackProperties{}; + if (fallbackFormat != VK_FORMAT_UNDEFINED && fallbackFormat != nativeFormat) { + vkGetPhysicalDeviceFormatProperties(physicalDevice, fallbackFormat, &fallbackProperties); + } + + for (SizeT targetIndex = 0; targetIndex < kFormatCapabilityTextureTargetCount; ++targetIndex) { + const auto target = static_cast(targetIndex); + const VkFormatFeatureFlags nativeFeatures = + IsTextureBufferTarget(target) ? nativeProperties.bufferFeatures + : nativeProperties.optimalTilingFeatures; + FormatCapabilityFlags nativeCaps = BuildVulkanCaps(logicalFormat, target, nativeFeatures); + cache.FullCaps[targetIndex][formatIndex] |= nativeCaps; + + const VkFormatFeatureFlags fallbackFeatures = + IsTextureBufferTarget(target) ? fallbackProperties.bufferFeatures + : fallbackProperties.optimalTilingFeatures; + FormatCapabilityFlags fallbackCaps = BuildVulkanCaps(logicalFormat, target, fallbackFeatures); + if (fallbackFormat != VK_FORMAT_UNDEFINED && fallbackFormat != nativeFormat) { + cache.CaveatCaps[targetIndex][formatIndex] |= fallbackCaps; + } + + if (HasFormatCapability(nativeCaps | fallbackCaps, FormatCapability::MultisampleTexture)) { + const Bool isDepth = MG_Util::IsDepthFormatInternalFormat(logicalFormat); + const Bool isStencil = MG_Util::IsStencilFormatInternalFormat(logicalFormat); + const Bool isInteger = IsIntegerInternalFormat(logicalFormat); + Int maxSamples = dynamicParameters.MaxColorTextureSamples; + if (isDepth || isStencil) { + maxSamples = dynamicParameters.MaxDepthTextureSamples; + } else if (isInteger) { + maxSamples = dynamicParameters.MaxIntegerSamples; + } + cache.SampleCounts[targetIndex][formatIndex] = BuildSampleCounts(maxSamples); + } + } + + const SizeT renderbufferTargetIndex = GetRenderbufferFormatCapabilityTargetIndex(); + FormatCapabilityFlags renderbufferCaps = + BuildVulkanCaps(logicalFormat, TextureTarget::Texture2D, nativeProperties.optimalTilingFeatures); + renderbufferCaps &= FormatCapability::Creatable; + if ((nativeProperties.optimalTilingFeatures & + (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT)) != 0) { + renderbufferCaps |= GetAttachmentCaps(logicalFormat); + renderbufferCaps |= FormatCapability::MultisampleRenderbuffer; + } + cache.FullCaps[renderbufferTargetIndex][formatIndex] |= renderbufferCaps; + + if (fallbackFormat != VK_FORMAT_UNDEFINED && fallbackFormat != nativeFormat) { + FormatCapabilityFlags fallbackRenderbufferCaps = + BuildVulkanCaps(logicalFormat, TextureTarget::Texture2D, + fallbackProperties.optimalTilingFeatures); + fallbackRenderbufferCaps &= FormatCapability::Creatable; + if ((fallbackProperties.optimalTilingFeatures & + (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT)) != + 0) { + fallbackRenderbufferCaps |= GetAttachmentCaps(logicalFormat); + fallbackRenderbufferCaps |= FormatCapability::MultisampleRenderbuffer; + } + cache.CaveatCaps[renderbufferTargetIndex][formatIndex] |= fallbackRenderbufferCaps; + } + + const FormatCapabilityFlags rbCaps = cache.FullCaps[renderbufferTargetIndex][formatIndex] | + cache.CaveatCaps[renderbufferTargetIndex][formatIndex]; + if (HasFormatCapability(rbCaps, FormatCapability::MultisampleRenderbuffer)) { + cache.SampleCounts[renderbufferTargetIndex][formatIndex] = + BuildSampleCounts(dynamicParameters.MaxFramebufferSamples); + } + } + } } // namespace BackendObject_DirectVulkan::~BackendObject_DirectVulkan() = default; @@ -88,6 +329,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { } UpdateDynamicBackendParameters(); UpdateAdvertisedExtensions(); + FillVulkanFormatCapabilities(physicalDevice.handle, m_dynamicParameters, MutableFormatCapabilities()); return true; } @@ -255,6 +497,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { m_vulkanCaps = capabilities; UpdateDynamicBackendParameters(); UpdateAdvertisedExtensions(); + MutableFormatCapabilities().Clear(); } void BackendObject_DirectVulkan::UpdateAdvertisedExtensions() { diff --git a/MobileGL/MG_Impl/GLImpl/Texture/GL_Texture.cpp b/MobileGL/MG_Impl/GLImpl/Texture/GL_Texture.cpp index b9d4270f..11894874 100644 --- a/MobileGL/MG_Impl/GLImpl/Texture/GL_Texture.cpp +++ b/MobileGL/MG_Impl/GLImpl/Texture/GL_Texture.cpp @@ -2895,38 +2895,165 @@ namespace MobileGL::MG_Impl::GLImpl { const Bool isDepthFormat = MG_Util::IsDepthFormatInternalFormat(textureInternalFormat); const Bool isStencilFormat = MG_Util::IsStencilFormatInternalFormat(textureInternalFormat); + const ComponentSizes componentSizes = MG_Util::GetComponentSizesForInternalFormat(textureInternalFormat); const Bool isIntegerFormat = imageFormat == GL_RED_INTEGER || imageFormat == GL_RG_INTEGER || imageFormat == GL_RGB_INTEGER || imageFormat == GL_RGBA_INTEGER; - const Bool isLayeredTarget = target == GL_TEXTURE_3D || target == GL_TEXTURE_1D_ARRAY || - target == GL_TEXTURE_2D_ARRAY || target == GL_TEXTURE_CUBE_MAP || - target == GL_TEXTURE_CUBE_MAP_ARRAY || target == GL_TEXTURE_2D_MULTISAMPLE_ARRAY; - GLint maxSamples = 1; + SizeT targetIndex = isRenderbufferTarget ? MG_Backend::GetRenderbufferFormatCapabilityTargetIndex() + : MG_Backend::GetFormatCapabilityTargetIndex(textureTarget); + if (targetIndex >= MG_Backend::kFormatCapabilityTargetCount) return; + const SizeT formatIndex = static_cast(textureInternalFormat); + + MG_Backend::FormatCapabilityFlags fullCaps; + MG_Backend::FormatCapabilityFlags caveatCaps; + const Vector* sampleCounts = nullptr; if (MG_Backend::pActiveBackendObject) { - const auto& dynamicParameters = MG_Backend::pActiveBackendObject->GetDynamicParameters(); - if (isDepthFormat || isStencilFormat) { - maxSamples = dynamicParameters.MaxDepthTextureSamples; - } else if (isIntegerFormat) { - maxSamples = dynamicParameters.MaxIntegerSamples; - } else { - maxSamples = dynamicParameters.MaxColorTextureSamples; - } - maxSamples = std::max(maxSamples, 1); + const auto& cache = MG_Backend::pActiveBackendObject->GetFormatCapabilities(); + fullCaps = cache.FullCaps[targetIndex][formatIndex]; + caveatCaps = cache.CaveatCaps[targetIndex][formatIndex]; + sampleCounts = &cache.SampleCounts[targetIndex][formatIndex]; } + auto hasFull = [&](MG_Backend::FormatCapability capability) { + return MG_Backend::HasFormatCapability(fullCaps, capability); + }; + auto hasCaveat = [&](MG_Backend::FormatCapability capability) { + return MG_Backend::HasFormatCapability(caveatCaps, capability); + }; + auto supportFor = [&](MG_Backend::FormatCapability capability) -> GLint { + if (hasFull(capability)) return GL_FULL_SUPPORT; + if (hasCaveat(capability)) return GL_CAVEAT_SUPPORT; + return GL_NONE; + }; + auto supportForWithFallback = [&](MG_Backend::FormatCapability primary, + MG_Backend::FormatCapability fallback) -> GLint { + if (hasFull(primary)) return GL_FULL_SUPPORT; + if (hasCaveat(primary) || hasFull(fallback) || hasCaveat(fallback)) return GL_CAVEAT_SUPPORT; + return GL_NONE; + }; + auto componentType = [&](GLint size, Bool depthComponent, Bool stencilComponent) -> GLint { + if (size <= 0) return GL_NONE; + if (stencilComponent) return GL_UNSIGNED_INT; + if (depthComponent) { + return (textureInternalFormat == TextureInternalFormat::DepthComponent32F || + textureInternalFormat == TextureInternalFormat::Depth32FStencil8) + ? GL_FLOAT + : GL_UNSIGNED_NORMALIZED; + } + switch (textureInternalFormat) { + case TextureInternalFormat::R8I: + case TextureInternalFormat::R16I: + case TextureInternalFormat::R32I: + case TextureInternalFormat::RG8I: + case TextureInternalFormat::RG16I: + case TextureInternalFormat::RG32I: + case TextureInternalFormat::RGB8I: + case TextureInternalFormat::RGB16I: + case TextureInternalFormat::RGB32I: + case TextureInternalFormat::RGBA8I: + case TextureInternalFormat::RGBA16I: + case TextureInternalFormat::RGBA32I: + return GL_INT; + case TextureInternalFormat::R8UI: + case TextureInternalFormat::R16UI: + case TextureInternalFormat::R32UI: + case TextureInternalFormat::RG8UI: + case TextureInternalFormat::RG16UI: + case TextureInternalFormat::RG32UI: + case TextureInternalFormat::RGB8UI: + case TextureInternalFormat::RGB16UI: + case TextureInternalFormat::RGB32UI: + case TextureInternalFormat::RGBA8UI: + case TextureInternalFormat::RGBA16UI: + case TextureInternalFormat::RGBA32UI: + case TextureInternalFormat::RGB10A2UI: + return GL_UNSIGNED_INT; + case TextureInternalFormat::R16F: + case TextureInternalFormat::RG16F: + case TextureInternalFormat::RGB16F: + case TextureInternalFormat::RGBA16F: + case TextureInternalFormat::R32F: + case TextureInternalFormat::RG32F: + case TextureInternalFormat::RGB32F: + case TextureInternalFormat::RGBA32F: + case TextureInternalFormat::R11FG11FB10F: + case TextureInternalFormat::RGB9E5: + return GL_FLOAT; + case TextureInternalFormat::R8Snorm: + case TextureInternalFormat::R16Snorm: + case TextureInternalFormat::RG8Snorm: + case TextureInternalFormat::RG16Snorm: + case TextureInternalFormat::RGB8Snorm: + case TextureInternalFormat::RGB16Snorm: + case TextureInternalFormat::RGBA8Snorm: + case TextureInternalFormat::RGBA16Snorm: + return GL_SIGNED_NORMALIZED; + default: + return GL_UNSIGNED_NORMALIZED; + } + }; + switch (pname) { case GL_INTERNALFORMAT_SUPPORTED: - writeValues({GL_TRUE}); + writeValues({(hasFull(MG_Backend::FormatCapability::Creatable) || + hasCaveat(MG_Backend::FormatCapability::Creatable)) + ? GL_TRUE + : GL_FALSE}); return; case GL_INTERNALFORMAT_PREFERRED: writeValues({static_cast(preferredInternalFormat)}); return; + case GL_INTERNALFORMAT_RED_SIZE: + writeValues({componentSizes.Red}); + return; + case GL_INTERNALFORMAT_GREEN_SIZE: + writeValues({componentSizes.Green}); + return; + case GL_INTERNALFORMAT_BLUE_SIZE: + writeValues({componentSizes.Blue}); + return; + case GL_INTERNALFORMAT_ALPHA_SIZE: + writeValues({componentSizes.Alpha}); + return; + case GL_INTERNALFORMAT_DEPTH_SIZE: + writeValues({componentSizes.Depth}); + return; + case GL_INTERNALFORMAT_STENCIL_SIZE: + writeValues({componentSizes.Stencil}); + return; + case GL_INTERNALFORMAT_SHARED_SIZE: + writeValues({textureInternalFormat == TextureInternalFormat::RGB9E5 ? 5 : 0}); + return; + case GL_INTERNALFORMAT_RED_TYPE: + writeValues({componentType(componentSizes.Red, false, false)}); + return; + case GL_INTERNALFORMAT_GREEN_TYPE: + writeValues({componentType(componentSizes.Green, false, false)}); + return; + case GL_INTERNALFORMAT_BLUE_TYPE: + writeValues({componentType(componentSizes.Blue, false, false)}); + return; + case GL_INTERNALFORMAT_ALPHA_TYPE: + writeValues({componentType(componentSizes.Alpha, false, false)}); + return; + case GL_INTERNALFORMAT_DEPTH_TYPE: + writeValues({componentType(componentSizes.Depth, true, false)}); + return; + case GL_INTERNALFORMAT_STENCIL_TYPE: + writeValues({componentType(componentSizes.Stencil, false, true)}); + return; case GL_TEXTURE_IMAGE_FORMAT: writeValues({static_cast(imageFormat)}); return; case GL_TEXTURE_IMAGE_TYPE: writeValues({static_cast(imageType)}); return; + case GL_TEXTURE_COMPRESSED: + case GL_TEXTURE_COMPRESSED_BLOCK_WIDTH: + case GL_TEXTURE_COMPRESSED_BLOCK_HEIGHT: + case GL_TEXTURE_COMPRESSED_BLOCK_SIZE: + writeValues({0}); + return; case GL_COLOR_COMPONENTS: writeValues({(!isDepthFormat && !isStencilFormat) ? GL_TRUE : GL_FALSE}); return; @@ -2937,20 +3064,42 @@ namespace MobileGL::MG_Impl::GLImpl { writeValues({isStencilFormat ? GL_TRUE : GL_FALSE}); return; case GL_FRAMEBUFFER_RENDERABLE: - writeValues({GL_FULL_SUPPORT}); + writeValues({supportFor(MG_Backend::FormatCapability::FramebufferRenderable)}); return; case GL_FRAMEBUFFER_RENDERABLE_LAYERED: - writeValues({isLayeredTarget ? GL_FULL_SUPPORT : GL_NONE}); + writeValues({supportFor(MG_Backend::FormatCapability::FramebufferLayered)}); + return; + case GL_FILTER: + writeValues({supportForWithFallback(MG_Backend::FormatCapability::LinearFilter, + MG_Backend::FormatCapability::Sampled)}); + return; + case GL_MIPMAP: + writeValues({supportForWithFallback(MG_Backend::FormatCapability::GenerateMipmap, + MG_Backend::FormatCapability::Sampled)}); + return; + case GL_TEXTURE_GATHER: + case GL_TEXTURE_GATHER_SHADOW: + writeValues({supportFor(MG_Backend::FormatCapability::TextureGather)}); + return; + case GL_TEXTURE_SHADOW: + writeValues({supportFor(MG_Backend::FormatCapability::TextureShadow)}); return; case GL_NUM_SAMPLE_COUNTS: - writeValues({maxSamples > 1 ? 2 : 1}); + writeValues({sampleCounts != nullptr ? static_cast(sampleCounts->size()) : 0}); return; case GL_SAMPLES: - if (maxSamples > 1) { - writeValues({maxSamples, 1}); - } else { - writeValues({1}); + if (sampleCounts != nullptr) { + GLsizei index = 0; + for (Int sampleCount : *sampleCounts) { + if (index >= bufSize) break; + params[index++] = sampleCount; + } + while (index < bufSize) { + params[index++] = 0; + } + return; } + writeValues({}); return; default: MG_State::pGLContext->RecordError( diff --git a/MobileGL/MG_Test/Texture/TextureTest.cpp b/MobileGL/MG_Test/Texture/TextureTest.cpp index a28a7f40..22d0dfd3 100644 --- a/MobileGL/MG_Test/Texture/TextureTest.cpp +++ b/MobileGL/MG_Test/Texture/TextureTest.cpp @@ -10,6 +10,7 @@ #include "Includes.h" #include "Init.h" +#include #include #include #include @@ -24,6 +25,93 @@ protected: void SetUp() override { MobileGL::Initialize(); } }; +namespace { + class FormatCapabilityBackend final : public MobileGL::MG_Backend::BackendObject { + public: + FormatCapabilityBackend() { + auto& cache = MutableFormatCapabilities(); + const auto texture2DIndex = + MobileGL::MG_Backend::GetFormatCapabilityTargetIndex(TextureTarget::Texture2D); + const auto texture3DIndex = + MobileGL::MG_Backend::GetFormatCapabilityTargetIndex(TextureTarget::Texture3D); + const auto renderbufferIndex = + MobileGL::MG_Backend::GetRenderbufferFormatCapabilityTargetIndex(); + const auto rgba8Index = static_cast(TextureInternalFormat::RGBA8); + const auto rg8Index = static_cast(TextureInternalFormat::RG8); + const auto depthStencilIndex = static_cast(TextureInternalFormat::Depth24Stencil8); + + cache.FullCaps[texture2DIndex][rgba8Index] |= MobileGL::MG_Backend::FormatCapability::Creatable; + cache.FullCaps[texture2DIndex][rgba8Index] |= MobileGL::MG_Backend::FormatCapability::Sampled; + cache.FullCaps[texture2DIndex][rgba8Index] |= MobileGL::MG_Backend::FormatCapability::LinearFilter; + cache.FullCaps[texture2DIndex][rgba8Index] |= MobileGL::MG_Backend::FormatCapability::GenerateMipmap; + cache.FullCaps[texture2DIndex][rgba8Index] |= + MobileGL::MG_Backend::FormatCapability::FramebufferRenderable; + cache.FullCaps[texture2DIndex][rgba8Index] |= MobileGL::MG_Backend::FormatCapability::ColorAttachment; + cache.SampleCounts[texture2DIndex][rgba8Index] = {1}; + + cache.CaveatCaps[texture2DIndex][rg8Index] |= MobileGL::MG_Backend::FormatCapability::Creatable; + cache.CaveatCaps[texture2DIndex][rg8Index] |= MobileGL::MG_Backend::FormatCapability::Sampled; + cache.CaveatCaps[texture2DIndex][rg8Index] |= MobileGL::MG_Backend::FormatCapability::LinearFilter; + + cache.FullCaps[texture3DIndex][depthStencilIndex] |= + MobileGL::MG_Backend::FormatCapability::Creatable; + cache.FullCaps[texture3DIndex][depthStencilIndex] |= + MobileGL::MG_Backend::FormatCapability::FramebufferRenderable; + cache.FullCaps[texture3DIndex][depthStencilIndex] |= + MobileGL::MG_Backend::FormatCapability::FramebufferLayered; + cache.FullCaps[texture3DIndex][depthStencilIndex] |= + MobileGL::MG_Backend::FormatCapability::DepthAttachment; + cache.FullCaps[texture3DIndex][depthStencilIndex] |= + MobileGL::MG_Backend::FormatCapability::StencilAttachment; + + cache.FullCaps[renderbufferIndex][depthStencilIndex] |= + MobileGL::MG_Backend::FormatCapability::Creatable; + cache.FullCaps[renderbufferIndex][depthStencilIndex] |= + MobileGL::MG_Backend::FormatCapability::FramebufferRenderable; + cache.FullCaps[renderbufferIndex][depthStencilIndex] |= + MobileGL::MG_Backend::FormatCapability::DepthAttachment; + cache.FullCaps[renderbufferIndex][depthStencilIndex] |= + MobileGL::MG_Backend::FormatCapability::StencilAttachment; + cache.FullCaps[renderbufferIndex][depthStencilIndex] |= + MobileGL::MG_Backend::FormatCapability::MultisampleRenderbuffer; + cache.SampleCounts[renderbufferIndex][depthStencilIndex] = {4, 2, 1}; + } + + void Initialize() override {} + Bool InitCapabilities() override { return true; } + Bool InitWindowSurface() override { return true; } + const RendererInfo& GetRendererInfo() const override { + static RendererInfo info = {}; + return info; + } + String GetBackendAPIVersionString() const override { return {}; } + const MobileGL::MG_Backend::GlobalBackendFunctionsTable& GetBackendFunctions() const override { + static MobileGL::MG_Backend::GlobalBackendFunctionsTable table = {}; + return table; + } + const MobileGL::MG_Backend::DynamicBackendParameters& GetDynamicParameters() const override { + static MobileGL::MG_Backend::DynamicBackendParameters params = {}; + return params; + } + BackendType GetBackendType() const override { return BackendType::Unknown; } + }; + + class ScopedBackendOverride { + public: + explicit ScopedBackendOverride(UniquePtr backend): + m_previous(Move(MobileGL::MG_Backend::pActiveBackendObject)) { + MobileGL::MG_Backend::pActiveBackendObject = Move(backend); + } + + ~ScopedBackendOverride() { + MobileGL::MG_Backend::pActiveBackendObject = Move(m_previous); + } + + private: + UniquePtr m_previous; + }; +} // namespace + TEST_F(TextureTest, CreateTexturesCreatesObjectsWithoutBinding) { GLuint texture = 0; MG_Impl::GLImpl::CreateTextures(GL_TEXTURE_2D, 1, &texture); @@ -346,20 +434,47 @@ TEST_F(TextureTest, NamedTextureVectorParametersAndGettersWorkWithoutBinding) { } TEST_F(TextureTest, GetInternalformativReportsBasicTextureMetadata) { + ScopedBackendOverride backend(MakeUnique()); GLint params[4] = {}; MG_Impl::GLImpl::GetInternalformativ(GL_TEXTURE_2D, GL_RGBA8, GL_INTERNALFORMAT_SUPPORTED, 1, params); EXPECT_EQ(params[0], GL_TRUE); + MG_Impl::GLImpl::GetInternalformativ(GL_TEXTURE_2D, GL_RGBA8, GL_FRAMEBUFFER_RENDERABLE, 1, params); + EXPECT_EQ(params[0], GL_FULL_SUPPORT); + + MG_Impl::GLImpl::GetInternalformativ(GL_TEXTURE_2D, GL_RGBA8, GL_FILTER, 1, params); + EXPECT_EQ(params[0], GL_FULL_SUPPORT); + + MG_Impl::GLImpl::GetInternalformativ(GL_TEXTURE_2D, GL_RGBA8, GL_INTERNALFORMAT_RED_SIZE, 1, params); + EXPECT_EQ(params[0], 8); + + MG_Impl::GLImpl::GetInternalformativ(GL_TEXTURE_2D, GL_RGBA8, GL_INTERNALFORMAT_RED_TYPE, 1, params); + EXPECT_EQ(params[0], GL_UNSIGNED_NORMALIZED); + MG_Impl::GLImpl::GetInternalformativ(GL_TEXTURE_2D, GL_RGBA8, GL_TEXTURE_IMAGE_FORMAT, 1, params); EXPECT_EQ(params[0], GL_RGBA); MG_Impl::GLImpl::GetInternalformativ(GL_TEXTURE_2D, GL_RGBA8, GL_TEXTURE_IMAGE_TYPE, 1, params); EXPECT_EQ(params[0], GL_UNSIGNED_BYTE); + MG_Impl::GLImpl::GetInternalformativ(GL_TEXTURE_2D, GL_RG8, GL_INTERNALFORMAT_SUPPORTED, 1, params); + EXPECT_EQ(params[0], GL_TRUE); + + MG_Impl::GLImpl::GetInternalformativ(GL_TEXTURE_2D, GL_RG8, GL_FILTER, 1, params); + EXPECT_EQ(params[0], GL_CAVEAT_SUPPORT); + MG_Impl::GLImpl::GetInternalformativ(GL_TEXTURE_3D, GL_DEPTH24_STENCIL8, GL_FRAMEBUFFER_RENDERABLE_LAYERED, 1, params); EXPECT_EQ(params[0], GL_FULL_SUPPORT); + + MG_Impl::GLImpl::GetInternalformativ(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, GL_NUM_SAMPLE_COUNTS, 1, params); + EXPECT_EQ(params[0], 3); + + MG_Impl::GLImpl::GetInternalformativ(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, GL_SAMPLES, 4, params); + EXPECT_EQ(params[0], 4); + EXPECT_EQ(params[1], 2); + EXPECT_EQ(params[2], 1); EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR); }