mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-07 19:58:32 +09:00
[Test] (Review): border-colour clamping, a readback across a mip gap, and blending on renderbuffer formats with no exact VkFormat
This commit is contained in:
@@ -114,6 +114,7 @@ add_executable(MobileGLIntegrationTest
|
||||
Scenarios/UnboundImageDescriptorScenario.cpp
|
||||
Scenarios/IntegerBorderColorScenario.cpp
|
||||
Scenarios/ClearTexImageUndefinedLevelZeroScenario.cpp
|
||||
Scenarios/RenderbufferBlendFormatScenario.cpp
|
||||
)
|
||||
|
||||
target_include_directories(MobileGLIntegrationTest PRIVATE
|
||||
|
||||
@@ -135,6 +135,26 @@ namespace MGITest {
|
||||
<< offenders << " of " << pixels.size() << " texels wrong)";
|
||||
}
|
||||
|
||||
// Level 0 defined, a GAP, then `level` defined. GL keeps the intervening levels at a zero
|
||||
// extent, so the backend's mip walk stops at the gap and the VkImage ends up with FEWER
|
||||
// mip levels than the GL level count - which is a different shape from "no image at all"
|
||||
// and is why the readback has to bound the level against the IMAGE.
|
||||
void MakeTextureWithAGapBefore(GLint level) {
|
||||
if (m_texture != 0) glDeleteTextures(1, &m_texture);
|
||||
glGenTextures(1, &m_texture);
|
||||
glBindTexture(GL_TEXTURE_2D, m_texture);
|
||||
const std::vector<Texel8> base(static_cast<std::size_t>(kLevelExtent) * kLevelExtent, kInitialValue);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, kLevelExtent, kLevelExtent, 0, GL_RGBA, GL_UNSIGNED_BYTE,
|
||||
base.data());
|
||||
const std::vector<Texel8> gapped(static_cast<std::size_t>(kLevelExtent) * kLevelExtent, kInitialValue);
|
||||
glTexImage2D(GL_TEXTURE_2D, level, GL_RGBA8, kLevelExtent, kLevelExtent, 0, GL_RGBA,
|
||||
GL_UNSIGNED_BYTE, gapped.data());
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, level);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
ASSERT_EQ(FirstGLError(), 0u) << "texture setup with a gap before level " << level;
|
||||
}
|
||||
|
||||
GLuint m_texture = 0;
|
||||
};
|
||||
|
||||
@@ -201,4 +221,30 @@ namespace MGITest {
|
||||
Gl().EndFrame();
|
||||
}
|
||||
|
||||
// The adjacent shape the first fix did NOT cover: level 0 defined, a gap, then the level being
|
||||
// read. This one DOES get a VkImage - just one with fewer mip levels than GL thinks the texture
|
||||
// has - so the "no VkImage" test passes and the GL level was written straight into
|
||||
// imageSubresource.mipLevel and into a VkImageMemoryBarrier's baseMipLevel. An out-of-range
|
||||
// subresource is a promise the driver takes at face value; the glCopyImageSubData path two
|
||||
// functions away grew the same guard after it SIGSEGV'd inside the Adreno driver.
|
||||
//
|
||||
// The level being read really does hold its own data (the shadow is its only copy, since nothing
|
||||
// ever uploaded it), so the correct answer is the uploaded bytes - not a decline.
|
||||
TEST_F(ClearTexImageUndefinedLevelZeroScenario, ReadBackALevelSeparatedFromLevelZeroByAGap) {
|
||||
if (!Ready()) GTEST_SKIP();
|
||||
MakeTextureWithAGapBefore(kDefinedLevel);
|
||||
|
||||
ExpectAllTexels("before the clear", ReadLevel(kDefinedLevel), kInitialValue);
|
||||
|
||||
glClearTexImage(m_texture, kDefinedLevel, GL_RGBA, GL_UNSIGNED_BYTE, &kClearValue);
|
||||
EXPECT_EQ(FirstGLError(), 0u) << "glClearTexImage was rejected";
|
||||
|
||||
ExpectAllTexels("after the clear", ReadLevel(kDefinedLevel), kClearValue);
|
||||
|
||||
// Level 0 is backed by the real image and must still read back from it, so the level bound is
|
||||
// about the level and not about the texture.
|
||||
ExpectAllTexels("level 0 after clearing level 3", ReadLevel(0), kInitialValue);
|
||||
Gl().EndFrame();
|
||||
}
|
||||
|
||||
} // namespace MGITest
|
||||
|
||||
@@ -130,6 +130,7 @@ void main()
|
||||
if (m_fbo != 0) glDeleteFramebuffers(1, &m_fbo);
|
||||
if (m_outputTexture != 0) glDeleteTextures(1, &m_outputTexture);
|
||||
if (m_sourceTexture != 0) glDeleteTextures(1, &m_sourceTexture);
|
||||
if (m_narrowTexture != 0) glDeleteTextures(1, &m_narrowTexture);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
}
|
||||
|
||||
@@ -195,11 +196,69 @@ void main()
|
||||
}
|
||||
}
|
||||
|
||||
// A narrow-format source built on demand, for the clamp cases. Returns the texture, which
|
||||
// the caller owns until TearDown deletes it through m_narrowTexture.
|
||||
void MakeNarrowSource(GLenum internalFormat, GLenum clientFormat, const void* texels,
|
||||
const GLint* border, bool borderIsUnsigned) {
|
||||
glGenTextures(1, &m_narrowTexture);
|
||||
glBindTexture(GL_TEXTURE_2D, m_narrowTexture);
|
||||
glTexStorage2D(GL_TEXTURE_2D, 1, internalFormat, 2, 2);
|
||||
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 2, 2, clientFormat,
|
||||
internalFormat == GL_R8UI ? GL_UNSIGNED_BYTE : GL_BYTE, texels);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
|
||||
if (borderIsUnsigned) {
|
||||
const GLuint asUnsigned[4] = {static_cast<GLuint>(border[0]), static_cast<GLuint>(border[1]),
|
||||
static_cast<GLuint>(border[2]), static_cast<GLuint>(border[3])};
|
||||
glTexParameterIuiv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, asUnsigned);
|
||||
} else {
|
||||
glTexParameterIiv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, border);
|
||||
}
|
||||
ASSERT_EQ(FirstGLError(), 0u) << "narrow source setup left a GL error behind";
|
||||
}
|
||||
|
||||
// The narrow sources are single-channel, so only component 0 carries anything, and the
|
||||
// sampler declaration has to match the format's signedness.
|
||||
std::vector<std::int32_t> RenderNarrowBorder(bool isUnsignedSampler) {
|
||||
const std::string fragment =
|
||||
std::string("#version 330 core\n\nuniform ") + (isUnsignedSampler ? "usampler2D" : "isampler2D") +
|
||||
" smp;\nuniform vec2 uCoord;\n\nout int out_color;\n\nvoid main()\n{\n"
|
||||
" out_color = int(texture(smp, uCoord).x);\n}\n";
|
||||
std::string error;
|
||||
const unsigned int program = CompileProgram(kVertexSource, fragment.c_str(), &error);
|
||||
if (program == 0) {
|
||||
ADD_FAILURE() << "narrow-border program did not build: " << error;
|
||||
return {};
|
||||
}
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, m_fbo);
|
||||
glViewport(0, 0, kOutputWidth, kOutputHeight);
|
||||
glDisable(GL_SCISSOR_TEST);
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
const GLint clearValue[4] = {-559038737, 0, 0, 0};
|
||||
glClearBufferiv(GL_COLOR, 0, clearValue);
|
||||
glUseProgram(program);
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glBindTexture(GL_TEXTURE_2D, m_narrowTexture);
|
||||
glUniform1i(glGetUniformLocation(program, "smp"), 0);
|
||||
glUniform2f(glGetUniformLocation(program, "uCoord"), -0.5f, -0.5f);
|
||||
glBindVertexArray(m_vao);
|
||||
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
|
||||
glBindVertexArray(0);
|
||||
std::vector<std::int32_t> texels(static_cast<std::size_t>(kOutputWidth) * kOutputHeight, 0);
|
||||
glReadPixels(0, 0, kOutputWidth, kOutputHeight, GL_RED_INTEGER, GL_INT, texels.data());
|
||||
glUseProgram(0);
|
||||
glDeleteProgram(program);
|
||||
return texels;
|
||||
}
|
||||
|
||||
GLuint m_sourceTexture = 0;
|
||||
GLuint m_outputTexture = 0;
|
||||
GLuint m_fbo = 0;
|
||||
GLuint m_vao = 0;
|
||||
GLuint m_sampler = 0;
|
||||
GLuint m_narrowTexture = 0;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
@@ -265,4 +324,68 @@ void main()
|
||||
Gl().EndFrame();
|
||||
}
|
||||
|
||||
// GL 4.6 core 8.14.2: "For floating-point and integer formats, border values are clamped to the
|
||||
// representable range of the format." A border of 300 on a GL_R8I texture is 127, not 300 - and
|
||||
// VK_BORDER_COLOR_INT_CUSTOM_EXT delivers whatever it is handed, with format VK_FORMAT_UNDEFINED
|
||||
// there is nothing for the driver to clamp against, so the clamp has to happen before the value
|
||||
// leaves MobileGL. DirectGLES gets it right for free (the ES driver knows the texture format),
|
||||
// which is what makes this a cross-backend divergence and not only a spec one.
|
||||
TEST_F(IntegerBorderColorScenario, ASignedIntegerBorderIsClampedToTheFormatsRepresentableRange) {
|
||||
if (!Ready()) GTEST_SKIP();
|
||||
|
||||
const std::int8_t texels[4] = {1, 1, 1, 1};
|
||||
const GLint border[4] = {300, 0, 0, 1};
|
||||
MakeNarrowSource(GL_R8I, GL_RED_INTEGER, texels, border, /*borderIsUnsigned=*/false);
|
||||
|
||||
const std::vector<std::int32_t> sampled = RenderNarrowBorder(/*isUnsignedSampler=*/false);
|
||||
EXPECT_EQ(FirstGLError(), 0u) << "the clamped-border draw left a GL error behind";
|
||||
ExpectAllTexels("R8I border 300", 0, 127, sampled);
|
||||
Gl().EndFrame();
|
||||
}
|
||||
|
||||
// The reciprocal half, and the one that decides how the two integer forms relate: -1 written
|
||||
// through glTexParameterIiv against an UNSIGNED format. GL 4.6 core 8.10 stores an "I"-form
|
||||
// border unmodified with an integer internal data type and defines no sign conversion between
|
||||
// the two integer forms, so the stored bits are reinterpreted in the sampled format's own
|
||||
// signedness: 0xFFFFFFFF, clamped to the format's maximum of 255.
|
||||
//
|
||||
// That is the DRIVER's answer, established by running this case rather than by reading the spec:
|
||||
// clamping to 0 is an equally defensible reading of the same paragraph, and DirectVulkan can be
|
||||
// made to produce either - but DirectGLES forwards the value to the ES driver verbatim and cannot
|
||||
// deviate, so choosing 0 would mean the same program sampling 0 on Magma and 255 on Espryt. The
|
||||
// whole point of carrying the border colour's form is to stop that class of divergence, so the
|
||||
// backends agree on the driver's answer.
|
||||
//
|
||||
// The clamp itself is still doing the work: without it the value reaches the driver as
|
||||
// 0xFFFFFFFF against a format whose maximum is 255, with format VK_FORMAT_UNDEFINED and so
|
||||
// nothing for the driver to clamp against.
|
||||
TEST_F(IntegerBorderColorScenario, ANegativeBorderOnAnUnsignedFormatClampsToTheFormatsMaximum) {
|
||||
if (!Ready()) GTEST_SKIP();
|
||||
|
||||
const std::uint8_t texels[4] = {1, 1, 1, 1};
|
||||
const GLint border[4] = {-1, 0, 0, 1};
|
||||
MakeNarrowSource(GL_R8UI, GL_RED_INTEGER, texels, border, /*borderIsUnsigned=*/false);
|
||||
|
||||
const std::vector<std::int32_t> sampled = RenderNarrowBorder(/*isUnsignedSampler=*/true);
|
||||
EXPECT_EQ(FirstGLError(), 0u) << "the clamped-border draw left a GL error behind";
|
||||
ExpectAllTexels("R8UI border -1", 0, 255, sampled);
|
||||
Gl().EndFrame();
|
||||
}
|
||||
|
||||
// The same clamp from the unambiguous side: a value written through the UNSIGNED form that is
|
||||
// simply too large for the format. No sign reinterpretation is involved, so both backends and
|
||||
// the spec agree that 5000 on a GL_R8UI texture is 255.
|
||||
TEST_F(IntegerBorderColorScenario, AnOversizedUnsignedBorderIsClampedToTheFormatsMaximum) {
|
||||
if (!Ready()) GTEST_SKIP();
|
||||
|
||||
const std::uint8_t texels[4] = {1, 1, 1, 1};
|
||||
const GLint border[4] = {5000, 0, 0, 1};
|
||||
MakeNarrowSource(GL_R8UI, GL_RED_INTEGER, texels, border, /*borderIsUnsigned=*/true);
|
||||
|
||||
const std::vector<std::int32_t> sampled = RenderNarrowBorder(/*isUnsignedSampler=*/true);
|
||||
EXPECT_EQ(FirstGLError(), 0u) << "the clamped-border draw left a GL error behind";
|
||||
ExpectAllTexels("R8UI border 5000", 0, 255, sampled);
|
||||
Gl().EndFrame();
|
||||
}
|
||||
|
||||
} // namespace MGITest
|
||||
|
||||
@@ -0,0 +1,227 @@
|
||||
// MobileGL - MobileGL/MG_IntegrationTest/Scenarios/RenderbufferBlendFormatScenario.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
|
||||
//
|
||||
// Scenario - BLENDING WORKS ON A RENDERBUFFER WHOSE GL FORMAT HAS NO EXACT VkFormat.
|
||||
//
|
||||
// DirectVulkan force-disables blending on an attachment whose VkFormat lacks
|
||||
// VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT, which is the right thing to do - blending on such a
|
||||
// format is invalid pipeline state. The probe has to ask about the format the attachment ACTUALLY
|
||||
// has, and for renderbuffers it asked a different question from the one that created the image: the
|
||||
// image comes from ResolveTextureFormatInfo (which widens GL formats with no Vulkan twin onto a real
|
||||
// one) while the probe used the strict 1:1 converter, which answers VK_FORMAT_UNDEFINED for RGBA2,
|
||||
// RGBA12, RGB10, RGB12, RGB16 and the three-channel formats, and the 16-bit packed formats for RGBA4
|
||||
// and RGB5_A1.
|
||||
//
|
||||
// VkFormatProperties for VK_FORMAT_UNDEFINED are all zero, so the probe concluded "not blendable"
|
||||
// and every pipeline for that attachment was built with blendEnable = VK_FALSE - permanently, and
|
||||
// silently apart from one log line. The source colour then overwrites the destination instead of
|
||||
// blending with it, which is a wrong PICTURE, not a wrong error code.
|
||||
//
|
||||
// GL_RGB8 is the ordinary shape and is what this scenario leads with: it is a required
|
||||
// colour-renderable format, its image has been R8G8B8A8_UNORM all along, and the probe asked about
|
||||
// the 24-bit R8G8B8_UNORM that most drivers do not support at all. GL_RGBA4 covers the other half -
|
||||
// a format whose probe answered a real-but-different VkFormat.
|
||||
//
|
||||
// DirectGLES is the control: it forwards the renderbuffer to the ES driver and blends whatever the
|
||||
// driver blends, so a disagreement between the two backends is the defect.
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "../Harness/HeadlessGL.h"
|
||||
#include "../Harness/ScenarioFixture.h"
|
||||
|
||||
#ifdef GLAPI
|
||||
#undef GLAPI
|
||||
#endif
|
||||
#define GL_GLEXT_PROTOTYPES
|
||||
#include <GL/gl.h>
|
||||
#include <GL/glcorearb.h>
|
||||
#undef GL_GLEXT_PROTOTYPES
|
||||
|
||||
namespace MGITest {
|
||||
namespace {
|
||||
|
||||
constexpr int kExtent = 16;
|
||||
|
||||
constexpr const char* kVertexSource = R"(#version 330 core
|
||||
void main()
|
||||
{
|
||||
switch (gl_VertexID)
|
||||
{
|
||||
case 0: gl_Position = vec4(-1.0, 1.0, 0.0, 1.0); break;
|
||||
case 1: gl_Position = vec4( 1.0, 1.0, 0.0, 1.0); break;
|
||||
case 2: gl_Position = vec4(-1.0,-1.0, 0.0, 1.0); break;
|
||||
case 3: gl_Position = vec4( 1.0,-1.0, 0.0, 1.0); break;
|
||||
}
|
||||
}
|
||||
)";
|
||||
|
||||
constexpr const char* kFragmentSource = R"(#version 330 core
|
||||
uniform vec4 uColor;
|
||||
out vec4 fragColor;
|
||||
void main()
|
||||
{
|
||||
fragColor = uColor;
|
||||
}
|
||||
)";
|
||||
|
||||
class RenderbufferBlendFormatScenario : public ScenarioTest {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
ScenarioTest::SetUp();
|
||||
if (!Ready()) return;
|
||||
glGenVertexArrays(1, &m_vao);
|
||||
std::string error;
|
||||
m_program = CompileProgram(kVertexSource, kFragmentSource, &error);
|
||||
ASSERT_NE(m_program, 0u) << "program did not build: " << error;
|
||||
ASSERT_EQ(FirstGLError(), 0u);
|
||||
}
|
||||
|
||||
void TearDown() override {
|
||||
if (!Ready()) return;
|
||||
Destroy();
|
||||
if (m_program != 0) glDeleteProgram(m_program);
|
||||
if (m_vao != 0) glDeleteVertexArrays(1, &m_vao);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
}
|
||||
|
||||
void Destroy() {
|
||||
if (m_fbo != 0) {
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
glDeleteFramebuffers(1, &m_fbo);
|
||||
m_fbo = 0;
|
||||
}
|
||||
if (m_renderbuffer != 0) {
|
||||
glDeleteRenderbuffers(1, &m_renderbuffer);
|
||||
m_renderbuffer = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Returns false (having skipped, not failed) when the driver will not give us a complete
|
||||
// framebuffer for this format - GL only requires a subset of formats to be
|
||||
// colour-renderable, and the point of the scenario is blending, not format support.
|
||||
bool MakeTarget(GLenum internalFormat) {
|
||||
Destroy();
|
||||
glGenRenderbuffers(1, &m_renderbuffer);
|
||||
glBindRenderbuffer(GL_RENDERBUFFER, m_renderbuffer);
|
||||
glRenderbufferStorage(GL_RENDERBUFFER, internalFormat, kExtent, kExtent);
|
||||
glGenFramebuffers(1, &m_fbo);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, m_fbo);
|
||||
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, m_renderbuffer);
|
||||
const GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
|
||||
for (int i = 0; i < 16 && glGetError() != GL_NO_ERROR; ++i) {
|
||||
}
|
||||
return status == GL_FRAMEBUFFER_COMPLETE;
|
||||
}
|
||||
|
||||
void DrawColor(float r, float g, float b, float a) {
|
||||
glUseProgram(m_program);
|
||||
glUniform4f(glGetUniformLocation(m_program, "uColor"), r, g, b, a);
|
||||
glBindVertexArray(m_vao);
|
||||
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
|
||||
glBindVertexArray(0);
|
||||
glUseProgram(0);
|
||||
}
|
||||
|
||||
GLuint m_renderbuffer = 0;
|
||||
GLuint m_fbo = 0;
|
||||
GLuint m_vao = 0;
|
||||
unsigned int m_program = 0;
|
||||
};
|
||||
|
||||
// One draw of opaque black, then a 50%-alpha white draw over it with the ordinary
|
||||
// SRC_ALPHA / ONE_MINUS_SRC_ALPHA function. Blending gives mid-grey; a pipeline built with
|
||||
// blendEnable = VK_FALSE gives white, because the source simply overwrites.
|
||||
//
|
||||
// The tolerance is wide on purpose: RGBA4 has four bits per channel, so "mid-grey" is one of
|
||||
// a handful of representable values and the test must not become a quantisation test.
|
||||
void ExpectBlendedRatherThanOverwritten(const char* what) {
|
||||
const Image image = ReadPixels(kExtent, kExtent);
|
||||
ASSERT_FALSE(image.Empty()) << what;
|
||||
const Rgba8 centre = image.At(kExtent / 2, kExtent / 2);
|
||||
EXPECT_GT(int(centre.r), 40) << what << ": got " << centre << ", which is darker than a blend of "
|
||||
"black and 50% white";
|
||||
EXPECT_LT(int(centre.r), 215) << what << ": got " << centre
|
||||
<< ", which is the source colour - blending was disabled";
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// The ordinary case, and the one broken today rather than only after the format table was
|
||||
// unified: a three-channel colour renderbuffer. Its image has been R8G8B8A8_UNORM all along while
|
||||
// the blend probe asked about R8G8B8_UNORM, which most drivers do not support at all.
|
||||
TEST_F(RenderbufferBlendFormatScenario, BlendingWorksOnAThreeChannelRenderbuffer) {
|
||||
if (!Ready()) GTEST_SKIP();
|
||||
if (!MakeTarget(GL_RGB8)) GTEST_SKIP() << "GL_RGB8 renderbuffer is not framebuffer-complete here";
|
||||
|
||||
glViewport(0, 0, kExtent, kExtent);
|
||||
glDisable(GL_SCISSOR_TEST);
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
glDisable(GL_BLEND);
|
||||
DrawColor(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
DrawColor(1.0f, 1.0f, 1.0f, 0.5f);
|
||||
glDisable(GL_BLEND);
|
||||
EXPECT_EQ(FirstGLError(), 0u) << "the blended draw left a GL error behind";
|
||||
|
||||
ExpectBlendedRatherThanOverwritten("GL_RGB8");
|
||||
Gl().EndFrame();
|
||||
}
|
||||
|
||||
// The other half: a format whose strict converter answers a real-but-different VkFormat
|
||||
// (R4G4B4A4_UNORM_PACK16) while the image is R8G8B8A8_UNORM. Blend support for the packed 16-bit
|
||||
// formats is optional in Vulkan, so the probe could legitimately answer "no" for a format the
|
||||
// attachment does not have.
|
||||
TEST_F(RenderbufferBlendFormatScenario, BlendingWorksOnALowBitPackedRenderbuffer) {
|
||||
if (!Ready()) GTEST_SKIP();
|
||||
if (!MakeTarget(GL_RGBA4)) GTEST_SKIP() << "GL_RGBA4 renderbuffer is not framebuffer-complete here";
|
||||
|
||||
glViewport(0, 0, kExtent, kExtent);
|
||||
glDisable(GL_SCISSOR_TEST);
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
glDisable(GL_BLEND);
|
||||
DrawColor(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
DrawColor(1.0f, 1.0f, 1.0f, 0.5f);
|
||||
glDisable(GL_BLEND);
|
||||
EXPECT_EQ(FirstGLError(), 0u) << "the blended draw left a GL error behind";
|
||||
|
||||
ExpectBlendedRatherThanOverwritten("GL_RGBA4");
|
||||
Gl().EndFrame();
|
||||
}
|
||||
|
||||
// The control that keeps both of the above honest: the same sequence on the format whose probe
|
||||
// and image always agreed. If this one ever fails, the scenario is measuring the blend setup
|
||||
// rather than the format resolution.
|
||||
TEST_F(RenderbufferBlendFormatScenario, BlendingWorksOnAnRgba8Renderbuffer) {
|
||||
if (!Ready()) GTEST_SKIP();
|
||||
if (!MakeTarget(GL_RGBA8)) GTEST_SKIP() << "GL_RGBA8 renderbuffer is not framebuffer-complete here";
|
||||
|
||||
glViewport(0, 0, kExtent, kExtent);
|
||||
glDisable(GL_SCISSOR_TEST);
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
glDisable(GL_BLEND);
|
||||
DrawColor(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
DrawColor(1.0f, 1.0f, 1.0f, 0.5f);
|
||||
glDisable(GL_BLEND);
|
||||
EXPECT_EQ(FirstGLError(), 0u) << "the blended draw left a GL error behind";
|
||||
|
||||
ExpectBlendedRatherThanOverwritten("GL_RGBA8");
|
||||
Gl().EndFrame();
|
||||
}
|
||||
|
||||
} // namespace MGITest
|
||||
Reference in New Issue
Block a user