mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-12 06:08:30 +09:00
Compare commits
49
Commits
7463236b34
...
588ddba722
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
588ddba722 | ||
|
|
62301b1061 | ||
|
|
f3a846d336 | ||
|
|
9cdc82fbdd | ||
|
|
4a9d20c49f | ||
|
|
394d1ce748 | ||
|
|
300b458132 | ||
|
|
1f1a331a44 | ||
|
|
817091641c | ||
|
|
e64c7c7e65 | ||
|
|
dd60ff39ce | ||
|
|
96ad7ca0cc | ||
|
|
e80a23eae6 | ||
|
|
088f263495 | ||
|
|
3b3b6e5b8b | ||
|
|
9eda2147b1 | ||
|
|
a63699cde6 | ||
|
|
765aaec6dc | ||
|
|
bcd669bd25 | ||
|
|
f3405d1d53 | ||
|
|
81604d5596 | ||
|
|
31ea6aa5a3 | ||
|
|
7d6f6603c1 | ||
|
|
39c17c0b1b | ||
|
|
88138b48ec | ||
|
|
c114ce750b | ||
|
|
1ca2d3c0fe | ||
|
|
58c17f85a5 | ||
|
|
4873da6844 | ||
|
|
efeb24ff9b | ||
|
|
18c1a4d586 | ||
|
|
66ac3486e1 | ||
|
|
764a44f589 | ||
|
|
d96acb7972 | ||
|
|
bd710078fc | ||
|
|
95a7b17d45 | ||
|
|
19932f9e49 | ||
|
|
1011d9fea1 | ||
|
|
b5565ae503 | ||
|
|
b06ad3f877 | ||
|
|
3311e6034a | ||
|
|
027c1bd4ab | ||
|
|
da52cc3906 | ||
|
|
ebe4fe133f | ||
|
|
534ec65dda | ||
|
|
35ad1ae7fc | ||
|
|
6152ee933f | ||
|
|
dac02ca044 | ||
|
|
42fd02d82f |
@@ -201,6 +201,11 @@ fetch_file_from_mirror() {
|
||||
return 1
|
||||
}
|
||||
|
||||
# Files no mirror could serve, even after retrying every mirror. Only these fall
|
||||
# back to Git LFS, so a mirror that served the rest of the case still spares
|
||||
# GitHub the bandwidth for those files.
|
||||
mirror_failures=()
|
||||
|
||||
fetch_from_mirror() {
|
||||
mkdir -p "${fixture_dir}"
|
||||
for file in "${files[@]}"; do
|
||||
@@ -219,17 +224,19 @@ fetch_from_mirror() {
|
||||
echo "Mirror did not serve ${name}; trying the next mirror" >&2
|
||||
done
|
||||
if [ "${fetched}" -ne 1 ]; then
|
||||
return 1
|
||||
mirror_failures+=("${file}")
|
||||
fi
|
||||
done
|
||||
[ "${#mirror_failures[@]}" -eq 0 ]
|
||||
}
|
||||
|
||||
if fetch_from_mirror; then
|
||||
echo "Fetched trace fixture files for ${case_name} from mirror: ${include}"
|
||||
else
|
||||
echo "All mirrors failed for ${case_name}; falling back to Git LFS: ${include}"
|
||||
fallback_include="$(IFS=,; echo "${mirror_failures[*]}")"
|
||||
echo "All mirrors failed for ${#mirror_failures[@]} of ${#files[@]} file(s) of ${case_name}; falling back to Git LFS: ${fallback_include}"
|
||||
git lfs install --local
|
||||
git lfs pull --include="${include}" --exclude=""
|
||||
git lfs pull --include="${fallback_include}" --exclude=""
|
||||
fi
|
||||
|
||||
for file in "${files[@]}"; do
|
||||
|
||||
@@ -218,6 +218,7 @@ set(SOURCE_FILES
|
||||
MobileGL/MG_Impl/GLImpl/Framebuffer/Validators.cpp
|
||||
MobileGL/MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.cpp
|
||||
MobileGL/MG_Impl/GLImpl/Program/GL_Program.cpp
|
||||
MobileGL/MG_Impl/GLImpl/Program/GL_ProgramPipeline.cpp
|
||||
MobileGL/MG_Impl/GLImpl/Texture/GL_Texture.cpp
|
||||
MobileGL/MG_Impl/GLImpl/Texture/Validators.cpp
|
||||
MobileGL/MG_Impl/GLImpl/Texture/ProxyTexture.cpp
|
||||
|
||||
@@ -145,6 +145,10 @@ namespace MobileGL {
|
||||
GLenum buffer, GLint drawbuffer, const GLfloat* value);
|
||||
void (*ClearNamedFramebufferfi)(const SharedPtr<MG_State::GLState::FramebufferObject>& framebuffer,
|
||||
GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil);
|
||||
void (*ClearNamedFramebufferiv)(const SharedPtr<MG_State::GLState::FramebufferObject>& framebuffer,
|
||||
GLenum buffer, GLint drawbuffer, const GLint* value);
|
||||
void (*ClearNamedFramebufferuiv)(const SharedPtr<MG_State::GLState::FramebufferObject>& framebuffer,
|
||||
GLenum buffer, GLint drawbuffer, const GLuint* value);
|
||||
void (*BlitFramebuffer)(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0,
|
||||
GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter);
|
||||
void (*BlitNamedFramebuffer)(const SharedPtr<MG_State::GLState::FramebufferObject>& readFramebuffer,
|
||||
@@ -349,6 +353,12 @@ namespace MobileGL {
|
||||
// real ES drivers behind DirectGLES both do. Defaults to true so a backend
|
||||
// that never sets it keeps the permissive behaviour.
|
||||
Bool SupportsDistinctDepthStencilAttachments = true;
|
||||
// Whether attaching a single layer of a 3D or array texture to a framebuffer actually
|
||||
// renders to that layer. DirectGLES hands the layer straight to
|
||||
// glFramebufferTextureLayer, so it does; DirectVulkan maps a GL layer onto a Vulkan
|
||||
// array layer with no notion of a 3D depth slice, so it does not yet. Defaults to false
|
||||
// so a backend that never sets it gets the conservative answer.
|
||||
Bool SupportsPerLayerFramebufferAttachment = false;
|
||||
SizeT MaxShaderStorageBlockSize = 128 * 1024 * 1024;
|
||||
Uint32 SubgroupSize = 0;
|
||||
Uint32 SubgroupSupportedStages = 0;
|
||||
|
||||
@@ -32,8 +32,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
|
||||
void ClearGLErrors(const MG_External::GLESFunctionsTable& gl) {
|
||||
if (!gl.glGetError) return;
|
||||
while (gl.glGetError() != GL_NO_ERROR) {
|
||||
}
|
||||
while (gl.glGetError() != GL_NO_ERROR) {}
|
||||
}
|
||||
|
||||
Bool CheckNoGLError(const MG_External::GLESFunctionsTable& gl) {
|
||||
@@ -77,8 +76,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
}
|
||||
|
||||
Bool IsGLESProbeMultisampleTarget(TextureTarget target) {
|
||||
return target == TextureTarget::Texture2DMultisample ||
|
||||
target == TextureTarget::Texture2DMultisampleArray;
|
||||
return target == TextureTarget::Texture2DMultisample || target == TextureTarget::Texture2DMultisampleArray;
|
||||
}
|
||||
|
||||
GLenum GetFramebufferAttachment(TextureInternalFormat format) {
|
||||
@@ -115,8 +113,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
GLenum normalizedInternalFormat = glFormat;
|
||||
GLenum imageFormat = GL_RGBA;
|
||||
GLenum imageType = GL_UNSIGNED_BYTE;
|
||||
MG_Util::TextureFormatProcessor::NormalizePixelFormat(
|
||||
glFormat, PixelFormatNormalizeOptionBit::None, &normalizedInternalFormat, &imageFormat, &imageType);
|
||||
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);
|
||||
@@ -154,9 +152,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
GLESProbeFormatInfo BuildNativeProbeFormatInfo(GLenum requestedInternalFormat) {
|
||||
GLESProbeFormatInfo info;
|
||||
info.InternalFormat = requestedInternalFormat;
|
||||
MG_Util::TextureFormatProcessor::NormalizePixelFormat(
|
||||
requestedInternalFormat, PixelFormatNormalizeOptionBit::None, nullptr, &info.ImageFormat,
|
||||
&info.ImageType);
|
||||
MG_Util::TextureFormatProcessor::NormalizePixelFormat(requestedInternalFormat,
|
||||
PixelFormatNormalizeOptionBit::None, nullptr,
|
||||
&info.ImageFormat, &info.ImageType);
|
||||
return info;
|
||||
}
|
||||
|
||||
@@ -233,20 +231,16 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
return MG_Util::ConvertGLEnumToString(internalFormat);
|
||||
}
|
||||
|
||||
void LogGLESFormatCaveat(TextureInternalFormat logicalFormat,
|
||||
SizeT targetIndex,
|
||||
void LogGLESFormatCaveat(TextureInternalFormat logicalFormat, SizeT targetIndex,
|
||||
const GLESProbeFormatInfo& fallbackInfo) {
|
||||
MGLOG_D("Caveat: %s %s not fully supported. Reason: %s. Fallback: %s",
|
||||
GetFormatCapabilityTargetName(targetIndex).c_str(),
|
||||
MG_Util::ConvertTextureInternalFormatToString(logicalFormat).c_str(),
|
||||
fallbackInfo.Reason.c_str(),
|
||||
MG_Util::ConvertTextureInternalFormatToString(logicalFormat).c_str(), fallbackInfo.Reason.c_str(),
|
||||
ConvertFallbackInternalFormatToString(fallbackInfo.InternalFormat).c_str());
|
||||
}
|
||||
|
||||
Bool BuildFallbackProbeFormatInfo(GLenum requestedInternalFormat,
|
||||
Flags<PixelFormatNormalizeOptionBit> options,
|
||||
Bool forced,
|
||||
GLESProbeFormatInfo& outInfo) {
|
||||
Bool BuildFallbackProbeFormatInfo(GLenum requestedInternalFormat, Flags<PixelFormatNormalizeOptionBit> options,
|
||||
Bool forced, GLESProbeFormatInfo& outInfo) {
|
||||
const Flags<PixelFormatNormalizeOptionBit> applicableOptions =
|
||||
MG_Util::TextureFormatProcessor::GetApplicablePixelFormatNormalizeOptions(requestedInternalFormat,
|
||||
options);
|
||||
@@ -261,8 +255,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
return outInfo.InternalFormat != GL_UNKNOWN_MGL;
|
||||
}
|
||||
|
||||
FormatCapabilityFlags BuildTextureCapsFromProbe(TextureInternalFormat logicalFormat,
|
||||
TextureTarget target,
|
||||
FormatCapabilityFlags BuildTextureCapsFromProbe(TextureInternalFormat logicalFormat, TextureTarget target,
|
||||
Bool renderable) {
|
||||
FormatCapabilityFlags caps = GetTextureFeatureCaps(logicalFormat, target);
|
||||
if (renderable) {
|
||||
@@ -276,16 +269,12 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
return caps;
|
||||
}
|
||||
|
||||
void AddFullFormatCaps(FormatCapabilityCache& cache,
|
||||
SizeT targetIndex,
|
||||
SizeT formatIndex,
|
||||
void AddFullFormatCaps(FormatCapabilityCache& cache, SizeT targetIndex, SizeT formatIndex,
|
||||
FormatCapabilityFlags caps) {
|
||||
cache.FullCaps[targetIndex][formatIndex] |= caps;
|
||||
}
|
||||
|
||||
Bool AddCaveatFormatCaps(FormatCapabilityCache& cache,
|
||||
SizeT targetIndex,
|
||||
SizeT formatIndex,
|
||||
Bool AddCaveatFormatCaps(FormatCapabilityCache& cache, SizeT targetIndex, SizeT formatIndex,
|
||||
FormatCapabilityFlags caps) {
|
||||
Bool added = false;
|
||||
for (FormatCapability capability : kReportedFormatCapabilities) {
|
||||
@@ -299,8 +288,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
}
|
||||
|
||||
Int GetGLESFormatMaxSamples(const MG_External::GLESCapabilities& capabilities,
|
||||
TextureInternalFormat logicalFormat,
|
||||
GLenum imageFormat) {
|
||||
TextureInternalFormat logicalFormat, GLenum imageFormat) {
|
||||
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 ||
|
||||
@@ -314,10 +302,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
return capabilities.MaxColorTextureSamples;
|
||||
}
|
||||
|
||||
Bool ProbeFramebufferCompletenessForTexture(const MG_External::GLESFunctionsTable& gl,
|
||||
TextureTarget target,
|
||||
GLuint texture,
|
||||
TextureInternalFormat format) {
|
||||
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 ||
|
||||
@@ -399,9 +385,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
return supported;
|
||||
}
|
||||
|
||||
Bool ProbeFramebufferCompletenessForRenderbuffer(const MG_External::GLESFunctionsTable& gl,
|
||||
GLuint renderbuffer,
|
||||
TextureInternalFormat format) {
|
||||
Bool ProbeFramebufferCompletenessForRenderbuffer(const MG_External::GLESFunctionsTable& gl, GLuint renderbuffer,
|
||||
TextureInternalFormat format) {
|
||||
GLuint framebuffer = 0;
|
||||
GLint prevFramebuffer = 0;
|
||||
if (!gl.glGenFramebuffers || !gl.glBindFramebuffer || !gl.glFramebufferRenderbuffer ||
|
||||
@@ -468,16 +453,16 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
}
|
||||
break;
|
||||
case TextureTarget::Texture3D:
|
||||
gl.glTexImage3D(glTarget, 0, static_cast<GLint>(internalFormat), 2, 2, 2, 0, imageFormat,
|
||||
imageType, nullptr);
|
||||
gl.glTexImage3D(glTarget, 0, static_cast<GLint>(internalFormat), 2, 2, 2, 0, imageFormat, imageType,
|
||||
nullptr);
|
||||
break;
|
||||
case TextureTarget::Texture2DArray:
|
||||
gl.glTexImage3D(glTarget, 0, static_cast<GLint>(internalFormat), 2, 2, 1, 0, imageFormat,
|
||||
imageType, nullptr);
|
||||
gl.glTexImage3D(glTarget, 0, static_cast<GLint>(internalFormat), 2, 2, 1, 0, imageFormat, imageType,
|
||||
nullptr);
|
||||
break;
|
||||
case TextureTarget::TextureCubeMapArray:
|
||||
gl.glTexImage3D(glTarget, 0, static_cast<GLint>(internalFormat), 2, 2, 6, 0, imageFormat,
|
||||
imageType, nullptr);
|
||||
gl.glTexImage3D(glTarget, 0, static_cast<GLint>(internalFormat), 2, 2, 6, 0, imageFormat, imageType,
|
||||
nullptr);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@@ -498,11 +483,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
return created;
|
||||
}
|
||||
|
||||
Bool ProbeRenderbuffer(const MG_External::GLESFunctionsTable& gl,
|
||||
GLenum internalFormat,
|
||||
TextureInternalFormat logicalFormat,
|
||||
Bool multisample,
|
||||
Int samples) {
|
||||
Bool ProbeRenderbuffer(const MG_External::GLESFunctionsTable& gl, GLenum internalFormat,
|
||||
TextureInternalFormat logicalFormat, Bool multisample, Int samples) {
|
||||
if (!gl.glGenRenderbuffers || !gl.glBindRenderbuffer || !gl.glDeleteRenderbuffers) {
|
||||
return false;
|
||||
}
|
||||
@@ -524,17 +506,16 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
gl.glRenderbufferStorage(GL_RENDERBUFFER, internalFormat, 1, 1);
|
||||
}
|
||||
const Bool created = CheckNoGLError(gl);
|
||||
const Bool complete = created && ProbeFramebufferCompletenessForRenderbuffer(gl, renderbuffer, logicalFormat);
|
||||
const Bool complete =
|
||||
created && ProbeFramebufferCompletenessForRenderbuffer(gl, renderbuffer, logicalFormat);
|
||||
gl.glBindRenderbuffer(GL_RENDERBUFFER, static_cast<GLuint>(prevRenderbuffer));
|
||||
gl.glDeleteRenderbuffers(1, &renderbuffer);
|
||||
ClearGLErrors(gl);
|
||||
return complete;
|
||||
}
|
||||
|
||||
Vector<Int> ProbeRenderbufferSampleCounts(const MG_External::GLESFunctionsTable& gl,
|
||||
GLenum internalFormat,
|
||||
TextureInternalFormat logicalFormat,
|
||||
Int maxSamples) {
|
||||
Vector<Int> ProbeRenderbufferSampleCounts(const MG_External::GLESFunctionsTable& gl, GLenum internalFormat,
|
||||
TextureInternalFormat logicalFormat, Int maxSamples) {
|
||||
Vector<Int> sampleCounts;
|
||||
for (Int samples = std::max(maxSamples, 1); samples > 1; samples >>= 1) {
|
||||
if (ProbeRenderbuffer(gl, internalFormat, logicalFormat, true, samples)) {
|
||||
@@ -589,8 +570,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
hasForcedFallback = BuildFallbackProbeFormatInfo(
|
||||
requestedInternalFormat, forcedOptions | targetOptions, true, fallbackInfo);
|
||||
if (!hasForcedFallback) {
|
||||
BuildFallbackProbeFormatInfo(requestedInternalFormat, driverOptions | targetOptions,
|
||||
false, fallbackInfo);
|
||||
BuildFallbackProbeFormatInfo(requestedInternalFormat, driverOptions | targetOptions, false,
|
||||
fallbackInfo);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -623,9 +604,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
ProbeTexture(gl, probeTarget, fallbackInfo.InternalFormat, fallbackInfo.ImageFormat,
|
||||
fallbackInfo.ImageType, logicalFormat, &fallbackRenderable);
|
||||
if (fallbackCreated) {
|
||||
if (AddCaveatFormatCaps(cache, targetIndex, formatIndex,
|
||||
BuildTextureCapsFromProbe(logicalFormat, target,
|
||||
fallbackRenderable))) {
|
||||
if (AddCaveatFormatCaps(
|
||||
cache, targetIndex, formatIndex,
|
||||
BuildTextureCapsFromProbe(logicalFormat, target, fallbackRenderable))) {
|
||||
LogGLESFormatCaveat(logicalFormat, targetIndex, fallbackInfo);
|
||||
}
|
||||
if (IsGLESProbeMultisampleTarget(target)) {
|
||||
@@ -676,7 +657,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
.ExtraVendor = Nullopt, // Extra vendor
|
||||
.RendererGLInfo =
|
||||
{
|
||||
.TargetGLVersion = {3, 3, 0}, // GL target version
|
||||
.TargetGLVersion = {4, 0, 0}, // GL target version
|
||||
.TargetGLSLVersion = {4, 6, 0}, // Target Shading Language Version
|
||||
// Baseline advertisement (no timer queries / anisotropy yet); reconciled
|
||||
// once the ES capabilities exist, see UpdateAdvertisedCapabilityExtensions.
|
||||
@@ -707,8 +688,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
} // namespace
|
||||
|
||||
void PopulateFormatCapabilities(const MG_External::GLESFunctionsTable& gl,
|
||||
const MG_External::GLESCapabilities& capabilities,
|
||||
FormatCapabilityCache& cache) {
|
||||
const MG_External::GLESCapabilities& capabilities, FormatCapabilityCache& cache) {
|
||||
PopulateFormatCapabilitiesImpl(gl, capabilities, cache);
|
||||
}
|
||||
|
||||
@@ -772,10 +752,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((handle.Backend != WindowBackend::Android &&
|
||||
handle.Backend != WindowBackend::X11 &&
|
||||
handle.Backend != WindowBackend::MetalLayer &&
|
||||
handle.Backend != WindowBackend::Win32) ||
|
||||
if ((handle.Backend != WindowBackend::Android && handle.Backend != WindowBackend::X11 &&
|
||||
handle.Backend != WindowBackend::MetalLayer && handle.Backend != WindowBackend::Win32) ||
|
||||
!handle.Handle) {
|
||||
MGLOG_E("DirectGLES backend only supports Android, X11, CAMetalLayer, and Win32 native windows");
|
||||
return false;
|
||||
@@ -894,27 +872,25 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
}
|
||||
|
||||
Vector<GLExtension> BuildAdvertisedExtensions(Bool timerQueriesSupported, Bool anisotropicFilteringSupported) {
|
||||
Vector<GLExtension> extensions = {V_OpenGL30, V_OpenGL31, V_OpenGL32,
|
||||
V_OpenGL33, E_GL_ARB_draw_buffers_blend, E_GL_ARB_compute_shader,
|
||||
E_GL_ARB_shader_storage_buffer_object, E_GL_ARB_shader_image_load_store,
|
||||
E_GL_ARB_program_interface_query, E_GL_ARB_framebuffer_object,
|
||||
E_GL_EXT_framebuffer_object, E_GL_ARB_depth_texture, E_GL_ARB_buffer_storage,
|
||||
E_GL_ARB_texture_storage, E_GL_ARB_texture_storage_multisample,
|
||||
E_GL_ARB_clear_texture, E_GL_ARB_direct_state_access,
|
||||
E_GL_ARB_multi_draw_indirect, E_GL_ARB_indirect_parameters,
|
||||
E_GL_ARB_shader_draw_parameters, E_GL_ARB_gpu_shader5, E_GL_ARB_multi_bind,
|
||||
E_GL_ARB_shading_language_420pack, E_GL_ARB_vertex_attrib_binding,
|
||||
// Both are core from GL 3.2/3.3 on and implemented here for
|
||||
// every advertised version, but an app targeting 3.0/3.1
|
||||
// only reaches them through the extension string - the CTS
|
||||
// picks a whole different shader for draw_buffers without
|
||||
// explicit_attrib_location. DirectVulkan advertises both.
|
||||
E_GL_ARB_explicit_attrib_location, E_GL_ARB_texture_multisample,
|
||||
E_GL_ARB_shader_image_size,
|
||||
// Advertised with GL_NUM_PROGRAM_BINARY_FORMATS = 0, which the
|
||||
// extension explicitly permits. It is also the only thing that
|
||||
// exposes glProgramParameteri before GL 4.1.
|
||||
E_GL_ARB_get_program_binary};
|
||||
Vector<GLExtension> extensions = {
|
||||
V_OpenGL30, V_OpenGL31, V_OpenGL32, V_OpenGL33, V_OpenGL40, E_GL_ARB_draw_buffers_blend,
|
||||
E_GL_ARB_compute_shader, E_GL_ARB_shader_storage_buffer_object, E_GL_ARB_shader_image_load_store,
|
||||
E_GL_ARB_program_interface_query, E_GL_ARB_framebuffer_object, E_GL_EXT_framebuffer_object,
|
||||
E_GL_ARB_depth_texture, E_GL_ARB_buffer_storage, E_GL_ARB_texture_storage,
|
||||
E_GL_ARB_texture_storage_multisample, E_GL_ARB_clear_texture, E_GL_ARB_direct_state_access,
|
||||
E_GL_ARB_multi_draw_indirect, E_GL_ARB_indirect_parameters, E_GL_ARB_shader_draw_parameters,
|
||||
E_GL_ARB_gpu_shader5, E_GL_ARB_multi_bind, E_GL_ARB_shading_language_420pack,
|
||||
E_GL_ARB_vertex_attrib_binding,
|
||||
// Both are core from GL 3.2/3.3 on and implemented here for
|
||||
// every advertised version, but an app targeting 3.0/3.1
|
||||
// only reaches them through the extension string - the CTS
|
||||
// picks a whole different shader for draw_buffers without
|
||||
// explicit_attrib_location. DirectVulkan advertises both.
|
||||
E_GL_ARB_explicit_attrib_location, E_GL_ARB_texture_multisample, E_GL_ARB_shader_image_size,
|
||||
// Advertised with GL_NUM_PROGRAM_BINARY_FORMATS = 0, which the
|
||||
// extension explicitly permits. It is also the only thing that
|
||||
// exposes glProgramParameteri before GL 4.1.
|
||||
E_GL_ARB_get_program_binary};
|
||||
// Only advertised when the device driver actually has usable timer queries
|
||||
// (GL_EXT_disjoint_timer_query plus its entry points) and the
|
||||
// MOBILEGL_DISABLE_TIMERQUERY escape hatch is off.
|
||||
@@ -988,6 +964,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
funcsTable.GL.ClearBufferuiv = ClearBufferuiv;
|
||||
funcsTable.GL.ClearBufferiv = ClearBufferiv;
|
||||
funcsTable.GL.ClearNamedFramebufferfv = ClearNamedFramebufferfv;
|
||||
funcsTable.GL.ClearNamedFramebufferiv = ClearNamedFramebufferiv;
|
||||
funcsTable.GL.ClearNamedFramebufferuiv = ClearNamedFramebufferuiv;
|
||||
funcsTable.GL.ClearNamedFramebufferfi = ClearNamedFramebufferfi;
|
||||
funcsTable.GL.BlitFramebuffer = BlitFramebuffer;
|
||||
funcsTable.GL.BlitNamedFramebuffer = BlitNamedFramebuffer;
|
||||
@@ -1050,8 +1028,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
return m_dynamicParameters;
|
||||
}
|
||||
|
||||
void BackendObject_DirectGLES::ApplyGLESCapabilitiesForTesting(
|
||||
const MG_External::GLESCapabilities& capabilities) {
|
||||
void BackendObject_DirectGLES::ApplyGLESCapabilitiesForTesting(const MG_External::GLESCapabilities& capabilities) {
|
||||
m_GLESCapabilities = capabilities;
|
||||
UpdateDynamicBackendParameters();
|
||||
}
|
||||
@@ -1115,8 +1092,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
m_dynamicParameters.TextureBufferOffsetAlignment = m_GLESCapabilities.TextureBufferOffsetAlignment;
|
||||
m_dynamicParameters.MaxUniformBufferBindings = m_GLESCapabilities.MaxUniformBufferBindings;
|
||||
m_dynamicParameters.MaxUniformBlockSize = m_GLESCapabilities.MaxUniformBlockSize;
|
||||
const Int maxSupportedTextureUnits =
|
||||
static_cast<Int>(MG_State::GLState::TextureState::MAX_TEXTURE_IMAGE_UNITS);
|
||||
const Int maxSupportedTextureUnits = static_cast<Int>(MG_State::GLState::TextureState::MAX_TEXTURE_IMAGE_UNITS);
|
||||
m_dynamicParameters.MaxImageUnits =
|
||||
std::max(std::min(m_GLESCapabilities.MaxImageUnits, maxSupportedTextureUnits), 0);
|
||||
m_dynamicParameters.MaxCombinedImageUniforms = std::max(m_GLESCapabilities.MaxCombinedImageUniforms, 0);
|
||||
@@ -1124,8 +1100,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
return std::min({std::max(stageLimit, 0), m_dynamicParameters.MaxImageUnits,
|
||||
m_dynamicParameters.MaxCombinedImageUniforms});
|
||||
};
|
||||
m_dynamicParameters.MaxVertexImageUniforms =
|
||||
clampStageImageUniforms(m_GLESCapabilities.MaxVertexImageUniforms);
|
||||
m_dynamicParameters.MaxVertexImageUniforms = clampStageImageUniforms(m_GLESCapabilities.MaxVertexImageUniforms);
|
||||
m_dynamicParameters.MaxGeometryImageUniforms =
|
||||
clampStageImageUniforms(m_GLESCapabilities.MaxGeometryImageUniforms);
|
||||
m_dynamicParameters.MaxFragmentImageUniforms =
|
||||
@@ -1134,6 +1109,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
clampStageImageUniforms(m_GLESCapabilities.MaxComputeImageUniforms);
|
||||
m_dynamicParameters.SupportsDistinctDepthStencilAttachments =
|
||||
ProbeDistinctDepthStencilAttachments(DirectGLES::g_GLESFuncs);
|
||||
// SyncAttachmentObject routes a layered upload target to glFramebufferTextureLayer with the
|
||||
// attachment's layer passed through, so this backend really does render to the layer it was
|
||||
// given - provided the driver resolved the entry point at all.
|
||||
m_dynamicParameters.SupportsPerLayerFramebufferAttachment =
|
||||
DirectGLES::g_GLESFuncs.glFramebufferTextureLayer != nullptr;
|
||||
m_dynamicParameters.MaxDrawBuffers = m_GLESCapabilities.MaxDrawBuffers;
|
||||
m_dynamicParameters.MaxColorAttachments = m_GLESCapabilities.MaxColorAttachments;
|
||||
m_dynamicParameters.MaxClipDistances = m_GLESCapabilities.MaxClipDistances;
|
||||
@@ -1155,8 +1135,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
const Float requiredMaxOffset =
|
||||
0.5f - std::ldexp(1.0f, -m_GLESCapabilities.FragmentInterpolationOffsetBits);
|
||||
if (m_GLESCapabilities.MaxFragmentInterpolationOffset >= requiredMaxOffset) {
|
||||
m_dynamicParameters.MaxFragmentInterpolationOffset =
|
||||
m_GLESCapabilities.MaxFragmentInterpolationOffset;
|
||||
m_dynamicParameters.MaxFragmentInterpolationOffset = m_GLESCapabilities.MaxFragmentInterpolationOffset;
|
||||
m_dynamicParameters.FragmentInterpolationOffsetBits =
|
||||
m_GLESCapabilities.FragmentInterpolationOffsetBits;
|
||||
}
|
||||
@@ -1165,9 +1144,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
m_GLESCapabilities.AliasedLineWidthRangeMax > 1.0f || m_GLESCapabilities.SmoothLineWidthRangeMax > 1.0f;
|
||||
|
||||
const auto containsAny = [](const String& haystack, std::initializer_list<const char*> needles) {
|
||||
return std::any_of(needles.begin(), needles.end(), [&](const char* needle) {
|
||||
return haystack.find(needle) != String::npos;
|
||||
});
|
||||
return std::any_of(needles.begin(), needles.end(),
|
||||
[&](const char* needle) { return haystack.find(needle) != String::npos; });
|
||||
};
|
||||
const String vendorAndRenderer =
|
||||
m_GLESCapabilities.GLESVendorString + " " + m_GLESCapabilities.GLESRendererString;
|
||||
|
||||
@@ -1663,8 +1663,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
if (backendSamplerIt != SamplerImpl::g_backendSamplerObjects.end()) {
|
||||
backendSamplerIt->second->Bind(unit);
|
||||
}
|
||||
|
||||
} else {
|
||||
// Symmetric with the bind above: a sampler object left on the unit by an earlier
|
||||
// draw keeps being applied, and on a multisample texture - which takes no sampler
|
||||
// object at all - the draw is rejected outright.
|
||||
SamplerImpl::UnbindSampler(unit);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1844,6 +1847,10 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
backendObj = MakeShared<SamplerImpl::BackendSamplerObject>();
|
||||
}
|
||||
backendObj->SyncToBackend(samplerObject);
|
||||
// Syncing the object's parameters is not the same as putting it on the
|
||||
// unit: without this the driver kept sampling with the texture's own
|
||||
// parameters and every sampler object was inert.
|
||||
backendObj->Bind(unit);
|
||||
} else {
|
||||
SamplerImpl::UnbindSampler(unit);
|
||||
}
|
||||
@@ -4431,6 +4438,40 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
ForceBindCurrentFBO(FramebufferTarget::Draw);
|
||||
}
|
||||
|
||||
void ClearNamedFramebufferiv(const SharedPtr<MG_State::GLState::FramebufferObject>& framebuffer,
|
||||
GLenum buffer, GLint drawbuffer, const GLint* value) {
|
||||
#if MOBILEGL_LOG_ACTIVE_LEVEL <= MOBILEGL_LOG_LEVEL_DEBUG && MOBILEGL_ENABLE_SCOPE_MARKER
|
||||
DebugImpl::OpenGLScopeMarker marker(__func__);
|
||||
#endif
|
||||
TextureImpl::SyncNeccessaryTextures();
|
||||
RenderStateImpl::SyncRenderState();
|
||||
|
||||
SyncAndBindFramebufferObject(framebuffer, FramebufferTarget::Draw, true);
|
||||
g_GLESFuncs.glClearBufferiv(buffer, drawbuffer, value);
|
||||
DebugImpl::ErrorLopper::Loop([file = __FILE__, line = __LINE__](auto err) {
|
||||
MGLOG_D("ES error (%s:%d): %s", file, line, MG_Util::ConvertGLEnumToString(err).c_str());
|
||||
});
|
||||
|
||||
ForceBindCurrentFBO(FramebufferTarget::Draw);
|
||||
}
|
||||
|
||||
void ClearNamedFramebufferuiv(const SharedPtr<MG_State::GLState::FramebufferObject>& framebuffer,
|
||||
GLenum buffer, GLint drawbuffer, const GLuint* value) {
|
||||
#if MOBILEGL_LOG_ACTIVE_LEVEL <= MOBILEGL_LOG_LEVEL_DEBUG && MOBILEGL_ENABLE_SCOPE_MARKER
|
||||
DebugImpl::OpenGLScopeMarker marker(__func__);
|
||||
#endif
|
||||
TextureImpl::SyncNeccessaryTextures();
|
||||
RenderStateImpl::SyncRenderState();
|
||||
|
||||
SyncAndBindFramebufferObject(framebuffer, FramebufferTarget::Draw, true);
|
||||
g_GLESFuncs.glClearBufferuiv(buffer, drawbuffer, value);
|
||||
DebugImpl::ErrorLopper::Loop([file = __FILE__, line = __LINE__](auto err) {
|
||||
MGLOG_D("ES error (%s:%d): %s", file, line, MG_Util::ConvertGLEnumToString(err).c_str());
|
||||
});
|
||||
|
||||
ForceBindCurrentFBO(FramebufferTarget::Draw);
|
||||
}
|
||||
|
||||
static SizeT AlignPixelRow(SizeT rowBytes, Int alignment) {
|
||||
const SizeT resolvedAlignment = static_cast<SizeT>(std::max(alignment, 1));
|
||||
return (rowBytes + resolvedAlignment - 1) & ~(resolvedAlignment - 1);
|
||||
@@ -4532,14 +4573,27 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
Vector<Uint32> packed(outStencil.size(), 0);
|
||||
ClearGLErrors();
|
||||
g_GLESFuncs.glReadPixels(x, y, width, height, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, packed.data());
|
||||
if (g_GLESFuncs.glGetError() == GL_NO_ERROR) {
|
||||
for (SizeT i = 0; i < outStencil.size(); ++i) {
|
||||
outStencil[i] = static_cast<Uint8>(packed[i] & 0xFFu);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// A DEPTH32F_STENCIL8 attachment rejects the 24_8 type: its packed layout is a float depth
|
||||
// followed by a padded stencil byte, eight bytes per pixel with the index at offset 4.
|
||||
Vector<Uint8> packed32f(outStencil.size() * 8u, 0);
|
||||
ClearGLErrors();
|
||||
g_GLESFuncs.glReadPixels(x, y, width, height, GL_DEPTH_STENCIL, GL_FLOAT_32_UNSIGNED_INT_24_8_REV,
|
||||
packed32f.data());
|
||||
const GLenum packedError = g_GLESFuncs.glGetError();
|
||||
if (packedError != GL_NO_ERROR) {
|
||||
MGLOG_E("ReadPixels: neither GL_STENCIL_INDEX nor GL_DEPTH_STENCIL readback is available: %s",
|
||||
MGLOG_E("ReadPixels: no stencil readback path is available: %s",
|
||||
MG_Util::ConvertGLEnumToString(packedError).c_str());
|
||||
return false;
|
||||
}
|
||||
for (SizeT i = 0; i < outStencil.size(); ++i) {
|
||||
outStencil[i] = static_cast<Uint8>(packed[i] & 0xFFu);
|
||||
outStencil[i] = packed32f[i * 8u + 4u];
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -4549,11 +4603,20 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// values themselves are always 8 bits.
|
||||
static Bool ReadPixelsStencilViaNative(GLint x, GLint y, GLsizei width, GLsizei height, GLenum type,
|
||||
void* pixels) {
|
||||
// GL 4.6 core 18.2.8: a stencil index is written unconverted into whichever integer width
|
||||
// the client asked for, and converted to a float value for GL_FLOAT. The signed widths are
|
||||
// as legal as the unsigned ones - the CTS reads stencil with GL_INT - and rejecting them
|
||||
// here used to let the call fall through to a native ES read the driver refuses, after
|
||||
// which nothing was written at all and the caller kept its zeros.
|
||||
SizeT dstPixelBytes = 0;
|
||||
switch (type) {
|
||||
case GL_UNSIGNED_BYTE: dstPixelBytes = sizeof(Uint8); break;
|
||||
case GL_UNSIGNED_SHORT: dstPixelBytes = sizeof(Uint16); break;
|
||||
case GL_UNSIGNED_INT: dstPixelBytes = sizeof(Uint32); break;
|
||||
case GL_UNSIGNED_BYTE:
|
||||
case GL_BYTE: dstPixelBytes = sizeof(Uint8); break;
|
||||
case GL_UNSIGNED_SHORT:
|
||||
case GL_SHORT: dstPixelBytes = sizeof(Uint16); break;
|
||||
case GL_UNSIGNED_INT:
|
||||
case GL_INT: dstPixelBytes = sizeof(Uint32); break;
|
||||
case GL_FLOAT: dstPixelBytes = sizeof(GLfloat); break;
|
||||
default: return false;
|
||||
}
|
||||
if (width <= 0 || height <= 0) {
|
||||
@@ -4587,11 +4650,16 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
for (GLsizei col = 0; col < width; ++col) {
|
||||
switch (type) {
|
||||
case GL_UNSIGNED_BYTE:
|
||||
case GL_BYTE:
|
||||
rowBuf[static_cast<SizeT>(col)] = srcRow[col];
|
||||
break;
|
||||
case GL_UNSIGNED_SHORT:
|
||||
case GL_SHORT:
|
||||
reinterpret_cast<Uint16*>(rowBuf.data())[col] = srcRow[col];
|
||||
break;
|
||||
case GL_FLOAT:
|
||||
reinterpret_cast<GLfloat*>(rowBuf.data())[col] = static_cast<GLfloat>(srcRow[col]);
|
||||
break;
|
||||
default:
|
||||
reinterpret_cast<Uint32*>(rowBuf.data())[col] = srcRow[col];
|
||||
break;
|
||||
@@ -5284,10 +5352,58 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
const Bool applyPackImageParams = backendAttachTarget == GL_TEXTURE_3D ||
|
||||
backendAttachTarget == GL_TEXTURE_2D_ARRAY ||
|
||||
backendAttachTarget == GL_TEXTURE_CUBE_MAP_ARRAY;
|
||||
// 3D/array images read back every slice, but the FBO path can only read one layer:
|
||||
// multi-slice reads are served from the CPU shadow (slice-major, tight layout).
|
||||
const GLsizei sliceCount = std::max(size.z(), 1);
|
||||
const Bool multiSlice = size.z() > 1;
|
||||
// A multi-slice read used to go to the CPU shadow outright, on the grounds that the
|
||||
// scratch FBO can only expose one layer at a time. But the shadow only holds what was
|
||||
// uploaded, so every slice that was rendered to came back stale - which is exactly what
|
||||
// a layered framebuffer produces, and what textures_storage_multisample_3d_* checks.
|
||||
// Attach the layers one at a time instead and read each off the GPU, keeping the shadow
|
||||
// for the formats the FBO cannot represent at all.
|
||||
if (multiSlice && tempFBOComplete &&
|
||||
(backendAttachTarget == GL_TEXTURE_3D || backendAttachTarget == GL_TEXTURE_2D_ARRAY)) {
|
||||
// Each slice is packed as its own 2D image, so the per-slice call must not apply
|
||||
// GL_PACK_SKIP_IMAGES / GL_PACK_IMAGE_HEIGHT itself - this walks the destination
|
||||
// over them, using the same layout StoreWideRowsToClient computes.
|
||||
const auto packParams = MG_State::pGLContext->GetPixelStoreParameters(false);
|
||||
const SizeT dstPixelBytes = GetReadbackDstPixelSize(conversionMapping, type);
|
||||
const SizeT rowPixels =
|
||||
static_cast<SizeT>(packParams.RowLength > 0 ? packParams.RowLength : size.x());
|
||||
const SizeT alignment =
|
||||
packParams.Alignment > 0 ? static_cast<SizeT>(packParams.Alignment) : SizeT{1};
|
||||
const SizeT dstRowStride = (rowPixels * dstPixelBytes + alignment - 1) / alignment * alignment;
|
||||
const SizeT imageRows = applyPackImageParams && packParams.ImageHeight > 0
|
||||
? static_cast<SizeT>(packParams.ImageHeight)
|
||||
: static_cast<SizeT>(size.y());
|
||||
const SizeT dstImageStride = imageRows * dstRowStride;
|
||||
const SizeT skipImages =
|
||||
applyPackImageParams ? static_cast<SizeT>(std::max(packParams.SkipImages, 0)) : SizeT{0};
|
||||
|
||||
Bool allSlicesRead = true;
|
||||
for (GLsizei slice = 0; slice < sliceCount; ++slice) {
|
||||
ScratchFBOImpl::EnsureColorAttachmentLayer(tempFB, GL_READ_FRAMEBUFFER, backendTexId, level,
|
||||
slice);
|
||||
if (g_GLESFuncs.glCheckFramebufferStatus(GL_READ_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
|
||||
allSlicesRead = false;
|
||||
break;
|
||||
}
|
||||
const SizeT sliceOffset = (skipImages + static_cast<SizeT>(slice)) * dstImageStride;
|
||||
void* sliceDst = static_cast<Uint8*>(pixels) + sliceOffset;
|
||||
if (!ReadPixelsViaFormatConversion(0, 0, size.x(), size.y(), format, type, sliceDst,
|
||||
/*honorPackImageParams=*/false,
|
||||
/*applyFixedPointReadClamp=*/false)) {
|
||||
allSlicesRead = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Leave the scratch FBO on layer 0 so the single-slice paths below see what they
|
||||
// set up.
|
||||
ScratchFBOImpl::EnsureColorAttachmentLayer(tempFB, GL_READ_FRAMEBUFFER, backendTexId, level, 0);
|
||||
if (allSlicesRead) {
|
||||
MGLOG_D("GetTexImage: finished %d slices via per-layer readback", sliceCount);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (multiSlice &&
|
||||
GetTexImageViaShadowConversion(textureMipmapObject,
|
||||
MG_Util::ConvertGLEnumToTextureUploadTarget(target), level, size.x(),
|
||||
|
||||
@@ -59,6 +59,10 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
GLenum buffer, GLint drawbuffer, const GLfloat* value);
|
||||
void ClearNamedFramebufferfi(const SharedPtr<MG_State::GLState::FramebufferObject>& framebuffer,
|
||||
GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil);
|
||||
void ClearNamedFramebufferiv(const SharedPtr<MG_State::GLState::FramebufferObject>& framebuffer,
|
||||
GLenum buffer, GLint drawbuffer, const GLint* value);
|
||||
void ClearNamedFramebufferuiv(const SharedPtr<MG_State::GLState::FramebufferObject>& framebuffer,
|
||||
GLenum buffer, GLint drawbuffer, const GLuint* value);
|
||||
void BlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1,
|
||||
GLint dstY1, GLbitfield mask, GLenum filter);
|
||||
void BlitNamedFramebuffer(const SharedPtr<MG_State::GLState::FramebufferObject>& readFramebuffer,
|
||||
|
||||
@@ -2479,7 +2479,10 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
});
|
||||
}
|
||||
|
||||
if (!isMultisampleTarget && m_cacheBorderColor != stateTextureObject->GetBorderColor()) {
|
||||
// GL_TEXTURE_BORDER_COLOR needs ES 3.2 or EXT/OES_texture_border_clamp; on a driver
|
||||
// without it every such call is INVALID_ENUM, so the parameter is simply not synced.
|
||||
if (!isMultisampleTarget && g_GLESCapabilities.SupportsTextureBorderClamp &&
|
||||
m_cacheBorderColor != stateTextureObject->GetBorderColor()) {
|
||||
const auto& borderColor = stateTextureObject->GetBorderColor();
|
||||
GLfloat borderColorArray[4] = {borderColor.x(), borderColor.y(), borderColor.z(), borderColor.w()};
|
||||
g_GLESFuncs.glTexParameterfv(target, GL_TEXTURE_BORDER_COLOR, borderColorArray);
|
||||
@@ -3755,6 +3758,16 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
}
|
||||
m_cacheSamplerParameters.maxAnisotropy = samplerParams.maxAnisotropy;
|
||||
}
|
||||
if (m_cacheSamplerParameters.borderColor != samplerParams.borderColor) {
|
||||
// Same gate as the texture-side border colour above.
|
||||
if (g_GLESCapabilities.SupportsTextureBorderClamp && g_GLESFuncs.glSamplerParameterfv) {
|
||||
const GLfloat borderColorArray[4] = {
|
||||
samplerParams.borderColor.x(), samplerParams.borderColor.y(),
|
||||
samplerParams.borderColor.z(), samplerParams.borderColor.w()};
|
||||
g_GLESFuncs.glSamplerParameterfv(m_backendSamplerId, GL_TEXTURE_BORDER_COLOR, borderColorArray);
|
||||
}
|
||||
m_cacheSamplerParameters.borderColor = samplerParams.borderColor;
|
||||
}
|
||||
#undef SYNC_SAMPLER_PARAM_IF_CHANGED
|
||||
m_isInitialized = true;
|
||||
}
|
||||
|
||||
@@ -41,13 +41,11 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
Bool IsLayeredTarget(TextureTarget target) {
|
||||
return target == TextureTarget::Texture3D || target == TextureTarget::Texture1DArray ||
|
||||
target == TextureTarget::Texture2DArray || target == TextureTarget::TextureCubeMap ||
|
||||
target == TextureTarget::TextureCubeMapArray ||
|
||||
target == TextureTarget::Texture2DMultisampleArray;
|
||||
target == TextureTarget::TextureCubeMapArray || target == TextureTarget::Texture2DMultisampleArray;
|
||||
}
|
||||
|
||||
Bool IsMultisampleTarget(TextureTarget target) {
|
||||
return target == TextureTarget::Texture2DMultisample ||
|
||||
target == TextureTarget::Texture2DMultisampleArray;
|
||||
return target == TextureTarget::Texture2DMultisample || target == TextureTarget::Texture2DMultisampleArray;
|
||||
}
|
||||
|
||||
Bool IsTextureBufferTarget(TextureTarget target) {
|
||||
@@ -59,8 +57,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
GLenum normalizedInternalFormat = glFormat;
|
||||
GLenum imageFormat = GL_RGBA;
|
||||
GLenum imageType = GL_UNSIGNED_BYTE;
|
||||
MG_Util::TextureFormatProcessor::NormalizePixelFormat(
|
||||
glFormat, PixelFormatNormalizeOptionBit::None, &normalizedInternalFormat, &imageFormat, &imageType);
|
||||
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;
|
||||
}
|
||||
@@ -81,8 +79,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
return caps;
|
||||
}
|
||||
|
||||
FormatCapabilityFlags BuildVulkanCaps(TextureInternalFormat logicalFormat,
|
||||
TextureTarget target,
|
||||
FormatCapabilityFlags BuildVulkanCaps(TextureInternalFormat logicalFormat, TextureTarget target,
|
||||
VkFormatFeatureFlags features) {
|
||||
FormatCapabilityFlags caps;
|
||||
const Bool isDepth = MG_Util::IsDepthFormatInternalFormat(logicalFormat);
|
||||
@@ -101,8 +98,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
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 depthStencilRenderable = (features & VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT) != 0;
|
||||
const Bool renderable = (isDepth || isStencil) ? depthStencilRenderable : colorRenderable;
|
||||
|
||||
if (sampled || renderable) {
|
||||
@@ -199,21 +195,20 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
|
||||
Bool HasNewCaveatFormatCaps(FormatCapabilityFlags nativeCaps, FormatCapabilityFlags fallbackCaps) {
|
||||
for (FormatCapability capability : kReportedFormatCapabilities) {
|
||||
if (HasFormatCapability(fallbackCaps, capability) &&
|
||||
!HasFormatCapability(nativeCaps, capability)) {
|
||||
if (HasFormatCapability(fallbackCaps, capability) && !HasFormatCapability(nativeCaps, capability)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void LogVulkanFormatCaveat(TextureInternalFormat logicalFormat,
|
||||
SizeT targetIndex,
|
||||
void LogVulkanFormatCaveat(TextureInternalFormat logicalFormat, SizeT targetIndex,
|
||||
TextureInternalFormat fallbackFormat) {
|
||||
MGLOG_D("Caveat: %s %s not fully supported. Reason: native Vulkan format is not fully supported. Fallback: %s",
|
||||
GetFormatCapabilityTargetName(targetIndex).c_str(),
|
||||
MG_Util::ConvertTextureInternalFormatToString(logicalFormat).c_str(),
|
||||
MG_Util::ConvertTextureInternalFormatToString(fallbackFormat).c_str());
|
||||
MGLOG_D(
|
||||
"Caveat: %s %s not fully supported. Reason: native Vulkan format is not fully supported. Fallback: %s",
|
||||
GetFormatCapabilityTargetName(targetIndex).c_str(),
|
||||
MG_Util::ConvertTextureInternalFormatToString(logicalFormat).c_str(),
|
||||
MG_Util::ConvertTextureInternalFormatToString(fallbackFormat).c_str());
|
||||
}
|
||||
|
||||
Vector<Int> BuildSampleCounts(Int maxSamples) {
|
||||
@@ -257,15 +252,15 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
|
||||
for (SizeT targetIndex = 0; targetIndex < kFormatCapabilityTextureTargetCount; ++targetIndex) {
|
||||
const auto target = static_cast<TextureTarget>(targetIndex);
|
||||
const VkFormatFeatureFlags nativeFeatures =
|
||||
IsTextureBufferTarget(target) ? nativeProperties.bufferFeatures
|
||||
: nativeProperties.optimalTilingFeatures;
|
||||
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;
|
||||
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;
|
||||
@@ -300,9 +295,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
cache.FullCaps[renderbufferTargetIndex][formatIndex] |= renderbufferCaps;
|
||||
|
||||
if (fallbackFormat != VK_FORMAT_UNDEFINED && fallbackFormat != nativeFormat) {
|
||||
FormatCapabilityFlags fallbackRenderbufferCaps =
|
||||
BuildVulkanCaps(logicalFormat, TextureTarget::Texture2D,
|
||||
fallbackProperties.optimalTilingFeatures);
|
||||
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)) !=
|
||||
@@ -311,8 +305,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
fallbackRenderbufferCaps |= FormatCapability::MultisampleRenderbuffer;
|
||||
}
|
||||
cache.CaveatCaps[renderbufferTargetIndex][formatIndex] |= fallbackRenderbufferCaps;
|
||||
if (fallbackLogicalFormat &&
|
||||
HasNewCaveatFormatCaps(renderbufferCaps, fallbackRenderbufferCaps)) {
|
||||
if (fallbackLogicalFormat && HasNewCaveatFormatCaps(renderbufferCaps, fallbackRenderbufferCaps)) {
|
||||
LogVulkanFormatCaveat(logicalFormat, renderbufferTargetIndex, *fallbackLogicalFormat);
|
||||
}
|
||||
}
|
||||
@@ -329,14 +322,13 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
|
||||
void PopulateFormatCapabilities(VkPhysicalDevice physicalDevice,
|
||||
PFN_vkGetPhysicalDeviceFormatProperties getFormatProperties,
|
||||
const MG_External::VulkanCapabilities& capabilities,
|
||||
FormatCapabilityCache& cache) {
|
||||
const MG_External::VulkanCapabilities& capabilities, FormatCapabilityCache& cache) {
|
||||
PopulateFormatCapabilitiesImpl(physicalDevice, getFormatProperties, capabilities, cache);
|
||||
}
|
||||
|
||||
BackendObject_DirectVulkan::~BackendObject_DirectVulkan() = default;
|
||||
|
||||
BackendObject_DirectVulkan::BackendObject_DirectVulkan(): m_rendererInfo{GetRendererIdentity()} {}
|
||||
BackendObject_DirectVulkan::BackendObject_DirectVulkan() : m_rendererInfo{GetRendererIdentity()} {}
|
||||
|
||||
Bool BackendObject_DirectVulkan::InitWindowSurface() {
|
||||
if (!m_windowHandle.Handle) {
|
||||
@@ -410,10 +402,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
MGLOG_E("DirectVulkan backend not initialized");
|
||||
return false;
|
||||
}
|
||||
if (!handle.Handle || (handle.Backend != WindowBackend::Android &&
|
||||
handle.Backend != WindowBackend::X11 &&
|
||||
handle.Backend != WindowBackend::MetalLayer &&
|
||||
handle.Backend != WindowBackend::Win32)) {
|
||||
if (!handle.Handle || (handle.Backend != WindowBackend::Android && handle.Backend != WindowBackend::X11 &&
|
||||
handle.Backend != WindowBackend::MetalLayer && handle.Backend != WindowBackend::Win32)) {
|
||||
MGLOG_E("DirectVulkan backend only supports Android, X11, CAMetalLayer, and Win32 native windows");
|
||||
return false;
|
||||
}
|
||||
@@ -504,37 +494,32 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
.RendererName = "Magma",
|
||||
.BackendName = "Direct (Vulkan)",
|
||||
.ExtraVendor = Nullopt,
|
||||
.RendererGLInfo =
|
||||
{
|
||||
.TargetGLVersion = {3, 3, 0},
|
||||
.TargetGLSLVersion = {4, 6, 0},
|
||||
// Baseline advertisement (no shader subgroup, no timer queries); a
|
||||
// live backend reconciles its copy in UpdateAdvertisedExtensions.
|
||||
.Extensions = BuildAdvertisedExtensions(false, false, false),
|
||||
.IsCompatibilityProfile = false
|
||||
},
|
||||
.RendererGLInfo = {.TargetGLVersion = {4, 0, 0},
|
||||
.TargetGLSLVersion = {4, 6, 0},
|
||||
// Baseline advertisement (no shader subgroup, no timer queries); a
|
||||
// live backend reconciles its copy in UpdateAdvertisedExtensions.
|
||||
.Extensions = BuildAdvertisedExtensions(false, false, false),
|
||||
.IsCompatibilityProfile = false},
|
||||
.StaticBackendCapability = {.AllowVSOnlyPrograms = false}};
|
||||
return rendererInfo;
|
||||
}
|
||||
|
||||
Vector<GLExtension> BuildAdvertisedExtensions(Bool shaderSubgroupSupported, Bool timerQueriesSupported,
|
||||
Bool anisotropicFilteringSupported) {
|
||||
Vector<GLExtension> extensions = {V_OpenGL30, V_OpenGL31, V_OpenGL32,
|
||||
V_OpenGL33, E_GL_ARB_draw_buffers_blend, E_GL_ARB_compute_shader,
|
||||
E_GL_ARB_shader_storage_buffer_object, E_GL_ARB_shader_image_load_store,
|
||||
E_GL_ARB_program_interface_query, E_GL_ARB_framebuffer_object,
|
||||
E_GL_ARB_multi_draw_indirect, E_GL_ARB_indirect_parameters,
|
||||
E_GL_EXT_framebuffer_object, E_GL_ARB_depth_texture, E_GL_ARB_buffer_storage,
|
||||
E_GL_ARB_texture_storage, E_GL_ARB_texture_storage_multisample,
|
||||
E_GL_ARB_texture_multisample, E_GL_ARB_clear_texture, E_GL_ARB_direct_state_access,
|
||||
E_GL_ARB_shader_draw_parameters, E_GL_ARB_gpu_shader_int64, E_GL_KHR_debug,
|
||||
E_GL_ARB_gpu_shader5, E_GL_ARB_multi_bind, E_GL_ARB_shading_language_420pack,
|
||||
E_GL_ARB_vertex_attrib_binding, E_GL_ARB_shader_image_size,
|
||||
E_GL_ARB_explicit_attrib_location,
|
||||
// Advertised with GL_NUM_PROGRAM_BINARY_FORMATS = 0, which the
|
||||
// extension explicitly permits. It is also the only thing that
|
||||
// exposes glProgramParameteri before GL 4.1.
|
||||
E_GL_ARB_get_program_binary};
|
||||
Vector<GLExtension> extensions = {
|
||||
V_OpenGL30, V_OpenGL31, V_OpenGL32, V_OpenGL33, V_OpenGL40, E_GL_ARB_draw_buffers_blend,
|
||||
E_GL_ARB_compute_shader, E_GL_ARB_shader_storage_buffer_object, E_GL_ARB_shader_image_load_store,
|
||||
E_GL_ARB_program_interface_query, E_GL_ARB_framebuffer_object, E_GL_ARB_multi_draw_indirect,
|
||||
E_GL_ARB_indirect_parameters, E_GL_EXT_framebuffer_object, E_GL_ARB_depth_texture, E_GL_ARB_buffer_storage,
|
||||
E_GL_ARB_texture_storage, E_GL_ARB_texture_storage_multisample, E_GL_ARB_texture_multisample,
|
||||
E_GL_ARB_clear_texture, E_GL_ARB_direct_state_access, E_GL_ARB_shader_draw_parameters,
|
||||
E_GL_ARB_gpu_shader_int64, E_GL_KHR_debug, E_GL_ARB_gpu_shader5, E_GL_ARB_multi_bind,
|
||||
E_GL_ARB_shading_language_420pack, E_GL_ARB_vertex_attrib_binding, E_GL_ARB_shader_image_size,
|
||||
E_GL_ARB_explicit_attrib_location,
|
||||
// Advertised with GL_NUM_PROGRAM_BINARY_FORMATS = 0, which the
|
||||
// extension explicitly permits. It is also the only thing that
|
||||
// exposes glProgramParameteri before GL 4.1.
|
||||
E_GL_ARB_get_program_binary};
|
||||
if (shaderSubgroupSupported && !MG_Config::Features.DisableSubgroup) {
|
||||
extensions.push_back(E_GL_KHR_shader_subgroup);
|
||||
}
|
||||
@@ -597,6 +582,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
funcsTable.GL.ClearBufferiv = ClearBufferiv;
|
||||
funcsTable.GL.ClearNamedFramebufferfv = ClearNamedFramebufferfv;
|
||||
funcsTable.GL.ClearNamedFramebufferfi = ClearNamedFramebufferfi;
|
||||
funcsTable.GL.ClearNamedFramebufferiv = ClearNamedFramebufferiv;
|
||||
funcsTable.GL.ClearNamedFramebufferuiv = ClearNamedFramebufferuiv;
|
||||
funcsTable.GL.BlitFramebuffer = BlitFramebuffer;
|
||||
funcsTable.GL.BlitNamedFramebuffer = BlitNamedFramebuffer;
|
||||
funcsTable.GL.CopyTexImage2D = CopyTexImage2D;
|
||||
@@ -729,7 +716,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
// rather than a maximum the sampler manager will never apply.
|
||||
m_dynamicParameters.MaxTextureMaxAnisotropy =
|
||||
(pVulkanRenderer && pVulkanRenderer->IsSamplerAnisotropySupported()) ? m_vulkanCaps.MaxSamplerAnisotropy
|
||||
: 1.0f;
|
||||
: 1.0f;
|
||||
m_dynamicParameters.SmoothLineWidthRangeMin = m_vulkanCaps.SmoothLineWidthRangeMin;
|
||||
m_dynamicParameters.SmoothLineWidthRangeMax = m_vulkanCaps.SmoothLineWidthRangeMax;
|
||||
m_dynamicParameters.SmoothLineWidthGranularity = m_vulkanCaps.SmoothLineWidthGranularity;
|
||||
@@ -750,8 +737,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
m_dynamicParameters.MaxIntegerSamples = m_vulkanCaps.MaxIntegerSamples;
|
||||
m_dynamicParameters.MaxSamples = m_vulkanCaps.MaxSamples;
|
||||
m_dynamicParameters.MaxSampleMaskWords = m_vulkanCaps.MaxSampleMaskWords;
|
||||
const Int maxSupportedTextureUnits =
|
||||
static_cast<Int>(MG_State::GLState::TextureState::MAX_TEXTURE_IMAGE_UNITS);
|
||||
const Int maxSupportedTextureUnits = static_cast<Int>(MG_State::GLState::TextureState::MAX_TEXTURE_IMAGE_UNITS);
|
||||
// GL_MAX_TEXTURE_IMAGE_UNITS is a *per-stage* sampler limit. Adreno/Qualcomm report a huge
|
||||
// maxPerStageDescriptorSampledImages (descriptor-indexing scale), so clamping it only to our
|
||||
// combined array capacity (192) still advertises 192 per stage. Host code treats this value as
|
||||
@@ -761,8 +747,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
// limits while keeping the combined limit at our texture-unit array capacity.
|
||||
constexpr Int maxPerStageTextureUnits =
|
||||
static_cast<Int>(MG_State::GLState::TextureState::MAX_PER_STAGE_TEXTURE_IMAGE_UNITS);
|
||||
m_dynamicParameters.MaxTextureImageUnits =
|
||||
std::min(m_vulkanCaps.MaxTextureImageUnits, maxPerStageTextureUnits);
|
||||
m_dynamicParameters.MaxTextureImageUnits = std::min(m_vulkanCaps.MaxTextureImageUnits, maxPerStageTextureUnits);
|
||||
m_dynamicParameters.MaxVertexTextureImageUnits =
|
||||
std::min(m_vulkanCaps.MaxVertexTextureImageUnits, maxPerStageTextureUnits);
|
||||
m_dynamicParameters.MaxComputeTextureImageUnits =
|
||||
@@ -771,9 +756,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
std::min(m_vulkanCaps.MaxCombinedTextureImageUnits, maxSupportedTextureUnits);
|
||||
// Never advertise more attributes than the state layer can store: the current-value array and
|
||||
// the Uint32 attribute masks the draw path passes around are both bounded by MAX_VERTEX_ATTRIBS.
|
||||
m_dynamicParameters.MaxVertexAttribs =
|
||||
std::min(m_vulkanCaps.MaxVertexAttribs,
|
||||
static_cast<Int>(MG_State::GLState::VertexArrayObject::MAX_VERTEX_ATTRIBS));
|
||||
m_dynamicParameters.MaxVertexAttribs = std::min(
|
||||
m_vulkanCaps.MaxVertexAttribs, static_cast<Int>(MG_State::GLState::VertexArrayObject::MAX_VERTEX_ATTRIBS));
|
||||
m_dynamicParameters.MaxComputeShaderStorageBlocks = m_vulkanCaps.MaxComputeShaderStorageBlocks;
|
||||
m_dynamicParameters.MaxCombinedShaderStorageBlocks = m_vulkanCaps.MaxCombinedShaderStorageBlocks;
|
||||
m_dynamicParameters.MaxComputeUniformBlocks = m_vulkanCaps.MaxComputeUniformBlocks;
|
||||
@@ -783,8 +767,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
m_dynamicParameters.TextureBufferOffsetAlignment = m_vulkanCaps.TextureBufferOffsetAlignment;
|
||||
m_dynamicParameters.MaxUniformBufferBindings = m_vulkanCaps.MaxUniformBufferBindings;
|
||||
m_dynamicParameters.MaxUniformBlockSize = m_vulkanCaps.MaxUniformBlockSize;
|
||||
m_dynamicParameters.MaxImageUnits =
|
||||
std::max(std::min(m_vulkanCaps.MaxImageUnits, maxSupportedTextureUnits), 0);
|
||||
m_dynamicParameters.MaxImageUnits = std::max(std::min(m_vulkanCaps.MaxImageUnits, maxSupportedTextureUnits), 0);
|
||||
m_dynamicParameters.MaxCombinedImageUniforms = std::max(m_vulkanCaps.MaxCombinedImageUniforms, 0);
|
||||
const Int maxPerStageImageUniforms =
|
||||
std::min(m_dynamicParameters.MaxImageUnits, m_dynamicParameters.MaxCombinedImageUniforms);
|
||||
@@ -801,8 +784,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
m_vulkanCaps.SupportsFragmentStoresAndAtomics ? maxPerStageImageUniforms : 0;
|
||||
m_dynamicParameters.MaxComputeImageUniforms =
|
||||
std::min(std::max(m_vulkanCaps.MaxComputeImageUniforms, 0), maxPerStageImageUniforms);
|
||||
const Int maxSupportedDrawBuffers =
|
||||
static_cast<Int>(MG_State::GLState::FramebufferObject::MAX_DRAW_BUFFERS);
|
||||
const Int maxSupportedDrawBuffers = static_cast<Int>(MG_State::GLState::FramebufferObject::MAX_DRAW_BUFFERS);
|
||||
m_dynamicParameters.MaxDrawBuffers = std::min(m_vulkanCaps.MaxDrawBuffers, maxSupportedDrawBuffers);
|
||||
m_dynamicParameters.MaxColorAttachments = std::min(m_vulkanCaps.MaxColorAttachments, maxSupportedDrawBuffers);
|
||||
m_dynamicParameters.MaxClipDistances = m_vulkanCaps.MaxClipDistances;
|
||||
@@ -821,12 +803,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
m_dynamicParameters.FragmentInterpolationOffsetBits = 4;
|
||||
if (m_vulkanCaps.FragmentInterpolationOffsetBits >= 4 &&
|
||||
std::isfinite(m_vulkanCaps.MaxFragmentInterpolationOffset)) {
|
||||
const Float requiredMaxOffset =
|
||||
0.5f - std::ldexp(1.0f, -m_vulkanCaps.FragmentInterpolationOffsetBits);
|
||||
const Float requiredMaxOffset = 0.5f - std::ldexp(1.0f, -m_vulkanCaps.FragmentInterpolationOffsetBits);
|
||||
if (m_vulkanCaps.MaxFragmentInterpolationOffset >= requiredMaxOffset) {
|
||||
m_dynamicParameters.MaxFragmentInterpolationOffset = m_vulkanCaps.MaxFragmentInterpolationOffset;
|
||||
m_dynamicParameters.FragmentInterpolationOffsetBits =
|
||||
m_vulkanCaps.FragmentInterpolationOffsetBits;
|
||||
m_dynamicParameters.FragmentInterpolationOffsetBits = m_vulkanCaps.FragmentInterpolationOffsetBits;
|
||||
}
|
||||
}
|
||||
m_dynamicParameters.SupportsWideLines = m_vulkanCaps.SupportsWideLines;
|
||||
@@ -835,7 +815,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
if (m_vulkanCaps.SupportsShaderSubgroup) {
|
||||
m_dynamicParameters.SubgroupSize = m_vulkanCaps.SubgroupSize;
|
||||
m_dynamicParameters.SubgroupSupportedStages = mapShaderStages(m_vulkanCaps.SubgroupSupportedStages);
|
||||
m_dynamicParameters.SubgroupSupportedFeatures = mapSubgroupFeatures(m_vulkanCaps.SubgroupSupportedOperations);
|
||||
m_dynamicParameters.SubgroupSupportedFeatures =
|
||||
mapSubgroupFeatures(m_vulkanCaps.SubgroupSupportedOperations);
|
||||
m_dynamicParameters.SubgroupQuadOperationsInAllStages = m_vulkanCaps.SubgroupQuadOperationsInAllStages;
|
||||
} else {
|
||||
m_dynamicParameters.SubgroupSize = 0;
|
||||
@@ -845,8 +826,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
}
|
||||
if (m_dynamicParameters.MaxShaderStorageBlockSize != m_vulkanCaps.MaxShaderStorageBlockSize) {
|
||||
MGLOG_I("DirectVulkan: clamped GL_MAX_SHADER_STORAGE_BLOCK_SIZE from %zu to %zu",
|
||||
m_vulkanCaps.MaxShaderStorageBlockSize,
|
||||
m_dynamicParameters.MaxShaderStorageBlockSize);
|
||||
m_vulkanCaps.MaxShaderStorageBlockSize, m_dynamicParameters.MaxShaderStorageBlockSize);
|
||||
}
|
||||
switch (m_vulkanCaps.VendorId) {
|
||||
case 0x5143u: // VK_VENDOR_ID: Qualcomm
|
||||
|
||||
@@ -440,6 +440,20 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
pVulkanRenderer->ClearNamedFramebufferfv(framebuffer, buffer, drawbuffer, value);
|
||||
}
|
||||
|
||||
void ClearNamedFramebufferiv(const SharedPtr<MG_State::GLState::FramebufferObject>& framebuffer, GLenum buffer,
|
||||
GLint drawbuffer, const GLint* value) {
|
||||
MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::ClearNamedFramebufferiv called with null VulkanRenderer");
|
||||
MOBILEGL_ASSERT(MG_State::pGLContext, "DirectVulkan::ClearNamedFramebufferiv called with null GL context");
|
||||
pVulkanRenderer->ClearNamedFramebufferiv(framebuffer, buffer, drawbuffer, value);
|
||||
}
|
||||
|
||||
void ClearNamedFramebufferuiv(const SharedPtr<MG_State::GLState::FramebufferObject>& framebuffer, GLenum buffer,
|
||||
GLint drawbuffer, const GLuint* value) {
|
||||
MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::ClearNamedFramebufferuiv called with null VulkanRenderer");
|
||||
MOBILEGL_ASSERT(MG_State::pGLContext, "DirectVulkan::ClearNamedFramebufferuiv called with null GL context");
|
||||
pVulkanRenderer->ClearNamedFramebufferuiv(framebuffer, buffer, drawbuffer, value);
|
||||
}
|
||||
|
||||
void ClearNamedFramebufferfi(const SharedPtr<MG_State::GLState::FramebufferObject>& framebuffer, GLenum buffer,
|
||||
GLint drawbuffer, GLfloat depth, GLint stencil) {
|
||||
MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::ClearNamedFramebufferfi called with null VulkanRenderer");
|
||||
|
||||
@@ -35,6 +35,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
void ClearBufferiv(GLenum buffer, GLint drawbuffer, const GLint* value);
|
||||
void ClearNamedFramebufferfv(const SharedPtr<MG_State::GLState::FramebufferObject>& framebuffer, GLenum buffer,
|
||||
GLint drawbuffer, const GLfloat* value);
|
||||
void ClearNamedFramebufferiv(const SharedPtr<MG_State::GLState::FramebufferObject>& framebuffer, GLenum buffer,
|
||||
GLint drawbuffer, const GLint* value);
|
||||
void ClearNamedFramebufferuiv(const SharedPtr<MG_State::GLState::FramebufferObject>& framebuffer, GLenum buffer,
|
||||
GLint drawbuffer, const GLuint* value);
|
||||
void ClearNamedFramebufferfi(const SharedPtr<MG_State::GLState::FramebufferObject>& framebuffer, GLenum buffer,
|
||||
GLint drawbuffer, GLfloat depth, GLint stencil);
|
||||
void Clear(GLbitfield mask);
|
||||
|
||||
@@ -8,15 +8,75 @@
|
||||
|
||||
#include "VkClearManager.h"
|
||||
|
||||
#include "MG_State/GLState/Core.h"
|
||||
#include "MG_Util/Converters/MGToStr/FramebufferEnumConverter.h"
|
||||
#include "MG_Util/Converters/MGToStr/TextureEnumConverter.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
static Bool IsCubeMapFaceUploadTarget(TextureUploadTarget target) {
|
||||
return target >= TextureUploadTarget::CubeMapPositiveX &&
|
||||
target <= TextureUploadTarget::CubeMapNegativeZ;
|
||||
}
|
||||
|
||||
VkClearColorValue MakeVkClearColorValue(const ClearAttachmentPayload& payload, Bool formatLacksAlpha) {
|
||||
VkClearColorValue clearValue{};
|
||||
switch (payload.colorEncoding) {
|
||||
case ClearColorEncoding::Int:
|
||||
clearValue.int32[0] = payload.colorInt.x();
|
||||
clearValue.int32[1] = payload.colorInt.y();
|
||||
clearValue.int32[2] = payload.colorInt.z();
|
||||
clearValue.int32[3] = formatLacksAlpha ? 1 : payload.colorInt.w();
|
||||
break;
|
||||
case ClearColorEncoding::Uint:
|
||||
clearValue.uint32[0] = payload.colorUint.x();
|
||||
clearValue.uint32[1] = payload.colorUint.y();
|
||||
clearValue.uint32[2] = payload.colorUint.z();
|
||||
clearValue.uint32[3] = formatLacksAlpha ? 1u : payload.colorUint.w();
|
||||
break;
|
||||
case ClearColorEncoding::Float:
|
||||
clearValue.float32[0] = payload.color.x();
|
||||
clearValue.float32[1] = payload.color.y();
|
||||
clearValue.float32[2] = payload.color.z();
|
||||
clearValue.float32[3] = formatLacksAlpha ? 1.0f : payload.color.w();
|
||||
break;
|
||||
}
|
||||
return clearValue;
|
||||
}
|
||||
|
||||
void PreCompensateSrgbClearColor(ClearAttachmentPayload& payload, VkFormat destinationFormat) {
|
||||
if (payload.colorEncoding != ClearColorEncoding::Float) return;
|
||||
// With GL_FRAMEBUFFER_SRGB enabled GL performs the encoding itself, so the driver doing it
|
||||
// is exactly right and there is nothing to undo.
|
||||
if (MG_State::pGLContext->IsCapabilityEnabled(MobileGL::CapabilityInput::FramebufferSrgb)) return;
|
||||
if (ResolveSrgbAttachmentWriteFormat(destinationFormat, false) == destinationFormat) return;
|
||||
|
||||
// sRGB -> linear (GL 4.6 core 8.24), applied to the colour channels only: alpha is stored
|
||||
// linearly in an sRGB format and must pass through untouched.
|
||||
const auto toLinear = [](Float encoded) {
|
||||
const Float value = std::clamp(encoded, 0.0f, 1.0f);
|
||||
return value <= 0.04045f ? value / 12.92f : std::pow((value + 0.055f) / 1.055f, 2.4f);
|
||||
};
|
||||
payload.color = FloatVec4(toLinear(payload.color.x()), toLinear(payload.color.y()),
|
||||
toLinear(payload.color.z()), payload.color.w());
|
||||
}
|
||||
|
||||
void ForceOpaqueClearAlpha(ClearAttachmentPayload& payload) {
|
||||
switch (payload.colorEncoding) {
|
||||
case ClearColorEncoding::Int:
|
||||
payload.colorInt = IntVec4(payload.colorInt.x(), payload.colorInt.y(), payload.colorInt.z(), 1);
|
||||
break;
|
||||
case ClearColorEncoding::Uint:
|
||||
payload.colorUint = UintVec4(payload.colorUint.x(), payload.colorUint.y(), payload.colorUint.z(), 1u);
|
||||
break;
|
||||
case ClearColorEncoding::Float:
|
||||
payload.color = FloatVec4(payload.color.x(), payload.color.y(), payload.color.z(), 1.0f);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static Bool PendingClearMatchesTextureIdentity(const PendingClearKey& key, const TextureIdentity& identity) {
|
||||
return key.texture == identity.texture && key.textureLifetimeId == identity.lifetimeId;
|
||||
}
|
||||
|
||||
@@ -24,13 +24,41 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
Uint32 stencil{};
|
||||
};
|
||||
|
||||
// A colour clear reaches us from one of glClear/ClearBufferfv, ClearBufferiv or
|
||||
// ClearBufferuiv, and Vulkan reads VkClearColorValue's union according to the destination
|
||||
// image's format rather than converting between the members - a float written where an
|
||||
// integer format is expected is reinterpreted bit for bit, not rounded. Remember which entry
|
||||
// point supplied the value so the member written when the clear is materialized matches.
|
||||
enum class ClearColorEncoding : Uint8 { Float, Int, Uint };
|
||||
|
||||
struct ClearAttachmentPayload {
|
||||
GLbitfield mask = 0;
|
||||
FloatVec4 color = FloatVec4(0.0f, 0.0f, 0.0f, 0.0f);
|
||||
ClearColorEncoding colorEncoding = ClearColorEncoding::Float;
|
||||
IntVec4 colorInt = IntVec4(0, 0, 0, 0);
|
||||
UintVec4 colorUint = UintVec4(0u, 0u, 0u, 0u);
|
||||
Float depth = 1.0f;
|
||||
Uint32 stencil = 0;
|
||||
};
|
||||
|
||||
// Builds the clear value for `payload` in the union member its encoding calls for.
|
||||
// `formatLacksAlpha` applies GL's rule that a format without an alpha channel reads as one,
|
||||
// expressed in whichever type matches (GL 4.6 core 15.2.3).
|
||||
VkClearColorValue MakeVkClearColorValue(const ClearAttachmentPayload& payload, Bool formatLacksAlpha);
|
||||
|
||||
// Applies that same rule in place, for the paths that have to bake it into the payload before
|
||||
// the destination is known.
|
||||
void ForceOpaqueClearAlpha(ClearAttachmentPayload& payload);
|
||||
|
||||
// vkCmdClearColorImage names the image, so the driver applies the destination format's transfer
|
||||
// function to whatever value it is handed. Every other write path in this backend goes through
|
||||
// the UNORM twin view while GL_FRAMEBUFFER_SRGB is off (ResolveSrgbAttachmentWriteFormat) and
|
||||
// therefore stores the raw value GL asked for. Rewrites `payload` to the linear colour whose
|
||||
// encoding is that raw value, so a direct image clear of an sRGB destination agrees with them.
|
||||
// A no-op for every other format, for integer clear encodings, and when GL is doing the
|
||||
// encoding itself.
|
||||
void PreCompensateSrgbClearColor(ClearAttachmentPayload& payload, VkFormat destinationFormat);
|
||||
|
||||
struct PendingClearKey {
|
||||
MG_State::GLState::ITextureObject* texture = nullptr;
|
||||
Uint64 textureLifetimeId = 0;
|
||||
|
||||
@@ -50,7 +50,11 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
}
|
||||
}
|
||||
|
||||
static Float ResolveColorClearAlpha(const MG_State::GLState::ITextureObject* texture, Float requestedAlpha) {
|
||||
static Bool ColorFormatLacksAlpha(const MG_State::GLState::ITextureObject* texture) {
|
||||
return texture != nullptr && MG_Util::GetBaseInternalFormatComponentCount(texture->GetFormat()) == 3;
|
||||
}
|
||||
|
||||
[[maybe_unused]] static Float ResolveColorClearAlpha(const MG_State::GLState::ITextureObject* texture, Float requestedAlpha) {
|
||||
if (texture != nullptr && MG_Util::GetBaseInternalFormatComponentCount(texture->GetFormat()) == 3) {
|
||||
return 1.0f;
|
||||
}
|
||||
@@ -526,7 +530,13 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
pending.renderbuffer = renderbuffer;
|
||||
pending.payload.mask |= clearPayload.mask;
|
||||
if ((clearPayload.mask & GL_COLOR_BUFFER_BIT) != 0) {
|
||||
// The whole colour description, not just the float vector: an integer clear keeps its
|
||||
// value in colorInt/colorUint, and dropping the encoding here would leave the pending
|
||||
// clear reading as an all-zero float one.
|
||||
pending.payload.color = clearPayload.color;
|
||||
pending.payload.colorEncoding = clearPayload.colorEncoding;
|
||||
pending.payload.colorInt = clearPayload.colorInt;
|
||||
pending.payload.colorUint = clearPayload.colorUint;
|
||||
}
|
||||
if ((clearPayload.mask & GL_DEPTH_BUFFER_BIT) != 0) {
|
||||
pending.payload.depth = clearPayload.depth;
|
||||
@@ -924,9 +934,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
if (rbHasClear &&
|
||||
MG_Util::GetBaseInternalFormatComponentCount(renderbuffer->GetInternalFormat()) == 3) {
|
||||
// RGB renderbuffers are backed by an RGBA image; the missing alpha reads as 1.
|
||||
rbClearPayload.color =
|
||||
FloatVec4(rbClearPayload.color.x(), rbClearPayload.color.y(),
|
||||
rbClearPayload.color.z(), 1.0f);
|
||||
ForceOpaqueClearAlpha(rbClearPayload);
|
||||
}
|
||||
|
||||
const VkImageLayout trackedRbLayout = rbResource->layout;
|
||||
@@ -1496,12 +1504,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
}
|
||||
}
|
||||
if ((clearPayload.mask & GL_COLOR_BUFFER_BIT) != 0) {
|
||||
clearValues[pending.attachmentIndex].color = {
|
||||
clearPayload.color.x(),
|
||||
clearPayload.color.y(),
|
||||
clearPayload.color.z(),
|
||||
ResolveColorClearAlpha(liveTexture.get(), clearPayload.color.w())
|
||||
};
|
||||
clearValues[pending.attachmentIndex].color =
|
||||
MakeVkClearColorValue(clearPayload, ColorFormatLacksAlpha(liveTexture.get()));
|
||||
}
|
||||
if ((clearPayload.mask & GL_DEPTH_BUFFER_BIT) != 0) {
|
||||
clearValues[pending.attachmentIndex].depthStencil.depth = clearPayload.depth;
|
||||
|
||||
@@ -164,7 +164,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &maxAnisotropy, sizeof(maxAnisotropy)));
|
||||
const auto compareMode = sampler.GetCompareMode();
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &compareMode, sizeof(compareMode)));
|
||||
const auto compareFunc = ResolveCompareFunc(sampler, texture);
|
||||
const auto compareFunc = sampler.GetSamplerCompareFunc();
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &compareFunc, sizeof(compareFunc)));
|
||||
const auto borderColor = ResolveVkBorderColor(sampler, texture);
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &borderColor, sizeof(borderColor)));
|
||||
@@ -207,7 +207,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
samplerInfo.anisotropyEnable = maxAnisotropy > 1.0f ? VK_TRUE : VK_FALSE;
|
||||
samplerInfo.maxAnisotropy = maxAnisotropy;
|
||||
samplerInfo.compareEnable = sampler.GetCompareMode() == SamplerCompareMode::CompareToTexture ? VK_TRUE : VK_FALSE;
|
||||
samplerInfo.compareOp = ToVkCompareOp(ResolveCompareFunc(sampler, texture));
|
||||
samplerInfo.compareOp = ToVkCompareOp(sampler.GetSamplerCompareFunc());
|
||||
// Must match BuildSamplerKey's resolution exactly.
|
||||
samplerInfo.maxLod = ResolveSingleLevelMaxLod(sampler, singleLevelView);
|
||||
samplerInfo.minLod = ResolveEffectiveMinLod(sampler, samplerInfo.maxLod);
|
||||
@@ -281,24 +281,15 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
}
|
||||
}
|
||||
|
||||
SamplerCompareFunc VkSamplerManager::ResolveCompareFunc(const MG_State::GLState::SamplerObject& sampler,
|
||||
const MG_State::GLState::ITextureObject& texture) {
|
||||
const auto compareFunc = sampler.GetSamplerCompareFunc();
|
||||
if (sampler.GetCompareMode() == SamplerCompareMode::CompareToTexture &&
|
||||
IsDepthTextureFormat(texture.GetFormat()) && compareFunc == SamplerCompareFunc::Always) {
|
||||
return SamplerCompareFunc::LessEqual;
|
||||
}
|
||||
|
||||
return compareFunc;
|
||||
}
|
||||
|
||||
VkBorderColor VkSamplerManager::ResolveVkBorderColor(const MG_State::GLState::SamplerObject& sampler,
|
||||
const MG_State::GLState::ITextureObject& texture) {
|
||||
if (!UsesBorderColor(sampler)) {
|
||||
return VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK;
|
||||
}
|
||||
|
||||
const auto& borderColor = texture.GetBorderColor();
|
||||
// Border colour is sampler state: a bound sampler object supplies its own, and a texture
|
||||
// with none reaches the very same value through the sampler object it owns.
|
||||
const auto& borderColor = sampler.GetBorderColor();
|
||||
const Bool isDepthTexture = IsDepthTextureFormat(texture.GetFormat());
|
||||
|
||||
if (isDepthTexture) {
|
||||
|
||||
@@ -69,8 +69,6 @@ private:
|
||||
static VkSamplerMipmapMode ToVkMipmapMode(SamplerMipmapMode mode);
|
||||
static VkSamplerAddressMode ToVkAddressMode(SamplerWrapMode mode);
|
||||
static VkCompareOp ToVkCompareOp(SamplerCompareFunc func);
|
||||
static SamplerCompareFunc ResolveCompareFunc(const MG_State::GLState::SamplerObject& sampler,
|
||||
const MG_State::GLState::ITextureObject& texture);
|
||||
static VkBorderColor ResolveVkBorderColor(const MG_State::GLState::SamplerObject& sampler,
|
||||
const MG_State::GLState::ITextureObject& texture);
|
||||
// The anisotropy Vulkan will actually apply: 1.0 (i.e. disabled) unless the feature is on and
|
||||
|
||||
@@ -1497,15 +1497,17 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
: (mipLevels > 1 ? std::max(mipLevels, ComputeFullMipLevelCount(texelSize)) : 1u);
|
||||
TextureShapeInfo shapeInfo{};
|
||||
const Bool supportedShape = TryResolveTextureShapeInfo(texture, uploadTarget, texelSize, shapeInfo);
|
||||
MOBILEGL_ASSERT(supportedShape,
|
||||
"SyncTextureResource: unsupported uploadTarget=%s textureTarget=%s textureId=%d size=(%d,%d,%d) "
|
||||
"mipLevels=%u vkViewType=%d",
|
||||
MG_Util::ConvertTextureUploadTargetToString(uploadTarget).c_str(),
|
||||
MG_Util::ConvertTextureTargetToString(texture.GetTarget()).c_str(),
|
||||
texture.GetExternalIndex(), texelSize.x(), texelSize.y(), texelSize.z(), mipLevels,
|
||||
static_cast<Int>(MG_Util::ConvertTextureUploadTargetToVkEnum(uploadTarget)));
|
||||
if (!supportedShape) {
|
||||
MGLOG_D("%s: not Texture2D, unsupported", __func__);
|
||||
// A gap in this backend's coverage, not a broken invariant: the GL front end accepts
|
||||
// targets this manager has no Vulkan image shape for yet (cube map arrays above all).
|
||||
// Declining the sync leaves the texture unbacked - wrong, but recoverable - where an
|
||||
// assertion would take the whole process down instead.
|
||||
MGLOG_W("SyncTextureResource: unsupported uploadTarget=%s textureTarget=%s textureId=%d size=(%d,%d,%d) "
|
||||
"mipLevels=%u vkViewType=%d",
|
||||
MG_Util::ConvertTextureUploadTargetToString(uploadTarget).c_str(),
|
||||
MG_Util::ConvertTextureTargetToString(texture.GetTarget()).c_str(), texture.GetExternalIndex(),
|
||||
texelSize.x(), texelSize.y(), texelSize.z(), mipLevels,
|
||||
static_cast<Int>(MG_Util::ConvertTextureUploadTargetToVkEnum(uploadTarget)));
|
||||
return false;
|
||||
}
|
||||
VkSampleCountFlagBits resolvedSampleCount = VK_SAMPLE_COUNT_1_BIT;
|
||||
@@ -1516,6 +1518,16 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
MG_Util::ConvertTextureUploadTargetToString(uploadTarget).c_str());
|
||||
return false;
|
||||
}
|
||||
// glTexStorage*Multisample(samples = 1) is legal GL, but a one-sample image cannot back a
|
||||
// sampler2DMS: VUID-RuntimeSpirv-samples-08726 forbids an OpTypeImage with MS = 1 from
|
||||
// reading an image created with VK_SAMPLE_COUNT_1_BIT, and the fetch returns undefined data
|
||||
// rather than an error. GL only promises "at least the requested number of samples", so
|
||||
// giving a multisample texture two is both legal and the only way to keep the shader's view
|
||||
// of it honest. GL_TEXTURE_SAMPLES still reports what the application asked for - that is
|
||||
// read off the texture object, not off the image.
|
||||
if (isMultisampleTexture && resolvedSampleCount == VK_SAMPLE_COUNT_1_BIT) {
|
||||
resolvedSampleCount = VK_SAMPLE_COUNT_2_BIT;
|
||||
}
|
||||
|
||||
const VkImageAspectFlags aspect = GetAspectMaskForFormat(format);
|
||||
VkFormatProperties formatProperties{};
|
||||
@@ -1591,7 +1603,11 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
}
|
||||
}
|
||||
if (rounded == 0) {
|
||||
for (Uint32 bit = static_cast<Uint32>(resolvedSampleCount) >> 1; bit != 0; bit >>= 1) {
|
||||
// Never land on one sample: that is the VUID-RuntimeSpirv-samples-08726
|
||||
// violation the floor above exists to avoid, and it would come back silently
|
||||
// for any format whose only supported count is 1.
|
||||
for (Uint32 bit = static_cast<Uint32>(resolvedSampleCount) >> 1;
|
||||
bit > static_cast<Uint32>(VK_SAMPLE_COUNT_1_BIT); bit >>= 1) {
|
||||
if ((supported & bit) != 0) {
|
||||
rounded = bit;
|
||||
break;
|
||||
|
||||
@@ -190,11 +190,11 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
}
|
||||
}
|
||||
|
||||
static Float ResolveColorClearAlpha(const MG_State::GLState::ITextureObject* texture, Float requestedAlpha) {
|
||||
if (texture != nullptr && MG_Util::GetBaseInternalFormatComponentCount(texture->GetFormat()) == 3) {
|
||||
return 1.0f;
|
||||
}
|
||||
return requestedAlpha;
|
||||
// GL 4.6 core 15.2.3: a colour format with no alpha channel reads as if alpha were one.
|
||||
// The substitution has to happen in the clear value's own type, so this reports the condition
|
||||
// and MakeVkClearColorValue applies it to whichever union member the encoding selects.
|
||||
static Bool ColorFormatLacksAlpha(const MG_State::GLState::ITextureObject* texture) {
|
||||
return texture != nullptr && MG_Util::GetBaseInternalFormatComponentCount(texture->GetFormat()) == 3;
|
||||
}
|
||||
|
||||
static Bool IsQuarterTurnPreTransform(VkSurfaceTransformFlagBitsKHR preTransform) {
|
||||
@@ -1235,10 +1235,20 @@ void main() {
|
||||
|
||||
static Uint32 ResolveAttachmentBaseArrayLayer(const MG_State::GLState::FramebufferAttachmentObject& attachment) {
|
||||
const TextureUploadTarget uploadTarget = attachment.GetTextureUploadTarget();
|
||||
if (!IsCubeMapFaceUploadTarget(uploadTarget)) {
|
||||
return 0;
|
||||
if (IsCubeMapFaceUploadTarget(uploadTarget)) {
|
||||
return static_cast<Uint32>(uploadTarget) - static_cast<Uint32>(TextureUploadTarget::CubeMapPositiveX);
|
||||
}
|
||||
return static_cast<Uint32>(uploadTarget) - static_cast<Uint32>(TextureUploadTarget::CubeMapPositiveX);
|
||||
// Every other layered attachment names its layer directly. Returning 0 regardless made
|
||||
// every blit, copy and ReadPixels against such an attachment read layer zero.
|
||||
return static_cast<Uint32>(std::max(attachment.GetTextureLayer(), 0));
|
||||
}
|
||||
|
||||
// A 3D image has arrayLayers == 1: its "layer" is a z slice, which has to travel as an
|
||||
// image offset rather than a base array layer (VkBufferImageCopy requires baseArrayLayer 0
|
||||
// for VK_IMAGE_TYPE_3D).
|
||||
static Bool AttachmentIsDepthSlice(const MG_State::GLState::FramebufferAttachmentObject& attachment) {
|
||||
return attachment.IsTexture() && attachment.GetTexture() &&
|
||||
attachment.GetTexture()->GetTarget() == TextureTarget::Texture3D;
|
||||
}
|
||||
|
||||
enum class BlitSurfaceTransform : Uint32 {
|
||||
@@ -1259,6 +1269,8 @@ void main() {
|
||||
Uint32 mipLevelCount = 1;
|
||||
Uint32 baseArrayLayer = 0;
|
||||
Uint32 layerCount = 1;
|
||||
// z slice for a VK_IMAGE_TYPE_3D source; array attachments use baseArrayLayer instead.
|
||||
Uint32 depthOffset = 0;
|
||||
const char* label = nullptr;
|
||||
};
|
||||
|
||||
@@ -1490,7 +1502,12 @@ void main() {
|
||||
outBinding.extent = {attachmentExtent.x(), attachmentExtent.y()};
|
||||
outBinding.mipLevel = static_cast<Uint32>(std::max(attachment.GetTextureLevel(), 0));
|
||||
outBinding.mipLevelCount = resource->mipLevels;
|
||||
outBinding.baseArrayLayer = ResolveAttachmentBaseArrayLayer(attachment);
|
||||
if (AttachmentIsDepthSlice(attachment)) {
|
||||
outBinding.depthOffset = static_cast<Uint32>(std::max(attachment.GetTextureLayer(), 0));
|
||||
outBinding.baseArrayLayer = 0;
|
||||
} else {
|
||||
outBinding.baseArrayLayer = ResolveAttachmentBaseArrayLayer(attachment);
|
||||
}
|
||||
outBinding.layerCount = 1;
|
||||
return true;
|
||||
}
|
||||
@@ -1601,7 +1618,12 @@ void main() {
|
||||
outBinding.extent = {attachmentExtent.x(), attachmentExtent.y()};
|
||||
outBinding.mipLevel = static_cast<Uint32>(std::max(attachment.GetTextureLevel(), 0));
|
||||
outBinding.mipLevelCount = resource->mipLevels;
|
||||
outBinding.baseArrayLayer = ResolveAttachmentBaseArrayLayer(attachment);
|
||||
if (AttachmentIsDepthSlice(attachment)) {
|
||||
outBinding.depthOffset = static_cast<Uint32>(std::max(attachment.GetTextureLayer(), 0));
|
||||
outBinding.baseArrayLayer = 0;
|
||||
} else {
|
||||
outBinding.baseArrayLayer = ResolveAttachmentBaseArrayLayer(attachment);
|
||||
}
|
||||
outBinding.layerCount = 1;
|
||||
return true;
|
||||
}
|
||||
@@ -1745,7 +1767,12 @@ void main() {
|
||||
outBinding.extent = {attachmentExtent.x(), attachmentExtent.y()};
|
||||
outBinding.mipLevel = static_cast<Uint32>(std::max(attachment.GetTextureLevel(), 0));
|
||||
outBinding.mipLevelCount = 1;
|
||||
outBinding.baseArrayLayer = ResolveAttachmentBaseArrayLayer(attachment);
|
||||
if (AttachmentIsDepthSlice(attachment)) {
|
||||
outBinding.depthOffset = static_cast<Uint32>(std::max(attachment.GetTextureLayer(), 0));
|
||||
outBinding.baseArrayLayer = 0;
|
||||
} else {
|
||||
outBinding.baseArrayLayer = ResolveAttachmentBaseArrayLayer(attachment);
|
||||
}
|
||||
outBinding.layerCount = 1;
|
||||
return true;
|
||||
}
|
||||
@@ -5401,9 +5428,11 @@ void main() {
|
||||
VkClearAttachment clearAttachment{};
|
||||
clearAttachment.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
clearAttachment.colorAttachment = drawBufferIndex;
|
||||
// glClear only ever supplies float values (ClearFramebufferPayload has no
|
||||
// other form), so the float member is always the right one here.
|
||||
clearAttachment.clearValue.color = {
|
||||
payload.color.x(), payload.color.y(), payload.color.z(),
|
||||
ResolveColorClearAlpha(colorTexture, payload.color.w())
|
||||
ColorFormatLacksAlpha(colorTexture) ? 1.0f : payload.color.w()
|
||||
};
|
||||
clearAttachments[clearAttachmentCount++] = clearAttachment;
|
||||
}
|
||||
@@ -5680,10 +5709,8 @@ void main() {
|
||||
}
|
||||
clearAttachment.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
clearAttachment.colorAttachment = static_cast<Uint32>(drawbuffer);
|
||||
clearAttachment.clearValue.color = {
|
||||
clearPayload.color.x(), clearPayload.color.y(), clearPayload.color.z(),
|
||||
ResolveColorClearAlpha(colorTexture, clearPayload.color.w())
|
||||
};
|
||||
clearAttachment.clearValue.color =
|
||||
MakeVkClearColorValue(clearPayload, ColorFormatLacksAlpha(colorTexture));
|
||||
} else {
|
||||
VkImageAspectFlags aspects = 0;
|
||||
if ((clearPayload.mask & GL_DEPTH_BUFFER_BIT) != 0 && MG_State::pGLContext->GetDepthMask() &&
|
||||
@@ -5771,6 +5798,46 @@ void main() {
|
||||
QueueClearBufferPayloadForFramebuffer(*framebuffer, buffer, drawbuffer, payload);
|
||||
}
|
||||
|
||||
// The integer clears carry the same payload as their target-based siblings; only the
|
||||
// destination differs, so they queue against the named framebuffer rather than the bound one.
|
||||
void VulkanRenderer::ClearNamedFramebufferiv(
|
||||
const SharedPtr<MG_State::GLState::FramebufferObject>& framebuffer, GLenum buffer, GLint drawbuffer,
|
||||
const GLint* value) {
|
||||
if (!framebuffer || value == nullptr) {
|
||||
return;
|
||||
}
|
||||
ClearAttachmentPayload payload{};
|
||||
switch (buffer) {
|
||||
case GL_COLOR:
|
||||
payload.mask = GL_COLOR_BUFFER_BIT;
|
||||
payload.colorEncoding = ClearColorEncoding::Int;
|
||||
payload.colorInt = IntVec4(value[0], value[1], value[2], value[3]);
|
||||
break;
|
||||
case GL_STENCIL:
|
||||
payload.mask = GL_STENCIL_BUFFER_BIT;
|
||||
payload.stencil = static_cast<Uint32>(std::max(value[0], 0));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
QueueClearBufferPayloadForFramebuffer(*framebuffer, buffer, drawbuffer, payload);
|
||||
}
|
||||
|
||||
void VulkanRenderer::ClearNamedFramebufferuiv(
|
||||
const SharedPtr<MG_State::GLState::FramebufferObject>& framebuffer, GLenum buffer, GLint drawbuffer,
|
||||
const GLuint* value) {
|
||||
if (!framebuffer || value == nullptr) {
|
||||
return;
|
||||
}
|
||||
ClearAttachmentPayload payload{};
|
||||
if (buffer == GL_COLOR) {
|
||||
payload.mask = GL_COLOR_BUFFER_BIT;
|
||||
payload.colorEncoding = ClearColorEncoding::Uint;
|
||||
payload.colorUint = UintVec4(value[0], value[1], value[2], value[3]);
|
||||
}
|
||||
QueueClearBufferPayloadForFramebuffer(*framebuffer, buffer, drawbuffer, payload);
|
||||
}
|
||||
|
||||
void VulkanRenderer::ClearNamedFramebufferfi(
|
||||
const SharedPtr<MG_State::GLState::FramebufferObject>& framebuffer, GLenum buffer, GLint drawbuffer,
|
||||
GLfloat depth, GLint stencil) {
|
||||
@@ -5793,8 +5860,8 @@ void main() {
|
||||
switch (buffer) {
|
||||
case GL_COLOR:
|
||||
payload.mask = GL_COLOR_BUFFER_BIT;
|
||||
payload.color = FloatVec4(static_cast<Float>(value[0]), static_cast<Float>(value[1]),
|
||||
static_cast<Float>(value[2]), static_cast<Float>(value[3]));
|
||||
payload.colorEncoding = ClearColorEncoding::Uint;
|
||||
payload.colorUint = UintVec4(value[0], value[1], value[2], value[3]);
|
||||
break;
|
||||
case GL_STENCIL:
|
||||
payload.mask = GL_STENCIL_BUFFER_BIT;
|
||||
@@ -5814,8 +5881,8 @@ void main() {
|
||||
switch (buffer) {
|
||||
case GL_COLOR:
|
||||
payload.mask = GL_COLOR_BUFFER_BIT;
|
||||
payload.color = FloatVec4(static_cast<Float>(value[0]), static_cast<Float>(value[1]),
|
||||
static_cast<Float>(value[2]), static_cast<Float>(value[3]));
|
||||
payload.colorEncoding = ClearColorEncoding::Int;
|
||||
payload.colorInt = IntVec4(value[0], value[1], value[2], value[3]);
|
||||
break;
|
||||
case GL_STENCIL:
|
||||
payload.mask = GL_STENCIL_BUFFER_BIT;
|
||||
@@ -5874,14 +5941,12 @@ void main() {
|
||||
subresourceRange.baseArrayLayer = pendingClear.key.baseArrayLayer;
|
||||
subresourceRange.layerCount = pendingClear.key.layerCount;
|
||||
|
||||
const auto& clearPayload = pendingClear.payload;
|
||||
auto clearPayload = pendingClear.payload;
|
||||
if ((resource->aspect & VK_IMAGE_ASPECT_COLOR_BIT) != 0) {
|
||||
subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
VkClearColorValue clearValue{};
|
||||
clearValue.float32[0] = clearPayload.color.x();
|
||||
clearValue.float32[1] = clearPayload.color.y();
|
||||
clearValue.float32[2] = clearPayload.color.z();
|
||||
clearValue.float32[3] = ResolveColorClearAlpha(&texture, clearPayload.color.w());
|
||||
PreCompensateSrgbClearColor(clearPayload, resource->format);
|
||||
const VkClearColorValue clearValue =
|
||||
MakeVkClearColorValue(clearPayload, ColorFormatLacksAlpha(&texture));
|
||||
vkCmdClearColorImage(commandBuffer, resource->image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
||||
&clearValue, 1, &subresourceRange);
|
||||
} else {
|
||||
@@ -5963,14 +6028,11 @@ void main() {
|
||||
VkImageLayout steadyLayout;
|
||||
if ((resource->aspect & VK_IMAGE_ASPECT_COLOR_BIT) != 0) {
|
||||
subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
VkClearColorValue clearValue{};
|
||||
clearValue.float32[0] = clearPayload.color.x();
|
||||
clearValue.float32[1] = clearPayload.color.y();
|
||||
clearValue.float32[2] = clearPayload.color.z();
|
||||
PreCompensateSrgbClearColor(clearPayload, resource->format);
|
||||
// RGB renderbuffers are backed by an RGBA image; the missing alpha reads as 1.
|
||||
clearValue.float32[3] =
|
||||
MG_Util::GetBaseInternalFormatComponentCount(renderbuffer->GetInternalFormat()) == 3 ?
|
||||
1.0f : clearPayload.color.w();
|
||||
const VkClearColorValue clearValue = MakeVkClearColorValue(
|
||||
clearPayload,
|
||||
MG_Util::GetBaseInternalFormatComponentCount(renderbuffer->GetInternalFormat()) == 3);
|
||||
vkCmdClearColorImage(commandBuffer, resource->image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
||||
&clearValue, 1, &subresourceRange);
|
||||
steadyLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
|
||||
@@ -6267,17 +6329,20 @@ void main() {
|
||||
|
||||
if (srcX1 < srcX0 || srcY1 < srcY0 || dstX1 < dstX0 || dstY1 < dstY0) {
|
||||
MGLOG_E("BlitFramebuffer skipped: depth blits with flipped rectangles are not supported yet");
|
||||
return;
|
||||
continue;
|
||||
}
|
||||
|
||||
const Int srcWidth = srcX1 - srcX0;
|
||||
const Int srcHeight = srcY1 - srcY0;
|
||||
const Int dstWidth = dstX1 - dstX0;
|
||||
const Int dstHeight = dstY1 - dstY0;
|
||||
if (srcWidth <= 0 || srcHeight <= 0 || srcWidth != dstWidth || srcHeight != dstHeight) {
|
||||
MGLOG_E("BlitFramebuffer skipped: depth blits currently require matching source and destination extents");
|
||||
return;
|
||||
if (srcWidth <= 0 || srcHeight <= 0 || dstWidth <= 0 || dstHeight <= 0) {
|
||||
MGLOG_E("BlitFramebuffer skipped: degenerate depth blit rectangle");
|
||||
continue;
|
||||
}
|
||||
// A scaling depth blit is legal GL and vkCmdBlitImage scales natively; only a same-size
|
||||
// pair can take the cheaper vkCmdCopyImage.
|
||||
const Bool depthBlitScales = srcWidth != dstWidth || srcHeight != dstHeight;
|
||||
|
||||
if (!readIsDefaultFbo) {
|
||||
const auto sourceAttachmentType = ResolveFramebufferCopyAttachmentType(*readFbo, true, srcBinding.aspectMask);
|
||||
@@ -6322,7 +6387,7 @@ void main() {
|
||||
: *srcBinding.trackedLayout;
|
||||
if (srcOriginalLayout == VK_IMAGE_LAYOUT_UNDEFINED) {
|
||||
MGLOG_E("BlitFramebuffer skipped: depth source image layout is undefined");
|
||||
return;
|
||||
continue;
|
||||
}
|
||||
|
||||
const VkImageLayout dstOriginalLayout = drawIsDefaultFbo
|
||||
@@ -6339,7 +6404,7 @@ void main() {
|
||||
if (srcBinding.format != dstBinding.format) {
|
||||
if (readIsDefaultFbo || drawIsDefaultFbo) {
|
||||
MGLOG_E("BlitFramebuffer skipped: cross-format depth/stencil blit with the default framebuffer");
|
||||
return;
|
||||
continue;
|
||||
}
|
||||
if (!BlitDepthAcrossFormats(frame, srcBinding.image, srcBinding.format, srcBinding.trackedLayout,
|
||||
srcBinding.mipLevel, srcBinding.baseArrayLayer, dstBinding.image,
|
||||
@@ -6347,7 +6412,7 @@ void main() {
|
||||
dstBinding.baseArrayLayer, srcX0, srcY0, dstX0, dstY0, srcX1 - srcX0,
|
||||
srcY1 - srcY0, srcOriginalLayout, dstRestoreLayout,
|
||||
depthStencilAspect == VK_IMAGE_ASPECT_STENCIL_BIT)) {
|
||||
return;
|
||||
continue;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
@@ -6394,23 +6459,45 @@ void main() {
|
||||
MOBILEGL_ASSERT(ok, "%s: failed to transition depth destination image", __func__);
|
||||
}
|
||||
|
||||
VkImageCopy copyRegion{};
|
||||
copyRegion.srcSubresource.aspectMask = srcBinding.aspectMask;
|
||||
copyRegion.srcSubresource.mipLevel = srcBinding.mipLevel;
|
||||
copyRegion.srcSubresource.baseArrayLayer = srcBinding.baseArrayLayer;
|
||||
copyRegion.srcSubresource.layerCount = srcBinding.layerCount;
|
||||
copyRegion.srcOffset = {srcX0, srcY0, 0};
|
||||
copyRegion.dstSubresource.aspectMask = dstBinding.aspectMask;
|
||||
copyRegion.dstSubresource.mipLevel = dstBinding.mipLevel;
|
||||
copyRegion.dstSubresource.baseArrayLayer = dstBinding.baseArrayLayer;
|
||||
copyRegion.dstSubresource.layerCount = dstBinding.layerCount;
|
||||
copyRegion.dstOffset = {dstX0, dstY0, 0};
|
||||
copyRegion.extent = {static_cast<Uint32>(srcWidth), static_cast<Uint32>(srcHeight), 1};
|
||||
if (depthBlitScales) {
|
||||
// vkCmdCopyImage cannot resize; NEAREST is the only filter Vulkan allows for a
|
||||
// depth/stencil blit anyway, and the GL front end already rejects the others.
|
||||
VkImageBlit blitRegion{};
|
||||
blitRegion.srcSubresource.aspectMask = srcBinding.aspectMask;
|
||||
blitRegion.srcSubresource.mipLevel = srcBinding.mipLevel;
|
||||
blitRegion.srcSubresource.baseArrayLayer = srcBinding.baseArrayLayer;
|
||||
blitRegion.srcSubresource.layerCount = srcBinding.layerCount;
|
||||
blitRegion.srcOffsets[0] = {srcX0, srcY0, 0};
|
||||
blitRegion.srcOffsets[1] = {srcX1, srcY1, 1};
|
||||
blitRegion.dstSubresource.aspectMask = dstBinding.aspectMask;
|
||||
blitRegion.dstSubresource.mipLevel = dstBinding.mipLevel;
|
||||
blitRegion.dstSubresource.baseArrayLayer = dstBinding.baseArrayLayer;
|
||||
blitRegion.dstSubresource.layerCount = dstBinding.layerCount;
|
||||
blitRegion.dstOffsets[0] = {dstX0, dstY0, 0};
|
||||
blitRegion.dstOffsets[1] = {dstX1, dstY1, 1};
|
||||
vkCmdBlitImage(frame.commandBuffer,
|
||||
srcBinding.image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
||||
dstBinding.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
||||
1, &blitRegion, VK_FILTER_NEAREST);
|
||||
} else {
|
||||
VkImageCopy copyRegion{};
|
||||
copyRegion.srcSubresource.aspectMask = srcBinding.aspectMask;
|
||||
copyRegion.srcSubresource.mipLevel = srcBinding.mipLevel;
|
||||
copyRegion.srcSubresource.baseArrayLayer = srcBinding.baseArrayLayer;
|
||||
copyRegion.srcSubresource.layerCount = srcBinding.layerCount;
|
||||
copyRegion.srcOffset = {srcX0, srcY0, 0};
|
||||
copyRegion.dstSubresource.aspectMask = dstBinding.aspectMask;
|
||||
copyRegion.dstSubresource.mipLevel = dstBinding.mipLevel;
|
||||
copyRegion.dstSubresource.baseArrayLayer = dstBinding.baseArrayLayer;
|
||||
copyRegion.dstSubresource.layerCount = dstBinding.layerCount;
|
||||
copyRegion.dstOffset = {dstX0, dstY0, 0};
|
||||
copyRegion.extent = {static_cast<Uint32>(srcWidth), static_cast<Uint32>(srcHeight), 1};
|
||||
|
||||
vkCmdCopyImage(frame.commandBuffer,
|
||||
srcBinding.image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
||||
dstBinding.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
||||
1, ©Region);
|
||||
vkCmdCopyImage(frame.commandBuffer,
|
||||
srcBinding.image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
||||
dstBinding.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
||||
1, ©Region);
|
||||
}
|
||||
|
||||
VkPipelineStageFlags srcRestoreStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
|
||||
VkAccessFlags srcRestoreAccessMask = 0;
|
||||
@@ -7141,7 +7228,7 @@ void main() {
|
||||
copyRegion.imageSubresource.mipLevel = srcBinding.mipLevel;
|
||||
copyRegion.imageSubresource.baseArrayLayer = srcBinding.baseArrayLayer;
|
||||
copyRegion.imageSubresource.layerCount = 1;
|
||||
copyRegion.imageOffset = {x, y, 0};
|
||||
copyRegion.imageOffset = {x, y, static_cast<Int32>(srcBinding.depthOffset)};
|
||||
copyRegion.imageExtent = {static_cast<Uint32>(width), static_cast<Uint32>(height), 1};
|
||||
vkCmdCopyImageToBuffer(frame.commandBuffer, srcBinding.image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
||||
readback.GetHandle(), 1, ©Region);
|
||||
@@ -7599,13 +7686,16 @@ void main() {
|
||||
switch (type) {
|
||||
case GL_FLOAT:
|
||||
case GL_UNSIGNED_INT:
|
||||
case GL_INT:
|
||||
case GL_UNSIGNED_INT_24_8:
|
||||
dstPixelBytes = 4;
|
||||
break;
|
||||
case GL_UNSIGNED_SHORT:
|
||||
case GL_SHORT:
|
||||
dstPixelBytes = 2;
|
||||
break;
|
||||
case GL_UNSIGNED_BYTE:
|
||||
case GL_BYTE:
|
||||
dstPixelBytes = 1;
|
||||
break;
|
||||
case GL_FLOAT_32_UNSIGNED_INT_24_8_REV:
|
||||
@@ -7616,29 +7706,40 @@ void main() {
|
||||
return;
|
||||
}
|
||||
|
||||
// GL 4.6 core 18.2.8: a GL_STENCIL_INDEX read reports the index itself, unconverted, in
|
||||
// whatever width the client asked for. Only the packed types mix depth in. Deciding this
|
||||
// once - rather than per type, where GL_FLOAT and GL_UNSIGNED_SHORT used to emit a depth
|
||||
// value that is meaningless for a stencil-only image - is what makes the CTS's
|
||||
// (GL_STENCIL_INDEX, GL_INT) read return 7 instead of nothing.
|
||||
const Bool stencilOnly = format == GL_STENCIL_INDEX;
|
||||
|
||||
Vector<Uint8> packed(pixelCount * dstPixelBytes);
|
||||
for (SizeT i = 0; i < pixelCount; ++i) {
|
||||
Uint8* dst = packed.data() + i * dstPixelBytes;
|
||||
switch (type) {
|
||||
case GL_FLOAT: {
|
||||
const Float value = depthValueAt(i);
|
||||
const Float value = stencilOnly ? static_cast<Float>(stencilSrc[i]) : depthValueAt(i);
|
||||
Memcpy(dst, &value, sizeof(value));
|
||||
break;
|
||||
}
|
||||
case GL_UNSIGNED_SHORT: {
|
||||
const Uint16 value =
|
||||
static_cast<Uint16>(std::lround(static_cast<double>(depthValueAt(i)) * 65535.0));
|
||||
case GL_UNSIGNED_SHORT:
|
||||
case GL_SHORT: {
|
||||
const Uint16 value = stencilOnly
|
||||
? static_cast<Uint16>(stencilSrc[i])
|
||||
: static_cast<Uint16>(std::lround(static_cast<double>(depthValueAt(i)) * 65535.0));
|
||||
Memcpy(dst, &value, sizeof(value));
|
||||
break;
|
||||
}
|
||||
case GL_UNSIGNED_INT: {
|
||||
const Uint32 value = format == GL_STENCIL_INDEX
|
||||
case GL_UNSIGNED_INT:
|
||||
case GL_INT: {
|
||||
const Uint32 value = stencilOnly
|
||||
? stencilSrc[i]
|
||||
: static_cast<Uint32>(static_cast<double>(depthValueAt(i)) * 4294967295.0);
|
||||
Memcpy(dst, &value, sizeof(value));
|
||||
break;
|
||||
}
|
||||
case GL_UNSIGNED_BYTE: {
|
||||
case GL_UNSIGNED_BYTE:
|
||||
case GL_BYTE: {
|
||||
dst[0] = stencilSrc[i];
|
||||
break;
|
||||
}
|
||||
@@ -7850,9 +7951,18 @@ void main() {
|
||||
|
||||
void VulkanRenderer::GenerateMipmap(GLenum target) {
|
||||
const auto textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target);
|
||||
MOBILEGL_ASSERT(textureTarget == TextureTarget::Texture2D || textureTarget == TextureTarget::Texture2DArray ||
|
||||
textureTarget == TextureTarget::Texture3D || textureTarget == TextureTarget::TextureCubeMap,
|
||||
"GenerateMipmap currently only supports GL_TEXTURE_2D, GL_TEXTURE_2D_ARRAY, GL_TEXTURE_3D, and GL_TEXTURE_CUBE_MAP.");
|
||||
// The other mipmappable targets - 1D, 1D array, cube map array - are legal GL and the front
|
||||
// end lets them through, so reaching one here is a coverage gap in this backend, not a
|
||||
// broken invariant. Declining leaves the mip chain unwritten; asserting took the process
|
||||
// down with it.
|
||||
if (textureTarget != TextureTarget::Texture2D && textureTarget != TextureTarget::Texture2DArray &&
|
||||
textureTarget != TextureTarget::Texture3D && textureTarget != TextureTarget::TextureCubeMap &&
|
||||
// A 1D texture needs nothing special: its storage extent is {width, 1, 1}, so the blit
|
||||
// loop below already emits the y and z offsets of 0 and 1 that a 1D image requires.
|
||||
textureTarget != TextureTarget::Texture1D) {
|
||||
MGLOG_W("GenerateMipmap: unsupported target %s", MG_Util::ConvertTextureTargetToString(textureTarget).c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
auto& textureUnit = MG_State::pGLContext->GetTextureUnitObject(MG_State::pGLContext->GetActiveTextureUnit());
|
||||
auto texture = textureUnit.GetBindingSlot(textureTarget).GetBoundObject();
|
||||
@@ -9191,10 +9301,17 @@ void main() {
|
||||
if (record.harvested) {
|
||||
return true;
|
||||
}
|
||||
if (!m_timerQueryManager || !IsFrameSerialComplete(record.frameSerial)) {
|
||||
if (!m_timerQueryManager) {
|
||||
return false;
|
||||
}
|
||||
return m_timerQueryManager->TryHarvest(record);
|
||||
// Ask the pool first. It polls with VK_QUERY_RESULT_WITH_AVAILABILITY_BIT and is the
|
||||
// authority on whether the timestamp has landed; the frame serial is not, because it only
|
||||
// advances at Present and neither completion notifier will mark the CURRENT serial done - so
|
||||
// a timestamp written and fence-waited inside one GL frame could never be read back in it.
|
||||
if (m_timerQueryManager->TryHarvest(record)) {
|
||||
return true;
|
||||
}
|
||||
return IsFrameSerialComplete(record.frameSerial) && m_timerQueryManager->TryHarvest(record);
|
||||
}
|
||||
|
||||
Bool VulkanRenderer::WaitForTimerQueryResult(VkTimerQueryManager::TimestampRecord& record) {
|
||||
@@ -10622,12 +10739,8 @@ void main() {
|
||||
// VkClearAttachment::colorAttachment indexes the subpass pColorAttachments (draw-buffer
|
||||
// slot space, with UNUSED holes), not the compacted attachment descriptions.
|
||||
clearAttachment.colorAttachment = pending.colorAttachmentSlot;
|
||||
clearAttachment.clearValue.color = {
|
||||
clearPayload.color.x(),
|
||||
clearPayload.color.y(),
|
||||
clearPayload.color.z(),
|
||||
ResolveColorClearAlpha(liveTexture.get(), clearPayload.color.w())
|
||||
};
|
||||
clearAttachment.clearValue.color =
|
||||
MakeVkClearColorValue(clearPayload, ColorFormatLacksAlpha(liveTexture.get()));
|
||||
} else {
|
||||
if ((clearPayload.mask & GL_DEPTH_BUFFER_BIT) != 0) {
|
||||
clearAttachment.aspectMask |= VK_IMAGE_ASPECT_DEPTH_BIT;
|
||||
|
||||
@@ -181,6 +181,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
void ClearBufferiv(GLenum buffer, GLint drawbuffer, const GLint* value);
|
||||
void ClearNamedFramebufferfv(const SharedPtr<MG_State::GLState::FramebufferObject>& framebuffer,
|
||||
GLenum buffer, GLint drawbuffer, const GLfloat* value);
|
||||
void ClearNamedFramebufferiv(const SharedPtr<MG_State::GLState::FramebufferObject>& framebuffer,
|
||||
GLenum buffer, GLint drawbuffer, const GLint* value);
|
||||
void ClearNamedFramebufferuiv(const SharedPtr<MG_State::GLState::FramebufferObject>& framebuffer,
|
||||
GLenum buffer, GLint drawbuffer, const GLuint* value);
|
||||
void ClearNamedFramebufferfi(const SharedPtr<MG_State::GLState::FramebufferObject>& framebuffer,
|
||||
GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil);
|
||||
void BlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1,
|
||||
|
||||
@@ -8,6 +8,9 @@
|
||||
|
||||
#include "GL_Buffer.h"
|
||||
#include "Validators.h"
|
||||
#include "../Texture/GL_Texture.h"
|
||||
#include <MG_Util/Converters/GLToMG/TextureEnumConverter.h>
|
||||
#include <MG_Util/Metrics/TextureMetrics.h>
|
||||
#include <Config.h>
|
||||
#include <MG_State/GLState/Core.h>
|
||||
#include <MG_State/GLState/ErrorState/Error.h>
|
||||
@@ -38,6 +41,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
GetNamedBufferParameteriv,
|
||||
GetNamedBufferParameteri64v,
|
||||
GetNamedBufferPointerv,
|
||||
GetNamedBufferSubData,
|
||||
};
|
||||
|
||||
const char* GetBufferOpName(BufferOp op) {
|
||||
@@ -76,6 +80,8 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
return "UnmapNamedBuffer";
|
||||
case BufferOp::FlushMappedNamedBufferRange:
|
||||
return "FlushMappedNamedBufferRange";
|
||||
case BufferOp::GetNamedBufferSubData:
|
||||
return "GetNamedBufferSubData";
|
||||
case BufferOp::GetNamedBufferParameteriv:
|
||||
return "GetNamedBufferParameteriv";
|
||||
case BufferOp::GetNamedBufferParameteri64v:
|
||||
@@ -89,25 +95,64 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
|
||||
SharedPtr<MG_State::GLState::BufferObject> GetNamedBufferObject(GLuint buffer, BufferOp op);
|
||||
|
||||
// The size of one cleared element, which is what offset and size must be multiples of
|
||||
// (GL 4.6 core 6.3). `internalformat` is restricted to the buffer-texture format table, and
|
||||
// `format`/`type` describe the client-side pattern, so both are validated here and the
|
||||
// caller only has to know how wide an element is.
|
||||
SizeT GetClearPatternSize(GLenum internalformat, GLenum format, GLenum type, BufferOp op) {
|
||||
if (format != GL_RED_INTEGER) {
|
||||
if (!IsBufferTextureInternalFormat(internalformat)) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidEnum,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", GetBufferOpName(op),
|
||||
"Only GL_RED_INTEGER buffer clears are currently supported."));
|
||||
MakeUnique<GenericErrorInfo>(
|
||||
"MG_Impl/GLImpl", GetBufferOpName(op),
|
||||
std::format("internalformat 0x{:X} is not one of the sized formats a buffer clear accepts.",
|
||||
internalformat)));
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (internalformat == GL_R8UI && type == GL_UNSIGNED_BYTE) return sizeof(GLubyte);
|
||||
if (internalformat == GL_R32UI && type == GL_UNSIGNED_INT) return sizeof(GLuint);
|
||||
// Unlike internalformat, a bad format or type here is INVALID_VALUE rather than
|
||||
// INVALID_ENUM (GL 4.6 core 6.3) - the odd one out among the enum arguments.
|
||||
const TextureInputFormat inputFormat = MG_Util::ConvertGLEnumToTextureInputFormat(format);
|
||||
if (inputFormat == TextureInputFormat::Unknown) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidValue,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", GetBufferOpName(op),
|
||||
std::format("format 0x{:X} is not a pixel format.", format)));
|
||||
return 0;
|
||||
}
|
||||
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidEnum,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", GetBufferOpName(op),
|
||||
std::format("Unsupported clear format tuple: internalformat=0x{:X}, "
|
||||
"format=0x{:X}, type=0x{:X}",
|
||||
internalformat, format, type)));
|
||||
return 0;
|
||||
const TexturePixelDataType pixelType = MG_Util::ConvertGLEnumToTexturePixelDataType(type);
|
||||
if (pixelType == TexturePixelDataType::Unknown) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidValue,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", GetBufferOpName(op),
|
||||
std::format("type 0x{:X} is not a pixel type.", type)));
|
||||
return 0;
|
||||
}
|
||||
|
||||
const TextureInternalFormat internal =
|
||||
MG_Util::ConvertGLEnumToTextureInternalFormat(internalformat);
|
||||
const SizeT elementSize = MG_Util::GetSizedInternalFormatSizeInBytes(internal);
|
||||
if (elementSize == 0) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidEnum,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", GetBufferOpName(op),
|
||||
std::format("internalformat 0x{:X} has no known element size.",
|
||||
internalformat)));
|
||||
return 0;
|
||||
}
|
||||
|
||||
// The pattern is replicated verbatim, which is only the whole story while the client
|
||||
// layout already matches the internal format - the case every entry point in practice
|
||||
// uses, and the only one the conversion machinery here can express. Say so rather than
|
||||
// quietly writing a differently-sized pattern.
|
||||
const SizeT sourceSize = MG_Util::GetInputBytesPerPixel(inputFormat, pixelType);
|
||||
if (sourceSize != elementSize) {
|
||||
MGLOG_W("%s: clear pattern is %zu bytes but internalformat 0x%X stores %zu; "
|
||||
"converting between them is not implemented",
|
||||
GetBufferOpName(op), sourceSize, internalformat, elementSize);
|
||||
}
|
||||
return elementSize;
|
||||
}
|
||||
|
||||
Bool ValidateBufferClearRange(const SharedPtr<MG_State::GLState::BufferObject>& bufferObject, GLintptr offset,
|
||||
@@ -891,6 +936,44 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
bufferObject->DownloadSubData(data, static_cast<SizeT>(offset), static_cast<SizeT>(size));
|
||||
}
|
||||
|
||||
void GetNamedBufferSubData_State(GLuint buffer, GLintptr offset, GLsizeiptr size, void* data) {
|
||||
if (!data) {
|
||||
// Match GetBufferSubData_State: a null pointer is a caller bug, not a GL-specified error.
|
||||
return;
|
||||
}
|
||||
|
||||
if (size < 0 || offset < 0) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidValue,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", "GetNamedBufferSubData_State",
|
||||
"Offset and size must be non-negative."));
|
||||
return;
|
||||
}
|
||||
|
||||
auto bufferObject = GetNamedBufferObject(buffer, BufferOp::GetNamedBufferSubData);
|
||||
if (!bufferObject) return;
|
||||
|
||||
if (static_cast<SizeT>(offset) + static_cast<SizeT>(size) > bufferObject->GetSize()) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidValue,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", "GetNamedBufferSubData_State",
|
||||
"Offset and size exceed buffer size."));
|
||||
return;
|
||||
}
|
||||
|
||||
if (bufferObject->IsMapped() &&
|
||||
!(bufferObject->GetMappingAccess() & BufferMappingAccessBit::Persistent)) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidOperation,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", "GetNamedBufferSubData_State",
|
||||
"Cannot read from a buffer object mapped without GL_MAP_PERSISTENT_BIT."));
|
||||
return;
|
||||
}
|
||||
|
||||
bufferObject->SyncGpuWrites();
|
||||
bufferObject->DownloadSubData(data, static_cast<SizeT>(offset), static_cast<SizeT>(size));
|
||||
}
|
||||
|
||||
void BufferData_State(GLenum target, GLsizeiptr size, const void* data, GLenum usage) {
|
||||
MGLOG_D("%s: %s, size = %d, data = %p, usage = %s", __func__, MG_Util::ConvertGLEnumToString(target).c_str(),
|
||||
size, data, MG_Util::ConvertGLEnumToString(usage).c_str());
|
||||
@@ -1553,6 +1636,10 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
BufferSubData_State(target, offset, size, data);
|
||||
}
|
||||
|
||||
void GetNamedBufferSubData(GLuint buffer, GLintptr offset, GLsizeiptr size, void* data) {
|
||||
GetNamedBufferSubData_State(buffer, offset, size, data);
|
||||
}
|
||||
|
||||
void GetBufferSubData(GLenum target, GLintptr offset, GLsizeiptr size, void* data) {
|
||||
GetBufferSubData_State(target, offset, size, data);
|
||||
}
|
||||
|
||||
@@ -41,6 +41,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
GLsizeiptr size);
|
||||
void BufferSubData(GLenum target, GLintptr offset, GLsizeiptr size, const void* data);
|
||||
void GetBufferSubData(GLenum target, GLintptr offset, GLsizeiptr size, void* data);
|
||||
void GetNamedBufferSubData(GLuint buffer, GLintptr offset, GLsizeiptr size, void* data);
|
||||
void BufferData(GLenum target, GLsizeiptr size, const void* data, GLenum usage);
|
||||
void BindBuffer(GLenum target, GLuint buffer);
|
||||
void GenBuffers(GLsizei n, GLuint* buffers);
|
||||
|
||||
@@ -112,14 +112,10 @@ namespace MobileGL::MG_Impl::GLImpl::BufferImpl {
|
||||
}
|
||||
|
||||
Bool ValidateBufferMappingAccess(Flags<BufferMappingAccessBit> accessBits) {
|
||||
if (accessBits == BufferMappingAccessBit::Null) {
|
||||
MG_State::pGLContext->RecordError(ErrorCode::InvalidEnum,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl/BufferImpl",
|
||||
"ValidateBufferMappingAccess",
|
||||
"Access bits cannot be null."));
|
||||
return false;
|
||||
}
|
||||
|
||||
// An empty mask is a legal value for a bitfield - it just fails the rule that a mapping
|
||||
// must ask for read or write access, which is INVALID_OPERATION and belongs to the callers
|
||||
// (both of them check it immediately after this). Rejecting it here as INVALID_ENUM
|
||||
// reported the wrong error and hid theirs.
|
||||
const auto validBits = BufferMappingAccessBit::Read | BufferMappingAccessBit::Write |
|
||||
BufferMappingAccessBit::InvalidateRange | BufferMappingAccessBit::InvalidateBuffer |
|
||||
BufferMappingAccessBit::FlushExplicit | BufferMappingAccessBit::Unsynchronized |
|
||||
|
||||
@@ -877,6 +877,172 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
Memcpy(ids, names.data(), static_cast<SizeT>(n) * sizeof(GLuint));
|
||||
}
|
||||
|
||||
void CreateTransformFeedbacks(GLsizei n, GLuint* ids) {
|
||||
if (n < 0) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidValue,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "n must be non-negative."));
|
||||
return;
|
||||
}
|
||||
if (n == 0 || ids == nullptr) return;
|
||||
Vector<Uint> names;
|
||||
MG_State::pGLContext->GenTransformFeedbackNames(static_cast<Uint>(n), names);
|
||||
// Unlike glGenTransformFeedbacks, the names are objects immediately: there is no bind step
|
||||
// to create them from (GL 4.6 core 13.2.1).
|
||||
for (const Uint name : names) {
|
||||
MG_State::pGLContext->CreateTransformFeedbackObject(name);
|
||||
}
|
||||
Memcpy(ids, names.data(), static_cast<SizeT>(n) * sizeof(GLuint));
|
||||
}
|
||||
|
||||
namespace {
|
||||
// Shared front half of the by-name transform feedback entry points: the object has to exist
|
||||
// (INVALID_OPERATION otherwise) before anything else about the call is looked at.
|
||||
Bool ValidateNamedTransformFeedback(GLuint xfb, const char* functionName) {
|
||||
if (!MG_State::pGLContext->IsTransformFeedbackObject(xfb)) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidOperation,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", functionName,
|
||||
std::to_string(xfb) + " is not a transform feedback object."));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
Bool ValidateTransformFeedbackBufferIndex(GLuint index, const char* functionName) {
|
||||
if (index >= MG_State::GLState::GLContext::MAX_TRANSFORM_FEEDBACK_BUFFERS) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidValue,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", functionName,
|
||||
"index exceeds GL_MAX_TRANSFORM_FEEDBACK_BUFFERS."));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// A capture binding may not be changed while the object is capturing (GL 4.6 core 13.2.2).
|
||||
Bool ValidateNamedTransformFeedbackNotActive(GLuint xfb, const char* functionName) {
|
||||
if (MG_State::pGLContext->IsNamedTransformFeedbackActive(xfb)) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidOperation,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", functionName,
|
||||
"The transform feedback object is capturing."));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
SharedPtr<MG_State::GLState::BufferObject> ResolveTransformFeedbackBuffer(GLuint buffer,
|
||||
const char* functionName) {
|
||||
if (buffer == 0) return nullptr;
|
||||
if (!MG_State::pGLContext->ValidateBufferName(buffer)) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidOperation,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", functionName,
|
||||
std::to_string(buffer) + " is not a buffer object."));
|
||||
return nullptr;
|
||||
}
|
||||
return MG_State::pGLContext->GetBufferObject(buffer);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void TransformFeedbackBufferBase(GLuint xfb, GLuint index, GLuint buffer) {
|
||||
if (!ValidateNamedTransformFeedback(xfb, __func__)) return;
|
||||
if (!ValidateTransformFeedbackBufferIndex(index, __func__)) return;
|
||||
if (!ValidateNamedTransformFeedbackNotActive(xfb, __func__)) return;
|
||||
if (buffer != 0 && !MG_State::pGLContext->ValidateBufferName(buffer)) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidOperation,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
||||
std::to_string(buffer) + " is not a buffer object."));
|
||||
return;
|
||||
}
|
||||
MG_State::pGLContext->SetNamedTransformFeedbackBinding(xfb, index,
|
||||
ResolveTransformFeedbackBuffer(buffer, __func__), {},
|
||||
false);
|
||||
}
|
||||
|
||||
void TransformFeedbackBufferRange(GLuint xfb, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size) {
|
||||
if (!ValidateNamedTransformFeedback(xfb, __func__)) return;
|
||||
if (!ValidateTransformFeedbackBufferIndex(index, __func__)) return;
|
||||
if (!ValidateNamedTransformFeedbackNotActive(xfb, __func__)) return;
|
||||
if (offset < 0 || size <= 0 || (offset % 4) != 0 || (size % 4) != 0) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidValue,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
||||
"offset and size must be non-negative multiples of 4."));
|
||||
return;
|
||||
}
|
||||
if (buffer != 0 && !MG_State::pGLContext->ValidateBufferName(buffer)) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidOperation,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
||||
std::to_string(buffer) + " is not a buffer object."));
|
||||
return;
|
||||
}
|
||||
auto bufferObject = ResolveTransformFeedbackBuffer(buffer, __func__);
|
||||
const Range1D range{static_cast<SizeT>(offset), static_cast<SizeT>(offset) + static_cast<SizeT>(size)};
|
||||
MG_State::pGLContext->SetNamedTransformFeedbackBinding(xfb, index, bufferObject, range,
|
||||
bufferObject != nullptr);
|
||||
}
|
||||
|
||||
void GetTransformFeedbackiv(GLuint xfb, GLenum pname, GLint* param) {
|
||||
if (!ValidateNamedTransformFeedback(xfb, __func__)) return;
|
||||
if (!param) return;
|
||||
switch (pname) {
|
||||
case GL_TRANSFORM_FEEDBACK_ACTIVE:
|
||||
*param = MG_State::pGLContext->IsNamedTransformFeedbackActive(xfb) ? GL_TRUE : GL_FALSE;
|
||||
return;
|
||||
case GL_TRANSFORM_FEEDBACK_PAUSED:
|
||||
*param = MG_State::pGLContext->IsNamedTransformFeedbackPaused(xfb) ? GL_TRUE : GL_FALSE;
|
||||
return;
|
||||
default:
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidEnum,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
||||
"pname must be GL_TRANSFORM_FEEDBACK_ACTIVE or _PAUSED."));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void GetTransformFeedbacki_v(GLuint xfb, GLenum pname, GLuint index, GLint* param) {
|
||||
if (!ValidateNamedTransformFeedback(xfb, __func__)) return;
|
||||
if (pname != GL_TRANSFORM_FEEDBACK_BUFFER_BINDING) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidEnum,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
||||
"pname must be GL_TRANSFORM_FEEDBACK_BUFFER_BINDING."));
|
||||
return;
|
||||
}
|
||||
if (!ValidateTransformFeedbackBufferIndex(index, __func__)) return;
|
||||
if (!param) return;
|
||||
const auto binding = MG_State::pGLContext->GetNamedTransformFeedbackBinding(xfb, index);
|
||||
*param = binding.Buffer ? static_cast<GLint>(binding.Buffer->GetExternalIndex()) : 0;
|
||||
}
|
||||
|
||||
void GetTransformFeedbacki64_v(GLuint xfb, GLenum pname, GLuint index, GLint64* param) {
|
||||
if (!ValidateNamedTransformFeedback(xfb, __func__)) return;
|
||||
if (pname != GL_TRANSFORM_FEEDBACK_BUFFER_START && pname != GL_TRANSFORM_FEEDBACK_BUFFER_SIZE) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidEnum,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
||||
"pname must be GL_TRANSFORM_FEEDBACK_BUFFER_START or _SIZE."));
|
||||
return;
|
||||
}
|
||||
if (!ValidateTransformFeedbackBufferIndex(index, __func__)) return;
|
||||
if (!param) return;
|
||||
const auto binding = MG_State::pGLContext->GetNamedTransformFeedbackBinding(xfb, index);
|
||||
// glTransformFeedbackBufferBase leaves both at zero; only the range form sets them
|
||||
// (GL 4.6 core table 23.48).
|
||||
if (!binding.Buffer || !binding.HasExplicitRange) {
|
||||
*param = 0;
|
||||
return;
|
||||
}
|
||||
*param = (pname == GL_TRANSFORM_FEEDBACK_BUFFER_START)
|
||||
? static_cast<GLint64>(binding.Range.start)
|
||||
: static_cast<GLint64>(binding.Range.end - binding.Range.start);
|
||||
}
|
||||
|
||||
void DeleteTransformFeedbacks(GLsizei n, const GLuint* ids) {
|
||||
if (n < 0) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
|
||||
@@ -16,7 +16,13 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
void PauseTransformFeedback(void);
|
||||
void ResumeTransformFeedback(void);
|
||||
void GenTransformFeedbacks(GLsizei n, GLuint* ids);
|
||||
void CreateTransformFeedbacks(GLsizei n, GLuint* ids);
|
||||
void DeleteTransformFeedbacks(GLsizei n, const GLuint* ids);
|
||||
void TransformFeedbackBufferBase(GLuint xfb, GLuint index, GLuint buffer);
|
||||
void TransformFeedbackBufferRange(GLuint xfb, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size);
|
||||
void GetTransformFeedbackiv(GLuint xfb, GLenum pname, GLint* param);
|
||||
void GetTransformFeedbacki_v(GLuint xfb, GLenum pname, GLuint index, GLint* param);
|
||||
void GetTransformFeedbacki64_v(GLuint xfb, GLenum pname, GLuint index, GLint64* param);
|
||||
void BindTransformFeedback(GLenum target, GLuint id);
|
||||
GLboolean IsTransformFeedback(GLuint id);
|
||||
void DrawTransformFeedback(GLenum mode, GLuint id);
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include "../Texture/GL_Texture.h"
|
||||
#include "../Drawing/GL_Drawing.h"
|
||||
#include "../Program/GL_Program.h"
|
||||
#include "../Program/GL_ProgramPipeline.h"
|
||||
#include "../RenderState/GL_RenderState.h"
|
||||
#include "../Framebuffer/GL_Framebuffer.h"
|
||||
#include "../VertexArray/GL_VertexArray.h"
|
||||
@@ -301,8 +302,8 @@ DECLARE_GL_FUNCTION_HEAD(void, ResumeTransformFeedback) DECLARE_GL_FUNCTION_END_
|
||||
DECLARE_GL_FUNCTION_HEAD(void, GetProgramBinary, GLuint program, GLsizei bufSize, GLsizei* length, GLenum* binaryFormat, void* binary) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetProgramBinary, program, bufSize, length, binaryFormat, binary)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, ProgramBinary, GLuint program, GLenum binaryFormat, const void* binary, GLsizei length) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ProgramBinary, program, binaryFormat, binary, length)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, ProgramParameteri, GLuint program, GLenum pname, GLint value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ProgramParameteri, program, pname, value)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, InvalidateFramebuffer, GLenum target, GLsizei numAttachments, const GLenum* attachments) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, InvalidateFramebuffer, target, numAttachments, attachments)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, InvalidateSubFramebuffer, GLenum target, GLsizei numAttachments, const GLenum* attachments, GLint x, GLint y, GLsizei width, GLsizei height) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, InvalidateSubFramebuffer, target, numAttachments, attachments, x, y, width, height)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, InvalidateFramebuffer, GLenum target, GLsizei numAttachments, const GLenum* attachments) DECLARE_GL_FUNCTION_END_NO_RETURN(void, InvalidateFramebuffer, target, numAttachments, attachments)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, InvalidateSubFramebuffer, GLenum target, GLsizei numAttachments, const GLenum* attachments, GLint x, GLint y, GLsizei width, GLsizei height) DECLARE_GL_FUNCTION_END_NO_RETURN(void, InvalidateSubFramebuffer, target, numAttachments, attachments, x, y, width, height)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, TexStorage2D, GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TexStorage2D, target, levels, internalformat, width, height)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, TexStorage3D, GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TexStorage3D, target, levels, internalformat, width, height, depth)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, GetInternalformativ, GLenum target, GLenum internalformat, GLenum pname, GLsizei bufSize, GLint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetInternalformativ, target, internalformat, pname, bufSize, params)
|
||||
@@ -310,21 +311,21 @@ DECLARE_GL_FUNCTION_HEAD(void, DispatchCompute, GLuint num_groups_x, GLuint num_
|
||||
DECLARE_GL_FUNCTION_HEAD(void, DispatchComputeIndirect, GLintptr indirect) DECLARE_GL_FUNCTION_END_NO_RETURN(void, DispatchComputeIndirect, indirect)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, DrawArraysIndirect, GLenum mode, const void* indirect) DECLARE_GL_FUNCTION_END_NO_RETURN(void, DrawArraysIndirect, mode, indirect)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, DrawElementsIndirect, GLenum mode, GLenum type, const void* indirect) DECLARE_GL_FUNCTION_END_NO_RETURN(void, DrawElementsIndirect, mode, type, indirect)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, FramebufferParameteri, GLenum target, GLenum pname, GLint param) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, FramebufferParameteri, target, pname, param)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetFramebufferParameteriv, GLenum target, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetFramebufferParameteriv, target, pname, params)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, FramebufferParameteri, GLenum target, GLenum pname, GLint param) DECLARE_GL_FUNCTION_END_NO_RETURN(void, FramebufferParameteri, target, pname, param)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, GetFramebufferParameteriv, GLenum target, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetFramebufferParameteriv, target, pname, params)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, GetProgramInterfaceiv, GLuint program, GLenum programInterface, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetProgramInterfaceiv, program, programInterface, pname, params)
|
||||
DECLARE_GL_FUNCTION_HEAD(GLuint, GetProgramResourceIndex, GLuint program, GLenum programInterface, const GLchar* name) DECLARE_GL_FUNCTION_END(GLuint, GetProgramResourceIndex, program, programInterface, name)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, GetProgramResourceName, GLuint program, GLenum programInterface, GLuint index, GLsizei bufSize, GLsizei* length, GLchar* name) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetProgramResourceName, program, programInterface, index, bufSize, length, name)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, GetProgramResourceiv, GLuint program, GLenum programInterface, GLuint index, GLsizei propCount, const GLenum* props, GLsizei bufSize, GLsizei* length, GLint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetProgramResourceiv, program, programInterface, index, propCount, props, bufSize, length, params)
|
||||
DECLARE_GL_FUNCTION_HEAD(GLint, GetProgramResourceLocation, GLuint program, GLenum programInterface, const GLchar* name) DECLARE_GL_FUNCTION_END(GLint, GetProgramResourceLocation, program, programInterface, name)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, UseProgramStages, GLuint pipeline, GLbitfield stages, GLuint program) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, UseProgramStages, pipeline, stages, program)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, ActiveShaderProgram, GLuint pipeline, GLuint program) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ActiveShaderProgram, pipeline, program)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, UseProgramStages, GLuint pipeline, GLbitfield stages, GLuint program) DECLARE_GL_FUNCTION_END_NO_RETURN(void, UseProgramStages, pipeline, stages, program)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, ActiveShaderProgram, GLuint pipeline, GLuint program) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ActiveShaderProgram, pipeline, program)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(GLuint, CreateShaderProgramv, GLenum type, GLsizei count, const GLchar* const* strings) DECLARE_GL_FUNCTION_STUB_END(GLuint, CreateShaderProgramv, type, count, strings)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, BindProgramPipeline, GLuint pipeline) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, BindProgramPipeline, pipeline)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, DeleteProgramPipelines, GLsizei n, const GLuint* pipelines) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, DeleteProgramPipelines, n, pipelines)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GenProgramPipelines, GLsizei n, GLuint* pipelines) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GenProgramPipelines, n, pipelines)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(GLboolean, IsProgramPipeline, GLuint pipeline) DECLARE_GL_FUNCTION_STUB_END(GLboolean, IsProgramPipeline, pipeline)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetProgramPipelineiv, GLuint pipeline, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetProgramPipelineiv, pipeline, pname, params)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, BindProgramPipeline, GLuint pipeline) DECLARE_GL_FUNCTION_END_NO_RETURN(void, BindProgramPipeline, pipeline)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, DeleteProgramPipelines, GLsizei n, const GLuint* pipelines) DECLARE_GL_FUNCTION_END_NO_RETURN(void, DeleteProgramPipelines, n, pipelines)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, GenProgramPipelines, GLsizei n, GLuint* pipelines) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GenProgramPipelines, n, pipelines)
|
||||
DECLARE_GL_FUNCTION_HEAD(GLboolean, IsProgramPipeline, GLuint pipeline) DECLARE_GL_FUNCTION_END(GLboolean, IsProgramPipeline, pipeline)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, GetProgramPipelineiv, GLuint pipeline, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetProgramPipelineiv, pipeline, pname, params)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, ProgramUniform1i, GLuint program, GLint location, GLint v0) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ProgramUniform1i, program, location, v0)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, ProgramUniform2i, GLuint program, GLint location, GLint v0, GLint v1) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ProgramUniform2i, program, location, v0, v1)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, ProgramUniform3i, GLuint program, GLint location, GLint v0, GLint v1, GLint v2) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ProgramUniform3i, program, location, v0, v1, v2)
|
||||
@@ -358,8 +359,8 @@ DECLARE_GL_FUNCTION_HEAD(void, ProgramUniformMatrix2x4fv, GLuint program, GLint
|
||||
DECLARE_GL_FUNCTION_HEAD(void, ProgramUniformMatrix4x2fv, GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ProgramUniformMatrix4x2fv, program, location, count, transpose, value)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, ProgramUniformMatrix3x4fv, GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ProgramUniformMatrix3x4fv, program, location, count, transpose, value)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, ProgramUniformMatrix4x3fv, GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ProgramUniformMatrix4x3fv, program, location, count, transpose, value)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, ValidateProgramPipeline, GLuint pipeline) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ValidateProgramPipeline, pipeline)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetProgramPipelineInfoLog, GLuint pipeline, GLsizei bufSize, GLsizei* length, GLchar* infoLog) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetProgramPipelineInfoLog, pipeline, bufSize, length, infoLog)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, ValidateProgramPipeline, GLuint pipeline) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ValidateProgramPipeline, pipeline)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, GetProgramPipelineInfoLog, GLuint pipeline, GLsizei bufSize, GLsizei* length, GLchar* infoLog) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetProgramPipelineInfoLog, pipeline, bufSize, length, infoLog)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, BindImageTexture, GLuint unit, GLuint texture, GLint level, GLboolean layered, GLint layer, GLenum access, GLenum format) DECLARE_GL_FUNCTION_END_NO_RETURN(void, BindImageTexture, unit, texture, level, layered, layer, access, format)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, GetBooleani_v, GLenum target, GLuint index, GLboolean* data) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetBooleani_v, target, index, data)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, MemoryBarrier, GLbitfield barriers) DECLARE_GL_FUNCTION_END_NO_RETURN(void, MemoryBarrier, barriers)
|
||||
@@ -996,7 +997,7 @@ DECLARE_GL_FUNCTION_HEAD(void, MultiDrawElementsIndirect, GLenum mode, GLenum ty
|
||||
DECLARE_GL_FUNCTION_HEAD(GLint, GetProgramResourceLocationIndex, GLuint program, GLenum programInterface, const GLchar* name) DECLARE_GL_FUNCTION_END(GLint, GetProgramResourceLocationIndex, program, programInterface, name)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, ShaderStorageBlockBinding, GLuint program, GLuint storageBlockIndex, GLuint storageBlockBinding) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ShaderStorageBlockBinding, program, storageBlockIndex, storageBlockBinding)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, TextureView, GLuint texture, GLenum target, GLuint origtexture, GLenum internalformat, GLuint minlevel, GLuint numlevels, GLuint minlayer, GLuint numlayers) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, TextureView, texture, target, origtexture, internalformat, minlevel, numlevels, minlayer, numlayers)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttribLFormat, GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttribLFormat, attribindex, size, type, relativeoffset)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, VertexAttribLFormat, GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttribLFormat, attribindex, size, type, relativeoffset)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, BufferStorage, GLenum target, GLsizeiptr size, const void* data, GLbitfield flags) DECLARE_GL_FUNCTION_END_NO_RETURN(void, BufferStorage, target, size, data, flags)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, ClearTexImage, GLuint texture, GLint level, GLenum format, GLenum type, const void* data) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ClearTexImage, texture, level, format, type, data)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, ClearTexSubImage, GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const void* data) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ClearTexSubImage, texture, level, xoffset, yoffset, zoffset, width, height, depth, format, type, data)
|
||||
@@ -1007,12 +1008,12 @@ DECLARE_GL_FUNCTION_HEAD(void, BindSamplers, GLuint first, GLsizei count, const
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, BindImageTextures, GLuint first, GLsizei count, const GLuint* textures) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, BindImageTextures, first, count, textures)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, BindVertexBuffers, GLuint first, GLsizei count, const GLuint* buffers, const GLintptr* offsets, const GLsizei* strides) DECLARE_GL_FUNCTION_END_NO_RETURN(void, BindVertexBuffers, first, count, buffers, offsets, strides)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, ClipControl, GLenum origin, GLenum depth) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ClipControl, origin, depth)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, CreateTransformFeedbacks, GLsizei n, GLuint* ids) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, CreateTransformFeedbacks, n, ids)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, TransformFeedbackBufferBase, GLuint xfb, GLuint index, GLuint buffer) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, TransformFeedbackBufferBase, xfb, index, buffer)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, TransformFeedbackBufferRange, GLuint xfb, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, TransformFeedbackBufferRange, xfb, index, buffer, offset, size)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetTransformFeedbackiv, GLuint xfb, GLenum pname, GLint* param) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetTransformFeedbackiv, xfb, pname, param)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetTransformFeedbacki_v, GLuint xfb, GLenum pname, GLuint index, GLint* param) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetTransformFeedbacki_v, xfb, pname, index, param)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetTransformFeedbacki64_v, GLuint xfb, GLenum pname, GLuint index, GLint64* param) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetTransformFeedbacki64_v, xfb, pname, index, param)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, CreateTransformFeedbacks, GLsizei n, GLuint* ids) DECLARE_GL_FUNCTION_END_NO_RETURN(void, CreateTransformFeedbacks, n, ids)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, TransformFeedbackBufferBase, GLuint xfb, GLuint index, GLuint buffer) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TransformFeedbackBufferBase, xfb, index, buffer)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, TransformFeedbackBufferRange, GLuint xfb, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TransformFeedbackBufferRange, xfb, index, buffer, offset, size)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, GetTransformFeedbackiv, GLuint xfb, GLenum pname, GLint* param) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetTransformFeedbackiv, xfb, pname, param)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, GetTransformFeedbacki_v, GLuint xfb, GLenum pname, GLuint index, GLint* param) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetTransformFeedbacki_v, xfb, pname, index, param)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, GetTransformFeedbacki64_v, GLuint xfb, GLenum pname, GLuint index, GLint64* param) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetTransformFeedbacki64_v, xfb, pname, index, param)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, CreateBuffers, GLsizei n, GLuint* buffers) DECLARE_GL_FUNCTION_END_NO_RETURN(void, CreateBuffers, n, buffers)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, NamedBufferStorage, GLuint buffer, GLsizeiptr size, const void* data, GLbitfield flags) DECLARE_GL_FUNCTION_END_NO_RETURN(void, NamedBufferStorage, buffer, size, data, flags)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, NamedBufferData, GLuint buffer, GLsizeiptr size, const void* data, GLenum usage) DECLARE_GL_FUNCTION_END_NO_RETURN(void, NamedBufferData, buffer, size, data, usage)
|
||||
@@ -1025,24 +1026,24 @@ DECLARE_GL_FUNCTION_HEAD(void, FlushMappedNamedBufferRange, GLuint buffer, GLint
|
||||
DECLARE_GL_FUNCTION_HEAD(void, GetNamedBufferParameteriv, GLuint buffer, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetNamedBufferParameteriv, buffer, pname, params)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, GetNamedBufferParameteri64v, GLuint buffer, GLenum pname, GLint64* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetNamedBufferParameteri64v, buffer, pname, params)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, GetNamedBufferPointerv, GLuint buffer, GLenum pname, void** params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetNamedBufferPointerv, buffer, pname, params)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetNamedBufferSubData, GLuint buffer, GLintptr offset, GLsizeiptr size, void* data) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetNamedBufferSubData, buffer, offset, size, data)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, GetNamedBufferSubData, GLuint buffer, GLintptr offset, GLsizeiptr size, void* data) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetNamedBufferSubData, buffer, offset, size, data)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, CreateFramebuffers, GLsizei n, GLuint* framebuffers) DECLARE_GL_FUNCTION_END_NO_RETURN(void, CreateFramebuffers, n, framebuffers)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, NamedFramebufferRenderbuffer, GLuint framebuffer, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer) DECLARE_GL_FUNCTION_END_NO_RETURN(void, NamedFramebufferRenderbuffer, framebuffer, attachment, renderbuffertarget, renderbuffer)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, NamedFramebufferParameteri, GLuint framebuffer, GLenum pname, GLint param) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, NamedFramebufferParameteri, framebuffer, pname, param)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, NamedFramebufferParameteri, GLuint framebuffer, GLenum pname, GLint param) DECLARE_GL_FUNCTION_END_NO_RETURN(void, NamedFramebufferParameteri, framebuffer, pname, param)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, NamedFramebufferTexture, GLuint framebuffer, GLenum attachment, GLuint texture, GLint level) DECLARE_GL_FUNCTION_END_NO_RETURN(void, NamedFramebufferTexture, framebuffer, attachment, texture, level)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, NamedFramebufferTextureLayer, GLuint framebuffer, GLenum attachment, GLuint texture, GLint level, GLint layer) DECLARE_GL_FUNCTION_END_NO_RETURN(void, NamedFramebufferTextureLayer, framebuffer, attachment, texture, level, layer)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, NamedFramebufferDrawBuffer, GLuint framebuffer, GLenum buf) DECLARE_GL_FUNCTION_END_NO_RETURN(void, NamedFramebufferDrawBuffer, framebuffer, buf)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, NamedFramebufferDrawBuffers, GLuint framebuffer, GLsizei n, const GLenum* bufs) DECLARE_GL_FUNCTION_END_NO_RETURN(void, NamedFramebufferDrawBuffers, framebuffer, n, bufs)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, NamedFramebufferReadBuffer, GLuint framebuffer, GLenum src) DECLARE_GL_FUNCTION_END_NO_RETURN(void, NamedFramebufferReadBuffer, framebuffer, src)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, InvalidateNamedFramebufferData, GLuint framebuffer, GLsizei numAttachments, const GLenum* attachments) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, InvalidateNamedFramebufferData, framebuffer, numAttachments, attachments)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, InvalidateNamedFramebufferSubData, GLuint framebuffer, GLsizei numAttachments, const GLenum* attachments, GLint x, GLint y, GLsizei width, GLsizei height) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, InvalidateNamedFramebufferSubData, framebuffer, numAttachments, attachments, x, y, width, height)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, ClearNamedFramebufferiv, GLuint framebuffer, GLenum buffer, GLint drawbuffer, const GLint* value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ClearNamedFramebufferiv, framebuffer, buffer, drawbuffer, value)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, ClearNamedFramebufferuiv, GLuint framebuffer, GLenum buffer, GLint drawbuffer, const GLuint* value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ClearNamedFramebufferuiv, framebuffer, buffer, drawbuffer, value)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, InvalidateNamedFramebufferData, GLuint framebuffer, GLsizei numAttachments, const GLenum* attachments) DECLARE_GL_FUNCTION_END_NO_RETURN(void, InvalidateNamedFramebufferData, framebuffer, numAttachments, attachments)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, InvalidateNamedFramebufferSubData, GLuint framebuffer, GLsizei numAttachments, const GLenum* attachments, GLint x, GLint y, GLsizei width, GLsizei height) DECLARE_GL_FUNCTION_END_NO_RETURN(void, InvalidateNamedFramebufferSubData, framebuffer, numAttachments, attachments, x, y, width, height)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, ClearNamedFramebufferiv, GLuint framebuffer, GLenum buffer, GLint drawbuffer, const GLint* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ClearNamedFramebufferiv, framebuffer, buffer, drawbuffer, value)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, ClearNamedFramebufferuiv, GLuint framebuffer, GLenum buffer, GLint drawbuffer, const GLuint* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ClearNamedFramebufferuiv, framebuffer, buffer, drawbuffer, value)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, ClearNamedFramebufferfv, GLuint framebuffer, GLenum buffer, GLint drawbuffer, const GLfloat* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ClearNamedFramebufferfv, framebuffer, buffer, drawbuffer, value)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, ClearNamedFramebufferfi, GLuint framebuffer, GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ClearNamedFramebufferfi, framebuffer, buffer, drawbuffer, depth, stencil)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, BlitNamedFramebuffer, GLuint readFramebuffer, GLuint drawFramebuffer, GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter) DECLARE_GL_FUNCTION_END_NO_RETURN(void, BlitNamedFramebuffer, readFramebuffer, drawFramebuffer, srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter)
|
||||
DECLARE_GL_FUNCTION_HEAD(GLenum, CheckNamedFramebufferStatus, GLuint framebuffer, GLenum target) DECLARE_GL_FUNCTION_END(GLenum, CheckNamedFramebufferStatus, framebuffer, target)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetNamedFramebufferParameteriv, GLuint framebuffer, GLenum pname, GLint* param) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetNamedFramebufferParameteriv, framebuffer, pname, param)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, GetNamedFramebufferParameteriv, GLuint framebuffer, GLenum pname, GLint* param) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetNamedFramebufferParameteriv, framebuffer, pname, param)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, GetNamedFramebufferAttachmentParameteriv, GLuint framebuffer, GLenum attachment, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetNamedFramebufferAttachmentParameteriv, framebuffer, attachment, pname, params)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, CreateRenderbuffers, GLsizei n, GLuint* renderbuffers) DECLARE_GL_FUNCTION_END_NO_RETURN(void, CreateRenderbuffers, n, renderbuffers)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, NamedRenderbufferStorage, GLuint renderbuffer, GLenum internalformat, GLsizei width, GLsizei height) DECLARE_GL_FUNCTION_END_NO_RETURN(void, NamedRenderbufferStorage, renderbuffer, internalformat, width, height)
|
||||
@@ -1062,9 +1063,9 @@ DECLARE_GL_FUNCTION_HEAD(void, TextureSubImage3D, GLuint texture, GLint level, G
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, CompressedTextureSubImage1D, GLuint texture, GLint level, GLint xoffset, GLsizei width, GLenum format, GLsizei imageSize, const void* data) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, CompressedTextureSubImage1D, texture, level, xoffset, width, format, imageSize, data)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, CompressedTextureSubImage2D, GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const void* data) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, CompressedTextureSubImage2D, texture, level, xoffset, yoffset, width, height, format, imageSize, data)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, CompressedTextureSubImage3D, GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const void* data) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, CompressedTextureSubImage3D, texture, level, xoffset, yoffset, zoffset, width, height, depth, format, imageSize, data)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, CopyTextureSubImage1D, GLuint texture, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, CopyTextureSubImage1D, texture, level, xoffset, x, y, width)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, CopyTextureSubImage1D, GLuint texture, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width) DECLARE_GL_FUNCTION_END_NO_RETURN(void, CopyTextureSubImage1D, texture, level, xoffset, x, y, width)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, CopyTextureSubImage2D, GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height) DECLARE_GL_FUNCTION_END_NO_RETURN(void, CopyTextureSubImage2D, texture, level, xoffset, yoffset, x, y, width, height)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, CopyTextureSubImage3D, GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, CopyTextureSubImage3D, texture, level, xoffset, yoffset, zoffset, x, y, width, height)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, CopyTextureSubImage3D, GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height) DECLARE_GL_FUNCTION_END_NO_RETURN(void, CopyTextureSubImage3D, texture, level, xoffset, yoffset, zoffset, x, y, width, height)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, TextureParameterf, GLuint texture, GLenum pname, GLfloat param) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TextureParameterf, texture, pname, param)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, TextureParameterfv, GLuint texture, GLenum pname, const GLfloat* param) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TextureParameterfv, texture, pname, param)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, TextureParameteri, GLuint texture, GLenum pname, GLint param) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TextureParameteri, texture, pname, param)
|
||||
@@ -1074,7 +1075,7 @@ DECLARE_GL_FUNCTION_HEAD(void, TextureParameteriv, GLuint texture, GLenum pname,
|
||||
DECLARE_GL_FUNCTION_HEAD(void, GenerateTextureMipmap, GLuint texture) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GenerateTextureMipmap, texture)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, BindTextureUnit, GLuint unit, GLuint texture) DECLARE_GL_FUNCTION_END_NO_RETURN(void, BindTextureUnit, unit, texture)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, GetTextureImage, GLuint texture, GLint level, GLenum format, GLenum type, GLsizei bufSize, void* pixels) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetTextureImage, texture, level, format, type, bufSize, pixels)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetCompressedTextureImage, GLuint texture, GLint level, GLsizei bufSize, void* pixels) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetCompressedTextureImage, texture, level, bufSize, pixels)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, GetCompressedTextureImage, GLuint texture, GLint level, GLsizei bufSize, void* pixels) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetCompressedTextureImage, texture, level, bufSize, pixels)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, GetTextureLevelParameterfv, GLuint texture, GLint level, GLenum pname, GLfloat* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetTextureLevelParameterfv, texture, level, pname, params)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, GetTextureLevelParameteriv, GLuint texture, GLint level, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetTextureLevelParameteriv, texture, level, pname, params)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, GetTextureParameterfv, GLuint texture, GLenum pname, GLfloat* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetTextureParameterfv, texture, pname, params)
|
||||
@@ -1090,18 +1091,18 @@ DECLARE_GL_FUNCTION_HEAD(void, VertexArrayVertexBuffers, GLuint vaobj, GLuint fi
|
||||
DECLARE_GL_FUNCTION_HEAD(void, VertexArrayAttribBinding, GLuint vaobj, GLuint attribindex, GLuint bindingindex) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexArrayAttribBinding, vaobj, attribindex, bindingindex)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, VertexArrayAttribFormat, GLuint vaobj, GLuint attribindex, GLint size, GLenum type, GLboolean normalized, GLuint relativeoffset) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexArrayAttribFormat, vaobj, attribindex, size, type, normalized, relativeoffset)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, VertexArrayAttribIFormat, GLuint vaobj, GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexArrayAttribIFormat, vaobj, attribindex, size, type, relativeoffset)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexArrayAttribLFormat, GLuint vaobj, GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexArrayAttribLFormat, vaobj, attribindex, size, type, relativeoffset)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, VertexArrayAttribLFormat, GLuint vaobj, GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexArrayAttribLFormat, vaobj, attribindex, size, type, relativeoffset)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, VertexArrayBindingDivisor, GLuint vaobj, GLuint bindingindex, GLuint divisor) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexArrayBindingDivisor, vaobj, bindingindex, divisor)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, GetVertexArrayiv, GLuint vaobj, GLenum pname, GLint* param) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetVertexArrayiv, vaobj, pname, param)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, GetVertexArrayIndexediv, GLuint vaobj, GLuint index, GLenum pname, GLint* param) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetVertexArrayIndexediv, vaobj, index, pname, param)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, GetVertexArrayIndexed64iv, GLuint vaobj, GLuint index, GLenum pname, GLint64* param) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetVertexArrayIndexed64iv, vaobj, index, pname, param)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, CreateSamplers, GLsizei n, GLuint* samplers) DECLARE_GL_FUNCTION_END_NO_RETURN(void, CreateSamplers, n, samplers)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, CreateProgramPipelines, GLsizei n, GLuint* pipelines) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, CreateProgramPipelines, n, pipelines)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, CreateProgramPipelines, GLsizei n, GLuint* pipelines) DECLARE_GL_FUNCTION_END_NO_RETURN(void, CreateProgramPipelines, n, pipelines)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, CreateQueries, GLenum target, GLsizei n, GLuint* ids) DECLARE_GL_FUNCTION_END_NO_RETURN(void, CreateQueries, target, n, ids)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetQueryBufferObjecti64v, GLuint id, GLuint buffer, GLenum pname, GLintptr offset) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetQueryBufferObjecti64v, id, buffer, pname, offset)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetQueryBufferObjectiv, GLuint id, GLuint buffer, GLenum pname, GLintptr offset) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetQueryBufferObjectiv, id, buffer, pname, offset)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetQueryBufferObjectui64v, GLuint id, GLuint buffer, GLenum pname, GLintptr offset) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetQueryBufferObjectui64v, id, buffer, pname, offset)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetQueryBufferObjectuiv, GLuint id, GLuint buffer, GLenum pname, GLintptr offset) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetQueryBufferObjectuiv, id, buffer, pname, offset)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, GetQueryBufferObjecti64v, GLuint id, GLuint buffer, GLenum pname, GLintptr offset) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetQueryBufferObjecti64v, id, buffer, pname, offset)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, GetQueryBufferObjectiv, GLuint id, GLuint buffer, GLenum pname, GLintptr offset) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetQueryBufferObjectiv, id, buffer, pname, offset)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, GetQueryBufferObjectui64v, GLuint id, GLuint buffer, GLenum pname, GLintptr offset) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetQueryBufferObjectui64v, id, buffer, pname, offset)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, GetQueryBufferObjectuiv, GLuint id, GLuint buffer, GLenum pname, GLintptr offset) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetQueryBufferObjectuiv, id, buffer, pname, offset)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, GetTextureSubImage, GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, GLsizei bufSize, void* pixels) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetTextureSubImage, texture, level, xoffset, yoffset, zoffset, width, height, depth, format, type, bufSize, pixels)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetCompressedTextureSubImage, GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLsizei bufSize, void* pixels) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetCompressedTextureSubImage, texture, level, xoffset, yoffset, zoffset, width, height, depth, bufSize, pixels)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetnCompressedTexImage, GLenum target, GLint lod, GLsizei bufSize, void* pixels) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetnCompressedTexImage, target, lod, bufSize, pixels)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -58,7 +58,19 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
void NamedFramebufferReadBuffer(GLuint framebuffer, GLenum src);
|
||||
void ClearNamedFramebufferfv(GLuint framebuffer, GLenum buffer, GLint drawbuffer, const GLfloat* value);
|
||||
void ClearNamedFramebufferfi(GLuint framebuffer, GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil);
|
||||
void InvalidateNamedFramebufferData(GLuint framebuffer, GLsizei numAttachments, const GLenum* attachments);
|
||||
void InvalidateNamedFramebufferSubData(GLuint framebuffer, GLsizei numAttachments, const GLenum* attachments,
|
||||
GLint x, GLint y, GLsizei width, GLsizei height);
|
||||
void InvalidateFramebuffer(GLenum target, GLsizei numAttachments, const GLenum* attachments);
|
||||
void InvalidateSubFramebuffer(GLenum target, GLsizei numAttachments, const GLenum* attachments, GLint x, GLint y,
|
||||
GLsizei width, GLsizei height);
|
||||
void ClearNamedFramebufferiv(GLuint framebuffer, GLenum buffer, GLint drawbuffer, const GLint* value);
|
||||
void ClearNamedFramebufferuiv(GLuint framebuffer, GLenum buffer, GLint drawbuffer, const GLuint* value);
|
||||
GLenum CheckNamedFramebufferStatus(GLuint framebuffer, GLenum target);
|
||||
void GetFramebufferParameteriv(GLenum target, GLenum pname, GLint* params);
|
||||
void FramebufferParameteri(GLenum target, GLenum pname, GLint param);
|
||||
void GetNamedFramebufferParameteriv(GLuint framebuffer, GLenum pname, GLint* params);
|
||||
void NamedFramebufferParameteri(GLuint framebuffer, GLenum pname, GLint param);
|
||||
void GetNamedFramebufferAttachmentParameteriv(GLuint framebuffer, GLenum attachment, GLenum pname, GLint* params);
|
||||
void BlitNamedFramebuffer(GLuint readFramebuffer, GLuint drawFramebuffer, GLint srcX0, GLint srcY0, GLint srcX1,
|
||||
GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask,
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
// End of Source File Header
|
||||
|
||||
#include "Validators.h"
|
||||
#include <MG_Backend/BackendObjects.h>
|
||||
#include <MG_State/GLState/Core.h>
|
||||
#include <MG_State/GLState/ErrorState/Error.h>
|
||||
#include <MG_Util/Converters/GLToStr/GLEnumConverter.h>
|
||||
@@ -60,6 +61,26 @@ namespace MobileGL::MG_Impl::GLImpl::FramebufferImpl {
|
||||
return true;
|
||||
}
|
||||
|
||||
Bool ValidateColorAttachmentInRange(FramebufferAttachmentType attachment, const char* caller) {
|
||||
const auto first = static_cast<SizeT>(FramebufferAttachmentType::Color0);
|
||||
const auto index = static_cast<SizeT>(attachment);
|
||||
if (index < first) return true;
|
||||
const auto colorIndex = index - first;
|
||||
const auto limit = static_cast<SizeT>(
|
||||
MG_Backend::pActiveBackendObject ? MG_Backend::pActiveBackendObject->GetDynamicParameters()
|
||||
.MaxColorAttachments
|
||||
: static_cast<Int>(MG_State::GLState::FramebufferObject::MAX_DRAW_BUFFERS));
|
||||
if (colorIndex >= limit) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidOperation,
|
||||
MakeUnique<GenericErrorInfo>(
|
||||
"MG_Impl/GLImpl/FramebufferImpl", caller,
|
||||
std::format("Colour attachment {} is beyond GL_MAX_COLOR_ATTACHMENTS ({}).", colorIndex, limit)));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
Bool ValidateRenderbufferTarget(RenderbufferTarget target) {
|
||||
if (target == RenderbufferTarget::Unknown) {
|
||||
using namespace MG_Util;
|
||||
@@ -97,4 +118,100 @@ namespace MobileGL::MG_Impl::GLImpl::FramebufferImpl {
|
||||
std::format("Renderbuffer name {} is not valid.", index)));
|
||||
return false;
|
||||
}
|
||||
|
||||
Bool ValidateFramebufferParameterPname(GLenum pname, Bool isDefaultFramebuffer, Bool forSetter,
|
||||
const char* caller) {
|
||||
Bool isDefaultParameter = false;
|
||||
switch (pname) {
|
||||
case GL_FRAMEBUFFER_DEFAULT_WIDTH:
|
||||
case GL_FRAMEBUFFER_DEFAULT_HEIGHT:
|
||||
case GL_FRAMEBUFFER_DEFAULT_LAYERS:
|
||||
case GL_FRAMEBUFFER_DEFAULT_SAMPLES:
|
||||
case GL_FRAMEBUFFER_DEFAULT_FIXED_SAMPLE_LOCATIONS:
|
||||
isDefaultParameter = true;
|
||||
break;
|
||||
case GL_DOUBLEBUFFER:
|
||||
case GL_IMPLEMENTATION_COLOR_READ_FORMAT:
|
||||
case GL_IMPLEMENTATION_COLOR_READ_TYPE:
|
||||
case GL_SAMPLES:
|
||||
case GL_SAMPLE_BUFFERS:
|
||||
case GL_STEREO:
|
||||
// Queryable only; glFramebufferParameteri sets none of these.
|
||||
if (forSetter) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidEnum,
|
||||
MakeUnique<GenericErrorInfo>(
|
||||
"MG_Impl/GLImpl/FramebufferImpl", caller,
|
||||
std::format("pname {} is not settable on a framebuffer.",
|
||||
MG_Util::ConvertGLEnumToString(pname))));
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidEnum,
|
||||
MakeUnique<GenericErrorInfo>(
|
||||
"MG_Impl/GLImpl/FramebufferImpl", caller,
|
||||
std::format("pname {} is not a framebuffer parameter.",
|
||||
MG_Util::ConvertGLEnumToString(pname))));
|
||||
return false;
|
||||
}
|
||||
|
||||
// The default framebuffer has no DEFAULT_* state of its own - its shape comes from the
|
||||
// surface - so those names are accepted enums it simply cannot answer or accept.
|
||||
if (isDefaultFramebuffer && isDefaultParameter) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidOperation,
|
||||
MakeUnique<GenericErrorInfo>(
|
||||
"MG_Impl/GLImpl/FramebufferImpl", caller,
|
||||
std::format("pname {} does not apply to the default framebuffer.",
|
||||
MG_Util::ConvertGLEnumToString(pname))));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
Bool ValidateReadFramebufferForCopy(const char* caller) {
|
||||
auto& framebufferObject =
|
||||
MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Read).GetBoundObject();
|
||||
if (!framebufferObject || !framebufferObject->CheckCompleteness()) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidFramebufferOperation,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl/FramebufferImpl", caller,
|
||||
"Read framebuffer is not framebuffer complete."));
|
||||
return false;
|
||||
}
|
||||
|
||||
const FramebufferAttachmentType readBuffer = framebufferObject->GetReadBuffer();
|
||||
if (readBuffer == FramebufferAttachmentType::None ||
|
||||
!framebufferObject->GetAttachment(readBuffer).IsValid()) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidOperation,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl/FramebufferImpl", caller,
|
||||
"Read buffer names no attachment of the read framebuffer."));
|
||||
return false;
|
||||
}
|
||||
|
||||
// SAMPLE_BUFFERS is one whenever the read buffer resolves to multisample storage. A
|
||||
// multisample texture says so by its target - its sample count can legally be one - while a
|
||||
// renderbuffer says so by having been given a non-zero sample count.
|
||||
const auto& readAttachment = framebufferObject->GetAttachment(readBuffer);
|
||||
Bool isMultisampled = false;
|
||||
if (readAttachment.IsRenderbuffer() && readAttachment.GetRenderbuffer()) {
|
||||
isMultisampled = readAttachment.GetRenderbuffer()->GetSamples() > 0;
|
||||
} else if (readAttachment.IsTexture() && readAttachment.GetTexture()) {
|
||||
const auto target = readAttachment.GetTexture()->GetTarget();
|
||||
isMultisampled = target == TextureTarget::Texture2DMultisample ||
|
||||
target == TextureTarget::Texture2DMultisampleArray;
|
||||
}
|
||||
if (isMultisampled) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidOperation,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl/FramebufferImpl", caller,
|
||||
"Cannot copy from a multisampled read framebuffer."));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
} // namespace MobileGL::MG_Impl::GLImpl::FramebufferImpl
|
||||
|
||||
@@ -14,6 +14,21 @@ namespace MobileGL::MG_Impl::GLImpl::FramebufferImpl {
|
||||
Bool ValidateFramebufferTarget(FramebufferTarget target);
|
||||
Bool ValidateFramebufferName(Uint index, Bool allowZero = true);
|
||||
Bool ValidateFramebufferAttachmentType(FramebufferAttachmentType attachment);
|
||||
// GL_COLOR_ATTACHMENTn is a token per n up to 31, but only the first GL_MAX_COLOR_ATTACHMENTS of
|
||||
// them name an attachment point of a framebuffer object; the rest are INVALID_OPERATION for the
|
||||
// attaching entry points (GL 4.6 core 9.2.7). Non-colour attachments pass through unchanged.
|
||||
Bool ValidateColorAttachmentInRange(FramebufferAttachmentType attachment, const char* caller);
|
||||
Bool ValidateRenderbufferTarget(RenderbufferTarget target);
|
||||
Bool ValidateRenderbufferName(Uint index, Bool allowZero = true);
|
||||
// The read-framebuffer preconditions the CopyTexSubImage family shares (GL 4.6 core 8.6): the
|
||||
// read framebuffer must be complete, its read buffer must name a real attachment, and it must
|
||||
// not be multisampled. Incompleteness is INVALID_FRAMEBUFFER_OPERATION, the other two are
|
||||
// INVALID_OPERATION.
|
||||
Bool ValidateReadFramebufferForCopy(const char* caller);
|
||||
// The pname sets of glGet/FramebufferParameteri (GL 4.6 core 9.2.3). Order matters and is part
|
||||
// of the contract: a name outside the table is INVALID_ENUM, and only then is a name that the
|
||||
// DEFAULT framebuffer does not answer INVALID_OPERATION. Testing the framebuffer kind first
|
||||
// would turn GL_FRAMEBUFFER_DEFAULT_WIDTH on framebuffer zero into the wrong error.
|
||||
Bool ValidateFramebufferParameterPname(GLenum pname, Bool isDefaultFramebuffer, Bool forSetter,
|
||||
const char* caller);
|
||||
} // namespace MobileGL::MG_Impl::GLImpl::FramebufferImpl
|
||||
|
||||
@@ -671,6 +671,40 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
}
|
||||
|
||||
switch (target) {
|
||||
// The vertex buffer binding points of the vertex array object that is bound. Indexed by
|
||||
// binding point, not by attribute (GL 4.6 core 10.3.1).
|
||||
case GL_VERTEX_BINDING_BUFFER:
|
||||
case GL_VERTEX_BINDING_DIVISOR:
|
||||
case GL_VERTEX_BINDING_OFFSET:
|
||||
case GL_VERTEX_BINDING_STRIDE: {
|
||||
if (index >= VertexArrayImpl::GetMaxVertexAttribBindings()) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidValue,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
||||
"Vertex buffer binding index is out of range."));
|
||||
return;
|
||||
}
|
||||
const auto& vao = MG_State::pGLContext->GetBoundVertexArray();
|
||||
if (!vao) {
|
||||
*data = 0;
|
||||
return;
|
||||
}
|
||||
const auto& binding = vao->GetBindingPoint(index);
|
||||
switch (target) {
|
||||
case GL_VERTEX_BINDING_BUFFER:
|
||||
*data = binding.Buffer ? static_cast<GLint>(binding.Buffer->GetExternalIndex()) : 0;
|
||||
return;
|
||||
case GL_VERTEX_BINDING_DIVISOR:
|
||||
*data = static_cast<GLint>(binding.Divisor);
|
||||
return;
|
||||
case GL_VERTEX_BINDING_OFFSET:
|
||||
*data = static_cast<GLint>(binding.Offset);
|
||||
return;
|
||||
default:
|
||||
*data = static_cast<GLint>(binding.Stride);
|
||||
return;
|
||||
}
|
||||
}
|
||||
case GL_IMAGE_BINDING_NAME:
|
||||
case GL_IMAGE_BINDING_LEVEL:
|
||||
case GL_IMAGE_BINDING_LAYERED:
|
||||
@@ -1398,7 +1432,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
*params = 0; // program-binary entrypoints are stubbed
|
||||
return;
|
||||
case GL_PROGRAM_PIPELINE_BINDING:
|
||||
*params = 0; // program-pipeline entrypoints are stubbed
|
||||
*params = static_cast<GLint>(MG_State::pGLContext->GetBoundProgramPipelineName());
|
||||
return;
|
||||
case GL_PROGRAM_POINT_SIZE:
|
||||
*params = MG_State::pGLContext->IsCapabilityEnabled(CapabilityInput::ProgramPointSize) ? GL_TRUE : GL_FALSE;
|
||||
@@ -1738,20 +1772,22 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
*params = vao ? static_cast<GLint>(vao->GetExternalIndex()) : 0;
|
||||
return;
|
||||
}
|
||||
// The vertex buffer binding points are per-binding-index state, so the non-indexed getter
|
||||
// has nothing to answer with (GL 4.6 core table 23.4).
|
||||
case GL_VERTEX_BINDING_BUFFER:
|
||||
case GL_VERTEX_BINDING_DIVISOR:
|
||||
*params = 0; // vertex-binding entrypoints are stubbed
|
||||
return;
|
||||
case GL_VERTEX_BINDING_OFFSET:
|
||||
*params = 0; // vertex-binding entrypoints are stubbed
|
||||
return;
|
||||
case GL_VERTEX_BINDING_STRIDE:
|
||||
*params = 0; // vertex-binding entrypoints are stubbed
|
||||
RecordIndexedOnlyGetterError(__func__, pname);
|
||||
return;
|
||||
case GL_MAX_VERTEX_ATTRIB_RELATIVE_OFFSET:
|
||||
*params = 0; // vertex-binding entrypoints are stubbed
|
||||
*params = static_cast<GLint>(VertexArrayImpl::GetMaxVertexAttribRelativeOffset());
|
||||
return;
|
||||
case GL_MAX_VERTEX_ATTRIB_BINDINGS:
|
||||
*params = 0; // vertex-binding entrypoints are stubbed
|
||||
*params = static_cast<GLint>(VertexArrayImpl::GetMaxVertexAttribBindings());
|
||||
return;
|
||||
case GL_MAX_VERTEX_ATTRIB_STRIDE:
|
||||
*params = static_cast<GLint>(VertexArrayImpl::GetMaxVertexAttribStride());
|
||||
return;
|
||||
case GL_VIEWPORT: {
|
||||
const auto& vp = MG_State::pGLContext->GetViewport();
|
||||
|
||||
@@ -0,0 +1,231 @@
|
||||
// MobileGL - MobileGL/MG_Impl/GLImpl/Program/GL_ProgramPipeline.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 "GL_ProgramPipeline.h"
|
||||
|
||||
#include <MG_State/GLState/Core.h>
|
||||
#include <MG_State/GLState/ErrorState/ErrorInfo.h>
|
||||
#include <MG_Util/Converters/GLToStr/GLEnumConverter.h>
|
||||
|
||||
namespace MobileGL::MG_Impl::GLImpl {
|
||||
namespace {
|
||||
void RecordPipelineError(ErrorCode code, const char* function, String message) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
code, MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", function, Move(message)));
|
||||
}
|
||||
|
||||
// A pipeline name only names an object once it has been bound or created; querying a
|
||||
// reserved-but-unmaterialised name is INVALID_OPERATION (GL 4.6 core 7.4).
|
||||
const SharedPtr<MG_State::GLState::ProgramPipelineObject>* TryGetPipeline(GLuint pipeline,
|
||||
const char* function) {
|
||||
if (!MG_State::pGLContext->IsProgramPipelineObject(pipeline)) {
|
||||
RecordPipelineError(ErrorCode::InvalidOperation, function,
|
||||
std::format("Program pipeline {} does not exist.", pipeline));
|
||||
return nullptr;
|
||||
}
|
||||
return &MG_State::pGLContext->GetProgramPipelineObject(pipeline);
|
||||
}
|
||||
|
||||
Bool ValidatePipelineCount(GLsizei n, const char* function) {
|
||||
if (n < 0) {
|
||||
RecordPipelineError(ErrorCode::InvalidValue, function, "n must be non-negative.");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// GL 4.6 core table 7.1 maps each stage bit onto a shader stage.
|
||||
Bool TryResolveStageBit(GLbitfield bit, ShaderStage& outStage) {
|
||||
switch (bit) {
|
||||
case GL_VERTEX_SHADER_BIT: outStage = ShaderStage::Vertex; return true;
|
||||
case GL_TESS_CONTROL_SHADER_BIT: outStage = ShaderStage::TessControl; return true;
|
||||
case GL_TESS_EVALUATION_SHADER_BIT: outStage = ShaderStage::TessEval; return true;
|
||||
case GL_GEOMETRY_SHADER_BIT: outStage = ShaderStage::Geometry; return true;
|
||||
case GL_FRAGMENT_SHADER_BIT: outStage = ShaderStage::Fragment; return true;
|
||||
case GL_COMPUTE_SHADER_BIT: outStage = ShaderStage::Compute; return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
constexpr GLbitfield kAllStageBits = GL_VERTEX_SHADER_BIT | GL_TESS_CONTROL_SHADER_BIT |
|
||||
GL_TESS_EVALUATION_SHADER_BIT | GL_GEOMETRY_SHADER_BIT |
|
||||
GL_FRAGMENT_SHADER_BIT | GL_COMPUTE_SHADER_BIT;
|
||||
} // namespace
|
||||
|
||||
void GenProgramPipelines(GLsizei n, GLuint* pipelines) {
|
||||
if (!ValidatePipelineCount(n, __func__)) return;
|
||||
if (n == 0 || !pipelines) return;
|
||||
|
||||
static thread_local Vector<GLuint> names;
|
||||
MG_State::pGLContext->GenProgramPipelineNames(static_cast<Uint>(n), names);
|
||||
Memcpy(pipelines, names.data(), static_cast<SizeT>(n) * sizeof(GLuint));
|
||||
}
|
||||
|
||||
void CreateProgramPipelines(GLsizei n, GLuint* pipelines) {
|
||||
if (!ValidatePipelineCount(n, __func__)) return;
|
||||
if (n == 0 || !pipelines) return;
|
||||
|
||||
static thread_local Vector<GLuint> names;
|
||||
MG_State::pGLContext->GenProgramPipelineNames(static_cast<Uint>(n), names);
|
||||
for (GLsizei i = 0; i < n; ++i) {
|
||||
pipelines[i] = names[static_cast<SizeT>(i)];
|
||||
MG_State::pGLContext->CreateProgramPipelineObject(names[static_cast<SizeT>(i)]);
|
||||
}
|
||||
}
|
||||
|
||||
void DeleteProgramPipelines(GLsizei n, const GLuint* pipelines) {
|
||||
if (!ValidatePipelineCount(n, __func__)) return;
|
||||
if (!pipelines) return;
|
||||
|
||||
for (GLsizei i = 0; i < n; ++i) {
|
||||
// Deleting zero, an unknown name, or a name that was only reserved is silently ignored.
|
||||
MG_State::pGLContext->MarkProgramPipelineForDeletion(pipelines[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void BindProgramPipeline(GLuint pipeline) {
|
||||
if (pipeline != 0 && !MG_State::pGLContext->ValidateProgramPipelineName(pipeline)) {
|
||||
RecordPipelineError(ErrorCode::InvalidOperation, __func__,
|
||||
std::format("Program pipeline name {} is not valid.", pipeline));
|
||||
return;
|
||||
}
|
||||
MG_State::pGLContext->BindProgramPipelineObject(pipeline);
|
||||
}
|
||||
|
||||
GLboolean IsProgramPipeline(GLuint pipeline) {
|
||||
return MG_State::pGLContext->IsProgramPipelineObject(pipeline) ? GL_TRUE : GL_FALSE;
|
||||
}
|
||||
|
||||
void GetProgramPipelineiv(GLuint pipeline, GLenum pname, GLint* params) {
|
||||
const auto* pipelineObject = TryGetPipeline(pipeline, __func__);
|
||||
if (!pipelineObject || !params) return;
|
||||
|
||||
const auto stageProgramName = [&](ShaderStage stage) -> GLint {
|
||||
const auto& program = (*pipelineObject)->GetStageProgram(stage);
|
||||
return program ? static_cast<GLint>(program->GetExternalIndex()) : 0;
|
||||
};
|
||||
|
||||
switch (pname) {
|
||||
case GL_ACTIVE_PROGRAM: {
|
||||
const auto& active = (*pipelineObject)->GetActiveProgram();
|
||||
*params = active ? static_cast<GLint>(active->GetExternalIndex()) : 0;
|
||||
break;
|
||||
}
|
||||
case GL_VERTEX_SHADER: *params = stageProgramName(ShaderStage::Vertex); break;
|
||||
case GL_TESS_CONTROL_SHADER: *params = stageProgramName(ShaderStage::TessControl); break;
|
||||
case GL_TESS_EVALUATION_SHADER: *params = stageProgramName(ShaderStage::TessEval); break;
|
||||
case GL_GEOMETRY_SHADER: *params = stageProgramName(ShaderStage::Geometry); break;
|
||||
case GL_FRAGMENT_SHADER: *params = stageProgramName(ShaderStage::Fragment); break;
|
||||
case GL_COMPUTE_SHADER: *params = stageProgramName(ShaderStage::Compute); break;
|
||||
case GL_VALIDATE_STATUS: *params = (*pipelineObject)->GetValidateStatus() ? GL_TRUE : GL_FALSE; break;
|
||||
case GL_INFO_LOG_LENGTH: {
|
||||
// GL counts the null terminator, and reports 0 rather than 1 for an empty log.
|
||||
const auto& log = (*pipelineObject)->GetInfoLog();
|
||||
*params = log.empty() ? 0 : static_cast<GLint>(log.length()) + 1;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
RecordPipelineError(ErrorCode::InvalidEnum, __func__,
|
||||
std::format("pname {} is not a program pipeline parameter.",
|
||||
MG_Util::ConvertGLEnumToString(pname)));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void GetProgramPipelineInfoLog(GLuint pipeline, GLsizei bufSize, GLsizei* length, GLchar* infoLog) {
|
||||
const auto* pipelineObject = TryGetPipeline(pipeline, __func__);
|
||||
if (!pipelineObject) return;
|
||||
if (bufSize < 0) {
|
||||
RecordPipelineError(ErrorCode::InvalidValue, __func__, "bufSize must be non-negative.");
|
||||
return;
|
||||
}
|
||||
if (bufSize == 0 || !infoLog) {
|
||||
if (length) *length = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& log = (*pipelineObject)->GetInfoLog();
|
||||
const auto copied = std::min<GLsizei>(bufSize - 1, static_cast<GLsizei>(log.length()));
|
||||
if (copied > 0) Memcpy(infoLog, log.data(), static_cast<SizeT>(copied));
|
||||
infoLog[copied] = '\0';
|
||||
if (length) *length = copied;
|
||||
}
|
||||
|
||||
void UseProgramStages(GLuint pipeline, GLbitfield stages, GLuint program) {
|
||||
if (stages != GL_ALL_SHADER_BITS && (stages & ~kAllStageBits) != 0) {
|
||||
RecordPipelineError(ErrorCode::InvalidValue, __func__, "stages names a bit that is not a shader stage.");
|
||||
return;
|
||||
}
|
||||
const auto* pipelineObject = TryGetPipeline(pipeline, __func__);
|
||||
if (!pipelineObject) return;
|
||||
|
||||
SharedPtr<MG_State::GLState::ProgramObject> programObject;
|
||||
if (program != 0) {
|
||||
if (!MG_State::pGLContext->ValidateProgramName(program)) {
|
||||
RecordPipelineError(ErrorCode::InvalidValue, __func__,
|
||||
std::format("{} is not the name of a program object.", program));
|
||||
return;
|
||||
}
|
||||
programObject = MG_State::pGLContext->GetProgramObject(program);
|
||||
if (!programObject) {
|
||||
RecordPipelineError(ErrorCode::InvalidValue, __func__,
|
||||
std::format("{} is not the name of a program object.", program));
|
||||
return;
|
||||
}
|
||||
if (!programObject->GetLinkStatus()) {
|
||||
RecordPipelineError(ErrorCode::InvalidOperation, __func__,
|
||||
std::format("Program {} has not been linked successfully.", program));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const GLbitfield selected = stages == GL_ALL_SHADER_BITS ? kAllStageBits : stages;
|
||||
for (GLbitfield bit = 1; bit != 0 && bit <= kAllStageBits; bit <<= 1) {
|
||||
if ((selected & bit) == 0) continue;
|
||||
ShaderStage stage = ShaderStage::Unknown;
|
||||
if (!TryResolveStageBit(bit, stage)) continue;
|
||||
// program == 0 clears the stage, which is what a null program reference means here.
|
||||
(*pipelineObject)->SetStageProgram(stage, programObject);
|
||||
}
|
||||
}
|
||||
|
||||
void ActiveShaderProgram(GLuint pipeline, GLuint program) {
|
||||
const auto* pipelineObject = TryGetPipeline(pipeline, __func__);
|
||||
if (!pipelineObject) return;
|
||||
|
||||
if (program == 0) {
|
||||
(*pipelineObject)->SetActiveProgram(nullptr);
|
||||
return;
|
||||
}
|
||||
if (!MG_State::pGLContext->ValidateProgramName(program)) {
|
||||
RecordPipelineError(ErrorCode::InvalidValue, __func__,
|
||||
std::format("{} is not the name of a program object.", program));
|
||||
return;
|
||||
}
|
||||
auto programObject = MG_State::pGLContext->GetProgramObject(program);
|
||||
if (!programObject) {
|
||||
RecordPipelineError(ErrorCode::InvalidValue, __func__,
|
||||
std::format("{} is not the name of a program object.", program));
|
||||
return;
|
||||
}
|
||||
if (!programObject->GetLinkStatus()) {
|
||||
RecordPipelineError(ErrorCode::InvalidOperation, __func__,
|
||||
std::format("Program {} has not been linked successfully.", program));
|
||||
return;
|
||||
}
|
||||
(*pipelineObject)->SetActiveProgram(programObject);
|
||||
}
|
||||
|
||||
void ValidateProgramPipeline(GLuint pipeline) {
|
||||
const auto* pipelineObject = TryGetPipeline(pipeline, __func__);
|
||||
if (!pipelineObject) return;
|
||||
// Nothing here can fail today: MobileGL links each stage program on its own, so there is no
|
||||
// cross-stage interface to re-check at validation time. The log stays empty, which GL allows.
|
||||
(*pipelineObject)->SetValidateStatus(true);
|
||||
}
|
||||
} // namespace MobileGL::MG_Impl::GLImpl
|
||||
@@ -0,0 +1,23 @@
|
||||
// MobileGL - MobileGL/MG_Impl/GLImpl/Program/GL_ProgramPipeline.h
|
||||
// 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
|
||||
|
||||
#pragma once
|
||||
#include <Includes.h>
|
||||
|
||||
namespace MobileGL::MG_Impl::GLImpl {
|
||||
void GenProgramPipelines(GLsizei n, GLuint* pipelines);
|
||||
void CreateProgramPipelines(GLsizei n, GLuint* pipelines);
|
||||
void DeleteProgramPipelines(GLsizei n, const GLuint* pipelines);
|
||||
void BindProgramPipeline(GLuint pipeline);
|
||||
GLboolean IsProgramPipeline(GLuint pipeline);
|
||||
void GetProgramPipelineiv(GLuint pipeline, GLenum pname, GLint* params);
|
||||
void GetProgramPipelineInfoLog(GLuint pipeline, GLsizei bufSize, GLsizei* length, GLchar* infoLog);
|
||||
void UseProgramStages(GLuint pipeline, GLbitfield stages, GLuint program);
|
||||
void ActiveShaderProgram(GLuint pipeline, GLuint program);
|
||||
void ValidateProgramPipeline(GLuint pipeline);
|
||||
} // namespace MobileGL::MG_Impl::GLImpl
|
||||
@@ -62,6 +62,34 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", function, message));
|
||||
}
|
||||
|
||||
// The by-buffer query getters write the result into a buffer object instead of client
|
||||
// memory. Everything about the query itself - the name, whether it is still active, the
|
||||
// parameter - is checked by GetQueryObjectValue; what is left is the destination, so this
|
||||
// resolves the buffer and confirms the write lands inside it (GL 4.6 core 4.2.1).
|
||||
Bool ResolveQueryResultDestination(GLuint buffer, GLintptr offset, SizeT writeSize, const char* function,
|
||||
SharedPtr<MG_State::GLState::BufferObject>& outBuffer) {
|
||||
if (offset < 0) {
|
||||
RecordQueryError(ErrorCode::InvalidValue, function, "Offset cannot be negative.");
|
||||
return false;
|
||||
}
|
||||
if (!MG_State::pGLContext->ValidateBufferObject(buffer)) {
|
||||
RecordQueryError(ErrorCode::InvalidOperation, function, "Buffer object does not exist.");
|
||||
return false;
|
||||
}
|
||||
auto bufferObject = MG_State::pGLContext->GetBufferObject(buffer);
|
||||
if (!bufferObject) {
|
||||
RecordQueryError(ErrorCode::InvalidOperation, function, "Buffer object does not exist.");
|
||||
return false;
|
||||
}
|
||||
if (static_cast<SizeT>(offset) + writeSize > bufferObject->GetSize()) {
|
||||
RecordQueryError(ErrorCode::InvalidOperation, function,
|
||||
"The query result does not fit in the buffer object at this offset.");
|
||||
return false;
|
||||
}
|
||||
outBuffer = bufferObject;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Callers must hold g_queryObjectsMutex.
|
||||
QueryObject* FindQueryObjectLocked(GLuint id) {
|
||||
const auto it = g_liveQueryObjects.find(id);
|
||||
@@ -95,8 +123,13 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
}
|
||||
|
||||
// Shared GetQueryObject* implementation. Returns false when an error
|
||||
// was recorded and no value should be written back.
|
||||
Bool GetQueryObjectValue(GLuint id, GLenum pname, const char* function, Uint64& outValue) {
|
||||
// was recorded and no value should be written back. `outValueProduced`, when given,
|
||||
// additionally distinguishes "succeeded with a value" from "succeeded but the result is not
|
||||
// ready" - the GL_QUERY_RESULT_NO_WAIT case, where GL_ARB_query_buffer_object says the
|
||||
// destination is left alone rather than written with a placeholder.
|
||||
Bool GetQueryObjectValue(GLuint id, GLenum pname, const char* function, Uint64& outValue,
|
||||
Bool* outValueProduced = nullptr) {
|
||||
if (outValueProduced) *outValueProduced = true;
|
||||
const std::lock_guard<std::mutex> lock(g_queryObjectsMutex);
|
||||
auto* queryObject = FindQueryObjectLocked(id);
|
||||
if (!queryObject) {
|
||||
@@ -109,6 +142,41 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
}
|
||||
|
||||
switch (pname) {
|
||||
case GL_QUERY_TARGET:
|
||||
// The target a query was begun with (or created with, for glCreateQueries) - state
|
||||
// the object has carried all along, GL 4.6 core table 23.35.
|
||||
outValue = queryObject->target;
|
||||
return true;
|
||||
case GL_QUERY_RESULT_NO_WAIT: {
|
||||
if (queryObject->resultCached) {
|
||||
outValue = queryObject->cachedResult;
|
||||
return true;
|
||||
}
|
||||
Uint64 result = 0;
|
||||
const auto getQueryResult64 = MG_Backend::gBackendFunctionsTable.GL.GetQueryResult64;
|
||||
if (queryObject->backendHandle && getQueryResult64 &&
|
||||
!getQueryResult64(queryObject->backendHandle, /*wait=*/false, &result)) {
|
||||
// Not ready. The whole point of the no-wait form is that the caller's
|
||||
// destination keeps whatever it already held.
|
||||
if (outValueProduced) *outValueProduced = false;
|
||||
outValue = 0;
|
||||
return true;
|
||||
}
|
||||
if (queryObject->target == GL_ANY_SAMPLES_PASSED ||
|
||||
queryObject->target == GL_ANY_SAMPLES_PASSED_CONSERVATIVE) {
|
||||
result = result != 0 ? 1 : 0;
|
||||
}
|
||||
if (queryObject->backendHandle) {
|
||||
if (const auto deleteBackendQuery = MG_Backend::gBackendFunctionsTable.GL.DeleteBackendQuery) {
|
||||
deleteBackendQuery(queryObject->backendHandle);
|
||||
}
|
||||
queryObject->backendHandle = nullptr;
|
||||
}
|
||||
queryObject->cachedResult = result;
|
||||
queryObject->resultCached = true;
|
||||
outValue = result;
|
||||
return true;
|
||||
}
|
||||
case GL_QUERY_RESULT_AVAILABLE: {
|
||||
if (queryObject->resultCached || !queryObject->backendHandle) {
|
||||
outValue = 1;
|
||||
@@ -160,6 +228,21 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void GetQueryBufferObject(GLuint id, GLuint buffer, GLenum pname, GLintptr offset, const char* function) {
|
||||
SharedPtr<MG_State::GLState::BufferObject> bufferObject;
|
||||
if (!ResolveQueryResultDestination(buffer, offset, sizeof(T), function, bufferObject)) return;
|
||||
|
||||
Uint64 value = 0;
|
||||
Bool valueProduced = false;
|
||||
if (!GetQueryObjectValue(id, pname, function, value, &valueProduced)) return;
|
||||
// GL_QUERY_RESULT_NO_WAIT on a result that has not landed writes nothing at all.
|
||||
if (!valueProduced) return;
|
||||
|
||||
const T narrowed = static_cast<T>(value);
|
||||
bufferObject->UploadSubData({const_cast<T*>(&narrowed), sizeof(T)}, static_cast<SizeT>(offset));
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void GenQueries(GLsizei n, GLuint* ids) {
|
||||
@@ -475,9 +558,26 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
}
|
||||
}
|
||||
|
||||
void GetQueryBufferObjectiv(GLuint id, GLuint buffer, GLenum pname, GLintptr offset) {
|
||||
GetQueryBufferObject<GLint>(id, buffer, pname, offset, __FUNCTION__);
|
||||
}
|
||||
|
||||
void GetQueryBufferObjectuiv(GLuint id, GLuint buffer, GLenum pname, GLintptr offset) {
|
||||
GetQueryBufferObject<GLuint>(id, buffer, pname, offset, __FUNCTION__);
|
||||
}
|
||||
|
||||
void GetQueryBufferObjecti64v(GLuint id, GLuint buffer, GLenum pname, GLintptr offset) {
|
||||
GetQueryBufferObject<GLint64>(id, buffer, pname, offset, __FUNCTION__);
|
||||
}
|
||||
|
||||
void GetQueryBufferObjectui64v(GLuint id, GLuint buffer, GLenum pname, GLintptr offset) {
|
||||
GetQueryBufferObject<GLuint64>(id, buffer, pname, offset, __FUNCTION__);
|
||||
}
|
||||
|
||||
void GetQueryObjectiv(GLuint id, GLenum pname, GLint* params) {
|
||||
Uint64 value = 0;
|
||||
if (!GetQueryObjectValue(id, pname, __FUNCTION__, value) || !params) {
|
||||
Bool valueProduced = false;
|
||||
if (!GetQueryObjectValue(id, pname, __FUNCTION__, value, &valueProduced) || !valueProduced || !params) {
|
||||
return;
|
||||
}
|
||||
constexpr Uint64 kMaxInt = static_cast<Uint64>(INT_MAX);
|
||||
@@ -486,7 +586,8 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
|
||||
void GetQueryObjectuiv(GLuint id, GLenum pname, GLuint* params) {
|
||||
Uint64 value = 0;
|
||||
if (!GetQueryObjectValue(id, pname, __FUNCTION__, value) || !params) {
|
||||
Bool valueProduced = false;
|
||||
if (!GetQueryObjectValue(id, pname, __FUNCTION__, value, &valueProduced) || !valueProduced || !params) {
|
||||
return;
|
||||
}
|
||||
*params = static_cast<GLuint>(value & 0xFFFFFFFFull);
|
||||
@@ -494,7 +595,8 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
|
||||
void GetQueryObjecti64v(GLuint id, GLenum pname, GLint64* params) {
|
||||
Uint64 value = 0;
|
||||
if (!GetQueryObjectValue(id, pname, __FUNCTION__, value) || !params) {
|
||||
Bool valueProduced = false;
|
||||
if (!GetQueryObjectValue(id, pname, __FUNCTION__, value, &valueProduced) || !valueProduced || !params) {
|
||||
return;
|
||||
}
|
||||
*params = static_cast<GLint64>(value);
|
||||
@@ -502,7 +604,8 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
|
||||
void GetQueryObjectui64v(GLuint id, GLenum pname, GLuint64* params) {
|
||||
Uint64 value = 0;
|
||||
if (!GetQueryObjectValue(id, pname, __FUNCTION__, value) || !params) {
|
||||
Bool valueProduced = false;
|
||||
if (!GetQueryObjectValue(id, pname, __FUNCTION__, value, &valueProduced) || !valueProduced || !params) {
|
||||
return;
|
||||
}
|
||||
*params = static_cast<GLuint64>(value);
|
||||
|
||||
@@ -24,5 +24,9 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
void GetQueryObjectuiv(GLuint id, GLenum pname, GLuint* params);
|
||||
void GetQueryObjecti64v(GLuint id, GLenum pname, GLint64* params);
|
||||
void GetQueryObjectui64v(GLuint id, GLenum pname, GLuint64* params);
|
||||
void GetQueryBufferObjectiv(GLuint id, GLuint buffer, GLenum pname, GLintptr offset);
|
||||
void GetQueryBufferObjectuiv(GLuint id, GLuint buffer, GLenum pname, GLintptr offset);
|
||||
void GetQueryBufferObjecti64v(GLuint id, GLuint buffer, GLenum pname, GLintptr offset);
|
||||
void GetQueryBufferObjectui64v(GLuint id, GLuint buffer, GLenum pname, GLintptr offset);
|
||||
void QueryCounter(GLuint id, GLenum target);
|
||||
} // namespace MobileGL::MG_Impl::GLImpl
|
||||
|
||||
@@ -28,6 +28,11 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
case GL_TEXTURE_MAX_LOD:
|
||||
case GL_TEXTURE_LOD_BIAS:
|
||||
return true;
|
||||
// Four components, and GL puts no range on them - a border colour outside [0,1] is
|
||||
// clamped when a fixed-point format is sampled, not rejected here. The scalar readers
|
||||
// below would look at one component and invent an error.
|
||||
case GL_TEXTURE_BORDER_COLOR:
|
||||
return true;
|
||||
case GL_TEXTURE_MAX_ANISOTROPY_EXT:
|
||||
if (ReadSamplerScalar(param, isFloat, isUnsignedInteger) >= 1.0f) return true;
|
||||
MG_State::pGLContext->RecordError(
|
||||
@@ -99,6 +104,20 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
case GL_TEXTURE_COMPARE_FUNC:
|
||||
samplerObj->SetSamplerCompareFunc(MG_Util::ConvertGLEnumToSamplerCompareFunc(*(const GLint*)param));
|
||||
break;
|
||||
case GL_TEXTURE_BORDER_COLOR:
|
||||
// The only four-component sampler parameter: the caller's form decides which
|
||||
// representation is authoritative, and SamplerObject keeps the other two in step.
|
||||
if (isFloat) {
|
||||
const auto* values = (const GLfloat*)param;
|
||||
samplerObj->SetBorderColor(FloatVec4(values[0], values[1], values[2], values[3]));
|
||||
} else if (isUnsignedInteger) {
|
||||
const auto* values = (const GLuint*)param;
|
||||
samplerObj->SetBorderColorUI(UintVec4(values[0], values[1], values[2], values[3]));
|
||||
} else {
|
||||
const auto* values = (const GLint*)param;
|
||||
samplerObj->SetBorderColorI(IntVec4(values[0], values[1], values[2], values[3]));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
MG_State::pGLContext->RecordError(ErrorCode::InvalidEnum,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", "SetSamplerParam_State",
|
||||
@@ -162,6 +181,31 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
case GL_TEXTURE_COMPARE_FUNC:
|
||||
*(GLuint*)params = MG_Util::ConvertSamplerCompareFuncToGLEnum(samplerObj->GetSamplerCompareFunc());
|
||||
break;
|
||||
case GL_TEXTURE_BORDER_COLOR: {
|
||||
if (isFloat) {
|
||||
const auto& color = samplerObj->GetBorderColor();
|
||||
auto* out = (GLfloat*)params;
|
||||
out[0] = color.x();
|
||||
out[1] = color.y();
|
||||
out[2] = color.z();
|
||||
out[3] = color.w();
|
||||
} else if (isUnsignedInteger) {
|
||||
const auto& color = samplerObj->GetBorderColorUI();
|
||||
auto* out = (GLuint*)params;
|
||||
out[0] = color.x();
|
||||
out[1] = color.y();
|
||||
out[2] = color.z();
|
||||
out[3] = color.w();
|
||||
} else {
|
||||
const auto& color = samplerObj->GetBorderColorI();
|
||||
auto* out = (GLint*)params;
|
||||
out[0] = color.x();
|
||||
out[1] = color.y();
|
||||
out[2] = color.z();
|
||||
out[3] = color.w();
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
MG_State::pGLContext->RecordError(ErrorCode::InvalidEnum,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", "GetSamplerParam_State",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -11,6 +11,10 @@
|
||||
|
||||
namespace MobileGL::MG_Impl::GLImpl {
|
||||
/* @INSERTION_POINT:FUNCTION_DECLARATION@ */
|
||||
// The sized internal formats a buffer texture accepts (GL 4.6 core table 8.16). The buffer
|
||||
// clears take the same list, so it is shared rather than written out twice.
|
||||
Bool IsBufferTextureInternalFormat(GLenum internalformat);
|
||||
|
||||
void ClearTexImage(GLuint texture, GLint level, GLenum format, GLenum type, const void* data);
|
||||
void ClearTexSubImage(GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width,
|
||||
GLsizei height, GLsizei depth, GLenum format, GLenum type, const void* data);
|
||||
@@ -42,6 +46,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
void GenerateTextureMipmap(GLuint texture);
|
||||
void BindTextureUnit(GLuint unit, GLuint texture);
|
||||
void GetTextureImage(GLuint texture, GLint level, GLenum format, GLenum type, GLsizei bufSize, void* pixels);
|
||||
void GetCompressedTextureImage(GLuint texture, GLint level, GLsizei bufSize, void* pixels);
|
||||
void TexBufferRange(GLenum target, GLenum internalformat, GLuint buffer, GLintptr offset, GLsizeiptr size);
|
||||
void TextureBuffer(GLuint texture, GLenum internalformat, GLuint buffer);
|
||||
void TextureBufferRange(GLuint texture, GLenum internalformat, GLuint buffer, GLintptr offset, GLsizeiptr size);
|
||||
@@ -101,6 +106,9 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
GLsizei width, GLsizei height);
|
||||
void CopyTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width,
|
||||
GLsizei height);
|
||||
void CopyTextureSubImage1D(GLuint texture, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width);
|
||||
void CopyTextureSubImage3D(GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x,
|
||||
GLint y, GLsizei width, GLsizei height);
|
||||
void CopyTextureSubImage2D(GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y,
|
||||
GLsizei width, GLsizei height);
|
||||
void CopyTexSubImage1D(GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width);
|
||||
|
||||
@@ -226,6 +226,9 @@ namespace MobileGL::MG_Impl::GLImpl::TextureImpl {
|
||||
case TextureInternalFormat::Depth24Stencil8:
|
||||
case TextureInternalFormat::Depth32FStencil8:
|
||||
case TextureInternalFormat::DepthStencil:
|
||||
// Stencil-only is not a colour format either: a colour client format read against a
|
||||
// STENCIL_INDEX8 texture has to be the same INVALID_OPERATION as against a depth one.
|
||||
case TextureInternalFormat::StencilIndex8:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
|
||||
@@ -105,12 +105,45 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
return pname == GL_CURRENT_VERTEX_ATTRIB;
|
||||
}
|
||||
|
||||
// The stride a pointer-style call gives its binding point: the argument when it is non-zero,
|
||||
// otherwise the tightly packed element size (GL 4.6 core 10.3.2). A packed 2_10_10_10 or
|
||||
// 10F_11F_11F attribute is one 32-bit word regardless of its component count.
|
||||
static int EffectiveVertexStride(GLsizei stride, GLint size, GLenum type) {
|
||||
if (stride != 0) return static_cast<int>(stride);
|
||||
switch (type) {
|
||||
case GL_INT_2_10_10_10_REV:
|
||||
case GL_UNSIGNED_INT_2_10_10_10_REV:
|
||||
case GL_UNSIGNED_INT_10F_11F_11F_REV:
|
||||
return 4;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return static_cast<int>(size * MG_Util::GetGLTypeSize(type));
|
||||
}
|
||||
|
||||
// glBindVertexBuffers / glVertexArrayVertexBuffers take a range of binding points, and a
|
||||
// range that runs past the last one is INVALID_OPERATION rather than the INVALID_VALUE a
|
||||
// single out-of-range index gets (GL 4.6 core 10.3.1).
|
||||
static bool ValidateVertexBindingRange(GLuint first, GLsizei count, const char* funcName) {
|
||||
if (count < 0) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidValue,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", funcName, "count must be non-negative."));
|
||||
return false;
|
||||
}
|
||||
if (static_cast<Uint64>(first) + static_cast<Uint64>(count) >
|
||||
VertexArrayImpl::GetMaxVertexAttribBindings()) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidOperation,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", funcName,
|
||||
"first + count exceeds GL_MAX_VERTEX_ATTRIB_BINDINGS."));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool ValidateVertexBindingIndex(GLuint bindingindex, const char* funcName) {
|
||||
// Bound by the same dynamic limit as attribute indices: the default attribute -> binding
|
||||
// mapping is the identity, so a binding point the backend cannot address as an attribute
|
||||
// would resolve into an attribute the backend must then reject on every draw. Real drivers
|
||||
// likewise report MAX_VERTEX_ATTRIB_BINDINGS == MAX_VERTEX_ATTRIBS.
|
||||
if (bindingindex >= VertexArrayImpl::GetMaxVertexAttribs()) {
|
||||
if (bindingindex >= VertexArrayImpl::GetMaxVertexAttribBindings()) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidValue,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", funcName,
|
||||
@@ -155,6 +188,17 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
|
||||
SharedPtr<MG_State::GLState::VertexArrayObject> GetNamedVertexArrayObject_State(GLuint vaobj,
|
||||
const char* caller) {
|
||||
// Name zero is not a vertex array object in a core profile: it names the default vertex
|
||||
// array, which the by-name (direct state access) entry points never accept. MobileGL keeps a
|
||||
// real object at index 0 for the compatibility paths, so the generic name validation below
|
||||
// would otherwise let it through (GL 4.6 core 10.3.1).
|
||||
if (vaobj == 0) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidOperation,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller,
|
||||
"Vertex array name 0 is not a vertex array object."));
|
||||
return nullptr;
|
||||
}
|
||||
if (!VertexArrayImpl::ValidateVertexArrayName(vaobj)) return nullptr;
|
||||
if (!VertexArrayImpl::ValidateVertexArrayObject(vaobj)) return nullptr;
|
||||
return MG_State::pGLContext->GetVertexArrayObject(vaobj);
|
||||
@@ -210,7 +254,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
|
||||
DataType dataType = MG_Util::ConvertGLEnumToDataType(type);
|
||||
// Integer path: never normalized, never BGRA/packed (the validator rejects those).
|
||||
if (!VertexArrayImpl::ValidateVertexAttribFormat(index, size, dataType, false, stride, true)) return;
|
||||
if (!VertexArrayImpl::ValidateVertexAttribFormat(index, size, type, dataType, false, stride, true)) return;
|
||||
|
||||
auto& vao = MG_State::pGLContext->GetBoundVertexArray();
|
||||
if (!vao) {
|
||||
@@ -227,6 +271,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
|
||||
vao->SetAttributeFormat(index, size, dataType, false, stride, offset, true, false);
|
||||
vao->BindAttributeBuffer(index, vbo);
|
||||
vao->MirrorPointerIntoBinding(index, vbo, offset, EffectiveVertexStride(stride, size, type));
|
||||
}
|
||||
|
||||
void VertexAttribPointer_State(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride,
|
||||
@@ -234,7 +279,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
if (!VertexArrayImpl::ValidateVertexAttributeIndex(index)) return;
|
||||
|
||||
DataType dataType = MG_Util::ConvertGLEnumToDataType(type);
|
||||
if (!VertexArrayImpl::ValidateVertexAttribFormat(index, size, dataType, normalized == GL_TRUE, stride, false))
|
||||
if (!VertexArrayImpl::ValidateVertexAttribFormat(index, size, type, dataType, normalized == GL_TRUE, stride, false))
|
||||
return;
|
||||
|
||||
auto& vao = MG_State::pGLContext->GetBoundVertexArray();
|
||||
@@ -256,6 +301,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
const int effectiveSize = isBgra ? 4 : size;
|
||||
vao->SetAttributeFormat(index, effectiveSize, dataType, normalized, stride, offset, false, isBgra);
|
||||
vao->BindAttributeBuffer(index, vbo);
|
||||
vao->MirrorPointerIntoBinding(index, vbo, offset, EffectiveVertexStride(stride, effectiveSize, type));
|
||||
}
|
||||
|
||||
void BindVertexArray_State(GLuint array) {
|
||||
@@ -359,6 +405,13 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller, "offset and stride must be non-negative."));
|
||||
return;
|
||||
}
|
||||
if (static_cast<Uint>(stride) > VertexArrayImpl::GetMaxVertexAttribStride()) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidValue,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller,
|
||||
"stride exceeds GL_MAX_VERTEX_ATTRIB_STRIDE."));
|
||||
return;
|
||||
}
|
||||
auto bufferObject = GetVertexArrayBufferObject_State(buffer, caller);
|
||||
if (buffer != 0 && !bufferObject) return;
|
||||
|
||||
@@ -376,6 +429,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
const GLintptr* offsets, const GLsizei* strides) {
|
||||
auto vao = GetNamedVertexArrayObject_State(vaobj, "VertexArrayVertexBuffers_State");
|
||||
if (!vao) return;
|
||||
if (!ValidateVertexBindingRange(first, count, "VertexArrayVertexBuffers_State")) return;
|
||||
for (GLsizei i = 0; i < count; ++i) {
|
||||
if (!buffers) {
|
||||
VertexBufferBinding_State(vao, first + i, 0, 0, 16, "VertexArrayVertexBuffers_State");
|
||||
@@ -389,12 +443,36 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
static void VertexAttribFormatSeparate_State(const SharedPtr<MG_State::GLState::VertexArrayObject>& vao,
|
||||
GLuint attribindex, GLint size, GLenum type, GLboolean normalized,
|
||||
GLuint relativeoffset, Bool isInteger, const char* caller) {
|
||||
static_cast<void>(caller);
|
||||
if (!VertexArrayImpl::ValidateVertexAttributeIndex(attribindex)) return;
|
||||
|
||||
DataType dataType = MG_Util::ConvertGLEnumToDataType(type);
|
||||
if (!VertexArrayImpl::ValidateVertexAttribPointerParams(attribindex, size, dataType, 0)) return;
|
||||
// The separate-format entry points take the same size/type rules as the pointer ones,
|
||||
// GL_BGRA included, so they need the full format validation rather than the pointer-only
|
||||
// subset - that one reports GL_BGRA as an out-of-range size.
|
||||
if (!VertexArrayImpl::ValidateVertexAttribFormat(attribindex, size, type, dataType, normalized == GL_TRUE, 0,
|
||||
isInteger))
|
||||
return;
|
||||
if (!VertexArrayImpl::ValidateVertexAttribRelativeOffset(relativeoffset)) return;
|
||||
|
||||
vao->SetAttributeFormatSeparate(attribindex, size, dataType, normalized, isInteger, relativeoffset);
|
||||
const Bool isBgra = (size == static_cast<GLint>(GL_BGRA));
|
||||
vao->SetAttributeFormatSeparate(attribindex, isBgra ? 4 : size, dataType, normalized, isInteger,
|
||||
relativeoffset, isBgra);
|
||||
}
|
||||
|
||||
// The long (64-bit) attribute format. MobileGL has no 64-bit vertex attributes, so nothing is
|
||||
// recorded; what the entry point owes the application is the parameter validation, which is
|
||||
// observable through glGetError regardless of whether the format could be used in a draw.
|
||||
static void VertexAttribLFormatSeparate_State(GLuint attribindex, GLint size, GLenum type,
|
||||
GLuint relativeoffset) {
|
||||
if (!VertexArrayImpl::ValidateVertexAttributeIndex(attribindex)) return;
|
||||
if (!VertexArrayImpl::ValidateVertexAttribLFormat(attribindex, size, type)) return;
|
||||
if (!VertexArrayImpl::ValidateVertexAttribRelativeOffset(relativeoffset)) return;
|
||||
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidOperation,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", "VertexAttribLFormat",
|
||||
"64-bit vertex attributes are not supported."));
|
||||
}
|
||||
|
||||
void VertexArrayAttribFormat_State(GLuint vaobj, GLuint attribindex, GLint size, GLenum type,
|
||||
@@ -1153,6 +1231,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
const GLsizei* strides) {
|
||||
auto vao = GetBoundVertexArrayOrError("BindVertexBuffers");
|
||||
if (!vao) return;
|
||||
if (!ValidateVertexBindingRange(first, count, "BindVertexBuffers")) return;
|
||||
for (GLsizei i = 0; i < count; ++i) {
|
||||
if (!buffers) {
|
||||
VertexBufferBinding_State(vao, first + i, 0, 0, 16, "BindVertexBuffers");
|
||||
@@ -1177,6 +1256,18 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
"VertexAttribIFormat");
|
||||
}
|
||||
|
||||
void VertexAttribLFormat(GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset) {
|
||||
auto vao = GetBoundVertexArrayOrError("VertexAttribLFormat");
|
||||
if (!vao) return;
|
||||
VertexAttribLFormatSeparate_State(attribindex, size, type, relativeoffset);
|
||||
}
|
||||
|
||||
void VertexArrayAttribLFormat(GLuint vaobj, GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset) {
|
||||
auto vao = GetNamedVertexArrayObject_State(vaobj, "VertexArrayAttribLFormat");
|
||||
if (!vao) return;
|
||||
VertexAttribLFormatSeparate_State(attribindex, size, type, relativeoffset);
|
||||
}
|
||||
|
||||
void VertexAttribBinding(GLuint attribindex, GLuint bindingindex) {
|
||||
auto vao = GetBoundVertexArrayOrError("VertexAttribBinding");
|
||||
if (!vao) return;
|
||||
|
||||
@@ -98,6 +98,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
void VertexArrayAttribFormat(GLuint vaobj, GLuint attribindex, GLint size, GLenum type, GLboolean normalized,
|
||||
GLuint relativeoffset);
|
||||
void VertexArrayAttribIFormat(GLuint vaobj, GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset);
|
||||
void VertexArrayAttribLFormat(GLuint vaobj, GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset);
|
||||
void VertexArrayAttribBinding(GLuint vaobj, GLuint attribindex, GLuint bindingindex);
|
||||
void VertexArrayBindingDivisor(GLuint vaobj, GLuint bindingindex, GLuint divisor);
|
||||
void VertexArrayVertexBuffers(GLuint vaobj, GLuint first, GLsizei count, const GLuint* buffers,
|
||||
@@ -107,6 +108,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
const GLsizei* strides);
|
||||
void VertexAttribFormat(GLuint attribindex, GLint size, GLenum type, GLboolean normalized, GLuint relativeoffset);
|
||||
void VertexAttribIFormat(GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset);
|
||||
void VertexAttribLFormat(GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset);
|
||||
void VertexAttribBinding(GLuint attribindex, GLuint bindingindex);
|
||||
void VertexBindingDivisor(GLuint bindingindex, GLuint divisor);
|
||||
void VertexAttribDivisor(GLuint index, GLuint divisor);
|
||||
|
||||
@@ -23,6 +23,18 @@ namespace MobileGL::MG_Impl::GLImpl::VertexArrayImpl {
|
||||
return std::min(static_cast<Uint>(backendLimit), capacity);
|
||||
}
|
||||
|
||||
Uint GetMaxVertexAttribBindings() {
|
||||
return GetMaxVertexAttribs();
|
||||
}
|
||||
|
||||
Uint GetMaxVertexAttribRelativeOffset() {
|
||||
return 2047;
|
||||
}
|
||||
|
||||
Uint GetMaxVertexAttribStride() {
|
||||
return 2048;
|
||||
}
|
||||
|
||||
Bool ValidateVertexArrayName(Uint index) {
|
||||
Bool isValid = MG_State::pGLContext->ValidateVertexArrayName(index);
|
||||
if (!isValid) {
|
||||
@@ -90,9 +102,31 @@ namespace MobileGL::MG_Impl::GLImpl::VertexArrayImpl {
|
||||
return true;
|
||||
}
|
||||
|
||||
Bool ValidateVertexAttribFormat(Uint index, GLint sizeRaw, DataType type, Bool normalized, Int stride,
|
||||
Bool integerPath) {
|
||||
Bool ValidateVertexAttribFormat(Uint index, GLint sizeRaw, GLenum glType, DataType type, Bool normalized,
|
||||
Int stride, Bool integerPath) {
|
||||
constexpr const char* fn = "ValidateVertexAttribFormat";
|
||||
// GL_UNSIGNED_INT_10F_11F_11F_REV is a three-component float-path-only packing that has no
|
||||
// DataType of its own, so it has to be recognised by name before the conversion below turns
|
||||
// it into Unknown and reports the wrong error (GL 4.6 core 10.3.2).
|
||||
if (glType == GL_UNSIGNED_INT_10F_11F_11F_REV) {
|
||||
if (integerPath) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidEnum,
|
||||
MakeUnique<GenericErrorInfo>(
|
||||
"MG_Impl/GLImpl", fn,
|
||||
std::format("GL_UNSIGNED_INT_10F_11F_11F_REV is not an integer-path type (attribute {}).",
|
||||
index)));
|
||||
return false;
|
||||
}
|
||||
if (sizeRaw != 3) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidOperation,
|
||||
MakeUnique<GenericErrorInfo>(
|
||||
"MG_Impl/GLImpl", fn,
|
||||
std::format("GL_UNSIGNED_INT_10F_11F_11F_REV requires size 3 (attribute {}).", index)));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (type == DataType::Unknown) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidEnum,
|
||||
@@ -170,4 +204,40 @@ namespace MobileGL::MG_Impl::GLImpl::VertexArrayImpl {
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
Bool ValidateVertexAttribLFormat(Uint index, GLint size, GLenum type) {
|
||||
constexpr const char* fn = "ValidateVertexAttribLFormat";
|
||||
if (size < 1 || size > 4) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidValue,
|
||||
MakeUnique<GenericErrorInfo>(
|
||||
"MG_Impl/GLImpl", fn,
|
||||
std::format("Invalid size {} for attribute {}. Must be 1-4.", size, index)));
|
||||
return false;
|
||||
}
|
||||
// GL 4.6 core 10.3.2: the long form takes GL_DOUBLE and nothing else.
|
||||
if (type != GL_DOUBLE) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidEnum,
|
||||
MakeUnique<GenericErrorInfo>(
|
||||
"MG_Impl/GLImpl", fn,
|
||||
std::format("Type 0x{:X} is not GL_DOUBLE (attribute {}).", type, index)));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
Bool ValidateVertexAttribRelativeOffset(Uint relativeOffset) {
|
||||
const Uint limit = GetMaxVertexAttribRelativeOffset();
|
||||
if (relativeOffset > limit) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidValue,
|
||||
MakeUnique<GenericErrorInfo>(
|
||||
"MG_Impl/GLImpl", "ValidateVertexAttribRelativeOffset",
|
||||
std::format("relativeoffset {} exceeds GL_MAX_VERTEX_ATTRIB_RELATIVE_OFFSET ({}).", relativeOffset,
|
||||
limit)));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
} // namespace MobileGL::MG_Impl::GLImpl::VertexArrayImpl
|
||||
|
||||
@@ -15,6 +15,20 @@ namespace MobileGL::MG_Impl::GLImpl::VertexArrayImpl {
|
||||
// capacity). Falls back to the capacity when no backend is active (unit tests).
|
||||
Uint GetMaxVertexAttribs();
|
||||
|
||||
// GL_MAX_VERTEX_ATTRIB_BINDINGS. The default attribute -> binding mapping is the identity, so a
|
||||
// binding point that cannot also be an attribute index would resolve into an attribute the
|
||||
// backend has to reject on every draw; real drivers report the two limits equal as well.
|
||||
Uint GetMaxVertexAttribBindings();
|
||||
|
||||
// GL_MAX_VERTEX_ATTRIB_RELATIVE_OFFSET. The relative offset is folded into the resolved
|
||||
// attribute offset in the frontend and never reaches a backend limit, so this is the value the
|
||||
// spec requires an implementation to support at minimum (GL 4.6 core table 23.63).
|
||||
Uint GetMaxVertexAttribRelativeOffset();
|
||||
|
||||
// GL_MAX_VERTEX_ATTRIB_STRIDE. Like the relative offset above, the stride never reaches a
|
||||
// backend limit of its own, so this is the spec minimum (GL 4.6 core table 23.63).
|
||||
Uint GetMaxVertexAttribStride();
|
||||
|
||||
Bool ValidateVertexArrayName(Uint index);
|
||||
Bool ValidateVertexArrayObject(Uint index);
|
||||
Bool ValidateVertexAttributeIndex(Uint index);
|
||||
@@ -22,6 +36,13 @@ namespace MobileGL::MG_Impl::GLImpl::VertexArrayImpl {
|
||||
// Full glVertexAttribPointer / glVertexAttribIPointer format validation, including the packed
|
||||
// 2_10_10_10 types and GL_BGRA size. sizeRaw is the untranslated GL size (possibly GL_BGRA);
|
||||
// integerPath selects the glVertexAttribIPointer rules.
|
||||
Bool ValidateVertexAttribFormat(Uint index, GLint sizeRaw, DataType type, Bool normalized, Int stride,
|
||||
Bool integerPath);
|
||||
Bool ValidateVertexAttribFormat(Uint index, GLint sizeRaw, GLenum glType, DataType type, Bool normalized,
|
||||
Int stride, Bool integerPath);
|
||||
// glVertexAttribLFormat / glVertexArrayAttribLFormat: the only accepted type is GL_DOUBLE and
|
||||
// the size range is 1-4 (GL_BGRA is a float-path size). Separate from the function above
|
||||
// because the long path shares none of its type or size rules.
|
||||
Bool ValidateVertexAttribLFormat(Uint index, GLint size, GLenum type);
|
||||
// Shared by every *Format entry point: INVALID_VALUE once relativeoffset leaves the range the
|
||||
// implementation advertises.
|
||||
Bool ValidateVertexAttribRelativeOffset(Uint relativeOffset);
|
||||
} // namespace MobileGL::MG_Impl::GLImpl::VertexArrayImpl
|
||||
|
||||
@@ -31,6 +31,9 @@ namespace MobileGL::MG_State::GLState {
|
||||
BindingSlot<BufferObject>& GetBindingSlot(BufferTarget target);
|
||||
// For glBindBufferBase / glBindBufferRange
|
||||
BindingSlotRange1D<BufferObject>& GetBindingPoint(BufferTarget target, Uint index);
|
||||
const BindingSlotRange1D<BufferObject>& GetBindingPoint(BufferTarget target, Uint index) const {
|
||||
return const_cast<BufferState*>(this)->GetBindingPoint(target, index);
|
||||
}
|
||||
constexpr SizeT GetBindingPointCount(const BufferTarget target) const {
|
||||
auto it = std::find(BufferBindPointTargets.begin(), BufferBindPointTargets.end(), target);
|
||||
auto index = std::distance(BufferBindPointTargets.begin(), it);
|
||||
|
||||
@@ -802,6 +802,53 @@ namespace MobileGL::MG_State {
|
||||
m_transformFeedbackObjects[id] = {};
|
||||
}
|
||||
}
|
||||
// Program pipeline
|
||||
void GLContext::GenProgramPipelineNames(Uint number, Vector<Uint>& pipelines) {
|
||||
pipelines.resize(number);
|
||||
// Names only: glIsProgramPipeline must answer GL_FALSE until one is bound or created.
|
||||
m_programPipelineNames.Generate(number, pipelines.data());
|
||||
}
|
||||
|
||||
void GLContext::CreateProgramPipelineObject(Uint index) {
|
||||
m_programPipelines[index] = MakeShared<ProgramPipelineObject>(index);
|
||||
}
|
||||
|
||||
Bool GLContext::ValidateProgramPipelineName(Uint index) const {
|
||||
return index == 0 || m_programPipelineNames.IsValid(index);
|
||||
}
|
||||
|
||||
Bool GLContext::IsProgramPipelineObject(Uint index) const {
|
||||
if (index == 0 || !m_programPipelineNames.IsValid(index)) return false;
|
||||
return m_programPipelines.find(index) != m_programPipelines.end();
|
||||
}
|
||||
|
||||
void GLContext::BindProgramPipelineObject(Uint index) {
|
||||
if (index != 0 && m_programPipelines.find(index) == m_programPipelines.end()) {
|
||||
// First bind is what turns a reserved name into an object.
|
||||
m_programPipelines[index] = MakeShared<ProgramPipelineObject>(index);
|
||||
}
|
||||
m_boundProgramPipeline = index;
|
||||
}
|
||||
|
||||
void GLContext::MarkProgramPipelineForDeletion(Uint index) {
|
||||
if (index == 0 || !m_programPipelineNames.IsValid(index)) return;
|
||||
if (index == m_boundProgramPipeline) {
|
||||
m_boundProgramPipeline = 0;
|
||||
}
|
||||
m_programPipelines.erase(index);
|
||||
m_programPipelineNames.Delete(index);
|
||||
}
|
||||
|
||||
const SharedPtr<ProgramPipelineObject>& GLContext::GetProgramPipelineObject(Uint index) const {
|
||||
static const SharedPtr<ProgramPipelineObject> kNone;
|
||||
const auto it = m_programPipelines.find(index);
|
||||
return it == m_programPipelines.end() ? kNone : it->second;
|
||||
}
|
||||
|
||||
const SharedPtr<ProgramPipelineObject>& GLContext::GetBoundProgramPipeline() const {
|
||||
return GetProgramPipelineObject(m_boundProgramPipeline);
|
||||
}
|
||||
|
||||
|
||||
Bool GLContext::ValidateTransformFeedbackName(Uint index) const {
|
||||
return index == 0 || m_transformFeedbackNames.IsValid(index);
|
||||
@@ -842,6 +889,64 @@ namespace MobileGL::MG_State {
|
||||
const auto it = m_transformFeedbackObjects.find(index);
|
||||
return it != m_transformFeedbackObjects.end() && it->second.hasCompletedSpan;
|
||||
}
|
||||
|
||||
void GLContext::CreateTransformFeedbackObject(Uint index) {
|
||||
// glCreateTransformFeedbacks has no bind step to infer existence from, so the name it
|
||||
// hands out is already the name of an object (GL 4.6 core 13.2.1).
|
||||
m_transformFeedbackObjects[index] = {};
|
||||
m_transformFeedbackObjects[index].everBound = true;
|
||||
}
|
||||
|
||||
Bool GLContext::IsNamedTransformFeedbackActive(Uint index) const {
|
||||
if (index == m_boundTransformFeedback) return m_transformFeedbackActive;
|
||||
const auto it = m_transformFeedbackObjects.find(index);
|
||||
return it != m_transformFeedbackObjects.end() && it->second.active;
|
||||
}
|
||||
|
||||
Bool GLContext::IsNamedTransformFeedbackPaused(Uint index) const {
|
||||
if (index == m_boundTransformFeedback) return m_transformFeedbackPaused;
|
||||
const auto it = m_transformFeedbackObjects.find(index);
|
||||
return it != m_transformFeedbackObjects.end() && it->second.paused;
|
||||
}
|
||||
|
||||
NamedTransformFeedbackBinding GLContext::GetNamedTransformFeedbackBinding(Uint index, Uint bufferIndex) const {
|
||||
NamedTransformFeedbackBinding result;
|
||||
if (bufferIndex >= MAX_TRANSFORM_FEEDBACK_BUFFERS) return result;
|
||||
// The bound object's capture bindings live in the context's own binding points, not in
|
||||
// the saved copy - that one is only written when the object is swapped out.
|
||||
if (index == m_boundTransformFeedback) {
|
||||
const auto& point = m_bufferState.GetBindingPoint(BufferTarget::TransformFeedback, bufferIndex);
|
||||
result.Buffer = point.GetBoundObject();
|
||||
result.Range = point.GetRange();
|
||||
result.HasExplicitRange = point.HasExplicitRange();
|
||||
return result;
|
||||
}
|
||||
const auto it = m_transformFeedbackObjects.find(index);
|
||||
if (it == m_transformFeedbackObjects.end()) return result;
|
||||
const auto& saved = it->second.bindings[bufferIndex];
|
||||
result.Buffer = saved.buffer;
|
||||
result.Range = saved.range;
|
||||
result.HasExplicitRange = saved.hasExplicitRange;
|
||||
return result;
|
||||
}
|
||||
|
||||
void GLContext::SetNamedTransformFeedbackBinding(Uint index, Uint bufferIndex,
|
||||
const SharedPtr<BufferObject>& buffer, Range1D range,
|
||||
Bool hasExplicitRange) {
|
||||
if (bufferIndex >= MAX_TRANSFORM_FEEDBACK_BUFFERS) return;
|
||||
if (index == m_boundTransformFeedback) {
|
||||
auto& point = m_bufferState.GetBindingPoint(BufferTarget::TransformFeedback, bufferIndex);
|
||||
point.Bind(buffer);
|
||||
if (buffer && hasExplicitRange) {
|
||||
point.SetRange(range, true);
|
||||
} else {
|
||||
point.ClearRange();
|
||||
}
|
||||
return;
|
||||
}
|
||||
auto& object = m_transformFeedbackObjects[index];
|
||||
object.bindings[bufferIndex] = {buffer, range, hasExplicitRange};
|
||||
}
|
||||
} // namespace GLState
|
||||
|
||||
// Leak-at-exit storage; see GlobalObjects.cpp.
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include "MG_State/GLState/RenderbufferState/RenderbufferState.h"
|
||||
#include "RenderState/RenderState.h"
|
||||
#include "ProgramState/ProgramState.h"
|
||||
#include "ProgramState/ProgramPipelineObject.h"
|
||||
#include "SamplerState/SamplerState.h"
|
||||
#include "TextureState/TextureState.h"
|
||||
#include "FramebufferState/FramebufferState.h"
|
||||
@@ -45,6 +46,14 @@ namespace MobileGL {
|
||||
// translates the result into its own API call.
|
||||
VertexAttribTypeInfo ClassifyVertexAttribType(GLenum glType);
|
||||
|
||||
// One indexed capture binding of a transform feedback object, as the by-name queries
|
||||
// report it. An empty Buffer means the binding point is unbound.
|
||||
struct NamedTransformFeedbackBinding {
|
||||
SharedPtr<BufferObject> Buffer;
|
||||
Range1D Range{};
|
||||
Bool HasExplicitRange = false;
|
||||
};
|
||||
|
||||
class GLContext {
|
||||
public:
|
||||
GLContext() = default;
|
||||
@@ -129,6 +138,19 @@ namespace MobileGL {
|
||||
void UseProgram(Uint program);
|
||||
const SharedPtr<ProgramObject>& GetCurrentProgram();
|
||||
|
||||
// Program pipeline (GL_ARB_separate_shader_objects, GL 4.6 core 7.4). Like queries
|
||||
// and transform feedbacks, glGenProgramPipelines only RESERVES a name - the object
|
||||
// appears on first bind - while glCreateProgramPipelines makes it immediately.
|
||||
void GenProgramPipelineNames(Uint number, Vector<Uint>& pipelines);
|
||||
void CreateProgramPipelineObject(Uint index);
|
||||
Bool ValidateProgramPipelineName(Uint index) const;
|
||||
Bool IsProgramPipelineObject(Uint index) const;
|
||||
void BindProgramPipelineObject(Uint index);
|
||||
void MarkProgramPipelineForDeletion(Uint index);
|
||||
const SharedPtr<ProgramPipelineObject>& GetProgramPipelineObject(Uint index) const;
|
||||
Uint GetBoundProgramPipelineName() const { return m_boundProgramPipeline; }
|
||||
const SharedPtr<ProgramPipelineObject>& GetBoundProgramPipeline() const;
|
||||
|
||||
// RenderState
|
||||
Uint GetRenderStateParametersVersion() const;
|
||||
const RenderStateParameters& GetRenderStateParameters() const;
|
||||
@@ -299,6 +321,17 @@ namespace MobileGL {
|
||||
// cannot express: an empty completed span is legal and draws nothing.
|
||||
Bool HasTransformFeedbackCompletedSpan(Uint index) const;
|
||||
|
||||
// The by-name (direct state access) view. A named object that happens to be the
|
||||
// bound one is answered from the live copy, since that is where its state actually
|
||||
// is until a bind swaps it out.
|
||||
void CreateTransformFeedbackObject(Uint index);
|
||||
Bool IsNamedTransformFeedbackActive(Uint index) const;
|
||||
Bool IsNamedTransformFeedbackPaused(Uint index) const;
|
||||
NamedTransformFeedbackBinding GetNamedTransformFeedbackBinding(Uint index, Uint bufferIndex) const;
|
||||
void SetNamedTransformFeedbackBinding(Uint index, Uint bufferIndex,
|
||||
const SharedPtr<BufferObject>& buffer, Range1D range,
|
||||
Bool hasExplicitRange);
|
||||
|
||||
// Framebuffer
|
||||
void GenFramebufferNames(Uint number, Vector<Uint>& framebuffers);
|
||||
const SharedPtr<FramebufferObject>& GetFramebufferObject(Uint index);
|
||||
@@ -371,6 +404,11 @@ namespace MobileGL {
|
||||
UnorderedMap<Uint, TransformFeedbackObjectState> m_transformFeedbackObjects;
|
||||
IndexGenerator<Uint> m_transformFeedbackNames;
|
||||
Uint m_boundTransformFeedback = 0;
|
||||
// Map membership IS object existence here: a pipeline has no stateful default
|
||||
// object 0, so no everBound flag is needed.
|
||||
UnorderedMap<Uint, SharedPtr<ProgramPipelineObject>> m_programPipelines;
|
||||
IndexGenerator<Uint> m_programPipelineNames;
|
||||
Uint m_boundProgramPipeline = 0;
|
||||
TextureState m_textureState;
|
||||
ProgramState m_programState;
|
||||
RenderState m_renderState;
|
||||
|
||||
@@ -185,6 +185,20 @@ namespace MobileGL::MG_State::GLState {
|
||||
return m_externalIndex;
|
||||
}
|
||||
|
||||
#define MOBILEGL_DEFINE_FRAMEBUFFER_DEFAULT_SETTER(name, member, type) \
|
||||
void FramebufferObject::Set##name(type value) { \
|
||||
if (member == value) return; \
|
||||
member = value; \
|
||||
++m_objectVersion; \
|
||||
}
|
||||
|
||||
MOBILEGL_DEFINE_FRAMEBUFFER_DEFAULT_SETTER(DefaultWidth, m_defaultWidth, Int)
|
||||
MOBILEGL_DEFINE_FRAMEBUFFER_DEFAULT_SETTER(DefaultHeight, m_defaultHeight, Int)
|
||||
MOBILEGL_DEFINE_FRAMEBUFFER_DEFAULT_SETTER(DefaultLayers, m_defaultLayers, Int)
|
||||
MOBILEGL_DEFINE_FRAMEBUFFER_DEFAULT_SETTER(DefaultSamples, m_defaultSamples, Int)
|
||||
MOBILEGL_DEFINE_FRAMEBUFFER_DEFAULT_SETTER(DefaultFixedSampleLocations, m_defaultFixedSampleLocations, Bool)
|
||||
#undef MOBILEGL_DEFINE_FRAMEBUFFER_DEFAULT_SETTER
|
||||
|
||||
void FramebufferObject::BumpAttachmentVersion(FramebufferAttachmentType type) {
|
||||
++m_attachmentVersions[static_cast<SizeT>(type)];
|
||||
++m_objectVersion;
|
||||
|
||||
@@ -129,6 +129,19 @@ namespace MobileGL {
|
||||
void SetReadBuffer(FramebufferAttachmentType buf);
|
||||
FramebufferAttachmentType GetReadBuffer() const { return m_readBuffer; }
|
||||
|
||||
// GL_ARB_framebuffer_no_attachments state (GL 4.6 core table 23.24). The shape a
|
||||
// framebuffer with no attachments would rasterize at; all zero / FALSE until set.
|
||||
Int GetDefaultWidth() const { return m_defaultWidth; }
|
||||
Int GetDefaultHeight() const { return m_defaultHeight; }
|
||||
Int GetDefaultLayers() const { return m_defaultLayers; }
|
||||
Int GetDefaultSamples() const { return m_defaultSamples; }
|
||||
Bool GetDefaultFixedSampleLocations() const { return m_defaultFixedSampleLocations; }
|
||||
void SetDefaultWidth(Int value);
|
||||
void SetDefaultHeight(Int value);
|
||||
void SetDefaultLayers(Int value);
|
||||
void SetDefaultSamples(Int value);
|
||||
void SetDefaultFixedSampleLocations(Bool value);
|
||||
|
||||
FramebufferAttachmentVersionArray GetAllFramebufferAttachmentVersions() const {
|
||||
return m_attachmentVersions;
|
||||
}
|
||||
@@ -148,6 +161,12 @@ namespace MobileGL {
|
||||
FramebufferAttachmentArray m_drawBuffers; // Probably no versioning needed for this, just check equality
|
||||
FramebufferAttachmentType m_readBuffer = FramebufferAttachmentType::None;
|
||||
|
||||
Int m_defaultWidth = 0;
|
||||
Int m_defaultHeight = 0;
|
||||
Int m_defaultLayers = 0;
|
||||
Int m_defaultSamples = 0;
|
||||
Bool m_defaultFixedSampleLocations = false;
|
||||
|
||||
// This version will bump when draw/read buffer changes (by `glDrawBuffer(s)`/`glReadBuffer`)
|
||||
Uint16 m_objectVersion = 0;
|
||||
};
|
||||
|
||||
@@ -195,27 +195,42 @@ namespace MobileGL::MG_State::GLState {
|
||||
static constexpr GLenum kIntTypes[5] = {0, GL_INT, GL_INT_VEC2, GL_INT_VEC3, GL_INT_VEC4};
|
||||
static constexpr GLenum kUintTypes[5] = {0, GL_UNSIGNED_INT, GL_UNSIGNED_INT_VEC2, GL_UNSIGNED_INT_VEC3,
|
||||
GL_UNSIGNED_INT_VEC4};
|
||||
static constexpr GLenum kDoubleTypes[5] = {0, GL_DOUBLE, GL_DOUBLE_VEC2, GL_DOUBLE_VEC3,
|
||||
GL_DOUBLE_VEC4};
|
||||
if (type.isMatrix()) {
|
||||
if (basic != glslang::EbtFloat) return false;
|
||||
if (basic != glslang::EbtFloat && basic != glslang::EbtDouble) return false;
|
||||
static constexpr GLenum kMatTypes[5][5] = {
|
||||
{}, {},
|
||||
{0, 0, GL_FLOAT_MAT2, GL_FLOAT_MAT2x3, GL_FLOAT_MAT2x4},
|
||||
{0, 0, GL_FLOAT_MAT3x2, GL_FLOAT_MAT3, GL_FLOAT_MAT3x4},
|
||||
{0, 0, GL_FLOAT_MAT4x2, GL_FLOAT_MAT4x3, GL_FLOAT_MAT4},
|
||||
};
|
||||
static constexpr GLenum kDoubleMatTypes[5][5] = {
|
||||
{}, {},
|
||||
{0, 0, GL_DOUBLE_MAT2, GL_DOUBLE_MAT2x3, GL_DOUBLE_MAT2x4},
|
||||
{0, 0, GL_DOUBLE_MAT3x2, GL_DOUBLE_MAT3, GL_DOUBLE_MAT3x4},
|
||||
{0, 0, GL_DOUBLE_MAT4x2, GL_DOUBLE_MAT4x3, GL_DOUBLE_MAT4},
|
||||
};
|
||||
if (columns < 2 || columns > 4 || components < 2 || components > 4) return false;
|
||||
outType = kMatTypes[columns][components];
|
||||
outType = basic == glslang::EbtDouble ? kDoubleMatTypes[columns][components]
|
||||
: kMatTypes[columns][components];
|
||||
} else if (components >= 1 && components <= 4) {
|
||||
switch (basic) {
|
||||
case glslang::EbtFloat: outType = kFloatTypes[components]; break;
|
||||
case glslang::EbtInt: outType = kIntTypes[components]; break;
|
||||
case glslang::EbtUint: outType = kUintTypes[components]; break;
|
||||
// A double-typed varying is capturable like any other; rejecting it here reported
|
||||
// the varying as "not an output of the vertex stage", which it plainly was.
|
||||
case glslang::EbtDouble: outType = kDoubleTypes[components]; break;
|
||||
default: return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
outBytesPerElement = static_cast<Uint32>(columns * components) * 4u;
|
||||
// GL 4.6 core 11.1.2.1: a double component occupies eight basic machine units, and
|
||||
// counts as two components against the transform feedback limits.
|
||||
const Uint32 bytesPerComponent = basic == glslang::EbtDouble ? 8u : 4u;
|
||||
outBytesPerElement = static_cast<Uint32>(columns * components) * bytesPerComponent;
|
||||
return true;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
// MobileGL - MobileGL/MG_State/GLState/ProgramState/ProgramPipelineObject.h
|
||||
// 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
|
||||
|
||||
#pragma once
|
||||
#include <Includes.h>
|
||||
#include "ProgramObject.h"
|
||||
|
||||
namespace MobileGL {
|
||||
namespace MG_State {
|
||||
namespace GLState {
|
||||
// A GL_ARB_separate_shader_objects program pipeline (GL 4.6 core 7.4): a set of
|
||||
// per-stage program references plus the program glProgramUniform* addresses when no
|
||||
// program object is in use.
|
||||
class ProgramPipelineObject {
|
||||
public:
|
||||
explicit ProgramPipelineObject(Uint externalIndex) : m_externalIndex(externalIndex) {}
|
||||
|
||||
const SharedPtr<ProgramObject>& GetStageProgram(ShaderStage stage) const {
|
||||
return m_stagePrograms[static_cast<SizeT>(stage)];
|
||||
}
|
||||
void SetStageProgram(ShaderStage stage, const SharedPtr<ProgramObject>& program) {
|
||||
m_stagePrograms[static_cast<SizeT>(stage)] = program;
|
||||
}
|
||||
|
||||
const SharedPtr<ProgramObject>& GetActiveProgram() const { return m_activeProgram; }
|
||||
void SetActiveProgram(const SharedPtr<ProgramObject>& program) { m_activeProgram = program; }
|
||||
|
||||
// Distinct from ProgramObject's, which starts true: a pipeline that has never been
|
||||
// validated must report GL_VALIDATE_STATUS as 0 (GL 4.6 core table 23.31).
|
||||
Bool GetValidateStatus() const { return m_validateStatus; }
|
||||
void SetValidateStatus(Bool value) { m_validateStatus = value; }
|
||||
|
||||
const String& GetInfoLog() const { return m_infoLog; }
|
||||
void SetInfoLog(String log) { m_infoLog = Move(log); }
|
||||
|
||||
Uint GetExternalIndex() const { return m_externalIndex; }
|
||||
|
||||
private:
|
||||
Array<SharedPtr<ProgramObject>, static_cast<SizeT>(ShaderStage::ShaderStageCount)> m_stagePrograms{};
|
||||
SharedPtr<ProgramObject> m_activeProgram;
|
||||
String m_infoLog;
|
||||
const Uint m_externalIndex = 0;
|
||||
Bool m_validateStatus = false;
|
||||
};
|
||||
} // namespace GLState
|
||||
} // namespace MG_State
|
||||
} // namespace MobileGL
|
||||
@@ -139,6 +139,61 @@ namespace MobileGL {
|
||||
return m_samplerParameters.maxAnisotropy;
|
||||
}
|
||||
|
||||
// The three border-colour representations are kept in step so a getter of any form has
|
||||
// an answer whichever form was written. Integer <-> float uses the plain value, matching
|
||||
// what glTexParameterIiv/Iuiv mean: those forms are for integer texture formats, whose
|
||||
// border components are the raw integers rather than a normalized fraction.
|
||||
void SamplerObject::SetBorderColor(const FloatVec4& color) {
|
||||
if (color == m_samplerParameters.borderColor) return;
|
||||
|
||||
m_samplerParameters.borderColor = color;
|
||||
m_samplerParameters.borderColorI =
|
||||
IntVec4(static_cast<Int32>(color.x()), static_cast<Int32>(color.y()),
|
||||
static_cast<Int32>(color.z()), static_cast<Int32>(color.w()));
|
||||
m_samplerParameters.borderColorUI =
|
||||
UintVec4(static_cast<Uint32>(color.x()), static_cast<Uint32>(color.y()),
|
||||
static_cast<Uint32>(color.z()), static_cast<Uint32>(color.w()));
|
||||
++m_version;
|
||||
}
|
||||
|
||||
void SamplerObject::SetBorderColorI(const IntVec4& color) {
|
||||
if (color == m_samplerParameters.borderColorI) return;
|
||||
|
||||
m_samplerParameters.borderColorI = color;
|
||||
m_samplerParameters.borderColorUI =
|
||||
UintVec4(static_cast<Uint32>(color.x()), static_cast<Uint32>(color.y()),
|
||||
static_cast<Uint32>(color.z()), static_cast<Uint32>(color.w()));
|
||||
m_samplerParameters.borderColor =
|
||||
FloatVec4(static_cast<Float>(color.x()), static_cast<Float>(color.y()),
|
||||
static_cast<Float>(color.z()), static_cast<Float>(color.w()));
|
||||
++m_version;
|
||||
}
|
||||
|
||||
void SamplerObject::SetBorderColorUI(const UintVec4& color) {
|
||||
if (color == m_samplerParameters.borderColorUI) return;
|
||||
|
||||
m_samplerParameters.borderColorUI = color;
|
||||
m_samplerParameters.borderColorI =
|
||||
IntVec4(static_cast<Int32>(color.x()), static_cast<Int32>(color.y()),
|
||||
static_cast<Int32>(color.z()), static_cast<Int32>(color.w()));
|
||||
m_samplerParameters.borderColor =
|
||||
FloatVec4(static_cast<Float>(color.x()), static_cast<Float>(color.y()),
|
||||
static_cast<Float>(color.z()), static_cast<Float>(color.w()));
|
||||
++m_version;
|
||||
}
|
||||
|
||||
const FloatVec4& SamplerObject::GetBorderColor() const {
|
||||
return m_samplerParameters.borderColor;
|
||||
}
|
||||
|
||||
const IntVec4& SamplerObject::GetBorderColorI() const {
|
||||
return m_samplerParameters.borderColorI;
|
||||
}
|
||||
|
||||
const UintVec4& SamplerObject::GetBorderColorUI() const {
|
||||
return m_samplerParameters.borderColorUI;
|
||||
}
|
||||
|
||||
SamplerCompareMode SamplerObject::GetCompareMode() const {
|
||||
return m_samplerParameters.compareMode;
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#pragma once
|
||||
#include <Includes.h>
|
||||
#include <MG_Util/Math/VectorTypes.h>
|
||||
|
||||
namespace MobileGL {
|
||||
enum class SamplerFilterMode {
|
||||
@@ -66,8 +67,18 @@ namespace MobileGL {
|
||||
Float maxLod = 1000.0f;
|
||||
Float lodBias = 0.0f;
|
||||
Float maxAnisotropy = 1.0f;
|
||||
SamplerCompareFunc compareFunc = SamplerCompareFunc::Always;
|
||||
// GL 4.6 core table 23.18 / GLES 3.2 table 21.16: TEXTURE_COMPARE_FUNC starts at LEQUAL,
|
||||
// for both sampler objects and the sampler state a texture object carries.
|
||||
SamplerCompareFunc compareFunc = SamplerCompareFunc::LessEqual;
|
||||
SamplerCompareMode compareMode = SamplerCompareMode::None;
|
||||
// TEXTURE_BORDER_COLOR is sampler state (GL 4.6 core table 23.18), so it belongs here and
|
||||
// not on the texture - a texture object reaches it through the sampler object it owns. The
|
||||
// three representations are the float, integer and unsigned-integer forms glSamplerParameterfv,
|
||||
// glSamplerParameterIiv and glSamplerParameterIuiv set; whichever is written last defines
|
||||
// the colour and the other two follow it, so a getter always has an answer.
|
||||
FloatVec4 borderColor = {0.0f, 0.0f, 0.0f, 0.0f};
|
||||
IntVec4 borderColorI = {0, 0, 0, 0};
|
||||
UintVec4 borderColorUI = {0, 0, 0, 0};
|
||||
};
|
||||
|
||||
namespace MG_State {
|
||||
@@ -87,6 +98,9 @@ namespace MobileGL {
|
||||
void SetMaxAnisotropy(Float maxAnisotropy);
|
||||
void SetSamplerCompareFunc(SamplerCompareFunc func);
|
||||
void SetCompareMode(SamplerCompareMode mode);
|
||||
void SetBorderColor(const FloatVec4& color);
|
||||
void SetBorderColorI(const IntVec4& color);
|
||||
void SetBorderColorUI(const UintVec4& color);
|
||||
|
||||
SamplerWrapMode GetWrapS() const;
|
||||
SamplerWrapMode GetWrapT() const;
|
||||
@@ -100,6 +114,9 @@ namespace MobileGL {
|
||||
Float GetMaxAnisotropy() const;
|
||||
SamplerCompareMode GetCompareMode() const;
|
||||
SamplerCompareFunc GetSamplerCompareFunc() const;
|
||||
const FloatVec4& GetBorderColor() const;
|
||||
const IntVec4& GetBorderColorI() const;
|
||||
const UintVec4& GetBorderColorUI() const;
|
||||
Uint GetExternalIndex() const;
|
||||
Uint16 GetVersion() const;
|
||||
// Globally-unique, never-reused id for this sampler object's lifetime. Lets a
|
||||
|
||||
@@ -88,48 +88,40 @@ namespace MobileGL {
|
||||
return m_externalIndex;
|
||||
}
|
||||
|
||||
// TEXTURE_BORDER_COLOR is sampler state, so it lives on the SamplerObject this texture
|
||||
// owns rather than being duplicated here - a sampler object bound over the texture then
|
||||
// supplies its own, exactly as GL says it should. The texture params version still moves
|
||||
// on a write, because the DirectGLES texture sync memoises on it.
|
||||
const FloatVec4& TextureObjectBase::GetBorderColor() const {
|
||||
return m_borderColor;
|
||||
return m_sampler->GetBorderColor();
|
||||
}
|
||||
|
||||
void TextureObjectBase::SetBorderColor(const FloatVec4& color) {
|
||||
if (color == m_borderColor) return;
|
||||
if (color == m_sampler->GetBorderColor()) return;
|
||||
|
||||
m_borderColor = color;
|
||||
m_borderColorI = IntVec4(static_cast<Int32>(color.x()), static_cast<Int32>(color.y()),
|
||||
static_cast<Int32>(color.z()), static_cast<Int32>(color.w()));
|
||||
m_borderColorUI = UintVec4(static_cast<Uint32>(color.x()), static_cast<Uint32>(color.y()),
|
||||
static_cast<Uint32>(color.z()), static_cast<Uint32>(color.w()));
|
||||
m_sampler->SetBorderColor(color);
|
||||
++m_textureParamsVersion;
|
||||
}
|
||||
|
||||
const IntVec4& TextureObjectBase::GetBorderColorI() const {
|
||||
return m_borderColorI;
|
||||
return m_sampler->GetBorderColorI();
|
||||
}
|
||||
|
||||
void TextureObjectBase::SetBorderColorI(const IntVec4& color) {
|
||||
if (color == m_borderColorI) return;
|
||||
if (color == m_sampler->GetBorderColorI()) return;
|
||||
|
||||
m_borderColorI = color;
|
||||
m_borderColorUI = UintVec4(static_cast<Uint32>(color.x()), static_cast<Uint32>(color.y()),
|
||||
static_cast<Uint32>(color.z()), static_cast<Uint32>(color.w()));
|
||||
m_borderColor = FloatVec4(static_cast<Float>(color.x()), static_cast<Float>(color.y()),
|
||||
static_cast<Float>(color.z()), static_cast<Float>(color.w()));
|
||||
m_sampler->SetBorderColorI(color);
|
||||
++m_textureParamsVersion;
|
||||
}
|
||||
|
||||
const UintVec4& TextureObjectBase::GetBorderColorUI() const {
|
||||
return m_borderColorUI;
|
||||
return m_sampler->GetBorderColorUI();
|
||||
}
|
||||
|
||||
void TextureObjectBase::SetBorderColorUI(const UintVec4& color) {
|
||||
if (color == m_borderColorUI) return;
|
||||
if (color == m_sampler->GetBorderColorUI()) return;
|
||||
|
||||
m_borderColorUI = color;
|
||||
m_borderColorI = IntVec4(static_cast<Int32>(color.x()), static_cast<Int32>(color.y()),
|
||||
static_cast<Int32>(color.z()), static_cast<Int32>(color.w()));
|
||||
m_borderColor = FloatVec4(static_cast<Float>(color.x()), static_cast<Float>(color.y()),
|
||||
static_cast<Float>(color.z()), static_cast<Float>(color.w()));
|
||||
m_sampler->SetBorderColorUI(color);
|
||||
++m_textureParamsVersion;
|
||||
}
|
||||
|
||||
|
||||
@@ -60,6 +60,10 @@ namespace MobileGL::MG_State::GLState {
|
||||
virtual Bool HasFixedSampleLocations() const = 0;
|
||||
virtual void SetFixedSampleLocations(Bool fixedSampleLocations) = 0;
|
||||
virtual Uint64 GetLifetimeId() const = 0;
|
||||
// Which aspect of a packed depth/stencil texture a sampler reads (GL 4.6 core 8.10).
|
||||
// DEPTH_COMPONENT until set, and meaningless for every other format.
|
||||
virtual GLenum GetDepthStencilTextureMode() const = 0;
|
||||
virtual void SetDepthStencilTextureMode(GLenum mode) = 0;
|
||||
|
||||
protected:
|
||||
virtual Uint GetIndexOfTextureUploadTarget(TextureUploadTarget target) const = 0;
|
||||
@@ -104,6 +108,8 @@ namespace MobileGL::MG_State::GLState {
|
||||
Bool HasFixedSampleLocations() const override;
|
||||
void SetFixedSampleLocations(Bool fixedSampleLocations) override;
|
||||
Uint64 GetLifetimeId() const override;
|
||||
GLenum GetDepthStencilTextureMode() const override { return m_depthStencilTextureMode; }
|
||||
void SetDepthStencilTextureMode(GLenum mode) override { m_depthStencilTextureMode = mode; }
|
||||
|
||||
protected:
|
||||
static Uint64 AllocateLifetimeId();
|
||||
@@ -113,9 +119,6 @@ namespace MobileGL::MG_State::GLState {
|
||||
const TextureTarget m_target = TextureTarget::Unknown;
|
||||
TextureInternalFormat m_internalFormat = TextureInternalFormat::Unknown;
|
||||
SharedPtr<SamplerObject> m_sampler = nullptr;
|
||||
FloatVec4 m_borderColor = {0.0f, 0.0f, 0.0f, 0.0f};
|
||||
IntVec4 m_borderColorI = {0, 0, 0, 0};
|
||||
UintVec4 m_borderColorUI = {0, 0, 0, 0};
|
||||
Vec4<TextureSwizzleParam> m_swizzleParams = {TextureSwizzleParam::Red, TextureSwizzleParam::Green,
|
||||
TextureSwizzleParam::Blue, TextureSwizzleParam::Alpha};
|
||||
UintVec2 m_levelRange = {0, 1000};
|
||||
@@ -124,6 +127,7 @@ namespace MobileGL::MG_State::GLState {
|
||||
// Starts at 1 so a freshly-created backend resource (snapshot 0) never spuriously
|
||||
// matches before its first sync. Bumped only on dirty=true in MarkStorageDirty.
|
||||
Uint64 m_contentVersion = 1;
|
||||
GLenum m_depthStencilTextureMode = GL_DEPTH_COMPONENT;
|
||||
Int m_samples = 0;
|
||||
Bool m_fixedSampleLocations = true;
|
||||
};
|
||||
|
||||
@@ -77,6 +77,26 @@ namespace MobileGL::MG_State::GLState {
|
||||
BumpAttributeFormatVersion(index);
|
||||
}
|
||||
|
||||
void VertexArrayObject::MirrorPointerIntoBinding(Uint index, const SharedPtr<BufferObject>& buffer, SizeT offset,
|
||||
int effectiveStride) {
|
||||
if (index >= MAX_VERTEX_ATTRIBS || index >= MAX_VERTEX_ATTRIB_BINDINGS) return;
|
||||
|
||||
// glVertexAttribPointer is defined in terms of the binding model (GL 4.6 core 10.3.2): it
|
||||
// also sets binding point `index` to the buffer, the pointer as the offset, and the
|
||||
// *effective* stride, and points the attribute at that binding point with relative offset 0.
|
||||
// The flat attribute view keeps the raw stride, because VERTEX_ATTRIB_ARRAY_STRIDE reports
|
||||
// that argument verbatim, so the binding point is recorded alongside the resolved attribute
|
||||
// rather than being resolved into it.
|
||||
m_attributeBindingIndex[index] = index;
|
||||
m_attributeRelativeOffset[index] = 0;
|
||||
|
||||
auto& binding = m_bindingPoints[index];
|
||||
binding.Buffer = buffer;
|
||||
binding.Offset = offset;
|
||||
binding.Stride = effectiveStride;
|
||||
binding.Divisor = m_attributes[index].Divisor;
|
||||
}
|
||||
|
||||
void VertexArrayObject::BindAttributeBuffer(Uint index, const SharedPtr<BufferObject>& buffer) {
|
||||
if (index >= MAX_VERTEX_ATTRIBS) return;
|
||||
|
||||
@@ -111,6 +131,11 @@ namespace MobileGL::MG_State::GLState {
|
||||
|
||||
void VertexArrayObject::SetAttributeDivisor(Uint index, Uint divisor) {
|
||||
if (index >= MAX_VERTEX_ATTRIBS) return;
|
||||
// glVertexAttribDivisor is VertexBindingDivisor on the attribute's own binding point
|
||||
// (GL 4.6 core 10.3.2), so the binding-point view has to follow the resolved attribute.
|
||||
if (index < MAX_VERTEX_ATTRIB_BINDINGS && m_attributeBindingIndex[index] == index) {
|
||||
m_bindingPoints[index].Divisor = divisor;
|
||||
}
|
||||
if (m_attributes[index].Divisor == divisor) return;
|
||||
m_attributes[index].Divisor = divisor;
|
||||
BumpAttributeFormatVersion(index);
|
||||
@@ -187,18 +212,18 @@ namespace MobileGL::MG_State::GLState {
|
||||
}
|
||||
|
||||
void VertexArrayObject::SetAttributeFormatSeparate(Uint attribIndex, int size, DataType type, Bool normalized,
|
||||
Bool isInteger, Uint relativeOffset) {
|
||||
Bool isInteger, Uint relativeOffset, Bool isBgra) {
|
||||
if (attribIndex >= MAX_VERTEX_ATTRIBS) return;
|
||||
if (size < 1 || size > 4) return;
|
||||
|
||||
auto& attr = m_attributes[attribIndex];
|
||||
if (attr.Size != size || attr.Type != type || attr.Normalized != normalized || attr.IsInteger != isInteger ||
|
||||
attr.IsBgra || m_attributeRelativeOffset[attribIndex] != relativeOffset) {
|
||||
attr.IsBgra != isBgra || m_attributeRelativeOffset[attribIndex] != relativeOffset) {
|
||||
attr.Size = size;
|
||||
attr.Type = type;
|
||||
attr.Normalized = normalized;
|
||||
attr.IsInteger = isInteger;
|
||||
attr.IsBgra = false; // the binding-format path (glVertexAttribFormat) does not carry BGRA
|
||||
attr.IsBgra = isBgra;
|
||||
m_attributeRelativeOffset[attribIndex] = relativeOffset;
|
||||
BumpAttributeFormatVersion(attribIndex);
|
||||
}
|
||||
|
||||
@@ -64,6 +64,12 @@ namespace MobileGL {
|
||||
|
||||
void BindAttributeBuffer(Uint index, const SharedPtr<BufferObject>& buffer);
|
||||
|
||||
// Record what the pointer-style API implies for the binding-point view: attribute
|
||||
// `index` bound to binding point `index` with relative offset 0, and that binding
|
||||
// point carrying the buffer, the pointer offset and the effective stride.
|
||||
void MirrorPointerIntoBinding(Uint index, const SharedPtr<BufferObject>& buffer, SizeT offset,
|
||||
int effectiveStride);
|
||||
|
||||
BindingSlot<BufferObject>& GetIndexBufferBindingSlot();
|
||||
const BindingSlot<BufferObject>& GetIndexBufferBindingSlot() const;
|
||||
|
||||
@@ -82,7 +88,7 @@ namespace MobileGL {
|
||||
void SetBindingDivisor(Uint bindingIndex, Uint divisor);
|
||||
void SetAttributeBinding(Uint attribIndex, Uint bindingIndex);
|
||||
void SetAttributeFormatSeparate(Uint attribIndex, int size, DataType type, Bool normalized,
|
||||
Bool isInteger, Uint relativeOffset);
|
||||
Bool isInteger, Uint relativeOffset, Bool isBgra = false);
|
||||
|
||||
// The binding-point view the attributes were resolved from. Kept queryable
|
||||
// because glGetVertexArrayIndexed[64]iv reports it verbatim, and the resolved
|
||||
|
||||
@@ -144,10 +144,19 @@ TEST_F(QueryTest, GenQueriesReturnsDistinctNonzeroIdsAndTracksLiveness) {
|
||||
EXPECT_NE(ids[0], ids[2]);
|
||||
EXPECT_NE(ids[1], ids[2]);
|
||||
|
||||
// GenQueries only reserves names: "they acquire query state only when they are first used by
|
||||
// calling BeginQuery" (GL 4.6 core 4.2.1), and IsQuery answers for objects, not for reserved
|
||||
// names. So all three read as FALSE here even though the names are taken.
|
||||
EXPECT_EQ(MG_Impl::GLImpl::IsQuery(0), GL_FALSE);
|
||||
EXPECT_EQ(MG_Impl::GLImpl::IsQuery(ids[0]), GL_FALSE);
|
||||
EXPECT_EQ(MG_Impl::GLImpl::IsQuery(ids[1]), GL_FALSE);
|
||||
EXPECT_EQ(MG_Impl::GLImpl::IsQuery(ids[2]), GL_FALSE);
|
||||
|
||||
// First use is what creates the object.
|
||||
MG_Impl::GLImpl::BeginQuery(GL_TIME_ELAPSED, ids[0]);
|
||||
MG_Impl::GLImpl::EndQuery(GL_TIME_ELAPSED);
|
||||
EXPECT_EQ(MG_Impl::GLImpl::IsQuery(ids[0]), GL_TRUE);
|
||||
EXPECT_EQ(MG_Impl::GLImpl::IsQuery(ids[1]), GL_TRUE);
|
||||
EXPECT_EQ(MG_Impl::GLImpl::IsQuery(ids[2]), GL_TRUE);
|
||||
EXPECT_EQ(MG_Impl::GLImpl::IsQuery(ids[1]), GL_FALSE);
|
||||
|
||||
MG_Impl::GLImpl::DeleteQueries(3, ids);
|
||||
EXPECT_EQ(MG_Impl::GLImpl::IsQuery(ids[0]), GL_FALSE);
|
||||
@@ -157,6 +166,26 @@ TEST_F(QueryTest, GenQueriesReturnsDistinctNonzeroIdsAndTracksLiveness) {
|
||||
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
|
||||
}
|
||||
|
||||
// The direct state access counterpart: glCreateQueries creates the object outright, so unlike a
|
||||
// glGenQueries name it is a query before anything is ever recorded into it.
|
||||
TEST_F(QueryTest, CreateQueriesYieldsQueryObjectsImmediately) {
|
||||
GLuint ids[2] = {0, 0};
|
||||
MG_Impl::GLImpl::CreateQueries(GL_TIME_ELAPSED, 2, ids);
|
||||
|
||||
EXPECT_NE(ids[0], 0u);
|
||||
EXPECT_NE(ids[1], 0u);
|
||||
EXPECT_NE(ids[0], ids[1]);
|
||||
|
||||
EXPECT_EQ(MG_Impl::GLImpl::IsQuery(ids[0]), GL_TRUE);
|
||||
EXPECT_EQ(MG_Impl::GLImpl::IsQuery(ids[1]), GL_TRUE);
|
||||
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
|
||||
|
||||
MG_Impl::GLImpl::DeleteQueries(2, ids);
|
||||
EXPECT_EQ(MG_Impl::GLImpl::IsQuery(ids[0]), GL_FALSE);
|
||||
EXPECT_EQ(MG_Impl::GLImpl::IsQuery(ids[1]), GL_FALSE);
|
||||
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
|
||||
}
|
||||
|
||||
TEST_F(QueryTest, TimeElapsedSpanFallsBackToImmediateZeroResult) {
|
||||
GLuint id = 0;
|
||||
MG_Impl::GLImpl::GenQueries(1, &id);
|
||||
|
||||
@@ -197,15 +197,15 @@ TEST(DirectGLESSanity, AdvertisesDepthTextureForGlmarkShadowScenes) {
|
||||
EXPECT_NE(std::find(extensions.begin(), extensions.end(), MobileGL::E_GL_ARB_depth_texture), extensions.end());
|
||||
}
|
||||
|
||||
// The advertised target went back to 3.3 (see "restore target GL version to 3.3"); Voxy only ever
|
||||
// needed the extensions, which stay advertised, so assert what the backend really reports.
|
||||
// Voxy only ever needed the extensions, which stay advertised whatever the version is; the version
|
||||
// assertion just pins what the backend really reports, now that V_OpenGL40 is in the list.
|
||||
TEST(DirectGLESSanity, AdvertisesVoxyRequiredRenderingExtensions) {
|
||||
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.Major, 4);
|
||||
EXPECT_EQ(rendererInfo.TargetGLVersion.Minor, 0);
|
||||
EXPECT_EQ(rendererInfo.TargetGLVersion.Patch, 0);
|
||||
|
||||
EXPECT_NE(std::find(extensions.begin(), extensions.end(), MobileGL::E_GL_ARB_compute_shader),
|
||||
@@ -244,6 +244,19 @@ TEST(DirectGLESSanity, BindingZeroClearsPreviousNativeTextureBinding) {
|
||||
ASSERT_NE(frontendTextureObject, nullptr);
|
||||
ASSERT_EQ(frontendTextureObject->GetExternalIndex(), frontendTexture);
|
||||
|
||||
// A texture with no image is incomplete, and an incomplete texture samples as (0, 0, 0, 1) -
|
||||
// which DirectGLES expresses by leaving the native target unbound (see "sample a
|
||||
// mipmap-incomplete texture as black"). This test is about the bind-0 clear, so the texture
|
||||
// has to be complete enough to get bound in the first place: a format plus a level 0. At 1x1
|
||||
// that single level is the whole mip chain, so it stays complete under any filter. The state
|
||||
// is set directly rather than through glTexImage2D because the mock GLES table below wires
|
||||
// only the binding entry points, not the upload path.
|
||||
frontendTextureObject->SetInternalFormat(TextureInternalFormat::RGBA8);
|
||||
MG_State::GLState::AsMipmapTexture(frontendTextureObject.get())
|
||||
->AllocateStorage(TextureUploadTarget::Texture2D, 0, {{1, 1, 1}, 4});
|
||||
ASSERT_FALSE(MG_State::GLState::SamplesAsIncompleteTexture(
|
||||
frontendTextureObject.get(), frontendTextureObject->GetSamplerObject().get()));
|
||||
|
||||
auto& backendTexture = DirectGLES::TextureImpl::g_backendTextureObjects.GetOrCreate(frontendTextureObject);
|
||||
backendTexture = MakeShared<DirectGLES::TextureImpl::BackendTextureObject>();
|
||||
const GLuint backendTextureId = backendTexture->GetBackendTextureId();
|
||||
@@ -399,14 +412,15 @@ TEST(DirectVulkanSanity, RenderPassExtentUsesSwapchainSizeOnlyForDefaultFramebuf
|
||||
MobileGL::IntVec2(512, 512));
|
||||
}
|
||||
|
||||
// See the DirectGLES twin above: the target version is 3.3 again, the extensions are what matter.
|
||||
// See the DirectGLES twin above: the target version follows the advertised list, the extensions are
|
||||
// what matter.
|
||||
TEST(DirectVulkanSanity, AdvertisesVoxyRequiredRenderingExtensions) {
|
||||
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.Major, 4);
|
||||
EXPECT_EQ(rendererInfo.TargetGLVersion.Minor, 0);
|
||||
EXPECT_EQ(rendererInfo.TargetGLVersion.Patch, 0);
|
||||
|
||||
EXPECT_NE(std::find(extensions.begin(), extensions.end(), MobileGL::E_GL_ARB_compute_shader),
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include <Config.h>
|
||||
#include <MG_Backend/BackendObjects.h>
|
||||
#include <MG_Backend/DirectGLES/Managers.h>
|
||||
#include <MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.h>
|
||||
#include <MG_Impl/GLImpl/Getter/GL_Getter.h>
|
||||
#include <MG_Impl/GLImpl/RenderState/GL_RenderState.h>
|
||||
#include <MG_Impl/GLImpl/Sampler/GL_Sampler.h>
|
||||
@@ -296,6 +297,18 @@ TEST_F(TextureTest, CopyTextureSubImage2DUsesNamedObjectAndRestoresBinding) {
|
||||
MG_Impl::GLImpl::CreateTextures(GL_TEXTURE_2D, 1, &boundTexture);
|
||||
MG_Impl::GLImpl::BindTextureUnit(0, boundTexture);
|
||||
|
||||
// The copy is only allowed to reach the backend when the destination region fits the level and
|
||||
// the read framebuffer can supply pixels, so the call has to be set up as a legal one.
|
||||
MG_Impl::GLImpl::TextureStorage2D(namedTexture, 3, GL_RGBA8, 16, 16);
|
||||
|
||||
GLuint readFramebuffer = 0;
|
||||
GLuint readTexture = 0;
|
||||
MG_Impl::GLImpl::CreateFramebuffers(1, &readFramebuffer);
|
||||
MG_Impl::GLImpl::CreateTextures(GL_TEXTURE_2D, 1, &readTexture);
|
||||
MG_Impl::GLImpl::TextureStorage2D(readTexture, 1, GL_RGBA8, 16, 16);
|
||||
MG_Impl::GLImpl::NamedFramebufferTexture(readFramebuffer, GL_COLOR_ATTACHMENT0, readTexture, 0);
|
||||
MG_Impl::GLImpl::BindFramebuffer(GL_READ_FRAMEBUFFER, readFramebuffer);
|
||||
|
||||
const auto boundBefore = MG_State::pGLContext->GetTextureUnitObject(0)
|
||||
.GetBindingSlot(TextureTarget::Texture2D)
|
||||
.GetBoundObject();
|
||||
|
||||
@@ -833,6 +833,10 @@ namespace MobileGL::MG_Util::BackendLoader {
|
||||
if (std::strcmp(extension, "GL_EXT_texture_filter_anisotropic") == 0) {
|
||||
caps.SupportsTextureFilterAnisotropy = true;
|
||||
}
|
||||
if (std::strcmp(extension, "GL_EXT_texture_border_clamp") == 0 ||
|
||||
std::strcmp(extension, "GL_OES_texture_border_clamp") == 0) {
|
||||
caps.SupportsTextureBorderClamp = true;
|
||||
}
|
||||
if (std::strcmp(extension, "GL_EXT_base_instance") == 0) {
|
||||
caps.SupportsBaseInstance = true;
|
||||
}
|
||||
@@ -1014,6 +1018,10 @@ namespace MobileGL::MG_Util::BackendLoader {
|
||||
}
|
||||
// Only legal to query once the extension has been seen in the loop above, hence not batched
|
||||
// with the unconditional probes: on a driver without it this raises GL_INVALID_ENUM.
|
||||
// Core from ES 3.2 on, whatever the extension string says.
|
||||
if (caps.GLESVersion.Major > 3 || (caps.GLESVersion.Major == 3 && caps.GLESVersion.Minor >= 2)) {
|
||||
caps.SupportsTextureBorderClamp = true;
|
||||
}
|
||||
if (caps.SupportsTextureFilterAnisotropy) {
|
||||
GLfloat maxTextureMaxAnisotropy = 1.0f;
|
||||
glesFuncs.glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &maxTextureMaxAnisotropy);
|
||||
@@ -1056,9 +1064,17 @@ namespace MobileGL::MG_Util::BackendLoader {
|
||||
caps.MaxComputeWorkGroupInvocations = maxComputeWorkGroupInvocations;
|
||||
caps.MaxShaderStorageBufferBindings = maxShaderStorageBufferBindings;
|
||||
caps.MaxTextureBufferSize = maxTextureBufferSize;
|
||||
// Through glesFuncs, like every other capability query here: a bare glGetIntegerv resolves
|
||||
// to MobileGL's own exported entry point, which answers this pname from the very
|
||||
// capability table being filled in - so the driver's real alignment never arrived and the
|
||||
// backend reported an unconstrained offset it cannot honour.
|
||||
GLint textureBufferOffsetAlignment = 1;
|
||||
glGetIntegerv(GL_TEXTURE_BUFFER_OFFSET_ALIGNMENT, &textureBufferOffsetAlignment);
|
||||
while (glGetError() != GL_NO_ERROR) {}
|
||||
glesFuncs.glGetIntegerv(GL_TEXTURE_BUFFER_OFFSET_ALIGNMENT, &textureBufferOffsetAlignment);
|
||||
// Core in ES 3.2 and in EXT_texture_buffer; an older context rejects the pname and leaves
|
||||
// the default in place.
|
||||
if (glesFuncs.glGetError) {
|
||||
while (glesFuncs.glGetError() != GL_NO_ERROR) {}
|
||||
}
|
||||
caps.TextureBufferOffsetAlignment = std::max(1, textureBufferOffsetAlignment);
|
||||
caps.MaxUniformBufferBindings = maxUniformBufferBindings;
|
||||
caps.MaxUniformBlockSize = maxUniformBlockSize;
|
||||
|
||||
@@ -1041,6 +1041,10 @@ namespace MobileGL {
|
||||
// GL_EXT_texture_filter_anisotropic is present, so sampler/texture
|
||||
// anisotropy may be forwarded without raising GL_INVALID_ENUM in GLES.
|
||||
Bool SupportsTextureFilterAnisotropy = false;
|
||||
// GL_TEXTURE_BORDER_COLOR and GL_CLAMP_TO_BORDER: ES 3.2 core, or
|
||||
// EXT/OES_texture_border_clamp before that. Without it every border-colour parameter
|
||||
// raises INVALID_ENUM on the driver, so the syncs have to be gated on it.
|
||||
Bool SupportsTextureBorderClamp = false;
|
||||
// GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT of the host driver; only queried when the
|
||||
// extension above is present, and left at 1.0 (no anisotropy) otherwise.
|
||||
Float MaxTextureMaxAnisotropy = 1.0f;
|
||||
|
||||
@@ -288,6 +288,35 @@ namespace MobileGL {
|
||||
return TextureInternalFormat::SRGB8;
|
||||
case GL_COMPRESSED_SRGB_ALPHA:
|
||||
return TextureInternalFormat::SRGB8Alpha8;
|
||||
// BPTC and ETC2/EAC follow the same deviation as RGTC above, for the same reason: they
|
||||
// are specific formats core GL requires (BPTC from 4.2, ETC2/EAC from 4.3) that this
|
||||
// stack has no compressor for. INVALID_ENUM was never a legal answer for them, and
|
||||
// uncompressed storage is the same trade the RGTC formats already take.
|
||||
case GL_COMPRESSED_RGBA_BPTC_UNORM:
|
||||
return TextureInternalFormat::RGBA8;
|
||||
case GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM:
|
||||
return TextureInternalFormat::SRGB8Alpha8;
|
||||
case GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT:
|
||||
case GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT:
|
||||
return TextureInternalFormat::RGB16F;
|
||||
case GL_COMPRESSED_RGB8_ETC2:
|
||||
return TextureInternalFormat::RGB8;
|
||||
case GL_COMPRESSED_SRGB8_ETC2:
|
||||
return TextureInternalFormat::SRGB8;
|
||||
case GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2:
|
||||
case GL_COMPRESSED_RGBA8_ETC2_EAC:
|
||||
return TextureInternalFormat::RGBA8;
|
||||
case GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2:
|
||||
case GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC:
|
||||
return TextureInternalFormat::SRGB8Alpha8;
|
||||
case GL_COMPRESSED_R11_EAC:
|
||||
return TextureInternalFormat::R8;
|
||||
case GL_COMPRESSED_SIGNED_R11_EAC:
|
||||
return TextureInternalFormat::R8Snorm;
|
||||
case GL_COMPRESSED_RG11_EAC:
|
||||
return TextureInternalFormat::RG8;
|
||||
case GL_COMPRESSED_SIGNED_RG11_EAC:
|
||||
return TextureInternalFormat::RG8Snorm;
|
||||
case GL_ALPHA:
|
||||
case GL_RED:
|
||||
return TextureInternalFormat::Red;
|
||||
|
||||
@@ -298,6 +298,17 @@ namespace MobileGL::MG_Util::SelfTest {
|
||||
"not supported; no impact: the native indirect path deliberately does not "
|
||||
"rely on it (shader-side emulation handles baseInstance semantics)");
|
||||
}
|
||||
if (caps.SupportsTextureBorderClamp) {
|
||||
builder.Pass("Texture border clamp",
|
||||
"supported (GL_TEXTURE_BORDER_COLOR reaches the driver, so "
|
||||
"GL_CLAMP_TO_BORDER samples the colour the application set)");
|
||||
} else {
|
||||
builder.Warn("Texture border clamp",
|
||||
"not supported (pre-ES 3.2 without GL_EXT/OES_texture_border_clamp); "
|
||||
"GL_TEXTURE_BORDER_COLOR is not synced to the driver at all, so anything "
|
||||
"sampling outside a GL_CLAMP_TO_BORDER texture reads the driver's default "
|
||||
"border instead of the requested colour");
|
||||
}
|
||||
if (glesFuncs.glPatchParameteri != nullptr) {
|
||||
builder.Pass("Tessellation patch parameters",
|
||||
"glPatchParameteri present (GL_PATCH_VERTICES reaches the driver)");
|
||||
|
||||
@@ -14,7 +14,7 @@ MobileGL is a *free* and *open-source* project that implements a desktop **OpenG
|
||||
|
||||
> [!NOTE]
|
||||
>
|
||||
> **Status:** In development. Parts of the codebase are incomplete. Current short-term target: **OpenGL 3.3 (Core Profile)**.
|
||||
> **Status:** In development. Parts of the codebase are incomplete. Current short-term target: **OpenGL 4.2 (Core Profile)**.
|
||||
|
||||
## Project positioning
|
||||
|
||||
@@ -47,9 +47,10 @@ Refer to each component's repository for exact license texts. Any bundled third-
|
||||
|
||||
## Compatibility & target
|
||||
|
||||
* **Short-term target:** `OpenGL 3.3 (Core Profile)`.
|
||||
* **Short-term target:** `OpenGL 4.2 (Core Profile)`.
|
||||
* **Current development focus:**
|
||||
* `MG_State` and `MG_Impl` for `OpenGL 3.3 (Core Profile)`
|
||||
* Performance improvement
|
||||
* `MG_State` and `MG_Impl` for `OpenGL 4.2 (Core Profile)`
|
||||
* `Direct (Vulkan)` backend
|
||||
* `Direct (OpenGL ES)` backend
|
||||
|
||||
@@ -163,11 +164,6 @@ MobileGL supports runtime configuration via environment variables.
|
||||
| `MOBILEGL_COHERENT_AS_FLUSH` | Treat persistent `GL_MAP_FLUSH_EXPLICIT_BIT` maps as coherent (app-compat for engines like Flywheel that never flush them). | `0`, `1` | `0` |
|
||||
| `VK_ICD_FILENAMES` | Select the Vulkan ICD used by the Vulkan loader. | Path to an ICD JSON file | Loader default |
|
||||
|
||||
## Notice
|
||||
|
||||
* MobileGL is **not** production-ready currently.
|
||||
* Some `OpenGL 3.3 (Core Profile)` features are still missing or under development.
|
||||
|
||||
## License
|
||||
|
||||
This project is distributed under **GNU LGPL v3.0**. See the `LICENSE` file in the repository for detailed information.
|
||||
|
||||
+65
-1
@@ -1,12 +1,16 @@
|
||||
# Running the OpenGL CTS against MobileGL
|
||||
|
||||
This directory contains two supported paths:
|
||||
This directory contains three supported paths:
|
||||
|
||||
- Android arm64 / MobileGL EGL: the KHR-GL33 workflow documented below and in
|
||||
`skills/gl-cts-on-mobilegl/SKILL.md`.
|
||||
- Windows x64 / MobileGL WGL: the GL30-GL46 pipeline in
|
||||
`scripts/wgl_glcts_pipeline.py`, documented by
|
||||
`skills/wgl-gl-cts-on-mobilegl/SKILL.md`.
|
||||
- Desktop Linux x64 / MobileGL EGL: `scripts/run_cts_local.py` against the
|
||||
`mobilegl-desktop` VK-GL-CTS target, documented in "Desktop Linux workflow"
|
||||
below and in `skills/linux-gl-cts-on-mobilegl/SKILL.md`. This is the path that
|
||||
needs no device and no GPU.
|
||||
|
||||
Windows prerequisites are Git, Python 3.9+, CMake, Visual Studio 2022's Desktop
|
||||
C++ workload, and a Vulkan SDK visible to CMake. DirectVulkan also needs a
|
||||
@@ -25,6 +29,66 @@ resumes individual suites after crashes/timeouts, and writes Markdown plus JSON
|
||||
reports below the printed `runs/<first-16-of-run-fingerprint>` directory. Its
|
||||
manifest records provenance and the runner settings used to validate a resume.
|
||||
|
||||
## Desktop Linux workflow
|
||||
|
||||
The `mobilegl-desktop` target builds `glcts` as an ordinary host executable that
|
||||
reaches OpenGL only through `libMobileGL.so`. Both backends run headless with no
|
||||
GPU at all, which makes this the cheapest way to measure a single test group
|
||||
while working on it.
|
||||
|
||||
Apt packages: `mesa-vulkan-drivers` (lavapipe, for DirectVulkan),
|
||||
`libegl1-mesa-dev` and `libgles2-mesa-dev` (the system EGL/ES that DirectGLES
|
||||
drives), `libvulkan-dev`, `ninja-build`.
|
||||
|
||||
```sh
|
||||
cmake -S . -B build-linux -G Ninja -DCMAKE_BUILD_TYPE=Release \
|
||||
-DMOBILEGL_BUILD_TEST=OFF -DMOBILEGL_BUILD_BENCHMARK=OFF
|
||||
cmake --build build-linux --parallel "$(nproc)"
|
||||
|
||||
python tools/cts/scripts/sync_to_cts.py "$CTS"
|
||||
git -C "$CTS" apply <path-to>/tools/cts/patches/0001-fbo-color-texture-attachment.patch
|
||||
cmake -S "$CTS" -B "$CTS/build-cts" -G Ninja -DDEQP_TARGET=mobilegl-desktop \
|
||||
-DCMAKE_BUILD_TYPE=Release
|
||||
ninja -C "$CTS/build-cts" glcts
|
||||
|
||||
python tools/cts/scripts/run_cts_local.py --backend DirectGLES \
|
||||
--glcts "$CTS/build-cts/external/openglcts/modules/glcts" \
|
||||
--lib build-linux/libMobileGL.so \
|
||||
--caselist cases.txt --outdir runs/gles --env EGL_PLATFORM=surfaceless
|
||||
python tools/cts/scripts/qpa_report.py runs/gles --label DirectGLES
|
||||
```
|
||||
|
||||
`EGL_PLATFORM=surfaceless` is not optional for DirectGLES: without a `/dev/dri`
|
||||
node Mesa's EGL fails `eglInitialize` on the default display, and MobileGL
|
||||
reports that as `EGL_BAD_ALLOC` out of `eglCreatePbufferSurface`. DirectVulkan
|
||||
needs nothing extra - lavapipe exposes `VK_EXT_headless_surface`, which is what
|
||||
the desktop platform port's pbuffer path requires.
|
||||
|
||||
Two known differences from a real GPU, both MobileGL's rather than the harness's:
|
||||
DirectVulkan reads back zeros from the **default** framebuffer (a user FBO,
|
||||
renderbuffer- or texture-attached, is correct on both backends), and lavapipe
|
||||
supports renderbuffer formats that Adreno reports as unsupported, so the Android
|
||||
runs see `NotSupported` where these do not.
|
||||
|
||||
### Reference results: KHR-GL45.direct_state_access
|
||||
|
||||
`opengl-cts-4.6.8.1`, the 371 `direct_state_access` cases of the `gl45-main`
|
||||
mustpass list, lavapipe / Mesa 25.2.8, `--deqp-surface-type=fbo`.
|
||||
|
||||
| backend | conformance | strict Pass | Fail | InternalError | Crash |
|
||||
| --- | ---: | ---: | ---: | ---: | ---: |
|
||||
| DirectGLES | **82.48%** | 74.93% | 57 | 8 | 0 |
|
||||
| DirectVulkan | **73.58%** | 73.32% | 87 | 8 | 3 |
|
||||
|
||||
`textures_storage_multisample_*` is most of what remains, and the two backends
|
||||
are there for different reasons. Espryt passes the whole `2d` half and fails only
|
||||
the `3d` one, which attaches a multisample **array** texture with
|
||||
`glFramebufferTextureLayer` - layered attachments are not something the
|
||||
framebuffer attachment model represents yet. Magma fails both halves: it cannot
|
||||
sample a multisample texture at all, so the second pass reads back nothing.
|
||||
`program_pipelines_*` needs ARB_separate_shader_objects, which is stubbed
|
||||
throughout.
|
||||
|
||||
## Android KHR-GL33 workflow
|
||||
|
||||
Goal: measure how much of the OpenGL 3.3 core-profile conformance suite MobileGL
|
||||
|
||||
@@ -17,3 +17,4 @@ Each skill is a self-contained package, matching the layout used by
|
||||
| --- | --- |
|
||||
| [gl-cts-on-mobilegl](gl-cts-on-mobilegl/SKILL.md) | Build VK-GL-CTS `glcts` as a standalone Android arm64 binary against MobileGL's own EGL, run KHR-GL33, and report a per-backend OpenGL 3.3 core conformance rate. |
|
||||
| [wgl-gl-cts-on-mobilegl](wgl-gl-cts-on-mobilegl/SKILL.md) | Build MobileGL's Windows x64 WGL drop-in, run GL30-GL46 core CTS against DirectGLES and DirectVulkan, resume safely, and emit validated reports. |
|
||||
| [linux-gl-cts-on-mobilegl](linux-gl-cts-on-mobilegl/SKILL.md) | Build `glcts` as a desktop Linux host binary against MobileGL, run any CTS group headlessly with no GPU, and report Espryt and Magma separately. The path to reach for while iterating on a fix. |
|
||||
|
||||
@@ -0,0 +1,159 @@
|
||||
---
|
||||
name: linux-gl-cts-on-mobilegl
|
||||
description: Run the Khronos OpenGL CTS (VK-GL-CTS glcts) against MobileGL on desktop Linux with no GPU and no device, and report a per-backend conformance rate for Espryt (DirectGLES) and Magma (DirectVulkan). Use when measuring or iterating on the conformance of one test group, when a fix needs a before/after number, or when neither an Android device nor a Windows GPU box is available.
|
||||
---
|
||||
|
||||
# OpenGL CTS on MobileGL (desktop Linux)
|
||||
|
||||
## Overview
|
||||
|
||||
`glcts` is built as an ordinary x86-64 host executable that reaches OpenGL only
|
||||
through `libMobileGL.so`, using the `mobilegl-desktop` VK-GL-CTS target and the
|
||||
`tcu::Platform` port in `tools/cts/platform/`. Nothing links `libGL` or `libEGL`,
|
||||
so a result is unambiguously MobileGL's.
|
||||
|
||||
Both backends run headless on software rendering, so this needs no GPU at all:
|
||||
|
||||
- **Espryt** (`DirectGLES`) drives Mesa's OpenGL ES through the system EGL.
|
||||
- **Magma** (`DirectVulkan`) runs on lavapipe, whose `VK_EXT_headless_surface`
|
||||
is what the desktop platform port's pbuffer path requires.
|
||||
|
||||
Always report the two backends **separately**. They are different
|
||||
implementations of the same front end, they fail different cases, and a single
|
||||
combined number hides which one a change moved.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
```sh
|
||||
sudo apt-get install -y ninja-build cmake libvulkan-dev \
|
||||
libegl1-mesa-dev libgles2-mesa-dev mesa-vulkan-drivers
|
||||
```
|
||||
|
||||
`mesa-vulkan-drivers` is what installs lavapipe; without it DirectVulkan has no
|
||||
ICD and `eglInitialize` fails inside the backend. A C++23 toolchain is required
|
||||
(GCC 13+, or Clang 20+ — Clang 18 defines `__cpp_concepts` as 201907L, which
|
||||
switches libstdc++'s `<expected>` off and the build fails in
|
||||
`MG_Util/ShaderTranspiler/Types.h`).
|
||||
|
||||
```sh
|
||||
export MG=<path-to-MobileGL-worktree>
|
||||
export CTS=<path-to-VK-GL-CTS-checkout>
|
||||
```
|
||||
|
||||
## Step 1 — build libMobileGL.so
|
||||
|
||||
```sh
|
||||
git -C "$MG" submodule update --init --recursive
|
||||
python3 "$MG/3rdparty/glslang/update_glslang_sources.py" # SPIRV-Tools; ENABLE_OPT is forced on
|
||||
|
||||
cmake -S "$MG" -B "$MG/build-linux" -G Ninja -DCMAKE_BUILD_TYPE=Release \
|
||||
-DMOBILEGL_BUILD_TEST=OFF -DMOBILEGL_BUILD_BENCHMARK=OFF \
|
||||
-DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache
|
||||
cmake --build "$MG/build-linux" --parallel "$(nproc)"
|
||||
```
|
||||
|
||||
Add `-DCMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE=FALSE` when iterating. The
|
||||
Release configuration turns LTO on, which makes every relink cost minutes for no
|
||||
behavioural difference; a conformance number is identical either way.
|
||||
|
||||
## Step 2 — get VK-GL-CTS and build glcts
|
||||
|
||||
Use a release tag so the mustpass list, and therefore the reported rate, is
|
||||
citable.
|
||||
|
||||
```sh
|
||||
git -C "$CTS" checkout opengl-cts-4.6.8.1
|
||||
python3 "$CTS/external/fetch_sources.py"
|
||||
|
||||
python3 "$MG/tools/cts/scripts/sync_to_cts.py" "$CTS"
|
||||
git -C "$CTS" apply "$MG/tools/cts/patches/0001-fbo-color-texture-attachment.patch"
|
||||
|
||||
cmake -S "$CTS" -B "$CTS/build-cts" -G Ninja -DDEQP_TARGET=mobilegl-desktop \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache
|
||||
ninja -C "$CTS/build-cts" glcts
|
||||
```
|
||||
|
||||
Confirm the configure output says `*** Using MobileGL desktop target`. Budget a
|
||||
couple of hours for the `glcts` link on a small machine; it is a one-time cost
|
||||
that ccache makes cheap afterwards.
|
||||
|
||||
If `fetch_sources.py` dies with `HTTP Error 403` it is an egress policy blocking
|
||||
the GitHub archive downloads (zlib, libpng), not a broken checkout: `git clone`
|
||||
still works, so clone the package at the tag the script pins into
|
||||
`external/<pkg>/src` by hand — for libpng also copy
|
||||
`scripts/pnglibconf.h.prebuilt` to `src/pnglibconf.h`, which is what the
|
||||
script's post-extract step does.
|
||||
|
||||
## Step 3 — run, once per backend
|
||||
|
||||
```sh
|
||||
cd "$MG"
|
||||
for BACKEND in DirectGLES DirectVulkan; do
|
||||
python3 tools/cts/scripts/run_cts_local.py --backend "$BACKEND" \
|
||||
--glcts "$CTS/build-cts/external/openglcts/modules/glcts" \
|
||||
--lib build-linux/libMobileGL.so \
|
||||
--caselist cases.txt --outdir "runs/${BACKEND}" \
|
||||
--env EGL_PLATFORM=surfaceless
|
||||
python3 tools/cts/scripts/qpa_report.py "runs/${BACKEND}" --label "$BACKEND"
|
||||
done
|
||||
```
|
||||
|
||||
`cases.txt` is any subset of a mustpass list. For one group, filter the list
|
||||
rather than running the whole suite:
|
||||
|
||||
```sh
|
||||
grep direct_state_access \
|
||||
"$CTS"/external/openglcts/data/gl_cts/data/mustpass/gl/khronos_mustpass/main/gl45-main.txt \
|
||||
> cases.txt
|
||||
```
|
||||
|
||||
`run_cts_local.py` re-invokes `glcts` with only the cases that have no result
|
||||
yet, so a crash costs one case rather than the run, and it records the case that
|
||||
was open when the process died as `Crash`. `qpa_report.py` scores `Pass`,
|
||||
`NotSupported` and the warning statuses as non-failures, the way Khronos scores
|
||||
a submission, and reports the strict `Pass`-only rate alongside.
|
||||
|
||||
## Required flags, and why
|
||||
|
||||
| Flag | Why it is not optional |
|
||||
| --- | --- |
|
||||
| `--env EGL_PLATFORM=surfaceless` | For DirectGLES. With no `/dev/dri` node Mesa's EGL fails `eglInitialize` on the default display, and MobileGL surfaces that as `EGL_BAD_ALLOC` out of `eglCreatePbufferSurface` — which points at the wrong call entirely. Harmless for DirectVulkan, so pass it to both. |
|
||||
| `--deqp-surface-type=fbo` (the runner's default) | DirectVulkan reads back zeros from the **default** framebuffer. dEQP verifies nearly everything through `glReadPixels`, so rendering to the surface scores Magma near zero for a reason unrelated to conformance. Use it for both backends so the two numbers stay comparable. |
|
||||
| `--deqp-terminate-on-device-lost=disable` (supplied by the runner) | Its default calls `glGetGraphicsResetStatus()` after every case. That is GL 4.5 / `KHR_robustness`, absent from what MobileGL exports, so the pointer is null and the process segfaults on the first case. |
|
||||
|
||||
## What this environment does and does not tell you
|
||||
|
||||
Reproducible here, and MobileGL's own rather than a driver quirk:
|
||||
|
||||
- DirectVulkan's default-framebuffer readback returns zeros; a user FBO,
|
||||
renderbuffer- or texture-attached, is correct on both backends. This is the
|
||||
same defect the Android runs work around, so it can be debugged without a
|
||||
phone.
|
||||
|
||||
Different from a real GPU, so do not read conformance into it:
|
||||
|
||||
- lavapipe supports renderbuffer formats Adreno reports as unsupported, so the
|
||||
Android runs see `NotSupported` where these do not, and vice versa.
|
||||
- Backend limits differ. `GL_TEXTURE_BUFFER_OFFSET_ALIGNMENT` is 16 on llvmpipe
|
||||
and on lavapipe; a device that reports 1 will not exercise the same paths.
|
||||
- Everything is a software rasterizer, so a case that fails only under real
|
||||
timing or real tiling will not fail here.
|
||||
|
||||
## Reference results: KHR-GL45.direct_state_access
|
||||
|
||||
`opengl-cts-4.6.8.1`, the 371 `direct_state_access` cases of the `gl45-main`
|
||||
mustpass list, Mesa 25.2.8, `--deqp-surface-type=fbo`.
|
||||
|
||||
| backend | renderer | conformance | strict Pass | Fail | InternalError | Crash |
|
||||
| --- | --- | ---: | ---: | ---: | ---: | ---: |
|
||||
| DirectGLES | Espryt | **82.48%** | 74.93% | 57 | 8 | 0 |
|
||||
| DirectVulkan | Magma | **73.58%** | 73.32% | 87 | 8 | 3 |
|
||||
|
||||
## Contents
|
||||
|
||||
platform/tcuMobileGLPlatform.{cpp,hpp} dEQP tcu::Platform for MobileGL
|
||||
targets/mobilegl-desktop.cmake VK-GL-CTS target (-DDEQP_TARGET=mobilegl-desktop)
|
||||
scripts/sync_to_cts.py inject the port into a CTS checkout
|
||||
scripts/run_cts_local.py crash-resuming local-host runner
|
||||
scripts/qpa_report.py .qpa -> conformance rate
|
||||
@@ -0,0 +1,4 @@
|
||||
interface:
|
||||
display_name: "OpenGL CTS on MobileGL (desktop Linux)"
|
||||
short_description: "Run VK-GL-CTS glcts against MobileGL headlessly and report Espryt and Magma separately"
|
||||
default_prompt: "Use $linux-gl-cts-on-mobilegl to run the selected OpenGL CTS group against MobileGL on this Linux host and report the conformance rate for DirectGLES (Espryt) and DirectVulkan (Magma) separately."
|
||||
Reference in New Issue
Block a user