mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-08 12:18:30 +09:00
Six pure-state entry points that were stubs or empty // TODO bodies, all backed by new
context state and read back through glGet*.
* glHint: Hint_State was an empty TODO. Store the 4 GL 3.3 core hint targets (LINE_SMOOTH,
POLYGON_SMOOTH, TEXTURE_COMPRESSION, FRAGMENT_SHADER_DERIVATIVE), default GL_DONT_CARE.
Validate target and mode (FASTEST/NICEST/DONT_CARE) -> GL_INVALID_ENUM otherwise. The
compatibility-only targets (GL_PERSPECTIVE_CORRECTION_HINT, GL_POINT_SMOOTH_HINT, GL_FOG_HINT,
GL_GENERATE_MIPMAP_HINT) are rejected. The glGetIntegerv hint cases, previously hardcoded to
GL_DONT_CARE, now read the stored value; glGetBooleanv on a hint is always GL_TRUE.
* glPointParameter{f,i,fv,iv}: the scalar _State bodies were empty TODOs and the *v forms were
stubs. Only the 2 core pnames are accepted: GL_POINT_FADE_THRESHOLD_SIZE (float, default 1.0,
GL_INVALID_VALUE if negative) and GL_POINT_SPRITE_COORD_ORIGIN (GL_LOWER_LEFT/GL_UPPER_LEFT,
default GL_UPPER_LEFT, GL_INVALID_ENUM on a bad value -- note the different error code from the
fade case). The compat pnames (POINT_SIZE_MIN/MAX, POINT_DISTANCE_ATTENUATION) are rejected. All
four forms funnel through one (pname, float) handler. glGetIntegerv(GL_POINT_FADE_THRESHOLD_SIZE)
was hardcoded to 1; it now rounds the stored float, glGetFloatv reads the float directly (keeping
the fractional part), and GL_POINT_SPRITE_COORD_ORIGIN gained a getter case (it had none).
* glPixelStoref: funnels into the existing glPixelStorei state, but converts per type -- boolean
pnames (PACK/UNPACK_SWAP_BYTES/LSB_FIRST) by a zero-test so 0.4 -> TRUE, integer pnames by
round-to-nearest. A blanket round would wrongly turn a fractional true flag into false.
* glGetDoublev: funnels through glGetFloatv and widens, writing exactly the pname's component count
(1/2/4) so a single-component query cannot overrun the caller's buffer. MobileGL stores no native
double state (depth range/clear are float), so widening from float matches its real resolution.
State added to RenderStateParameters + RenderState Set/Get + GLContext wrappers, following the
existing LineWidth/DepthRange pattern. Covered by 4 SanityTest cases (set-then-get round trips, the
core-vs-compat enum rejections, the two different error codes, and the glPixelStoref boolean
zero-test, which was verified to fail against a blanket-round implementation).
764 lines
33 KiB
C++
764 lines
33 KiB
C++
// MobileGL - MobileGL/MG_Test/SanityTest.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
|
|
|
|
#include <gtest/gtest.h>
|
|
#include <algorithm>
|
|
#include <cstdlib>
|
|
#include <filesystem>
|
|
#include <fstream>
|
|
#include <string>
|
|
|
|
#include <MG_Backend/DirectGLES/BackendObject_DirectGLES.h>
|
|
#include <MG_Backend/DirectGLES/Managers.h>
|
|
#include <MG_Backend/DirectVulkan/BackendObject_DirectVulkan.h>
|
|
#include <MG_Backend/BackendObjects.h>
|
|
#include <MG_Impl/GLImpl/Getter/GL_Getter.h>
|
|
#include <MG_Impl/GLImpl/RenderState/GL_RenderState.h>
|
|
#include <MG_Impl/GLImpl/VertexArray/Validators.h>
|
|
#include <MG_State/GLState/Core.h>
|
|
#include <MG_State/GLState/FramebufferState/FramebufferObject.h>
|
|
#include <MG_State/GLState/TextureState/TextureState.h>
|
|
#include <MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.h>
|
|
#include <MG_Backend/DirectVulkan/Renderer/VkTextureManager.h>
|
|
#include <MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h>
|
|
#include <MG_Util/ShaderTranspiler/ShaderCompiler.h>
|
|
#include <MG_Util/ShaderTranspiler/ShaderSourceProcessor.h>
|
|
#include <MG_Util/Debug/Log.h>
|
|
|
|
namespace {
|
|
class DynamicParameterBackend final : public MobileGL::MG_Backend::BackendObject {
|
|
public:
|
|
explicit DynamicParameterBackend(MobileGL::MG_Backend::DynamicBackendParameters params):
|
|
m_params(params) {}
|
|
|
|
void Initialize() override {}
|
|
MobileGL::Bool InitCapabilities() override { return true; }
|
|
MobileGL::Bool InitWindowSurface() override { return true; }
|
|
const MobileGL::RendererInfo& GetRendererInfo() const override { return m_info; }
|
|
MobileGL::String GetBackendAPIVersionString() const override { return "test"; }
|
|
const MobileGL::MG_Backend::GlobalBackendFunctionsTable& GetBackendFunctions() const override {
|
|
return m_functions;
|
|
}
|
|
const MobileGL::MG_Backend::DynamicBackendParameters& GetDynamicParameters() const override {
|
|
return m_params;
|
|
}
|
|
MobileGL::BackendType GetBackendType() const override { return MobileGL::BackendType::Unknown; }
|
|
|
|
private:
|
|
MobileGL::MG_Backend::DynamicBackendParameters m_params;
|
|
MobileGL::MG_Backend::GlobalBackendFunctionsTable m_functions{};
|
|
MobileGL::RendererInfo m_info{
|
|
.RendererName = "Test",
|
|
.BackendName = "DynamicParameterBackend",
|
|
.ExtraVendor = MobileGL::Nullopt,
|
|
.RendererGLInfo = {.TargetGLVersion = {3, 3, 0},
|
|
.TargetGLSLVersion = {4, 6, 0},
|
|
.Extensions = {},
|
|
.IsCompatibilityProfile = false},
|
|
.StaticBackendCapability = {.AllowVSOnlyPrograms = false}};
|
|
};
|
|
|
|
void SetEnvVar(const char* name, const char* value) {
|
|
#if defined(_WIN32)
|
|
_putenv_s(name, value);
|
|
#else
|
|
setenv(name, value, 1);
|
|
#endif
|
|
}
|
|
|
|
void UnsetEnvVar(const char* name) {
|
|
#if defined(_WIN32)
|
|
_putenv_s(name, "");
|
|
#else
|
|
unsetenv(name);
|
|
#endif
|
|
}
|
|
|
|
MobileGL::SizeT CountOccurrences(const MobileGL::String& haystack, const MobileGL::String& needle) {
|
|
MobileGL::SizeT count = 0;
|
|
for (MobileGL::SizeT pos = haystack.find(needle); pos != MobileGL::String::npos;
|
|
pos = haystack.find(needle, pos + needle.size())) {
|
|
++count;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
// Snapshots the DirectGLES capability globals on construction and restores them on
|
|
// destruction, so tests that mutate g_GLESCapabilities cannot leak state into later
|
|
// tests even if an assertion or exception unwinds the test body early.
|
|
struct ScopedGLESCapabilitiesOverride {
|
|
ScopedGLESCapabilitiesOverride():
|
|
m_snapshot(MobileGL::MG_Backend::DirectGLES::g_GLESCapabilities) {}
|
|
~ScopedGLESCapabilitiesOverride() {
|
|
MobileGL::MG_Backend::DirectGLES::g_GLESCapabilities = m_snapshot;
|
|
}
|
|
ScopedGLESCapabilitiesOverride(const ScopedGLESCapabilitiesOverride&) = delete;
|
|
ScopedGLESCapabilitiesOverride& operator=(const ScopedGLESCapabilitiesOverride&) = delete;
|
|
|
|
private:
|
|
MobileGL::MG_External::GLESCapabilities m_snapshot;
|
|
};
|
|
} // namespace
|
|
|
|
TEST(Sanity, BasicAssertions) {
|
|
// Expect two strings not to be equal.
|
|
EXPECT_STRNE("hello", "world");
|
|
// Expect equality.
|
|
EXPECT_EQ(7 * 6, 42);
|
|
}
|
|
|
|
TEST(DirectGLESSanity, AdvertisesDepthTextureForGlmarkShadowScenes) {
|
|
MobileGL::MG_Backend::DirectGLES::BackendObject_DirectGLES backend;
|
|
const auto& extensions = backend.GetRendererInfo().RendererGLInfo.Extensions;
|
|
|
|
EXPECT_NE(std::find(extensions.begin(), extensions.end(), MobileGL::E_GL_ARB_depth_texture), extensions.end());
|
|
}
|
|
|
|
TEST(DirectGLESSanity, AdvertisesVoxyRequiredRenderingExtensionsWithoutRaisingGLVersion) {
|
|
MobileGL::MG_Backend::DirectGLES::BackendObject_DirectGLES backend;
|
|
const auto& rendererInfo = backend.GetRendererInfo().RendererGLInfo;
|
|
const auto& extensions = rendererInfo.Extensions;
|
|
|
|
EXPECT_EQ(rendererInfo.TargetGLVersion.Major, 3);
|
|
EXPECT_EQ(rendererInfo.TargetGLVersion.Minor, 3);
|
|
EXPECT_EQ(rendererInfo.TargetGLVersion.Patch, 0);
|
|
|
|
EXPECT_NE(std::find(extensions.begin(), extensions.end(), MobileGL::E_GL_ARB_compute_shader),
|
|
extensions.end());
|
|
EXPECT_NE(std::find(extensions.begin(), extensions.end(), MobileGL::E_GL_ARB_shader_storage_buffer_object),
|
|
extensions.end());
|
|
EXPECT_NE(std::find(extensions.begin(), extensions.end(), MobileGL::E_GL_ARB_texture_storage),
|
|
extensions.end());
|
|
EXPECT_NE(std::find(extensions.begin(), extensions.end(), MobileGL::E_GL_ARB_direct_state_access),
|
|
extensions.end());
|
|
EXPECT_NE(std::find(extensions.begin(), extensions.end(), MobileGL::E_GL_ARB_multi_draw_indirect),
|
|
extensions.end());
|
|
EXPECT_NE(std::find(extensions.begin(), extensions.end(), MobileGL::E_GL_ARB_indirect_parameters),
|
|
extensions.end());
|
|
EXPECT_NE(std::find(extensions.begin(), extensions.end(), MobileGL::E_GL_ARB_shader_draw_parameters),
|
|
extensions.end());
|
|
EXPECT_EQ(std::find(extensions.begin(), extensions.end(), MobileGL::E_GL_ARB_gpu_shader_int64),
|
|
extensions.end());
|
|
EXPECT_EQ(std::find(extensions.begin(), extensions.end(), MobileGL::E_GL_KHR_shader_subgroup),
|
|
extensions.end());
|
|
}
|
|
|
|
TEST(DirectGLESSanity, ProvidesNamedFramebufferBlitForDirectStateAccess) {
|
|
MobileGL::MG_Backend::DirectGLES::BackendObject_DirectGLES backend;
|
|
const auto& funcs = backend.GetBackendFunctions().GL;
|
|
|
|
EXPECT_NE(funcs.ClearNamedFramebufferfv, nullptr);
|
|
EXPECT_NE(funcs.ClearNamedFramebufferfi, nullptr);
|
|
EXPECT_NE(funcs.BlitFramebuffer, nullptr);
|
|
EXPECT_NE(funcs.BlitNamedFramebuffer, nullptr);
|
|
}
|
|
|
|
TEST(DirectGLESSanity, RewritesBaseInstanceBuiltinForEsslVertexShaders) {
|
|
const MobileGL::String source = R"(#version 320 es
|
|
void main() {
|
|
uint drawId = gl_BaseInstance;
|
|
uint untouched = my_gl_BaseInstance_value;
|
|
}
|
|
)";
|
|
|
|
const auto rewritten = MobileGL::MG_Backend::DirectGLES::EmulateBaseInstanceInVertexShader(
|
|
source, GL_VERTEX_SHADER);
|
|
|
|
EXPECT_NE(rewritten.find("uniform highp int mg_BaseInstance;"), MobileGL::String::npos);
|
|
EXPECT_NE(rewritten.find("uint drawId = mg_BaseInstance;"), MobileGL::String::npos);
|
|
EXPECT_NE(rewritten.find("my_gl_BaseInstance_value"), MobileGL::String::npos);
|
|
EXPECT_EQ(rewritten.find("uint drawId = gl_BaseInstance;"), MobileGL::String::npos);
|
|
}
|
|
|
|
TEST(DirectGLESSanity, LeavesBaseInstanceBuiltinAloneOutsideVertexShaders) {
|
|
const MobileGL::String source = "#version 320 es\nuint value = gl_BaseInstance;\n";
|
|
|
|
const auto rewritten = MobileGL::MG_Backend::DirectGLES::EmulateBaseInstanceInVertexShader(
|
|
source, GL_FRAGMENT_SHADER);
|
|
|
|
EXPECT_EQ(rewritten, source);
|
|
}
|
|
|
|
TEST(DirectGLESSanity, RebasesInstanceIdWhenIndirectDrawsLeakBaseInstance) {
|
|
const ScopedGLESCapabilitiesOverride capsGuard;
|
|
auto& caps = MobileGL::MG_Backend::DirectGLES::g_GLESCapabilities;
|
|
caps.IndirectDrawInstanceIdIncludesBaseInstance = true;
|
|
// Deliberately not the GLESCapabilities default (8): the injected block must land at
|
|
// MaxShaderStorageBufferBindings - 1 = 12, so a regression that stops reading the
|
|
// probed cap and falls back to the struct default would surface as "binding = 7".
|
|
caps.MaxShaderStorageBufferBindings = 13;
|
|
|
|
const MobileGL::String source = R"(#version 310 es
|
|
highp int mg_BaseInstanceLowered;
|
|
void main() {
|
|
int instance = gl_InstanceID + mg_BaseInstanceLowered;
|
|
gl_Position = vec4(float(instance));
|
|
}
|
|
)";
|
|
|
|
const auto rewritten = MobileGL::MG_Backend::DirectGLES::PromoteDrawParameterGlobalsToUniforms(
|
|
source, GL_VERTEX_SHADER);
|
|
|
|
EXPECT_NE(rewritten.find("int instance = mg_ZeroBasedInstanceID + mg_BaseInstanceLowered;"),
|
|
MobileGL::String::npos);
|
|
EXPECT_NE(rewritten.find("#define mg_ZeroBasedInstanceID (gl_InstanceID - ((mg_BaseInstanceWordIndex >= 0) ? "
|
|
"int(mg_indirectWords[uint(mg_BaseInstanceWordIndex)]) : 0))"),
|
|
MobileGL::String::npos);
|
|
EXPECT_NE(rewritten.find(
|
|
"layout(std430, binding = 12) readonly buffer mg_IndirectParams { highp uint mg_indirectWords[]; };"),
|
|
MobileGL::String::npos);
|
|
// The one inside the #define machinery must be the only surviving gl_InstanceID.
|
|
EXPECT_EQ(CountOccurrences(rewritten, "gl_InstanceID"), 1u);
|
|
}
|
|
|
|
TEST(DirectGLESSanity, KeepsInstanceIdWhenIndirectDrawsAreConforming) {
|
|
const ScopedGLESCapabilitiesOverride capsGuard;
|
|
auto& caps = MobileGL::MG_Backend::DirectGLES::g_GLESCapabilities;
|
|
caps.IndirectDrawInstanceIdIncludesBaseInstance = false;
|
|
caps.MaxShaderStorageBufferBindings = 13;
|
|
|
|
const MobileGL::String source = R"(#version 310 es
|
|
highp int mg_BaseInstanceLowered;
|
|
void main() {
|
|
int instance = gl_InstanceID + mg_BaseInstanceLowered;
|
|
gl_Position = vec4(float(instance));
|
|
}
|
|
)";
|
|
|
|
const auto rewritten = MobileGL::MG_Backend::DirectGLES::PromoteDrawParameterGlobalsToUniforms(
|
|
source, GL_VERTEX_SHADER);
|
|
|
|
EXPECT_EQ(rewritten.find("mg_ZeroBasedInstanceID"), MobileGL::String::npos);
|
|
EXPECT_NE(rewritten.find("int instance = gl_InstanceID + mg_BaseInstanceLowered;"), MobileGL::String::npos);
|
|
}
|
|
|
|
TEST(DirectGLESSanity, LeavesDrawParameterGlobalsAloneOutsideVertexShaders) {
|
|
const ScopedGLESCapabilitiesOverride capsGuard;
|
|
auto& caps = MobileGL::MG_Backend::DirectGLES::g_GLESCapabilities;
|
|
caps.IndirectDrawInstanceIdIncludesBaseInstance = true;
|
|
caps.MaxShaderStorageBufferBindings = 13;
|
|
|
|
const MobileGL::String source =
|
|
"#version 310 es\nhighp int mg_BaseInstanceLowered;\nint value = gl_InstanceID + mg_BaseInstanceLowered;\n";
|
|
|
|
const auto rewritten = MobileGL::MG_Backend::DirectGLES::PromoteDrawParameterGlobalsToUniforms(
|
|
source, GL_FRAGMENT_SHADER);
|
|
|
|
EXPECT_EQ(rewritten, source);
|
|
}
|
|
|
|
TEST(DirectVulkanSanity, AdvertisesTextureStorageForDirectStateAccess) {
|
|
MobileGL::MG_Backend::DirectVulkan::BackendObject_DirectVulkan backend;
|
|
const auto& extensions = backend.GetRendererInfo().RendererGLInfo.Extensions;
|
|
|
|
EXPECT_NE(std::find(extensions.begin(), extensions.end(), MobileGL::E_GL_ARB_direct_state_access),
|
|
extensions.end());
|
|
EXPECT_NE(std::find(extensions.begin(), extensions.end(), MobileGL::E_GL_ARB_texture_storage), extensions.end());
|
|
}
|
|
|
|
TEST(DirectVulkanSanity, RenderPassExtentUsesSwapchainSizeOnlyForDefaultFramebuffer) {
|
|
using MobileGL::MG_Backend::DirectVulkan::ResolveRenderPassFramebufferExtent;
|
|
|
|
const MobileGL::TextureSize attachmentExtent = {512, 512, 1};
|
|
const VkExtent2D swapchainExtent = {3200u, 1440u};
|
|
|
|
EXPECT_EQ(ResolveRenderPassFramebufferExtent(true, attachmentExtent, swapchainExtent),
|
|
MobileGL::IntVec2(3200, 1440));
|
|
EXPECT_EQ(ResolveRenderPassFramebufferExtent(false, attachmentExtent, swapchainExtent),
|
|
MobileGL::IntVec2(512, 512));
|
|
}
|
|
|
|
TEST(DirectVulkanSanity, AdvertisesVoxyRequiredRenderingExtensionsWithoutRaisingGLVersion) {
|
|
MobileGL::MG_Backend::DirectVulkan::BackendObject_DirectVulkan backend;
|
|
const auto& rendererInfo = backend.GetRendererInfo().RendererGLInfo;
|
|
const auto& extensions = rendererInfo.Extensions;
|
|
|
|
EXPECT_EQ(rendererInfo.TargetGLVersion.Major, 3);
|
|
EXPECT_EQ(rendererInfo.TargetGLVersion.Minor, 3);
|
|
EXPECT_EQ(rendererInfo.TargetGLVersion.Patch, 0);
|
|
|
|
EXPECT_NE(std::find(extensions.begin(), extensions.end(), MobileGL::E_GL_ARB_compute_shader),
|
|
extensions.end());
|
|
EXPECT_NE(std::find(extensions.begin(), extensions.end(), MobileGL::E_GL_ARB_shader_storage_buffer_object),
|
|
extensions.end());
|
|
EXPECT_NE(std::find(extensions.begin(), extensions.end(), MobileGL::E_GL_ARB_multi_draw_indirect),
|
|
extensions.end());
|
|
EXPECT_NE(std::find(extensions.begin(), extensions.end(), MobileGL::E_GL_ARB_indirect_parameters),
|
|
extensions.end());
|
|
EXPECT_NE(std::find(extensions.begin(), extensions.end(), MobileGL::E_GL_ARB_shader_draw_parameters),
|
|
extensions.end());
|
|
EXPECT_NE(std::find(extensions.begin(), extensions.end(), MobileGL::E_GL_ARB_gpu_shader_int64),
|
|
extensions.end());
|
|
}
|
|
|
|
TEST(DirectVulkanSanity, ClampsAdvertisedTextureAndDrawBufferLimitsToFrontendState) {
|
|
using namespace MobileGL;
|
|
|
|
MG_Backend::DirectVulkan::BackendObject_DirectVulkan backend;
|
|
|
|
MG_External::VulkanCapabilities highCaps;
|
|
highCaps.MaxTextureImageUnits = 256;
|
|
highCaps.MaxVertexTextureImageUnits = 256;
|
|
highCaps.MaxComputeTextureImageUnits = 256;
|
|
highCaps.MaxCombinedTextureImageUnits = 256;
|
|
highCaps.MaxDrawBuffers = 128;
|
|
highCaps.MaxColorAttachments = 128;
|
|
backend.ApplyVulkanCapabilitiesForTesting(highCaps);
|
|
|
|
const auto& highParams = backend.GetDynamicParameters();
|
|
EXPECT_EQ(highParams.MaxTextureImageUnits, MG_State::GLState::TextureState::MAX_TEXTURE_IMAGE_UNITS);
|
|
EXPECT_EQ(highParams.MaxVertexTextureImageUnits, MG_State::GLState::TextureState::MAX_TEXTURE_IMAGE_UNITS);
|
|
EXPECT_EQ(highParams.MaxComputeTextureImageUnits, MG_State::GLState::TextureState::MAX_TEXTURE_IMAGE_UNITS);
|
|
EXPECT_EQ(highParams.MaxCombinedTextureImageUnits, MG_State::GLState::TextureState::MAX_TEXTURE_IMAGE_UNITS);
|
|
EXPECT_EQ(highParams.MaxDrawBuffers, MG_State::GLState::FramebufferObject::MAX_DRAW_BUFFERS);
|
|
EXPECT_EQ(highParams.MaxColorAttachments, MG_State::GLState::FramebufferObject::MAX_DRAW_BUFFERS);
|
|
|
|
MG_External::VulkanCapabilities lowCaps;
|
|
lowCaps.MaxTextureImageUnits = 16;
|
|
lowCaps.MaxVertexTextureImageUnits = 12;
|
|
lowCaps.MaxComputeTextureImageUnits = 10;
|
|
lowCaps.MaxCombinedTextureImageUnits = 20;
|
|
lowCaps.MaxDrawBuffers = 4;
|
|
lowCaps.MaxColorAttachments = 6;
|
|
backend.ApplyVulkanCapabilitiesForTesting(lowCaps);
|
|
|
|
const auto& lowParams = backend.GetDynamicParameters();
|
|
EXPECT_EQ(lowParams.MaxTextureImageUnits, 16);
|
|
EXPECT_EQ(lowParams.MaxVertexTextureImageUnits, 12);
|
|
EXPECT_EQ(lowParams.MaxComputeTextureImageUnits, 10);
|
|
EXPECT_EQ(lowParams.MaxCombinedTextureImageUnits, 20);
|
|
EXPECT_EQ(lowParams.MaxDrawBuffers, 4);
|
|
EXPECT_EQ(lowParams.MaxColorAttachments, 6);
|
|
}
|
|
|
|
TEST(DirectVulkanSanity, AdvertisesSubgroupOnlyWhenVulkanReportsUsableSupport) {
|
|
using namespace MobileGL;
|
|
|
|
MG_Backend::DirectVulkan::BackendObject_DirectVulkan backend;
|
|
|
|
MG_External::VulkanCapabilities unsupportedCaps;
|
|
unsupportedCaps.SupportsShaderSubgroup = false;
|
|
unsupportedCaps.SubgroupSize = 32;
|
|
unsupportedCaps.SubgroupSupportedStages = VK_SHADER_STAGE_COMPUTE_BIT;
|
|
unsupportedCaps.SubgroupSupportedOperations = VK_SUBGROUP_FEATURE_BASIC_BIT;
|
|
backend.ApplyVulkanCapabilitiesForTesting(unsupportedCaps);
|
|
|
|
const auto& unsupportedExtensions = backend.GetRendererInfo().RendererGLInfo.Extensions;
|
|
EXPECT_EQ(std::find(unsupportedExtensions.begin(), unsupportedExtensions.end(), E_GL_KHR_shader_subgroup),
|
|
unsupportedExtensions.end());
|
|
EXPECT_EQ(backend.GetDynamicParameters().SubgroupSize, 0u);
|
|
EXPECT_EQ(backend.GetDynamicParameters().SubgroupSupportedStages, 0u);
|
|
EXPECT_EQ(backend.GetDynamicParameters().SubgroupSupportedFeatures, 0u);
|
|
|
|
MG_External::VulkanCapabilities supportedCaps;
|
|
supportedCaps.SupportsShaderSubgroup = true;
|
|
supportedCaps.SubgroupSize = 32;
|
|
supportedCaps.SubgroupSupportedStages = VK_SHADER_STAGE_FRAGMENT_BIT | VK_SHADER_STAGE_COMPUTE_BIT;
|
|
supportedCaps.SubgroupSupportedOperations =
|
|
VK_SUBGROUP_FEATURE_BASIC_BIT | VK_SUBGROUP_FEATURE_ARITHMETIC_BIT | VK_SUBGROUP_FEATURE_QUAD_BIT;
|
|
supportedCaps.SubgroupQuadOperationsInAllStages = true;
|
|
backend.ApplyVulkanCapabilitiesForTesting(supportedCaps);
|
|
|
|
const auto& supportedExtensions = backend.GetRendererInfo().RendererGLInfo.Extensions;
|
|
EXPECT_NE(std::find(supportedExtensions.begin(), supportedExtensions.end(), E_GL_KHR_shader_subgroup),
|
|
supportedExtensions.end());
|
|
EXPECT_EQ(backend.GetDynamicParameters().SubgroupSize, 32u);
|
|
EXPECT_EQ(backend.GetDynamicParameters().SubgroupSupportedStages,
|
|
static_cast<Uint32>(GL_FRAGMENT_SHADER_BIT | GL_COMPUTE_SHADER_BIT));
|
|
EXPECT_EQ(backend.GetDynamicParameters().SubgroupSupportedFeatures,
|
|
static_cast<Uint32>(GL_SUBGROUP_FEATURE_BASIC_BIT_KHR |
|
|
GL_SUBGROUP_FEATURE_ARITHMETIC_BIT_KHR |
|
|
GL_SUBGROUP_FEATURE_QUAD_BIT_KHR));
|
|
EXPECT_TRUE(backend.GetDynamicParameters().SubgroupQuadOperationsInAllStages);
|
|
}
|
|
|
|
TEST(DirectVulkanSanity, KeepsOptionalGpuShaderInt64BranchForVoxyQuadDecode) {
|
|
using namespace MobileGL;
|
|
|
|
MG_Backend::pActiveBackendObject = MakeUnique<MG_Backend::DirectVulkan::BackendObject_DirectVulkan>();
|
|
String source = R"(#version 460 core
|
|
#extension GL_ARB_gpu_shader_int64 : enable
|
|
#ifdef GL_ARB_gpu_shader_int64
|
|
uint getLowBits(uint64_t v) {
|
|
return uint(v & uint64_t(0xffu));
|
|
}
|
|
#else
|
|
#error int64 branch should be enabled for DirectVulkan
|
|
#endif
|
|
void main() {
|
|
gl_Position = vec4(float(getLowBits(uint64_t(0x2au))));
|
|
}
|
|
)";
|
|
|
|
MG_Util::ShaderTranspiler::PreprocessShaderSource(ShaderStage::Vertex, source);
|
|
EXPECT_NE(source.find("#extension GL_ARB_gpu_shader_int64"), String::npos);
|
|
EXPECT_NE(source.find("GL_ARB_gpu_shader_int64"), String::npos);
|
|
|
|
auto shaderResult = MG_Util::ShaderTranspiler::ShaderCompiler::CompileShader({
|
|
.shaderType = GL_VERTEX_SHADER,
|
|
.sourceStr = source,
|
|
.flags = MG_Util::ShaderTranspiler::ShaderCompileBits::CompileForOpenGL,
|
|
});
|
|
EXPECT_TRUE(shaderResult) << (shaderResult ? "" : shaderResult.error().log);
|
|
|
|
MG_Backend::pActiveBackendObject.reset();
|
|
}
|
|
|
|
// GL_MAX_VERTEX_ATTRIBS must follow the backend but never exceed the state layer's current-value
|
|
// storage: the DirectVulkan draw path indexes that array by shader input location, so advertising more
|
|
// than it can hold is an out-of-bounds read waiting to happen.
|
|
TEST(GetterSanity, ClampsMaxVertexAttribsToCurrentValueStorageCapacity) {
|
|
using namespace MobileGL;
|
|
constexpr GLint capacity = MG_State::GLState::VertexArrayObject::MAX_VERTEX_ATTRIBS;
|
|
|
|
MG_State::pGLContext = MakeUnique<MG_State::GLState::GLContext>();
|
|
|
|
// A driver reporting more attributes than MobileGL can store gets clamped.
|
|
{
|
|
MG_Backend::DynamicBackendParameters params;
|
|
params.MaxVertexAttribs = capacity * 2;
|
|
MG_Backend::pActiveBackendObject = MakeUnique<DynamicParameterBackend>(params);
|
|
|
|
EXPECT_EQ(MG_Impl::GLImpl::VertexArrayImpl::GetMaxVertexAttribs(), static_cast<Uint>(capacity));
|
|
GLint reported = 0;
|
|
MG_Impl::GLImpl::GetIntegerv(GL_MAX_VERTEX_ATTRIBS, &reported);
|
|
EXPECT_EQ(reported, capacity);
|
|
MG_Backend::pActiveBackendObject.reset();
|
|
}
|
|
|
|
// A driver below the capacity is followed exactly, and validation enforces that same bound.
|
|
{
|
|
MG_Backend::DynamicBackendParameters params;
|
|
params.MaxVertexAttribs = 16;
|
|
MG_Backend::pActiveBackendObject = MakeUnique<DynamicParameterBackend>(params);
|
|
|
|
EXPECT_EQ(MG_Impl::GLImpl::VertexArrayImpl::GetMaxVertexAttribs(), 16u);
|
|
GLint reported = 0;
|
|
MG_Impl::GLImpl::GetIntegerv(GL_MAX_VERTEX_ATTRIBS, &reported);
|
|
EXPECT_EQ(reported, 16);
|
|
|
|
MG_State::pGLContext->ClearErrors();
|
|
EXPECT_FALSE(MG_Impl::GLImpl::VertexArrayImpl::ValidateVertexAttributeIndex(16));
|
|
EXPECT_TRUE(MG_State::pGLContext->HasGLError());
|
|
MG_State::pGLContext->ClearErrors();
|
|
EXPECT_TRUE(MG_Impl::GLImpl::VertexArrayImpl::ValidateVertexAttributeIndex(15));
|
|
EXPECT_FALSE(MG_State::pGLContext->HasGLError());
|
|
|
|
MG_Backend::pActiveBackendObject.reset();
|
|
}
|
|
|
|
// With no active backend the storage capacity is the bound, and nothing dereferences a null backend.
|
|
EXPECT_EQ(MG_Impl::GLImpl::VertexArrayImpl::GetMaxVertexAttribs(), static_cast<Uint>(capacity));
|
|
|
|
MG_State::pGLContext.reset();
|
|
}
|
|
|
|
TEST(GetterSanity, ReportsKhrSubgroupDynamicParameters) {
|
|
using namespace MobileGL;
|
|
|
|
MG_State::pGLContext = MakeUnique<MG_State::GLState::GLContext>();
|
|
|
|
MG_Backend::DynamicBackendParameters params;
|
|
params.SubgroupSize = 32;
|
|
params.SubgroupSupportedStages = GL_VERTEX_SHADER_BIT | GL_FRAGMENT_SHADER_BIT | GL_COMPUTE_SHADER_BIT;
|
|
params.SubgroupSupportedFeatures = GL_SUBGROUP_FEATURE_BASIC_BIT_KHR |
|
|
GL_SUBGROUP_FEATURE_ARITHMETIC_BIT_KHR |
|
|
GL_SUBGROUP_FEATURE_CLUSTERED_BIT_KHR |
|
|
GL_SUBGROUP_FEATURE_QUAD_BIT_KHR;
|
|
params.SubgroupQuadOperationsInAllStages = true;
|
|
MG_Backend::pActiveBackendObject = MakeUnique<DynamicParameterBackend>(params);
|
|
|
|
GLint intValue = 0;
|
|
MG_Impl::GLImpl::GetIntegerv(GL_SUBGROUP_SIZE_KHR, &intValue);
|
|
EXPECT_EQ(intValue, 32);
|
|
MG_Impl::GLImpl::GetIntegerv(GL_SUBGROUP_SUPPORTED_STAGES_KHR, &intValue);
|
|
EXPECT_EQ(intValue, static_cast<GLint>(params.SubgroupSupportedStages));
|
|
MG_Impl::GLImpl::GetIntegerv(GL_SUBGROUP_SUPPORTED_FEATURES_KHR, &intValue);
|
|
EXPECT_EQ(intValue, static_cast<GLint>(params.SubgroupSupportedFeatures));
|
|
MG_Impl::GLImpl::GetIntegerv(GL_SUBGROUP_QUAD_ALL_STAGES_KHR, &intValue);
|
|
EXPECT_EQ(intValue, GL_TRUE);
|
|
|
|
GLint64 int64Value = 0;
|
|
MG_Impl::GLImpl::GetInteger64v(GL_SUBGROUP_SUPPORTED_FEATURES_KHR, &int64Value);
|
|
EXPECT_EQ(int64Value, static_cast<GLint64>(params.SubgroupSupportedFeatures));
|
|
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
|
|
|
|
MG_Backend::pActiveBackendObject.reset();
|
|
MG_State::pGLContext.reset();
|
|
}
|
|
|
|
TEST(DirectVulkanSanity, CommandMemoryBarrierMakesIndirectDrawCommandsVisible) {
|
|
using namespace MobileGL;
|
|
using namespace MobileGL::MG_Backend::DirectVulkan;
|
|
|
|
const VkMemoryBarrier commandBarrier =
|
|
VulkanRenderer::BuildMemoryBarrierForGlBarriers(GL_COMMAND_BARRIER_BIT | GL_SHADER_STORAGE_BARRIER_BIT);
|
|
EXPECT_NE(commandBarrier.dstAccessMask & VK_ACCESS_INDIRECT_COMMAND_READ_BIT, 0u);
|
|
|
|
const VkMemoryBarrier storageOnlyBarrier =
|
|
VulkanRenderer::BuildMemoryBarrierForGlBarriers(GL_SHADER_STORAGE_BARRIER_BIT);
|
|
EXPECT_EQ(storageOnlyBarrier.dstAccessMask & VK_ACCESS_INDIRECT_COMMAND_READ_BIT, 0u);
|
|
}
|
|
|
|
TEST(DirectVulkanSanity, DrawIndexedIndirectCommandMatchesGlAndVulkanLayout) {
|
|
using namespace MobileGL::MG_Backend::DirectVulkan;
|
|
|
|
EXPECT_EQ(sizeof(DrawIndexedCmdParam), 20u);
|
|
EXPECT_EQ(offsetof(DrawIndexedCmdParam, indexCount), 0u);
|
|
EXPECT_EQ(offsetof(DrawIndexedCmdParam, instanceCount), 4u);
|
|
EXPECT_EQ(offsetof(DrawIndexedCmdParam, firstIndex), 8u);
|
|
EXPECT_EQ(offsetof(DrawIndexedCmdParam, vertexOffset), 12u);
|
|
EXPECT_EQ(offsetof(DrawIndexedCmdParam, firstInstance), 16u);
|
|
}
|
|
|
|
TEST(DirectVulkanSanity, UndefinedDepthStencilLayoutUsesDontCareForUnclearedAspects) {
|
|
using namespace MobileGL;
|
|
using namespace MobileGL::MG_Backend::DirectVulkan;
|
|
|
|
auto noClear = ResolveDepthStencilAttachmentLoadInfo(VK_IMAGE_LAYOUT_UNDEFINED, false, false);
|
|
EXPECT_EQ(noClear.depthLoadOp, VK_ATTACHMENT_LOAD_OP_DONT_CARE);
|
|
EXPECT_EQ(noClear.stencilLoadOp, VK_ATTACHMENT_LOAD_OP_DONT_CARE);
|
|
EXPECT_EQ(noClear.initialLayout, VK_IMAGE_LAYOUT_UNDEFINED);
|
|
|
|
auto depthOnlyClear = ResolveDepthStencilAttachmentLoadInfo(VK_IMAGE_LAYOUT_UNDEFINED, true, false);
|
|
EXPECT_EQ(depthOnlyClear.depthLoadOp, VK_ATTACHMENT_LOAD_OP_CLEAR);
|
|
EXPECT_EQ(depthOnlyClear.stencilLoadOp, VK_ATTACHMENT_LOAD_OP_DONT_CARE);
|
|
EXPECT_EQ(depthOnlyClear.initialLayout, VK_IMAGE_LAYOUT_UNDEFINED);
|
|
|
|
auto knownLayout = ResolveDepthStencilAttachmentLoadInfo(
|
|
VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, false, false);
|
|
EXPECT_EQ(knownLayout.depthLoadOp, VK_ATTACHMENT_LOAD_OP_LOAD);
|
|
EXPECT_EQ(knownLayout.stencilLoadOp, VK_ATTACHMENT_LOAD_OP_LOAD);
|
|
EXPECT_EQ(knownLayout.initialLayout, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL);
|
|
}
|
|
|
|
TEST(DirectVulkanSanity, SampledDepthStencilViewUsesSingleDepthAspect) {
|
|
using namespace MobileGL::MG_Backend::DirectVulkan;
|
|
|
|
EXPECT_EQ(VkTextureManager::ResolveSampledImageViewAspectMask(VK_IMAGE_ASPECT_COLOR_BIT),
|
|
VK_IMAGE_ASPECT_COLOR_BIT);
|
|
EXPECT_EQ(VkTextureManager::ResolveSampledImageViewAspectMask(VK_IMAGE_ASPECT_DEPTH_BIT),
|
|
VK_IMAGE_ASPECT_DEPTH_BIT);
|
|
EXPECT_EQ(VkTextureManager::ResolveSampledImageViewAspectMask(VK_IMAGE_ASPECT_STENCIL_BIT),
|
|
VK_IMAGE_ASPECT_STENCIL_BIT);
|
|
EXPECT_EQ(VkTextureManager::ResolveSampledImageViewAspectMask(
|
|
VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT),
|
|
VK_IMAGE_ASPECT_DEPTH_BIT);
|
|
}
|
|
|
|
TEST(RenderStateSanity, ProvokingVertexUpdatesStateAndValidatesEnum) {
|
|
using namespace MobileGL;
|
|
|
|
MG_State::pGLContext = MakeUnique<MG_State::GLState::GLContext>();
|
|
MG_Backend::pActiveBackendObject = MakeUnique<MG_Backend::DirectGLES::BackendObject_DirectGLES>();
|
|
|
|
GLint mode = 0;
|
|
MG_Impl::GLImpl::GetIntegerv(GL_PROVOKING_VERTEX, &mode);
|
|
EXPECT_EQ(mode, GL_LAST_VERTEX_CONVENTION);
|
|
|
|
const Uint initialVersion = MG_State::pGLContext->GetRenderStateParametersVersion();
|
|
MG_Impl::GLImpl::ProvokingVertex(GL_FIRST_VERTEX_CONVENTION);
|
|
MG_Impl::GLImpl::GetIntegerv(GL_PROVOKING_VERTEX, &mode);
|
|
EXPECT_EQ(mode, GL_FIRST_VERTEX_CONVENTION);
|
|
EXPECT_GT(MG_State::pGLContext->GetRenderStateParametersVersion(), initialVersion);
|
|
|
|
const Uint updatedVersion = MG_State::pGLContext->GetRenderStateParametersVersion();
|
|
MG_Impl::GLImpl::ProvokingVertex(GL_FIRST_VERTEX_CONVENTION);
|
|
EXPECT_EQ(MG_State::pGLContext->GetRenderStateParametersVersion(), updatedVersion);
|
|
|
|
MG_Impl::GLImpl::ProvokingVertex(GL_TRIANGLES);
|
|
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_INVALID_ENUM);
|
|
MG_Impl::GLImpl::GetIntegerv(GL_PROVOKING_VERTEX, &mode);
|
|
EXPECT_EQ(mode, GL_FIRST_VERTEX_CONVENTION);
|
|
|
|
MG_Backend::pActiveBackendObject.reset();
|
|
MG_State::pGLContext.reset();
|
|
}
|
|
|
|
TEST(LogSanity, UsesEnvOverrideForFilePath) {
|
|
namespace fs = std::filesystem;
|
|
|
|
MobileGL::MG_Util::Debug::Close();
|
|
|
|
const fs::path logPath = fs::temp_directory_path() / "mobilegl-log-env-override-test.log";
|
|
const std::string message = "mobilegl-log-env-override-regression";
|
|
fs::remove(logPath);
|
|
|
|
SetEnvVar("MOBILEGL_LOG_FILE_PATH", logPath.string().c_str());
|
|
MobileGL::MG_Util::Debug::Log("INFO", ANDROID_LOG_INFO, "%s", message.c_str());
|
|
MobileGL::MG_Util::Debug::Close();
|
|
UnsetEnvVar("MOBILEGL_LOG_FILE_PATH");
|
|
|
|
{
|
|
std::ifstream logFile(logPath);
|
|
ASSERT_TRUE(logFile.good());
|
|
|
|
const std::string contents((std::istreambuf_iterator<char>(logFile)), std::istreambuf_iterator<char>());
|
|
EXPECT_NE(contents.find(message), std::string::npos);
|
|
}
|
|
|
|
fs::remove(logPath);
|
|
}
|
|
|
|
// ---- Pure-state entry points: glHint / glPointParameter* / glPixelStoref / glGetDoublev -----------
|
|
|
|
TEST(RenderStateSanity, HintStoresAndReadsBack) {
|
|
using namespace MobileGL;
|
|
using namespace MobileGL::MG_Impl::GLImpl;
|
|
MG_State::pGLContext = MakeUnique<MG_State::GLState::GLContext>();
|
|
|
|
// Default is GL_DONT_CARE.
|
|
GLint value = -1;
|
|
GetIntegerv(GL_LINE_SMOOTH_HINT, &value);
|
|
EXPECT_EQ(value, GL_DONT_CARE);
|
|
|
|
// Each of the 4 core targets round-trips.
|
|
Hint(GL_LINE_SMOOTH_HINT, GL_NICEST);
|
|
Hint(GL_POLYGON_SMOOTH_HINT, GL_FASTEST);
|
|
Hint(GL_TEXTURE_COMPRESSION_HINT, GL_NICEST);
|
|
Hint(GL_FRAGMENT_SHADER_DERIVATIVE_HINT, GL_FASTEST);
|
|
EXPECT_EQ(GetError(), GL_NO_ERROR);
|
|
|
|
GetIntegerv(GL_LINE_SMOOTH_HINT, &value);
|
|
EXPECT_EQ(value, GL_NICEST);
|
|
GetIntegerv(GL_POLYGON_SMOOTH_HINT, &value);
|
|
EXPECT_EQ(value, GL_FASTEST);
|
|
GetIntegerv(GL_TEXTURE_COMPRESSION_HINT, &value);
|
|
EXPECT_EQ(value, GL_NICEST);
|
|
GetIntegerv(GL_FRAGMENT_SHADER_DERIVATIVE_HINT, &value);
|
|
EXPECT_EQ(value, GL_FASTEST);
|
|
|
|
// glGetBooleanv on a hint is always GL_TRUE (all hint enums are non-zero).
|
|
GLboolean b = GL_FALSE;
|
|
GetBooleanv(GL_LINE_SMOOTH_HINT, &b);
|
|
EXPECT_EQ(b, GL_TRUE);
|
|
|
|
// A compatibility-only target and a bad mode both raise GL_INVALID_ENUM and change nothing.
|
|
Hint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
|
|
EXPECT_EQ(GetError(), GL_INVALID_ENUM);
|
|
Hint(GL_LINE_SMOOTH_HINT, GL_LINEAR);
|
|
EXPECT_EQ(GetError(), GL_INVALID_ENUM);
|
|
GetIntegerv(GL_LINE_SMOOTH_HINT, &value);
|
|
EXPECT_EQ(value, GL_NICEST); // unchanged by the failed calls
|
|
|
|
MG_State::pGLContext.reset();
|
|
}
|
|
|
|
TEST(RenderStateSanity, PointParameterStoresAndReadsBack) {
|
|
using namespace MobileGL;
|
|
using namespace MobileGL::MG_Impl::GLImpl;
|
|
MG_State::pGLContext = MakeUnique<MG_State::GLState::GLContext>();
|
|
|
|
// Defaults: fade threshold 1.0, coord origin GL_UPPER_LEFT.
|
|
GLfloat f = -1.0f;
|
|
GetFloatv(GL_POINT_FADE_THRESHOLD_SIZE, &f);
|
|
EXPECT_FLOAT_EQ(f, 1.0f);
|
|
GLint origin = -1;
|
|
GetIntegerv(GL_POINT_SPRITE_COORD_ORIGIN, &origin);
|
|
EXPECT_EQ(origin, GL_UPPER_LEFT);
|
|
|
|
// Scalar float sets the fade threshold; GetFloatv keeps the fractional part, GetIntegerv rounds.
|
|
PointParameterf(GL_POINT_FADE_THRESHOLD_SIZE, 2.5f);
|
|
GetFloatv(GL_POINT_FADE_THRESHOLD_SIZE, &f);
|
|
EXPECT_FLOAT_EQ(f, 2.5f);
|
|
GLint fi = 0;
|
|
GetIntegerv(GL_POINT_FADE_THRESHOLD_SIZE, &fi);
|
|
EXPECT_EQ(fi, 3); // 2.5 rounds to nearest (to even or up; lround gives 3)
|
|
|
|
// The v-form reads params[0]; the integer form sets the coord-origin enum.
|
|
const GLint lowerLeft = GL_LOWER_LEFT;
|
|
PointParameteriv(GL_POINT_SPRITE_COORD_ORIGIN, &lowerLeft);
|
|
GetIntegerv(GL_POINT_SPRITE_COORD_ORIGIN, &origin);
|
|
EXPECT_EQ(origin, GL_LOWER_LEFT);
|
|
EXPECT_EQ(GetError(), GL_NO_ERROR);
|
|
|
|
// Errors: negative fade -> GL_INVALID_VALUE; bad coord-origin -> GL_INVALID_ENUM; compat pname ->
|
|
// GL_INVALID_ENUM. None change state.
|
|
PointParameterf(GL_POINT_FADE_THRESHOLD_SIZE, -1.0f);
|
|
EXPECT_EQ(GetError(), GL_INVALID_VALUE);
|
|
PointParameteri(GL_POINT_SPRITE_COORD_ORIGIN, GL_FASTEST);
|
|
EXPECT_EQ(GetError(), GL_INVALID_ENUM);
|
|
PointParameterf(GL_POINT_SIZE_MIN, 0.0f);
|
|
EXPECT_EQ(GetError(), GL_INVALID_ENUM);
|
|
|
|
GetFloatv(GL_POINT_FADE_THRESHOLD_SIZE, &f);
|
|
EXPECT_FLOAT_EQ(f, 2.5f); // unchanged
|
|
GetIntegerv(GL_POINT_SPRITE_COORD_ORIGIN, &origin);
|
|
EXPECT_EQ(origin, GL_LOWER_LEFT); // unchanged
|
|
|
|
MG_State::pGLContext.reset();
|
|
}
|
|
|
|
TEST(RenderStateSanity, PixelStorefRoundsAndZeroTestsBooleans) {
|
|
using namespace MobileGL;
|
|
using namespace MobileGL::MG_Impl::GLImpl;
|
|
MG_State::pGLContext = MakeUnique<MG_State::GLState::GLContext>();
|
|
|
|
// Integer pname: round to nearest.
|
|
PixelStoref(GL_UNPACK_ROW_LENGTH, 7.4f);
|
|
GLint iv = 0;
|
|
GetIntegerv(GL_UNPACK_ROW_LENGTH, &iv);
|
|
EXPECT_EQ(iv, 7);
|
|
PixelStoref(GL_UNPACK_ROW_LENGTH, 7.6f);
|
|
GetIntegerv(GL_UNPACK_ROW_LENGTH, &iv);
|
|
EXPECT_EQ(iv, 8);
|
|
|
|
// Boolean pname: a fractional value must map to TRUE via a zero-test, NOT round-to-zero.
|
|
PixelStoref(GL_PACK_SWAP_BYTES, 0.4f);
|
|
GLboolean bv = GL_FALSE;
|
|
GetBooleanv(GL_PACK_SWAP_BYTES, &bv);
|
|
EXPECT_EQ(bv, GL_TRUE);
|
|
PixelStoref(GL_PACK_SWAP_BYTES, 0.0f);
|
|
GetBooleanv(GL_PACK_SWAP_BYTES, &bv);
|
|
EXPECT_EQ(bv, GL_FALSE);
|
|
|
|
// glPixelStoref matches glPixelStorei for an integer pname.
|
|
PixelStorei(GL_PACK_ALIGNMENT, 8);
|
|
GLint viaI = 0;
|
|
GetIntegerv(GL_PACK_ALIGNMENT, &viaI);
|
|
PixelStoref(GL_PACK_ALIGNMENT, 8.0f);
|
|
GLint viaF = 0;
|
|
GetIntegerv(GL_PACK_ALIGNMENT, &viaF);
|
|
EXPECT_EQ(viaI, viaF);
|
|
EXPECT_EQ(GetError(), GL_NO_ERROR);
|
|
|
|
MG_State::pGLContext.reset();
|
|
}
|
|
|
|
TEST(RenderStateSanity, GetDoublevMatchesGetFloatvWidened) {
|
|
using namespace MobileGL;
|
|
using namespace MobileGL::MG_Impl::GLImpl;
|
|
MG_State::pGLContext = MakeUnique<MG_State::GLState::GLContext>();
|
|
|
|
// Single-component pname.
|
|
PointParameterf(GL_POINT_FADE_THRESHOLD_SIZE, 2.5f);
|
|
GLdouble d1[4] = {-1, -1, -1, -1};
|
|
GetDoublev(GL_POINT_FADE_THRESHOLD_SIZE, d1);
|
|
EXPECT_DOUBLE_EQ(d1[0], 2.5);
|
|
EXPECT_DOUBLE_EQ(d1[1], -1.0); // second component untouched (1-component pname)
|
|
|
|
// 2-component pname: GL_DEPTH_RANGE (default 0..1).
|
|
GLdouble d2[2] = {-1, -1};
|
|
GetDoublev(GL_DEPTH_RANGE, d2);
|
|
EXPECT_DOUBLE_EQ(d2[0], 0.0);
|
|
EXPECT_DOUBLE_EQ(d2[1], 1.0);
|
|
|
|
// 4-component pname: GL_COLOR_CLEAR_VALUE (default 0,0,0,1) matches GetFloatv widened.
|
|
GLfloat cf[4] = {};
|
|
GetFloatv(GL_COLOR_CLEAR_VALUE, cf);
|
|
GLdouble cd[4] = {};
|
|
GetDoublev(GL_COLOR_CLEAR_VALUE, cd);
|
|
for (int i = 0; i < 4; ++i) EXPECT_DOUBLE_EQ(cd[i], static_cast<GLdouble>(cf[i]));
|
|
|
|
// Null params -> GL_INVALID_VALUE.
|
|
GetDoublev(GL_DEPTH_RANGE, nullptr);
|
|
EXPECT_EQ(GetError(), GL_INVALID_VALUE);
|
|
|
|
MG_State::pGLContext.reset();
|
|
}
|