mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-07 19:58:32 +09:00
[Fix, Test] (MG_Impl, MG_State): GL entry points record errors instead of throwing through the C ABI - CopyTexImage superset rule, TEXTURE_BUFFER level queries, indexed cap toggles
This commit is contained in:
@@ -2809,7 +2809,14 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
break;
|
||||
}
|
||||
default:
|
||||
THROW_UNIMPL_EXCEPTION;
|
||||
// TextureStorageType is {Mipmap, Buffer}, both handled above, so this is a
|
||||
// backstop for a state object that grew a new storage kind. Skipping the upload
|
||||
// renders wrong; throwing unwinds through the C GL ABI and kills the process.
|
||||
MGLOG_I("DirectGLES texture sync: no upload path for storage type %d on texture %u; "
|
||||
"skipping this sync",
|
||||
static_cast<int>(stateTextureObject->GetStorageType()),
|
||||
stateTextureObject->GetExternalIndex());
|
||||
break;
|
||||
}
|
||||
|
||||
DebugImpl::ErrorLopper::Loop([file = __FILE__, line = __LINE__, func = __func__](GLenum err) {
|
||||
|
||||
@@ -613,6 +613,23 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller,
|
||||
"Compressed texture formats are not supported."));
|
||||
}
|
||||
|
||||
// glGetTexLevelParameter{i,f}v answers WIDTH/HEIGHT/DEPTH out of the mipmap chain. The only
|
||||
// other storage type the state layer knows is GL_TEXTURE_BUFFER (TextureStorageType is
|
||||
// {Mipmap, Buffer}), whose level geometry this stack does not track yet. Report that instead
|
||||
// of throwing: THROW_UNIMPL_EXCEPTION unwinds a C++ exception through the C GL ABI and takes
|
||||
// the process down, which is never an acceptable answer to a query - see the same reasoning
|
||||
// above for the compressed-format path.
|
||||
void RecordUnsupportedLevelQueryStorage(const char* caller, GLenum pname) {
|
||||
MGLOG_I("%s: glGetTexLevelParameter(pname=%s) is not implemented for texture-buffer "
|
||||
"storage; recording GL_INVALID_OPERATION instead of terminating",
|
||||
caller, MG_Util::ConvertGLEnumToString(pname).c_str());
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidOperation,
|
||||
MakeUnique<GenericErrorInfo>(
|
||||
"MG_Impl/GLImpl", caller,
|
||||
"Level queries are not supported for texture-buffer storage."));
|
||||
}
|
||||
} // namespace
|
||||
|
||||
const SharedPtr<MG_State::GLState::ITextureObject>& GetTextureObjectByName(GLuint texture, const char* caller) {
|
||||
@@ -2910,7 +2927,8 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
break;
|
||||
}
|
||||
default:
|
||||
THROW_UNIMPL_EXCEPTION;
|
||||
RecordUnsupportedLevelQueryStorage("GetTexLevelParameteriv_State", pname);
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -2924,7 +2942,8 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
break;
|
||||
}
|
||||
default:
|
||||
THROW_UNIMPL_EXCEPTION;
|
||||
RecordUnsupportedLevelQueryStorage("GetTexLevelParameteriv_State", pname);
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -2938,7 +2957,8 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
break;
|
||||
}
|
||||
default:
|
||||
THROW_UNIMPL_EXCEPTION;
|
||||
RecordUnsupportedLevelQueryStorage("GetTexLevelParameteriv_State", pname);
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -3045,7 +3065,8 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
break;
|
||||
}
|
||||
default:
|
||||
THROW_UNIMPL_EXCEPTION;
|
||||
RecordUnsupportedLevelQueryStorage("GetTexLevelParameterfv_State", pname);
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -3059,7 +3080,8 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
break;
|
||||
}
|
||||
default:
|
||||
THROW_UNIMPL_EXCEPTION;
|
||||
RecordUnsupportedLevelQueryStorage("GetTexLevelParameterfv_State", pname);
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -3073,7 +3095,8 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
break;
|
||||
}
|
||||
default:
|
||||
THROW_UNIMPL_EXCEPTION;
|
||||
RecordUnsupportedLevelQueryStorage("GetTexLevelParameterfv_State", pname);
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -3403,7 +3426,10 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
GET_SRC_INTERNAL_FORMAT(readBufferType);
|
||||
}
|
||||
|
||||
if (!TextureImpl::ValidateBaseInternalFormatMatch(internalFormat, srcInternalFormat)) THROW_UNIMPL_EXCEPTION;
|
||||
// The validator has already recorded GL_INVALID_OPERATION; just decline. Throwing
|
||||
// here unwound a C++ exception through the C GL ABI and killed the process (see the
|
||||
// same reasoning at :604-609).
|
||||
if (!TextureImpl::ValidateCopyTexImageBaseFormatSubset(internalFormat, srcInternalFormat)) return false;
|
||||
|
||||
GLenum outInternalFormat = MG_Util::ConvertTextureInternalFormatToGLEnum(srcInternalFormat);
|
||||
GLenum realInternalFormat = GL_RGBA8;
|
||||
@@ -3426,8 +3452,13 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
|
||||
void CopyTexImage1D_State(GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width,
|
||||
GLint border) {
|
||||
// TODO: implement
|
||||
THROW_UNIMPL_EXCEPTION;
|
||||
// 1D textures are not implemented by this backend set. Record the error the way every
|
||||
// other unsupported entry point does - throwing unwinds through the C GL ABI and kills
|
||||
// the process, which is never an acceptable answer to an unsupported call.
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidOperation,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", "CopyTexImage1D",
|
||||
"1D textures are not supported by this implementation"));
|
||||
}
|
||||
|
||||
void CompressedTexSubImage3D_State(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset,
|
||||
|
||||
@@ -424,19 +424,81 @@ namespace MobileGL::MG_Impl::GLImpl::TextureImpl {
|
||||
return true;
|
||||
}
|
||||
|
||||
namespace {
|
||||
// Component set of an UNSIZED base internal format, as the bitmask GL 4.6 SS 8.6
|
||||
// reasons about. Colour components are independent bits so "subset" is a plain
|
||||
// mask test; depth and stencil are their own components and never satisfy a
|
||||
// colour request (or each other).
|
||||
enum : Uint32 {
|
||||
kComponentR = 1u << 0,
|
||||
kComponentG = 1u << 1,
|
||||
kComponentB = 1u << 2,
|
||||
kComponentA = 1u << 3,
|
||||
kComponentDepth = 1u << 4,
|
||||
kComponentStencil = 1u << 5,
|
||||
};
|
||||
|
||||
Uint32 BaseFormatComponents(TextureInternalFormat unsizedFormat) {
|
||||
switch (unsizedFormat) {
|
||||
case TextureInternalFormat::Red:
|
||||
return kComponentR;
|
||||
case TextureInternalFormat::RG:
|
||||
return kComponentR | kComponentG;
|
||||
case TextureInternalFormat::RGB:
|
||||
return kComponentR | kComponentG | kComponentB;
|
||||
case TextureInternalFormat::RGBA:
|
||||
return kComponentR | kComponentG | kComponentB | kComponentA;
|
||||
case TextureInternalFormat::DepthComponent:
|
||||
return kComponentDepth;
|
||||
case TextureInternalFormat::DepthStencil:
|
||||
return kComponentDepth | kComponentStencil;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
Bool ValidateBaseInternalFormatMatch(TextureInternalFormat format1, TextureInternalFormat format2) {
|
||||
auto unsizedFormat1 = MG_Util::ConvertInternalFormatToUnsized(format1);
|
||||
auto unsizedFormat2 = MG_Util::ConvertInternalFormatToUnsized(format2);
|
||||
const auto unsizedFormat1 = MG_Util::ConvertInternalFormatToUnsized(format1);
|
||||
const auto unsizedFormat2 = MG_Util::ConvertInternalFormatToUnsized(format2);
|
||||
if (unsizedFormat1 != unsizedFormat2) {
|
||||
// The 3-argument GenericErrorInfo constructor used to be spelled as a single
|
||||
// std::format() call whose format string was the component name, so every
|
||||
// diagnostic collapsed to the literal "MG_Impl/GLImpl". Format the message, then
|
||||
// hand over component/function/message separately.
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidOperation,
|
||||
MakeUnique<GenericErrorInfo>(
|
||||
std::format("MG_Impl/GLImpl", "ValidateBaseInternalFormatMatch",
|
||||
"The base internal format of the two formats do not match ({} vs. {})",
|
||||
MG_Util::ConvertTextureInternalFormatToString(unsizedFormat1).c_str(),
|
||||
MG_Util::ConvertTextureInternalFormatToString(unsizedFormat2).c_str())));
|
||||
"MG_Impl/GLImpl", "ValidateBaseInternalFormatMatch",
|
||||
std::format("The base internal format of the two formats do not match ({} vs. {})",
|
||||
MG_Util::ConvertTextureInternalFormatToString(unsizedFormat1),
|
||||
MG_Util::ConvertTextureInternalFormatToString(unsizedFormat2))));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} // namespace TextureImpl
|
||||
}
|
||||
|
||||
Bool ValidateCopyTexImageBaseFormatSubset(TextureInternalFormat destFormat, TextureInternalFormat srcFormat) {
|
||||
const auto unsizedDest = MG_Util::ConvertInternalFormatToUnsized(destFormat);
|
||||
const auto unsizedSrc = MG_Util::ConvertInternalFormatToUnsized(srcFormat);
|
||||
// GL 4.6 SS 8.6: glCopyTexImage* may request a SUBSET of the read buffer's components,
|
||||
// not an exact match - GL_RGB from an RGBA8 framebuffer is textbook legal and is what
|
||||
// Minecraft and its mods do. glCopyTexImage2D used to run the exact-match predicate
|
||||
// above and turn its rejection into an uncaught exception through the C GL ABI, so the
|
||||
// app died rather than seeing a GL error.
|
||||
const Uint32 destComponents = BaseFormatComponents(unsizedDest);
|
||||
const Uint32 srcComponents = BaseFormatComponents(unsizedSrc);
|
||||
if (destComponents == 0 || srcComponents == 0 || (destComponents & ~srcComponents) != 0) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidOperation,
|
||||
MakeUnique<GenericErrorInfo>(
|
||||
"MG_Impl/GLImpl", "ValidateCopyTexImageBaseFormatSubset",
|
||||
std::format("the read buffer's base internal format {} does not provide every component of "
|
||||
"the requested internal format {}",
|
||||
MG_Util::ConvertTextureInternalFormatToString(unsizedSrc),
|
||||
MG_Util::ConvertTextureInternalFormatToString(unsizedDest))));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
} // namespace MobileGL::MG_Impl::GLImpl::TextureImpl
|
||||
|
||||
@@ -40,5 +40,9 @@ namespace MobileGL::MG_Impl::GLImpl::TextureImpl {
|
||||
TextureTarget target);
|
||||
Bool ValidateTextureSubImageOffsets(const SharedPtr<MG_State::GLState::ITextureObject>& textureObject, Int xoffset,
|
||||
Int width, Int yoffset = 0, Int height = 0, Int zoffset = 0, Int depth = 0);
|
||||
// Exact base-format equality - what glCopyImageSubData's format compatibility needs.
|
||||
Bool ValidateBaseInternalFormatMatch(TextureInternalFormat format1, TextureInternalFormat format2);
|
||||
// GL 4.6 SS 8.6 subset rule for glCopyTexImage*: the read buffer must supply every component
|
||||
// the requested internalformat asks for, but may supply more.
|
||||
Bool ValidateCopyTexImageBaseFormatSubset(TextureInternalFormat destFormat, TextureInternalFormat srcFormat);
|
||||
} // namespace MobileGL::MG_Impl::GLImpl::TextureImpl
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
// End of Source File Header
|
||||
|
||||
#include "RenderState.h"
|
||||
#include "MG_Util/Debug/Log.h"
|
||||
#include "MG_Util/Types.h"
|
||||
|
||||
namespace MobileGL {
|
||||
@@ -268,9 +269,14 @@ namespace MobileGL {
|
||||
}
|
||||
|
||||
void RenderState::SetCapabilityIndexed(CapabilityInput cap, Uint index, Bool enabled) {
|
||||
// Only for BlendState currently
|
||||
// Only for BlendState currently. The GL entry points (glEnablei/glDisablei) already
|
||||
// reject every non-GL_BLEND target with GL_INVALID_ENUM before reaching here, so this
|
||||
// is a backstop - but it must stay a backstop: THROW_UNIMPL_EXCEPTION unwinds a C++
|
||||
// exception through the C GL ABI and terminates the process.
|
||||
if (cap != CapabilityInput::Blend) {
|
||||
THROW_UNIMPL_EXCEPTION;
|
||||
MGLOG_I("RenderState::SetCapabilityIndexed: indexed capability state exists only for "
|
||||
"GL_BLEND (cap=%d, index=%u); ignoring",
|
||||
static_cast<int>(cap), index);
|
||||
return;
|
||||
}
|
||||
if (index >= MG_State::GLState::FramebufferObject::MAX_DRAW_BUFFERS) {
|
||||
@@ -284,9 +290,13 @@ namespace MobileGL {
|
||||
}
|
||||
|
||||
Bool RenderState::IsCapabilityEnabledIndexed(CapabilityInput cap, Uint index) const {
|
||||
// Only for BlendState currently
|
||||
// Only for BlendState currently - same backstop reasoning as SetCapabilityIndexed:
|
||||
// glIsEnabledi has already answered GL_INVALID_ENUM/GL_FALSE for anything else, and a
|
||||
// query must never be able to terminate the process.
|
||||
if (cap != CapabilityInput::Blend) {
|
||||
THROW_UNIMPL_EXCEPTION;
|
||||
MGLOG_I("RenderState::IsCapabilityEnabledIndexed: indexed capability state exists only "
|
||||
"for GL_BLEND (cap=%d, index=%u); reporting disabled",
|
||||
static_cast<int>(cap), index);
|
||||
return false;
|
||||
}
|
||||
if (index >= MG_State::GLState::FramebufferObject::MAX_DRAW_BUFFERS) {
|
||||
|
||||
@@ -25,3 +25,28 @@ endif()
|
||||
|
||||
include(GoogleTest)
|
||||
gtest_discover_tests(ObjectLifetimeIdTest DISCOVERY_TIMEOUT 30 PROPERTIES LABELS unit)
|
||||
|
||||
add_executable(
|
||||
RenderStateTest
|
||||
RenderStateTest.cpp
|
||||
)
|
||||
|
||||
target_include_directories(RenderStateTest PRIVATE
|
||||
${MGL_ROOT}/include
|
||||
${MGL_ROOT}/MobileGL
|
||||
${MGL_ROOT}/3rdparty/xxHash
|
||||
${MGL_ROOT}/3rdparty/Vulkan-Headers/include
|
||||
${MGL_ROOT}/3rdparty/SPIRV-Reflect
|
||||
)
|
||||
|
||||
target_link_libraries(
|
||||
RenderStateTest PRIVATE
|
||||
GTest::gtest_main
|
||||
${LINK_LIBRARIES}
|
||||
)
|
||||
|
||||
if (MSVC)
|
||||
target_compile_options(RenderStateTest PRIVATE /Zc:preprocessor)
|
||||
endif()
|
||||
|
||||
gtest_discover_tests(RenderStateTest DISCOVERY_TIMEOUT 30 PROPERTIES LABELS unit)
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
// MobileGL - MobileGL/MG_Test/State/RenderStateTest.cpp
|
||||
// Copyright (c) 2025-2026 MobileGL-Dev
|
||||
// Licensed under the GNU Lesser General Public License v3.0:
|
||||
// https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
// https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
// End of Source File Header
|
||||
//
|
||||
// Indexed capability state (glEnablei/glDisablei/glIsEnabledi) exists only for GL_BLEND in this
|
||||
// stack. Every other capability must come back as GL_INVALID_ENUM per GL 4.6 sec. 17.3.3 - and,
|
||||
// far more importantly, must come back at all: RenderState::SetCapabilityIndexed and
|
||||
// IsCapabilityEnabledIndexed used to answer a non-blend capability with THROW_UNIMPL_EXCEPTION,
|
||||
// which unwinds a C++ exception through the C GL ABI and terminates the process.
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "Includes.h"
|
||||
#include "Init.h"
|
||||
|
||||
#include <MG_Impl/GLImpl/Getter/GL_Getter.h>
|
||||
#include <MG_Impl/GLImpl/RenderState/GL_RenderState.h>
|
||||
#include <MG_State/GLState/Core.h>
|
||||
#include <MG_State/GLState/FramebufferState/FramebufferObject.h>
|
||||
|
||||
using namespace MobileGL;
|
||||
|
||||
namespace {
|
||||
class RenderStateTest: public ::testing::Test {
|
||||
protected:
|
||||
// GL error flags are sticky per code and the context outlives an individual test in this
|
||||
// binary, so a pending error from an earlier case would be handed to the next GetError().
|
||||
static void DrainPendingGlErrors() {
|
||||
for (Int drained = 0; drained < 16 && MG_Impl::GLImpl::GetError() != GL_NO_ERROR; ++drained) {
|
||||
}
|
||||
}
|
||||
|
||||
static void ExpectSingleGlError(GLenum expected) {
|
||||
EXPECT_EQ(MG_Impl::GLImpl::GetError(), expected);
|
||||
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR) << "the call recorded more than one error";
|
||||
}
|
||||
|
||||
void SetUp() override {
|
||||
MobileGL::Initialize();
|
||||
DrainPendingGlErrors();
|
||||
}
|
||||
|
||||
void TearDown() override {
|
||||
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR) << "test left an unconsumed GL error behind";
|
||||
}
|
||||
};
|
||||
} // namespace
|
||||
|
||||
TEST_F(RenderStateTest, IndexedCapabilityTogglesRejectNonBlendCapabilities) {
|
||||
// GL_CLIP_DISTANCE0 is a real capability, just not an indexed one - the shape an application or
|
||||
// a CTS negative test would hit.
|
||||
for (const GLenum cap : {GL_CLIP_DISTANCE0, GL_DEPTH_TEST, GL_SCISSOR_TEST}) {
|
||||
MG_Impl::GLImpl::Enablei(cap, 0);
|
||||
ExpectSingleGlError(GL_INVALID_ENUM);
|
||||
|
||||
MG_Impl::GLImpl::Disablei(cap, 0);
|
||||
ExpectSingleGlError(GL_INVALID_ENUM);
|
||||
|
||||
EXPECT_EQ(MG_Impl::GLImpl::IsEnabledi(cap, 0), GL_FALSE);
|
||||
ExpectSingleGlError(GL_INVALID_ENUM);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(RenderStateTest, IndexedCapabilityTogglesRejectAnOutOfRangeBufferIndex) {
|
||||
const GLuint outOfRange = MG_State::GLState::FramebufferObject::MAX_DRAW_BUFFERS;
|
||||
|
||||
MG_Impl::GLImpl::Enablei(GL_BLEND, outOfRange);
|
||||
ExpectSingleGlError(GL_INVALID_VALUE);
|
||||
|
||||
MG_Impl::GLImpl::Disablei(GL_BLEND, outOfRange);
|
||||
ExpectSingleGlError(GL_INVALID_VALUE);
|
||||
|
||||
EXPECT_EQ(MG_Impl::GLImpl::IsEnabledi(GL_BLEND, outOfRange), GL_FALSE);
|
||||
ExpectSingleGlError(GL_INVALID_VALUE);
|
||||
}
|
||||
|
||||
TEST_F(RenderStateTest, IndexedBlendTogglesStillWork) {
|
||||
// The rejection path must not have cost the one capability that is genuinely indexed.
|
||||
MG_Impl::GLImpl::Enablei(GL_BLEND, 1);
|
||||
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
|
||||
EXPECT_EQ(MG_Impl::GLImpl::IsEnabledi(GL_BLEND, 1), GL_TRUE);
|
||||
|
||||
MG_Impl::GLImpl::Disablei(GL_BLEND, 1);
|
||||
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
|
||||
EXPECT_EQ(MG_Impl::GLImpl::IsEnabledi(GL_BLEND, 1), GL_FALSE);
|
||||
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
|
||||
}
|
||||
@@ -3176,3 +3176,115 @@ TEST_F(TextureTest, WidenedRenderTargetUploadExpandsThreeChannelDataWithOpaqueAl
|
||||
EXPECT_EQ(PrepareChannelWidenedUpload(3, texelSize, nullptr, 0, GL_FLOAT, widened), nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
// A GL entry point may return an error, but it may never throw through the C GL ABI: unwinding a
|
||||
// C++ exception across it terminates the process. These cover the sites that used to do exactly
|
||||
// that (KHR-GL30.api.coverage died on the first of them on both backends).
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
|
||||
namespace {
|
||||
struct CopyTexImage2DCall {
|
||||
Bool Called = false;
|
||||
GLenum Target = 0;
|
||||
GLint Level = 0;
|
||||
GLenum InternalFormat = 0;
|
||||
GLsizei Width = 0;
|
||||
GLsizei Height = 0;
|
||||
};
|
||||
|
||||
CopyTexImage2DCall g_copyTexImage2DCall;
|
||||
|
||||
void RecordCopyTexImage2D(GLenum target, GLint level, GLenum internalformat, GLint, GLint, GLsizei width,
|
||||
GLsizei height, GLint) {
|
||||
g_copyTexImage2DCall = {true, target, level, internalformat, width, height};
|
||||
}
|
||||
|
||||
// A colour read framebuffer of the requested sized format, bound to GL_READ_FRAMEBUFFER, which
|
||||
// is what glCopyTexImage2D takes its source base format from.
|
||||
void BindReadFramebufferWithColorFormat(GLenum sizedInternalFormat) {
|
||||
GLuint framebuffer = 0;
|
||||
GLuint texture = 0;
|
||||
MG_Impl::GLImpl::CreateFramebuffers(1, &framebuffer);
|
||||
MG_Impl::GLImpl::CreateTextures(GL_TEXTURE_2D, 1, &texture);
|
||||
MG_Impl::GLImpl::TextureStorage2D(texture, 1, sizedInternalFormat, 16, 16);
|
||||
MG_Impl::GLImpl::NamedFramebufferTexture(framebuffer, GL_COLOR_ATTACHMENT0, texture, 0);
|
||||
MG_Impl::GLImpl::BindFramebuffer(GL_READ_FRAMEBUFFER, framebuffer);
|
||||
}
|
||||
|
||||
GLuint BindFreshMutableTexture2D() {
|
||||
GLuint texture = 0;
|
||||
MG_Impl::GLImpl::CreateTextures(GL_TEXTURE_2D, 1, &texture);
|
||||
MG_Impl::GLImpl::BindTexture(GL_TEXTURE_2D, texture);
|
||||
return texture;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST_F(TextureTest, CopyTexImage2DAcceptsEveryComponentSubsetOfTheReadBuffer) {
|
||||
const ScopedTextureBackendFunctionsOverride backendGuard;
|
||||
MG_Backend::gBackendFunctionsTable.GL.CopyTexImage2D = RecordCopyTexImage2D;
|
||||
|
||||
BindReadFramebufferWithColorFormat(GL_RGBA8);
|
||||
ASSERT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR) << "read framebuffer setup itself failed";
|
||||
|
||||
// GL 4.6 sec. 8.6: internalformat may name a SUBSET of the read buffer's components. This is
|
||||
// exactly the list KHR-GL30.api.coverage walks against an rgba8888 colour buffer, and it is
|
||||
// also what an ordinary GL app does with glCopyTexImage2D(GL_RGB) from an RGBA8 framebuffer.
|
||||
for (const GLenum internalFormat : {GL_RED, GL_RG, GL_RGB, GL_RGBA}) {
|
||||
BindFreshMutableTexture2D();
|
||||
g_copyTexImage2DCall = {};
|
||||
|
||||
MG_Impl::GLImpl::CopyTexImage2D(GL_TEXTURE_2D, 0, internalFormat, 0, 0, 1, 1, 0);
|
||||
|
||||
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR) << "internalformat " << internalFormat;
|
||||
EXPECT_TRUE(g_copyTexImage2DCall.Called) << "internalformat " << internalFormat;
|
||||
EXPECT_EQ(g_copyTexImage2DCall.InternalFormat, internalFormat);
|
||||
EXPECT_EQ(g_copyTexImage2DCall.Width, 1);
|
||||
EXPECT_EQ(g_copyTexImage2DCall.Height, 1);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(TextureTest, CopyTexImage2DRejectsAFormatTheReadBufferCannotSupply) {
|
||||
const ScopedTextureBackendFunctionsOverride backendGuard;
|
||||
MG_Backend::gBackendFunctionsTable.GL.CopyTexImage2D = RecordCopyTexImage2D;
|
||||
|
||||
BindReadFramebufferWithColorFormat(GL_R8);
|
||||
ASSERT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR) << "read framebuffer setup itself failed";
|
||||
|
||||
BindFreshMutableTexture2D();
|
||||
g_copyTexImage2DCall = {};
|
||||
|
||||
// The subset rule still has a wrong side: GL_RGBA asks for components a GL_R8 read buffer does
|
||||
// not have. That must be GL_INVALID_OPERATION and nothing else - not a throw, not silence.
|
||||
MG_Impl::GLImpl::CopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 0, 0, 1, 1, 0);
|
||||
|
||||
ExpectSingleGlError(GL_INVALID_OPERATION);
|
||||
EXPECT_FALSE(g_copyTexImage2DCall.Called) << "a rejected copy must not reach the backend";
|
||||
}
|
||||
|
||||
TEST_F(TextureTest, CopyTexImage1DReportsUnsupportedInsteadOfTerminating) {
|
||||
// 1D textures have no upload path in this stack; the entry point used to throw unconditionally.
|
||||
MG_Impl::GLImpl::CopyTexImage1D(GL_TEXTURE_1D, 0, GL_RGBA, 0, 0, 1, 0);
|
||||
ExpectSingleGlError(GL_INVALID_OPERATION);
|
||||
}
|
||||
|
||||
TEST_F(TextureTest, GetTexLevelParameterOnBufferStorageReportsErrorInsteadOfTerminating) {
|
||||
// TextureStorageType is {Mipmap, Buffer} and the level queries only answer out of a mipmap
|
||||
// chain, so every glGetTexLevelParameter* on a GL_TEXTURE_BUFFER texture reached a
|
||||
// THROW_UNIMPL_EXCEPTION default: label and killed the process.
|
||||
GLuint texture = 0;
|
||||
MG_Impl::GLImpl::CreateTextures(GL_TEXTURE_BUFFER, 1, &texture);
|
||||
MG_Impl::GLImpl::BindTexture(GL_TEXTURE_BUFFER, texture);
|
||||
MG_Impl::GLImpl::TexBuffer(GL_TEXTURE_BUFFER, GL_R8, 0);
|
||||
DrainPendingGlErrors();
|
||||
|
||||
for (const GLenum pname : {GL_TEXTURE_WIDTH, GL_TEXTURE_HEIGHT, GL_TEXTURE_DEPTH}) {
|
||||
GLint intParam = 0x20202020;
|
||||
MG_Impl::GLImpl::GetTexLevelParameteriv(GL_TEXTURE_BUFFER, 0, pname, &intParam);
|
||||
ExpectSingleGlError(GL_INVALID_OPERATION);
|
||||
|
||||
GLfloat floatParam = 12345.0f;
|
||||
MG_Impl::GLImpl::GetTexLevelParameterfv(GL_TEXTURE_BUFFER, 0, pname, &floatParam);
|
||||
ExpectSingleGlError(GL_INVALID_OPERATION);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user