mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-12 06:08:30 +09:00
Compare commits
10
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4322427e78 | ||
|
|
203d4bce5e | ||
|
|
761114d022 | ||
|
|
9caf34d5b1 | ||
|
|
5e676b338b | ||
|
|
db01bfa3e8 | ||
|
|
cc3dcfd80e | ||
|
|
d9556ff041 | ||
|
|
39f21e52ea | ||
|
|
0e933b8f2f |
@@ -201,11 +201,6 @@ fetch_file_from_mirror() {
|
|||||||
return 1
|
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() {
|
fetch_from_mirror() {
|
||||||
mkdir -p "${fixture_dir}"
|
mkdir -p "${fixture_dir}"
|
||||||
for file in "${files[@]}"; do
|
for file in "${files[@]}"; do
|
||||||
@@ -224,19 +219,17 @@ fetch_from_mirror() {
|
|||||||
echo "Mirror did not serve ${name}; trying the next mirror" >&2
|
echo "Mirror did not serve ${name}; trying the next mirror" >&2
|
||||||
done
|
done
|
||||||
if [ "${fetched}" -ne 1 ]; then
|
if [ "${fetched}" -ne 1 ]; then
|
||||||
mirror_failures+=("${file}")
|
return 1
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
[ "${#mirror_failures[@]}" -eq 0 ]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if fetch_from_mirror; then
|
if fetch_from_mirror; then
|
||||||
echo "Fetched trace fixture files for ${case_name} from mirror: ${include}"
|
echo "Fetched trace fixture files for ${case_name} from mirror: ${include}"
|
||||||
else
|
else
|
||||||
fallback_include="$(IFS=,; echo "${mirror_failures[*]}")"
|
echo "All mirrors failed for ${case_name}; falling back to Git LFS: ${include}"
|
||||||
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 install --local
|
||||||
git lfs pull --include="${fallback_include}" --exclude=""
|
git lfs pull --include="${include}" --exclude=""
|
||||||
fi
|
fi
|
||||||
|
|
||||||
for file in "${files[@]}"; do
|
for file in "${files[@]}"; do
|
||||||
|
|||||||
@@ -25,5 +25,3 @@ MobileGL/MG*/cmake-build*
|
|||||||
/android-plugin/app/src/trace/jniLibs
|
/android-plugin/app/src/trace/jniLibs
|
||||||
/android-plugin/local.properties
|
/android-plugin/local.properties
|
||||||
tools/trace_replay/work/
|
tools/trace_replay/work/
|
||||||
__pycache__/
|
|
||||||
*.py[cod]
|
|
||||||
|
|||||||
Vendored
+1
-1
Submodule 3rdparty/glslang updated: 900b29d449...26fe5ceb45
+3
-17
@@ -190,12 +190,13 @@ set(SOURCE_FILES
|
|||||||
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/DecomposeWorkgroupVec3Pass.cpp
|
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/DecomposeWorkgroupVec3Pass.cpp
|
||||||
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/DecoratePositionInvariantPass.cpp
|
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/DecoratePositionInvariantPass.cpp
|
||||||
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/LowerDrawParametersPass.cpp
|
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/LowerDrawParametersPass.cpp
|
||||||
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/PackDoubleVertexInputsPass.cpp
|
|
||||||
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/RebaseInstanceIndexPass.cpp
|
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/RebaseInstanceIndexPass.cpp
|
||||||
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/NormalizeRectCoordinatesPass.cpp
|
|
||||||
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/StripUboMemberRelaxedPrecisionPass.cpp
|
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/StripUboMemberRelaxedPrecisionPass.cpp
|
||||||
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/StripNoPerspectivePass.cpp
|
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/StripNoPerspectivePass.cpp
|
||||||
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/EmulateNoPerspectivePass.cpp
|
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/EmulateNoPerspectivePass.cpp
|
||||||
|
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/FoldConstOffsetFor1DFetchPass.cpp
|
||||||
|
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/LowerClipDistanceForEsslPass.cpp
|
||||||
|
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/DefeatConstStructArrayLutPass.cpp
|
||||||
|
|
||||||
MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.cpp
|
MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.cpp
|
||||||
MobileGL/MG_Util/BackendLoaders/Vulkan/Loader.cpp
|
MobileGL/MG_Util/BackendLoaders/Vulkan/Loader.cpp
|
||||||
@@ -219,7 +220,6 @@ set(SOURCE_FILES
|
|||||||
MobileGL/MG_Impl/GLImpl/Framebuffer/Validators.cpp
|
MobileGL/MG_Impl/GLImpl/Framebuffer/Validators.cpp
|
||||||
MobileGL/MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.cpp
|
MobileGL/MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.cpp
|
||||||
MobileGL/MG_Impl/GLImpl/Program/GL_Program.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/GL_Texture.cpp
|
||||||
MobileGL/MG_Impl/GLImpl/Texture/Validators.cpp
|
MobileGL/MG_Impl/GLImpl/Texture/Validators.cpp
|
||||||
MobileGL/MG_Impl/GLImpl/Texture/ProxyTexture.cpp
|
MobileGL/MG_Impl/GLImpl/Texture/ProxyTexture.cpp
|
||||||
@@ -458,21 +458,8 @@ if (ANDROID)
|
|||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (APPLE AND NOT MOBILEGL_IOS)
|
if (APPLE AND NOT MOBILEGL_IOS)
|
||||||
# MobileGL statically embeds glslang, SPIRV-Tools, and SPIRV-Cross. When
|
|
||||||
# this dylib is injected with DYLD_INSERT_LIBRARIES, exporting those C++
|
|
||||||
# symbols interposes incompatible copies embedded by host libraries such
|
|
||||||
# as shaderc. Keep only the public GL/EGL/CGL loader surface globally
|
|
||||||
# visible; GetProcAddress can still return pointers to hidden internals.
|
|
||||||
set(MOBILEGL_MACOS_EXPORTED_SYMBOLS
|
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/MobileGL/MG_Impl/DyldInterpose/ExportedSymbols.txt")
|
|
||||||
target_link_options(${CMAKE_PROJECT_NAME} PRIVATE
|
|
||||||
"LINKER:-exported_symbols_list,${MOBILEGL_MACOS_EXPORTED_SYMBOLS}")
|
|
||||||
set_property(TARGET ${CMAKE_PROJECT_NAME} APPEND PROPERTY
|
|
||||||
LINK_DEPENDS "${MOBILEGL_MACOS_EXPORTED_SYMBOLS}")
|
|
||||||
|
|
||||||
target_link_libraries(${CMAKE_PROJECT_NAME} PUBLIC
|
target_link_libraries(${CMAKE_PROJECT_NAME} PUBLIC
|
||||||
"-framework Cocoa"
|
"-framework Cocoa"
|
||||||
"-framework CoreVideo"
|
|
||||||
"-framework QuartzCore"
|
"-framework QuartzCore"
|
||||||
"-framework Foundation"
|
"-framework Foundation"
|
||||||
"-framework OpenGL"
|
"-framework OpenGL"
|
||||||
@@ -480,7 +467,6 @@ if (APPLE AND NOT MOBILEGL_IOS)
|
|||||||
if(TARGET ${CMAKE_PROJECT_NAME}_s)
|
if(TARGET ${CMAKE_PROJECT_NAME}_s)
|
||||||
target_link_libraries(${CMAKE_PROJECT_NAME}_s PUBLIC
|
target_link_libraries(${CMAKE_PROJECT_NAME}_s PUBLIC
|
||||||
"-framework Cocoa"
|
"-framework Cocoa"
|
||||||
"-framework CoreVideo"
|
|
||||||
"-framework QuartzCore"
|
"-framework QuartzCore"
|
||||||
"-framework Foundation"
|
"-framework Foundation"
|
||||||
"-framework OpenGL"
|
"-framework OpenGL"
|
||||||
|
|||||||
+6
-1
@@ -14,7 +14,7 @@ namespace MobileGL::MG_Config {
|
|||||||
inline const String ProjectName = "MobileGL";
|
inline const String ProjectName = "MobileGL";
|
||||||
inline const String CoreName = "MobileGL Core";
|
inline const String CoreName = "MobileGL Core";
|
||||||
inline const String CoreVendor = "MobileGL-Dev (BZLZHH, Swung0x48, Tungsten)";
|
inline const String CoreVendor = "MobileGL-Dev (BZLZHH, Swung0x48, Tungsten)";
|
||||||
inline const Version CoreVersion = {26, 8, 0, "-dev", VersionType::Development};
|
inline const Version CoreVersion = {26, 7, 0, "-dev", VersionType::Development};
|
||||||
inline const VersionStringFormatAttrib DefaultVersionStringFormatAttrib = {2, 2, 0, true, true};
|
inline const VersionStringFormatAttrib DefaultVersionStringFormatAttrib = {2, 2, 0, true, true};
|
||||||
inline const Uint64 CacheVersion = 0;
|
inline const Uint64 CacheVersion = 0;
|
||||||
|
|
||||||
@@ -80,6 +80,11 @@ namespace MobileGL::MG_Config {
|
|||||||
// rewrites the recognized workgroup prefix-scan template on Qualcomm devices with
|
// rewrites the recognized workgroup prefix-scan template on Qualcomm devices with
|
||||||
// subgroups wider than 32 lanes (see ShaderSourceProcessor's quirk registry).
|
// subgroups wider than 32 lanes (see ShaderSourceProcessor's quirk registry).
|
||||||
QuirkOverride SubgroupPrefixScanQuirk = QuirkOverride::Auto;
|
QuirkOverride SubgroupPrefixScanQuirk = QuirkOverride::Auto;
|
||||||
|
// MOBILEGL_QUIRK_CLIP_DISTANCE: overrides the DirectGLES quirk that lowers
|
||||||
|
// gl_ClipDistance for Adreno's ESSL compiler (shadow Private arrays with
|
||||||
|
// constant-index builtin flushes, dynamic-index gl_in copy loop, redeclaration
|
||||||
|
// strip, and const struct-array LUT splitting). Auto detects Qualcomm.
|
||||||
|
QuirkOverride ClipDistanceQuirk = QuirkOverride::Auto;
|
||||||
// MOBILEGL_MAGMA_DISABLE_BLENDED_DEPTH_WRITE: overrides the DirectVulkan quirk that
|
// MOBILEGL_MAGMA_DISABLE_BLENDED_DEPTH_WRITE: overrides the DirectVulkan quirk that
|
||||||
// strips depth writes from accumulation-blended pipelines (MIN/MAX or additive
|
// strips depth writes from accumulation-blended pipelines (MIN/MAX or additive
|
||||||
// ONE+ONE - the multi-pass depth-equality signature) on drivers without
|
// ONE+ONE - the multi-pass depth-equality signature) on drivers without
|
||||||
|
|||||||
@@ -135,6 +135,7 @@ namespace MobileGL::MG_ConfigLoader {
|
|||||||
features.DisableUboRing = QueryEnvFlag("MOBILEGL_DISABLE_UBO_RING");
|
features.DisableUboRing = QueryEnvFlag("MOBILEGL_DISABLE_UBO_RING");
|
||||||
features.RelaxedSemantics = QueryEnvFlag("MOBILEGL_RELAXED_SEMANTICS");
|
features.RelaxedSemantics = QueryEnvFlag("MOBILEGL_RELAXED_SEMANTICS");
|
||||||
features.SubgroupPrefixScanQuirk = QueryEnvQuirkOverride("MOBILEGL_QUIRK_SUBGROUP_PREFIX_SCAN");
|
features.SubgroupPrefixScanQuirk = QueryEnvQuirkOverride("MOBILEGL_QUIRK_SUBGROUP_PREFIX_SCAN");
|
||||||
|
features.ClipDistanceQuirk = QueryEnvQuirkOverride("MOBILEGL_QUIRK_CLIP_DISTANCE");
|
||||||
features.MagmaDisableBlendedDepthWriteQuirk =
|
features.MagmaDisableBlendedDepthWriteQuirk =
|
||||||
QueryEnvQuirkOverride("MOBILEGL_MAGMA_DISABLE_BLENDED_DEPTH_WRITE");
|
QueryEnvQuirkOverride("MOBILEGL_MAGMA_DISABLE_BLENDED_DEPTH_WRITE");
|
||||||
features.DisableRobustBufferAccess = QueryEnvFlag("MOBILEGL_DISABLE_ROBUST_BUFFER_ACCESS");
|
features.DisableRobustBufferAccess = QueryEnvFlag("MOBILEGL_DISABLE_ROBUST_BUFFER_ACCESS");
|
||||||
|
|||||||
+4
-13
@@ -14,7 +14,6 @@
|
|||||||
#include <MG_State/EGLState/Core.h>
|
#include <MG_State/EGLState/Core.h>
|
||||||
#include <MG_Impl/GLImpl/Texture/ProxyTexture.h>
|
#include <MG_Impl/GLImpl/Texture/ProxyTexture.h>
|
||||||
#include <MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.h>
|
#include <MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.h>
|
||||||
#include <MG_Impl/GLImpl/Sync/GL_Sync.h>
|
|
||||||
|
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
@@ -38,12 +37,6 @@ namespace MobileGL {
|
|||||||
MGLOG_I("MobileGL closing...");
|
MGLOG_I("MobileGL closing...");
|
||||||
}
|
}
|
||||||
glslang::FinalizeProcess();
|
glslang::FinalizeProcess();
|
||||||
// GL syncs die with their contexts, and every context is gone by the
|
|
||||||
// time full teardown runs: drain the live-sync registry while the
|
|
||||||
// backend function table can still release the backend handles (and
|
|
||||||
// before a re-initialized library could pair them with the wrong
|
|
||||||
// backend's DeleteSync).
|
|
||||||
MG_Impl::GLImpl::DestroyAllSyncObjects();
|
|
||||||
MG_Backend::pActiveBackendObject.reset();
|
MG_Backend::pActiveBackendObject.reset();
|
||||||
MG_State::pGLContext.reset();
|
MG_State::pGLContext.reset();
|
||||||
MG_State::pEGLContext.reset();
|
MG_State::pEGLContext.reset();
|
||||||
@@ -107,11 +100,9 @@ namespace MobileGL {
|
|||||||
// (EGL/WGL/CGL): initialization happens lazily on the first entry point
|
// (EGL/WGL/CGL): initialization happens lazily on the first entry point
|
||||||
// via EnsureInitialized(), and full teardown happens deterministically
|
// via EnsureInitialized(), and full teardown happens deterministically
|
||||||
// when the last EGL display is terminated with nothing current (EGLImpl
|
// when the last EGL display is terminated with nothing current (EGLImpl
|
||||||
// calls Destroy()). There is intentionally no backend-initializing static
|
// calls Destroy()). There is intentionally no static constructor, no
|
||||||
// constructor, no static destructor, and no DllMain: the global singletons
|
// static destructor, and no DllMain: the global singletons use
|
||||||
// use leak-at-exit storage (see GlobalObjects.cpp), so a process that exits
|
// leak-at-exit storage (see GlobalObjects.cpp), so a process that exits
|
||||||
// without eglTerminate simply leaks them to the OS instead of running
|
// without eglTerminate simply leaks them to the OS instead of running
|
||||||
// backend destructors during static teardown. macOS has a lightweight
|
// backend destructors during static teardown.
|
||||||
// dyld constructor that installs NSOpenGL dispatch hooks only; full backend
|
|
||||||
// initialization still enters here from the first hooked CGL context.
|
|
||||||
} // namespace MobileGL
|
} // namespace MobileGL
|
||||||
|
|||||||
+3
-4
@@ -13,10 +13,9 @@ namespace MobileGL {
|
|||||||
void Initialize();
|
void Initialize();
|
||||||
// Thread-safe, idempotent, and re-entrant wrapper around Initialize().
|
// Thread-safe, idempotent, and re-entrant wrapper around Initialize().
|
||||||
// Host layers (EGL/WGL/CGL entry points) call this lazily on first use so
|
// Host layers (EGL/WGL/CGL entry points) call this lazily on first use so
|
||||||
// full backend initialization never depends on ELF/DLL static constructors,
|
// MobileGL's lifecycle never depends on ELF/DLL static constructors, and
|
||||||
// and so a fresh init can follow a full Destroy() (e.g. after the last
|
// so a fresh init can follow a full Destroy() (e.g. after the last
|
||||||
// eglTerminate). The macOS dyld bootstrap installs only lightweight
|
// eglTerminate).
|
||||||
// NSOpenGL method hooks.
|
|
||||||
void EnsureInitialized();
|
void EnsureInitialized();
|
||||||
void Destroy();
|
void Destroy();
|
||||||
|
|
||||||
|
|||||||
@@ -145,10 +145,6 @@ namespace MobileGL {
|
|||||||
GLenum buffer, GLint drawbuffer, const GLfloat* value);
|
GLenum buffer, GLint drawbuffer, const GLfloat* value);
|
||||||
void (*ClearNamedFramebufferfi)(const SharedPtr<MG_State::GLState::FramebufferObject>& framebuffer,
|
void (*ClearNamedFramebufferfi)(const SharedPtr<MG_State::GLState::FramebufferObject>& framebuffer,
|
||||||
GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil);
|
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,
|
void (*BlitFramebuffer)(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0,
|
||||||
GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter);
|
GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter);
|
||||||
void (*BlitNamedFramebuffer)(const SharedPtr<MG_State::GLState::FramebufferObject>& readFramebuffer,
|
void (*BlitNamedFramebuffer)(const SharedPtr<MG_State::GLState::FramebufferObject>& readFramebuffer,
|
||||||
@@ -224,31 +220,6 @@ namespace MobileGL {
|
|||||||
// and leave the query readable later.
|
// and leave the query readable later.
|
||||||
Bool (*GetQueryResult64)(BackendQueryHandle query, Bool wait, Uint64* outNanoseconds);
|
Bool (*GetQueryResult64)(BackendQueryHandle query, Bool wait, Uint64* outNanoseconds);
|
||||||
void (*DeleteBackendQuery)(BackendQueryHandle query);
|
void (*DeleteBackendQuery)(BackendQueryHandle query);
|
||||||
// GL_SAMPLES_PASSED occlusion queries (optional; null = unsupported,
|
|
||||||
// the frontend then rejects the target). Results/deletion flow through
|
|
||||||
// GetQueryResult64 / DeleteBackendQuery like timer queries.
|
|
||||||
BackendQueryHandle (*BeginOcclusionQuery)();
|
|
||||||
void (*EndOcclusionQuery)(BackendQueryHandle query);
|
|
||||||
// Transform feedback primitive queries backed by real GPU query pools
|
|
||||||
// (optional; null = frontend falls back to CPU accounting).
|
|
||||||
BackendQueryHandle (*BeginXfbPrimitivesQuery)(Bool generated);
|
|
||||||
void (*EndXfbPrimitivesQuery)(BackendQueryHandle query);
|
|
||||||
// Transform feedback capture spans, for backends whose own GL/ES driver
|
|
||||||
// performs the capture (DirectGLES). Both optional; null means the backend
|
|
||||||
// drives capture from its draw recording instead (DirectVulkan). End is
|
|
||||||
// called while the frontend capture state is still active, so the backend
|
|
||||||
// can still see the capture program and buffer bindings.
|
|
||||||
// GL_PATCH_VERTICES; ES 3.2 spells it the same way.
|
|
||||||
void (*PatchParameteri)(GLenum pname, GLint value);
|
|
||||||
void (*BeginTransformFeedback)(GLenum primitiveMode);
|
|
||||||
void (*EndTransformFeedback)();
|
|
||||||
// ARB_transform_feedback2. A backend that leaves these null keeps the single
|
|
||||||
// implicit capture span the frontend has always modelled; the frontend state
|
|
||||||
// (paused flag, per-object bindings) is tracked either way.
|
|
||||||
void (*PauseTransformFeedback)();
|
|
||||||
void (*ResumeTransformFeedback)();
|
|
||||||
void (*BindTransformFeedback)(GLuint name);
|
|
||||||
void (*DeleteTransformFeedback)(GLuint name);
|
|
||||||
Int64 (*GetGpuTimestampNs)(); // glGetInteger64v(GL_TIMESTAMP); 0 if unsupported
|
Int64 (*GetGpuTimestampNs)(); // glGetInteger64v(GL_TIMESTAMP); 0 if unsupported
|
||||||
};
|
};
|
||||||
struct GlobalBackendFunctionsTable {
|
struct GlobalBackendFunctionsTable {
|
||||||
@@ -301,13 +272,6 @@ namespace MobileGL {
|
|||||||
Int MaxIntegerSamples = 1;
|
Int MaxIntegerSamples = 1;
|
||||||
Int MaxSamples = 1;
|
Int MaxSamples = 1;
|
||||||
Int MaxSampleMaskWords = 1;
|
Int MaxSampleMaskWords = 1;
|
||||||
// Tessellation limits; defaults are the GL 4.0 core minimums.
|
|
||||||
Int MaxPatchVertices = 32;
|
|
||||||
Int MaxTessGenLevel = 64;
|
|
||||||
// GL_MIN/MAX_PROGRAM_TEXTURE_GATHER_OFFSET. Defaults are the GL 4.0 core
|
|
||||||
// minimums, which every ES 3.1 driver also guarantees.
|
|
||||||
Int MinProgramTextureGatherOffset = -8;
|
|
||||||
Int MaxProgramTextureGatherOffset = 7;
|
|
||||||
Int MaxTextureImageUnits = 32;
|
Int MaxTextureImageUnits = 32;
|
||||||
Int MaxVertexTextureImageUnits = 32;
|
Int MaxVertexTextureImageUnits = 32;
|
||||||
Int MaxComputeTextureImageUnits = 32;
|
Int MaxComputeTextureImageUnits = 32;
|
||||||
@@ -319,8 +283,6 @@ namespace MobileGL {
|
|||||||
Int MaxComputeWorkGroupInvocations = 128;
|
Int MaxComputeWorkGroupInvocations = 128;
|
||||||
Int MaxShaderStorageBufferBindings = 8;
|
Int MaxShaderStorageBufferBindings = 8;
|
||||||
Int MaxTextureBufferSize = 65536;
|
Int MaxTextureBufferSize = 65536;
|
||||||
// GL_TEXTURE_BUFFER_OFFSET_ALIGNMENT; 1 means the offset is unconstrained.
|
|
||||||
Int TextureBufferOffsetAlignment = 1;
|
|
||||||
Int MaxUniformBufferBindings = 24;
|
Int MaxUniformBufferBindings = 24;
|
||||||
Int MaxUniformBlockSize = 16384;
|
Int MaxUniformBlockSize = 16384;
|
||||||
Int MaxImageUnits = 8;
|
Int MaxImageUnits = 8;
|
||||||
@@ -338,55 +300,7 @@ namespace MobileGL {
|
|||||||
Float ViewportBoundsRangeMin = 0.0f;
|
Float ViewportBoundsRangeMin = 0.0f;
|
||||||
Float ViewportBoundsRangeMax = 0.0f;
|
Float ViewportBoundsRangeMax = 0.0f;
|
||||||
Int ViewportSubpixelBits = 0;
|
Int ViewportSubpixelBits = 0;
|
||||||
// GL 4.x fragment-interpolation offset limits. These defaults are the
|
|
||||||
// core minimums and are replaced by live GLES/Vulkan device limits.
|
|
||||||
Float MinFragmentInterpolationOffset = -0.5f;
|
|
||||||
// For four fractional bits the greatest required legal offset is
|
|
||||||
// 0.5 - 2^-4 = 0.4375 (GL 4.6 table 23.70).
|
|
||||||
Float MaxFragmentInterpolationOffset = 0.4375f;
|
|
||||||
Int FragmentInterpolationOffsetBits = 4;
|
|
||||||
Bool SupportsWideLines = false;
|
Bool SupportsWideLines = false;
|
||||||
// Whether a framebuffer whose depth and stencil attachments are distinct
|
|
||||||
// images can be rendered to. GL only requires support when both refer to the
|
|
||||||
// same image and lets an implementation answer GL_FRAMEBUFFER_UNSUPPORTED
|
|
||||||
// otherwise, which is what DirectVulkan (one combined attachment) and the
|
|
||||||
// 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.
|
|
||||||
// Which layered texture targets this backend can attach ONE layer of to a framebuffer
|
|
||||||
// and then really clear, render and read back that layer. Bit (1u << TextureTarget) is
|
|
||||||
// set for each supported target. Deliberately per target rather than one flag: the three
|
|
||||||
// ways a GL layer maps onto Vulkan are independent capabilities. A 2D or 2D multisample
|
|
||||||
// array layer IS a VkImage array layer and needs nothing extra; a 3D texture's layer is
|
|
||||||
// a z slice, which needs a 2D-array-compatible image and a per-slice clear that
|
|
||||||
// vkCmdClearColorImage cannot express; a cube map array needs an image shape and the
|
|
||||||
// imageCubeArray feature before it can be attached at any layer at all. Defaults to 0 so
|
|
||||||
// a backend that never sets it gets the conservative answer.
|
|
||||||
Uint32 PerLayerFramebufferAttachmentTargets = 0;
|
|
||||||
|
|
||||||
static constexpr Uint32 PerLayerFramebufferAttachmentBit(TextureTarget target) {
|
|
||||||
return (static_cast<Int>(target) >= 0 &&
|
|
||||||
static_cast<Int>(target) < static_cast<Int>(TextureTarget::TextureTargetCount))
|
|
||||||
? (1u << static_cast<Uint32>(target))
|
|
||||||
: 0u;
|
|
||||||
}
|
|
||||||
|
|
||||||
Bool SupportsPerLayerFramebufferAttachment(TextureTarget target) const {
|
|
||||||
const Uint32 bit = PerLayerFramebufferAttachmentBit(target);
|
|
||||||
return bit != 0 && (PerLayerFramebufferAttachmentTargets & bit) != 0;
|
|
||||||
}
|
|
||||||
// Whether glVertexAttribLFormat / glVertexArrayAttribLFormat can be honoured, i.e.
|
|
||||||
// whether a 64-bit vertex attribute can actually reach a shader unconverted. Detected,
|
|
||||||
// never assumed: DirectVulkan needs VkPhysicalDeviceFeatures::shaderFloat64 (the
|
|
||||||
// attribute travels as its 32-bit word pair, so no VK_FORMAT_R64* is required, but the
|
|
||||||
// bitcast result is Float64); DirectGLES can never have it, ESSL having no fp64 type at
|
|
||||||
// all. Defaults to false so a backend that never sets it gets the conservative answer.
|
|
||||||
Bool SupportsFloat64VertexAttributes = false;
|
|
||||||
SizeT MaxShaderStorageBlockSize = 128 * 1024 * 1024;
|
SizeT MaxShaderStorageBlockSize = 128 * 1024 * 1024;
|
||||||
Uint32 SubgroupSize = 0;
|
Uint32 SubgroupSize = 0;
|
||||||
Uint32 SubgroupSupportedStages = 0;
|
Uint32 SubgroupSupportedStages = 0;
|
||||||
|
|||||||
@@ -20,7 +20,6 @@
|
|||||||
#include <MG_Util/Texture/TextureFormatProcessor.h>
|
#include <MG_Util/Texture/TextureFormatProcessor.h>
|
||||||
#include <Config.h>
|
#include <Config.h>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cmath>
|
|
||||||
#include <format>
|
#include <format>
|
||||||
|
|
||||||
namespace MobileGL::MG_Backend::DirectGLES {
|
namespace MobileGL::MG_Backend::DirectGLES {
|
||||||
@@ -32,7 +31,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
|
|
||||||
void ClearGLErrors(const MG_External::GLESFunctionsTable& gl) {
|
void ClearGLErrors(const MG_External::GLESFunctionsTable& gl) {
|
||||||
if (!gl.glGetError) return;
|
if (!gl.glGetError) return;
|
||||||
while (gl.glGetError() != GL_NO_ERROR) {}
|
while (gl.glGetError() != GL_NO_ERROR) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Bool CheckNoGLError(const MG_External::GLESFunctionsTable& gl) {
|
Bool CheckNoGLError(const MG_External::GLESFunctionsTable& gl) {
|
||||||
@@ -76,7 +76,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Bool IsGLESProbeMultisampleTarget(TextureTarget target) {
|
Bool IsGLESProbeMultisampleTarget(TextureTarget target) {
|
||||||
return target == TextureTarget::Texture2DMultisample || target == TextureTarget::Texture2DMultisampleArray;
|
return target == TextureTarget::Texture2DMultisample ||
|
||||||
|
target == TextureTarget::Texture2DMultisampleArray;
|
||||||
}
|
}
|
||||||
|
|
||||||
GLenum GetFramebufferAttachment(TextureInternalFormat format) {
|
GLenum GetFramebufferAttachment(TextureInternalFormat format) {
|
||||||
@@ -113,8 +114,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
GLenum normalizedInternalFormat = glFormat;
|
GLenum normalizedInternalFormat = glFormat;
|
||||||
GLenum imageFormat = GL_RGBA;
|
GLenum imageFormat = GL_RGBA;
|
||||||
GLenum imageType = GL_UNSIGNED_BYTE;
|
GLenum imageType = GL_UNSIGNED_BYTE;
|
||||||
MG_Util::TextureFormatProcessor::NormalizePixelFormat(glFormat, PixelFormatNormalizeOptionBit::None,
|
MG_Util::TextureFormatProcessor::NormalizePixelFormat(
|
||||||
&normalizedInternalFormat, &imageFormat, &imageType);
|
glFormat, PixelFormatNormalizeOptionBit::None, &normalizedInternalFormat, &imageFormat, &imageType);
|
||||||
return imageFormat != GL_RED_INTEGER && imageFormat != GL_RG_INTEGER && imageFormat != GL_RGB_INTEGER &&
|
return imageFormat != GL_RED_INTEGER && imageFormat != GL_RG_INTEGER && imageFormat != GL_RGB_INTEGER &&
|
||||||
imageFormat != GL_RGBA_INTEGER && !MG_Util::IsDepthFormatInternalFormat(format) &&
|
imageFormat != GL_RGBA_INTEGER && !MG_Util::IsDepthFormatInternalFormat(format) &&
|
||||||
!MG_Util::IsStencilFormatInternalFormat(format);
|
!MG_Util::IsStencilFormatInternalFormat(format);
|
||||||
@@ -152,9 +153,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
GLESProbeFormatInfo BuildNativeProbeFormatInfo(GLenum requestedInternalFormat) {
|
GLESProbeFormatInfo BuildNativeProbeFormatInfo(GLenum requestedInternalFormat) {
|
||||||
GLESProbeFormatInfo info;
|
GLESProbeFormatInfo info;
|
||||||
info.InternalFormat = requestedInternalFormat;
|
info.InternalFormat = requestedInternalFormat;
|
||||||
MG_Util::TextureFormatProcessor::NormalizePixelFormat(requestedInternalFormat,
|
MG_Util::TextureFormatProcessor::NormalizePixelFormat(
|
||||||
PixelFormatNormalizeOptionBit::None, nullptr,
|
requestedInternalFormat, PixelFormatNormalizeOptionBit::None, nullptr, &info.ImageFormat,
|
||||||
&info.ImageFormat, &info.ImageType);
|
&info.ImageType);
|
||||||
return info;
|
return info;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -208,12 +209,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
if (options & PixelFormatNormalizeOptionBit::NoDepthComponent32) {
|
if (options & PixelFormatNormalizeOptionBit::NoDepthComponent32) {
|
||||||
reasons.push_back("GL_DEPTH_COMPONENT32 native probe failed on OpenGL ES");
|
reasons.push_back("GL_DEPTH_COMPONENT32 native probe failed on OpenGL ES");
|
||||||
}
|
}
|
||||||
if (options & PixelFormatNormalizeOptionBit::NoThreeChannelRenderTarget) {
|
|
||||||
reasons.push_back("no three-channel multisample storage format on OpenGL ES");
|
|
||||||
}
|
|
||||||
if (options & PixelFormatNormalizeOptionBit::NoSnorm16RenderTarget) {
|
|
||||||
reasons.push_back("EXT_render_snorm not supported");
|
|
||||||
}
|
|
||||||
|
|
||||||
String reason;
|
String reason;
|
||||||
for (SizeT i = 0; i < reasons.size(); ++i) {
|
for (SizeT i = 0; i < reasons.size(); ++i) {
|
||||||
@@ -231,16 +226,20 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
return MG_Util::ConvertGLEnumToString(internalFormat);
|
return MG_Util::ConvertGLEnumToString(internalFormat);
|
||||||
}
|
}
|
||||||
|
|
||||||
void LogGLESFormatCaveat(TextureInternalFormat logicalFormat, SizeT targetIndex,
|
void LogGLESFormatCaveat(TextureInternalFormat logicalFormat,
|
||||||
|
SizeT targetIndex,
|
||||||
const GLESProbeFormatInfo& fallbackInfo) {
|
const GLESProbeFormatInfo& fallbackInfo) {
|
||||||
MGLOG_D("Caveat: %s %s not fully supported. Reason: %s. Fallback: %s",
|
MGLOG_D("Caveat: %s %s not fully supported. Reason: %s. Fallback: %s",
|
||||||
GetFormatCapabilityTargetName(targetIndex).c_str(),
|
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());
|
ConvertFallbackInternalFormatToString(fallbackInfo.InternalFormat).c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
Bool BuildFallbackProbeFormatInfo(GLenum requestedInternalFormat, Flags<PixelFormatNormalizeOptionBit> options,
|
Bool BuildFallbackProbeFormatInfo(GLenum requestedInternalFormat,
|
||||||
Bool forced, GLESProbeFormatInfo& outInfo) {
|
Flags<PixelFormatNormalizeOptionBit> options,
|
||||||
|
Bool forced,
|
||||||
|
GLESProbeFormatInfo& outInfo) {
|
||||||
const Flags<PixelFormatNormalizeOptionBit> applicableOptions =
|
const Flags<PixelFormatNormalizeOptionBit> applicableOptions =
|
||||||
MG_Util::TextureFormatProcessor::GetApplicablePixelFormatNormalizeOptions(requestedInternalFormat,
|
MG_Util::TextureFormatProcessor::GetApplicablePixelFormatNormalizeOptions(requestedInternalFormat,
|
||||||
options);
|
options);
|
||||||
@@ -255,7 +254,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
return outInfo.InternalFormat != GL_UNKNOWN_MGL;
|
return outInfo.InternalFormat != GL_UNKNOWN_MGL;
|
||||||
}
|
}
|
||||||
|
|
||||||
FormatCapabilityFlags BuildTextureCapsFromProbe(TextureInternalFormat logicalFormat, TextureTarget target,
|
FormatCapabilityFlags BuildTextureCapsFromProbe(TextureInternalFormat logicalFormat,
|
||||||
|
TextureTarget target,
|
||||||
Bool renderable) {
|
Bool renderable) {
|
||||||
FormatCapabilityFlags caps = GetTextureFeatureCaps(logicalFormat, target);
|
FormatCapabilityFlags caps = GetTextureFeatureCaps(logicalFormat, target);
|
||||||
if (renderable) {
|
if (renderable) {
|
||||||
@@ -269,12 +269,16 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
return caps;
|
return caps;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AddFullFormatCaps(FormatCapabilityCache& cache, SizeT targetIndex, SizeT formatIndex,
|
void AddFullFormatCaps(FormatCapabilityCache& cache,
|
||||||
|
SizeT targetIndex,
|
||||||
|
SizeT formatIndex,
|
||||||
FormatCapabilityFlags caps) {
|
FormatCapabilityFlags caps) {
|
||||||
cache.FullCaps[targetIndex][formatIndex] |= caps;
|
cache.FullCaps[targetIndex][formatIndex] |= caps;
|
||||||
}
|
}
|
||||||
|
|
||||||
Bool AddCaveatFormatCaps(FormatCapabilityCache& cache, SizeT targetIndex, SizeT formatIndex,
|
Bool AddCaveatFormatCaps(FormatCapabilityCache& cache,
|
||||||
|
SizeT targetIndex,
|
||||||
|
SizeT formatIndex,
|
||||||
FormatCapabilityFlags caps) {
|
FormatCapabilityFlags caps) {
|
||||||
Bool added = false;
|
Bool added = false;
|
||||||
for (FormatCapability capability : kReportedFormatCapabilities) {
|
for (FormatCapability capability : kReportedFormatCapabilities) {
|
||||||
@@ -288,7 +292,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Int GetGLESFormatMaxSamples(const MG_External::GLESCapabilities& capabilities,
|
Int GetGLESFormatMaxSamples(const MG_External::GLESCapabilities& capabilities,
|
||||||
TextureInternalFormat logicalFormat, GLenum imageFormat) {
|
TextureInternalFormat logicalFormat,
|
||||||
|
GLenum imageFormat) {
|
||||||
const Bool isDepth = MG_Util::IsDepthFormatInternalFormat(logicalFormat);
|
const Bool isDepth = MG_Util::IsDepthFormatInternalFormat(logicalFormat);
|
||||||
const Bool isStencil = MG_Util::IsStencilFormatInternalFormat(logicalFormat);
|
const Bool isStencil = MG_Util::IsStencilFormatInternalFormat(logicalFormat);
|
||||||
const Bool isInteger = imageFormat == GL_RED_INTEGER || imageFormat == GL_RG_INTEGER ||
|
const Bool isInteger = imageFormat == GL_RED_INTEGER || imageFormat == GL_RG_INTEGER ||
|
||||||
@@ -302,8 +307,10 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
return capabilities.MaxColorTextureSamples;
|
return capabilities.MaxColorTextureSamples;
|
||||||
}
|
}
|
||||||
|
|
||||||
Bool ProbeFramebufferCompletenessForTexture(const MG_External::GLESFunctionsTable& gl, TextureTarget target,
|
Bool ProbeFramebufferCompletenessForTexture(const MG_External::GLESFunctionsTable& gl,
|
||||||
GLuint texture, TextureInternalFormat format) {
|
TextureTarget target,
|
||||||
|
GLuint texture,
|
||||||
|
TextureInternalFormat format) {
|
||||||
GLuint framebuffer = 0;
|
GLuint framebuffer = 0;
|
||||||
GLint prevFramebuffer = 0;
|
GLint prevFramebuffer = 0;
|
||||||
if (!gl.glGenFramebuffers || !gl.glBindFramebuffer || !gl.glCheckFramebufferStatus ||
|
if (!gl.glGenFramebuffers || !gl.glBindFramebuffer || !gl.glCheckFramebufferStatus ||
|
||||||
@@ -349,44 +356,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
return complete;
|
return complete;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Whether the driver renders to a framebuffer whose depth and stencil come from
|
Bool ProbeFramebufferCompletenessForRenderbuffer(const MG_External::GLESFunctionsTable& gl,
|
||||||
// two different renderbuffers. GL only requires support when both attachments are
|
GLuint renderbuffer,
|
||||||
// the same image, and ES drivers commonly answer GL_FRAMEBUFFER_UNSUPPORTED here;
|
TextureInternalFormat format) {
|
||||||
// reporting COMPLETE from the frontend and then rendering into a framebuffer the
|
|
||||||
// driver refuses leaves the results silently empty.
|
|
||||||
Bool ProbeDistinctDepthStencilAttachments(const MG_External::GLESFunctionsTable& gl) {
|
|
||||||
if (!gl.glGenFramebuffers || !gl.glBindFramebuffer || !gl.glFramebufferRenderbuffer ||
|
|
||||||
!gl.glCheckFramebufferStatus || !gl.glDeleteFramebuffers || !gl.glGenRenderbuffers ||
|
|
||||||
!gl.glBindRenderbuffer || !gl.glRenderbufferStorage || !gl.glDeleteRenderbuffers) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
GLint prevFramebuffer = 0, prevRenderbuffer = 0;
|
|
||||||
gl.glGetIntegerv(GL_FRAMEBUFFER_BINDING, &prevFramebuffer);
|
|
||||||
gl.glGetIntegerv(GL_RENDERBUFFER_BINDING, &prevRenderbuffer);
|
|
||||||
|
|
||||||
GLuint framebuffer = 0;
|
|
||||||
GLuint renderbuffers[2] = {0, 0};
|
|
||||||
gl.glGenFramebuffers(1, &framebuffer);
|
|
||||||
gl.glGenRenderbuffers(2, renderbuffers);
|
|
||||||
gl.glBindRenderbuffer(GL_RENDERBUFFER, renderbuffers[0]);
|
|
||||||
gl.glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, 4, 4);
|
|
||||||
gl.glBindRenderbuffer(GL_RENDERBUFFER, renderbuffers[1]);
|
|
||||||
gl.glRenderbufferStorage(GL_RENDERBUFFER, GL_STENCIL_INDEX8, 4, 4);
|
|
||||||
gl.glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
|
|
||||||
gl.glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, renderbuffers[0]);
|
|
||||||
gl.glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, renderbuffers[1]);
|
|
||||||
const Bool supported = gl.glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE;
|
|
||||||
|
|
||||||
gl.glBindFramebuffer(GL_FRAMEBUFFER, static_cast<GLuint>(prevFramebuffer));
|
|
||||||
gl.glBindRenderbuffer(GL_RENDERBUFFER, static_cast<GLuint>(prevRenderbuffer));
|
|
||||||
gl.glDeleteFramebuffers(1, &framebuffer);
|
|
||||||
gl.glDeleteRenderbuffers(2, renderbuffers);
|
|
||||||
return supported;
|
|
||||||
}
|
|
||||||
|
|
||||||
Bool ProbeFramebufferCompletenessForRenderbuffer(const MG_External::GLESFunctionsTable& gl, GLuint renderbuffer,
|
|
||||||
TextureInternalFormat format) {
|
|
||||||
GLuint framebuffer = 0;
|
GLuint framebuffer = 0;
|
||||||
GLint prevFramebuffer = 0;
|
GLint prevFramebuffer = 0;
|
||||||
if (!gl.glGenFramebuffers || !gl.glBindFramebuffer || !gl.glFramebufferRenderbuffer ||
|
if (!gl.glGenFramebuffers || !gl.glBindFramebuffer || !gl.glFramebufferRenderbuffer ||
|
||||||
@@ -453,16 +425,16 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case TextureTarget::Texture3D:
|
case TextureTarget::Texture3D:
|
||||||
gl.glTexImage3D(glTarget, 0, static_cast<GLint>(internalFormat), 2, 2, 2, 0, imageFormat, imageType,
|
gl.glTexImage3D(glTarget, 0, static_cast<GLint>(internalFormat), 2, 2, 2, 0, imageFormat,
|
||||||
nullptr);
|
imageType, nullptr);
|
||||||
break;
|
break;
|
||||||
case TextureTarget::Texture2DArray:
|
case TextureTarget::Texture2DArray:
|
||||||
gl.glTexImage3D(glTarget, 0, static_cast<GLint>(internalFormat), 2, 2, 1, 0, imageFormat, imageType,
|
gl.glTexImage3D(glTarget, 0, static_cast<GLint>(internalFormat), 2, 2, 1, 0, imageFormat,
|
||||||
nullptr);
|
imageType, nullptr);
|
||||||
break;
|
break;
|
||||||
case TextureTarget::TextureCubeMapArray:
|
case TextureTarget::TextureCubeMapArray:
|
||||||
gl.glTexImage3D(glTarget, 0, static_cast<GLint>(internalFormat), 2, 2, 6, 0, imageFormat, imageType,
|
gl.glTexImage3D(glTarget, 0, static_cast<GLint>(internalFormat), 2, 2, 6, 0, imageFormat,
|
||||||
nullptr);
|
imageType, nullptr);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
@@ -483,8 +455,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
return created;
|
return created;
|
||||||
}
|
}
|
||||||
|
|
||||||
Bool ProbeRenderbuffer(const MG_External::GLESFunctionsTable& gl, GLenum internalFormat,
|
Bool ProbeRenderbuffer(const MG_External::GLESFunctionsTable& gl,
|
||||||
TextureInternalFormat logicalFormat, Bool multisample, Int samples) {
|
GLenum internalFormat,
|
||||||
|
TextureInternalFormat logicalFormat,
|
||||||
|
Bool multisample,
|
||||||
|
Int samples) {
|
||||||
if (!gl.glGenRenderbuffers || !gl.glBindRenderbuffer || !gl.glDeleteRenderbuffers) {
|
if (!gl.glGenRenderbuffers || !gl.glBindRenderbuffer || !gl.glDeleteRenderbuffers) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -506,16 +481,17 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
gl.glRenderbufferStorage(GL_RENDERBUFFER, internalFormat, 1, 1);
|
gl.glRenderbufferStorage(GL_RENDERBUFFER, internalFormat, 1, 1);
|
||||||
}
|
}
|
||||||
const Bool created = CheckNoGLError(gl);
|
const Bool created = CheckNoGLError(gl);
|
||||||
const Bool complete =
|
const Bool complete = created && ProbeFramebufferCompletenessForRenderbuffer(gl, renderbuffer, logicalFormat);
|
||||||
created && ProbeFramebufferCompletenessForRenderbuffer(gl, renderbuffer, logicalFormat);
|
|
||||||
gl.glBindRenderbuffer(GL_RENDERBUFFER, static_cast<GLuint>(prevRenderbuffer));
|
gl.glBindRenderbuffer(GL_RENDERBUFFER, static_cast<GLuint>(prevRenderbuffer));
|
||||||
gl.glDeleteRenderbuffers(1, &renderbuffer);
|
gl.glDeleteRenderbuffers(1, &renderbuffer);
|
||||||
ClearGLErrors(gl);
|
ClearGLErrors(gl);
|
||||||
return complete;
|
return complete;
|
||||||
}
|
}
|
||||||
|
|
||||||
Vector<Int> ProbeRenderbufferSampleCounts(const MG_External::GLESFunctionsTable& gl, GLenum internalFormat,
|
Vector<Int> ProbeRenderbufferSampleCounts(const MG_External::GLESFunctionsTable& gl,
|
||||||
TextureInternalFormat logicalFormat, Int maxSamples) {
|
GLenum internalFormat,
|
||||||
|
TextureInternalFormat logicalFormat,
|
||||||
|
Int maxSamples) {
|
||||||
Vector<Int> sampleCounts;
|
Vector<Int> sampleCounts;
|
||||||
for (Int samples = std::max(maxSamples, 1); samples > 1; samples >>= 1) {
|
for (Int samples = std::max(maxSamples, 1); samples > 1; samples >>= 1) {
|
||||||
if (ProbeRenderbuffer(gl, internalFormat, logicalFormat, true, samples)) {
|
if (ProbeRenderbuffer(gl, internalFormat, logicalFormat, true, samples)) {
|
||||||
@@ -543,50 +519,20 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const GLESProbeFormatInfo nativeInfo = BuildNativeProbeFormatInfo(requestedInternalFormat);
|
const GLESProbeFormatInfo nativeInfo = BuildNativeProbeFormatInfo(requestedInternalFormat);
|
||||||
GLESProbeFormatInfo outerFallbackInfo;
|
GLESProbeFormatInfo fallbackInfo;
|
||||||
const Bool outerHasForcedFallback =
|
const Bool hasForcedFallback =
|
||||||
BuildFallbackProbeFormatInfo(requestedInternalFormat, forcedOptions, true, outerFallbackInfo);
|
BuildFallbackProbeFormatInfo(requestedInternalFormat, forcedOptions, true, fallbackInfo);
|
||||||
if (!outerHasForcedFallback) {
|
if (!hasForcedFallback) {
|
||||||
BuildFallbackProbeFormatInfo(requestedInternalFormat, driverOptions, false, outerFallbackInfo);
|
BuildFallbackProbeFormatInfo(requestedInternalFormat, driverOptions, false, fallbackInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (SizeT targetIndex = 0; targetIndex < kFormatCapabilityTextureTargetCount; ++targetIndex) {
|
for (SizeT targetIndex = 0; targetIndex < kFormatCapabilityTextureTargetCount; ++targetIndex) {
|
||||||
const auto target = static_cast<TextureTarget>(targetIndex);
|
const auto target = static_cast<TextureTarget>(targetIndex);
|
||||||
// A multisample texture can only ever be rendered into, so its storage format
|
|
||||||
// has to stay colour-renderable; the ordinary fallback for a three-channel
|
|
||||||
// format is a three-channel one, which ES accepts as a texture but rejects as
|
|
||||||
// multisample storage. Recompute the fallback per target so those formats get
|
|
||||||
// widened here and nowhere else.
|
|
||||||
Flags<PixelFormatNormalizeOptionBit> targetOptions;
|
|
||||||
if (IsGLESProbeMultisampleTarget(target)) {
|
|
||||||
targetOptions |= PixelFormatNormalizeOptionBit::NoThreeChannelRenderTarget;
|
|
||||||
if (!capabilities.SupportsRenderSnorm || !capabilities.SupportsNorm16Texture) {
|
|
||||||
targetOptions |= PixelFormatNormalizeOptionBit::NoSnorm16RenderTarget;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
GLESProbeFormatInfo fallbackInfo = outerFallbackInfo;
|
|
||||||
Bool hasForcedFallback = outerHasForcedFallback;
|
|
||||||
if (targetOptions) {
|
|
||||||
hasForcedFallback = BuildFallbackProbeFormatInfo(
|
|
||||||
requestedInternalFormat, forcedOptions | targetOptions, true, fallbackInfo);
|
|
||||||
if (!hasForcedFallback) {
|
|
||||||
BuildFallbackProbeFormatInfo(requestedInternalFormat, driverOptions | targetOptions, false,
|
|
||||||
fallbackInfo);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 1D, 1D-array and rectangle textures live on an ES target (see
|
|
||||||
// TextureImpl::MapToBackendTextureTarget), so they have to be probed there too -
|
|
||||||
// probing the desktop-only target itself always failed, which left those slots
|
|
||||||
// of the cache empty and stopped any fallback format from being selected for
|
|
||||||
// them (a GL_DEPTH_COMPONENT32 1D texture then got no storage at all).
|
|
||||||
const TextureTarget probeTarget = TextureImpl::MapToBackendTextureTarget(target);
|
|
||||||
|
|
||||||
Bool shouldProbeFallback = hasForcedFallback;
|
Bool shouldProbeFallback = hasForcedFallback;
|
||||||
if (!hasForcedFallback) {
|
if (!hasForcedFallback) {
|
||||||
Bool nativeRenderable = false;
|
Bool nativeRenderable = false;
|
||||||
const Bool nativeCreated =
|
const Bool nativeCreated =
|
||||||
ProbeTexture(gl, probeTarget, nativeInfo.InternalFormat, nativeInfo.ImageFormat,
|
ProbeTexture(gl, target, nativeInfo.InternalFormat, nativeInfo.ImageFormat,
|
||||||
nativeInfo.ImageType, logicalFormat, &nativeRenderable);
|
nativeInfo.ImageType, logicalFormat, &nativeRenderable);
|
||||||
if (nativeCreated) {
|
if (nativeCreated) {
|
||||||
AddFullFormatCaps(cache, targetIndex, formatIndex,
|
AddFullFormatCaps(cache, targetIndex, formatIndex,
|
||||||
@@ -601,12 +547,12 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
if (shouldProbeFallback && fallbackInfo.InternalFormat != GL_UNKNOWN_MGL) {
|
if (shouldProbeFallback && fallbackInfo.InternalFormat != GL_UNKNOWN_MGL) {
|
||||||
Bool fallbackRenderable = false;
|
Bool fallbackRenderable = false;
|
||||||
const Bool fallbackCreated =
|
const Bool fallbackCreated =
|
||||||
ProbeTexture(gl, probeTarget, fallbackInfo.InternalFormat, fallbackInfo.ImageFormat,
|
ProbeTexture(gl, target, fallbackInfo.InternalFormat, fallbackInfo.ImageFormat,
|
||||||
fallbackInfo.ImageType, logicalFormat, &fallbackRenderable);
|
fallbackInfo.ImageType, logicalFormat, &fallbackRenderable);
|
||||||
if (fallbackCreated) {
|
if (fallbackCreated) {
|
||||||
if (AddCaveatFormatCaps(
|
if (AddCaveatFormatCaps(cache, targetIndex, formatIndex,
|
||||||
cache, targetIndex, formatIndex,
|
BuildTextureCapsFromProbe(logicalFormat, target,
|
||||||
BuildTextureCapsFromProbe(logicalFormat, target, fallbackRenderable))) {
|
fallbackRenderable))) {
|
||||||
LogGLESFormatCaveat(logicalFormat, targetIndex, fallbackInfo);
|
LogGLESFormatCaveat(logicalFormat, targetIndex, fallbackInfo);
|
||||||
}
|
}
|
||||||
if (IsGLESProbeMultisampleTarget(target)) {
|
if (IsGLESProbeMultisampleTarget(target)) {
|
||||||
@@ -617,8 +563,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const SizeT renderbufferTargetIndex = GetRenderbufferFormatCapabilityTargetIndex();
|
const SizeT renderbufferTargetIndex = GetRenderbufferFormatCapabilityTargetIndex();
|
||||||
Bool shouldProbeFallbackRenderbuffer = outerHasForcedFallback;
|
Bool shouldProbeFallbackRenderbuffer = hasForcedFallback;
|
||||||
if (!outerHasForcedFallback) {
|
if (!hasForcedFallback) {
|
||||||
const Bool nativeRenderbufferComplete =
|
const Bool nativeRenderbufferComplete =
|
||||||
ProbeRenderbuffer(gl, nativeInfo.InternalFormat, logicalFormat, false, 1);
|
ProbeRenderbuffer(gl, nativeInfo.InternalFormat, logicalFormat, false, 1);
|
||||||
if (nativeRenderbufferComplete) {
|
if (nativeRenderbufferComplete) {
|
||||||
@@ -632,16 +578,16 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
shouldProbeFallbackRenderbuffer = true;
|
shouldProbeFallbackRenderbuffer = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (shouldProbeFallbackRenderbuffer && outerFallbackInfo.InternalFormat != GL_UNKNOWN_MGL &&
|
if (shouldProbeFallbackRenderbuffer && fallbackInfo.InternalFormat != GL_UNKNOWN_MGL &&
|
||||||
ProbeRenderbuffer(gl, outerFallbackInfo.InternalFormat, logicalFormat, false, 1)) {
|
ProbeRenderbuffer(gl, fallbackInfo.InternalFormat, logicalFormat, false, 1)) {
|
||||||
if (AddCaveatFormatCaps(cache, renderbufferTargetIndex, formatIndex,
|
if (AddCaveatFormatCaps(cache, renderbufferTargetIndex, formatIndex,
|
||||||
GetRenderbufferFeatureCaps(logicalFormat))) {
|
GetRenderbufferFeatureCaps(logicalFormat))) {
|
||||||
LogGLESFormatCaveat(logicalFormat, renderbufferTargetIndex, outerFallbackInfo);
|
LogGLESFormatCaveat(logicalFormat, renderbufferTargetIndex, fallbackInfo);
|
||||||
}
|
}
|
||||||
const Int maxSamples =
|
const Int maxSamples =
|
||||||
GetGLESFormatMaxSamples(capabilities, logicalFormat, outerFallbackInfo.ImageFormat);
|
GetGLESFormatMaxSamples(capabilities, logicalFormat, fallbackInfo.ImageFormat);
|
||||||
cache.SampleCounts[renderbufferTargetIndex][formatIndex] =
|
cache.SampleCounts[renderbufferTargetIndex][formatIndex] =
|
||||||
ProbeRenderbufferSampleCounts(gl, outerFallbackInfo.InternalFormat, logicalFormat, maxSamples);
|
ProbeRenderbufferSampleCounts(gl, fallbackInfo.InternalFormat, logicalFormat, maxSamples);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -657,7 +603,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
.ExtraVendor = Nullopt, // Extra vendor
|
.ExtraVendor = Nullopt, // Extra vendor
|
||||||
.RendererGLInfo =
|
.RendererGLInfo =
|
||||||
{
|
{
|
||||||
.TargetGLVersion = {4, 0, 0}, // GL target version
|
.TargetGLVersion = {3, 3, 0}, // Target OpenGL Version
|
||||||
.TargetGLSLVersion = {4, 6, 0}, // Target Shading Language Version
|
.TargetGLSLVersion = {4, 6, 0}, // Target Shading Language Version
|
||||||
// Baseline advertisement (no timer queries / anisotropy yet); reconciled
|
// Baseline advertisement (no timer queries / anisotropy yet); reconciled
|
||||||
// once the ES capabilities exist, see UpdateAdvertisedCapabilityExtensions.
|
// once the ES capabilities exist, see UpdateAdvertisedCapabilityExtensions.
|
||||||
@@ -688,7 +634,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
void PopulateFormatCapabilities(const MG_External::GLESFunctionsTable& gl,
|
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);
|
PopulateFormatCapabilitiesImpl(gl, capabilities, cache);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -752,8 +699,10 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((handle.Backend != WindowBackend::Android && handle.Backend != WindowBackend::X11 &&
|
if ((handle.Backend != WindowBackend::Android &&
|
||||||
handle.Backend != WindowBackend::MetalLayer && handle.Backend != WindowBackend::Win32) ||
|
handle.Backend != WindowBackend::X11 &&
|
||||||
|
handle.Backend != WindowBackend::MetalLayer &&
|
||||||
|
handle.Backend != WindowBackend::Win32) ||
|
||||||
!handle.Handle) {
|
!handle.Handle) {
|
||||||
MGLOG_E("DirectGLES backend only supports Android, X11, CAMetalLayer, and Win32 native windows");
|
MGLOG_E("DirectGLES backend only supports Android, X11, CAMetalLayer, and Win32 native windows");
|
||||||
return false;
|
return false;
|
||||||
@@ -872,25 +821,17 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Vector<GLExtension> BuildAdvertisedExtensions(Bool timerQueriesSupported, Bool anisotropicFilteringSupported) {
|
Vector<GLExtension> BuildAdvertisedExtensions(Bool timerQueriesSupported, Bool anisotropicFilteringSupported) {
|
||||||
Vector<GLExtension> extensions = {
|
Vector<GLExtension> extensions = {V_OpenGL30, V_OpenGL31, V_OpenGL32,
|
||||||
V_OpenGL30, V_OpenGL31, V_OpenGL32, V_OpenGL33, V_OpenGL40, E_GL_ARB_draw_buffers_blend,
|
V_OpenGL33, E_GL_ARB_draw_buffers_blend, E_GL_ARB_compute_shader,
|
||||||
E_GL_ARB_compute_shader, E_GL_ARB_shader_storage_buffer_object, E_GL_ARB_shader_image_load_store,
|
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_program_interface_query, E_GL_ARB_framebuffer_object,
|
||||||
E_GL_ARB_depth_texture, E_GL_ARB_buffer_storage, E_GL_ARB_texture_storage,
|
E_GL_EXT_framebuffer_object, E_GL_ARB_depth_texture, E_GL_ARB_buffer_storage,
|
||||||
E_GL_ARB_texture_storage_multisample, E_GL_ARB_clear_texture, E_GL_ARB_direct_state_access,
|
E_GL_ARB_texture_storage, E_GL_ARB_texture_storage_multisample,
|
||||||
E_GL_ARB_multi_draw_indirect, E_GL_ARB_indirect_parameters, E_GL_ARB_shader_draw_parameters,
|
E_GL_ARB_clear_texture, E_GL_ARB_direct_state_access,
|
||||||
E_GL_ARB_gpu_shader5, E_GL_ARB_multi_bind, E_GL_ARB_shading_language_420pack,
|
E_GL_ARB_multi_draw_indirect, E_GL_ARB_indirect_parameters,
|
||||||
E_GL_ARB_vertex_attrib_binding,
|
E_GL_ARB_shader_draw_parameters, E_GL_ARB_gpu_shader5, E_GL_ARB_multi_bind,
|
||||||
// Both are core from GL 3.2/3.3 on and implemented here for
|
E_GL_ARB_shading_language_420pack, E_GL_ARB_vertex_attrib_binding,
|
||||||
// every advertised version, but an app targeting 3.0/3.1
|
E_GL_ARB_shader_image_size};
|
||||||
// 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
|
// Only advertised when the device driver actually has usable timer queries
|
||||||
// (GL_EXT_disjoint_timer_query plus its entry points) and the
|
// (GL_EXT_disjoint_timer_query plus its entry points) and the
|
||||||
// MOBILEGL_DISABLE_TIMERQUERY escape hatch is off.
|
// MOBILEGL_DISABLE_TIMERQUERY escape hatch is off.
|
||||||
@@ -964,8 +905,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
funcsTable.GL.ClearBufferuiv = ClearBufferuiv;
|
funcsTable.GL.ClearBufferuiv = ClearBufferuiv;
|
||||||
funcsTable.GL.ClearBufferiv = ClearBufferiv;
|
funcsTable.GL.ClearBufferiv = ClearBufferiv;
|
||||||
funcsTable.GL.ClearNamedFramebufferfv = ClearNamedFramebufferfv;
|
funcsTable.GL.ClearNamedFramebufferfv = ClearNamedFramebufferfv;
|
||||||
funcsTable.GL.ClearNamedFramebufferiv = ClearNamedFramebufferiv;
|
|
||||||
funcsTable.GL.ClearNamedFramebufferuiv = ClearNamedFramebufferuiv;
|
|
||||||
funcsTable.GL.ClearNamedFramebufferfi = ClearNamedFramebufferfi;
|
funcsTable.GL.ClearNamedFramebufferfi = ClearNamedFramebufferfi;
|
||||||
funcsTable.GL.BlitFramebuffer = BlitFramebuffer;
|
funcsTable.GL.BlitFramebuffer = BlitFramebuffer;
|
||||||
funcsTable.GL.BlitNamedFramebuffer = BlitNamedFramebuffer;
|
funcsTable.GL.BlitNamedFramebuffer = BlitNamedFramebuffer;
|
||||||
@@ -995,30 +934,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
funcsTable.GL.BeginTimeElapsedQuery = BeginTimeElapsedQuery;
|
funcsTable.GL.BeginTimeElapsedQuery = BeginTimeElapsedQuery;
|
||||||
funcsTable.GL.EndTimeElapsedQuery = EndTimeElapsedQuery;
|
funcsTable.GL.EndTimeElapsedQuery = EndTimeElapsedQuery;
|
||||||
funcsTable.GL.QueryCounterTimestamp = QueryCounterTimestamp;
|
funcsTable.GL.QueryCounterTimestamp = QueryCounterTimestamp;
|
||||||
|
funcsTable.GL.IsQueryResultAvailable = IsQueryResultAvailable;
|
||||||
|
funcsTable.GL.GetQueryResult64 = GetQueryResult64;
|
||||||
|
funcsTable.GL.DeleteBackendQuery = DeleteBackendQuery;
|
||||||
funcsTable.GL.GetGpuTimestampNs = GetGpuTimestampNs;
|
funcsTable.GL.GetGpuTimestampNs = GetGpuTimestampNs;
|
||||||
}
|
}
|
||||||
// Occlusion queries are core ES3 (independent of MOBILEGL_DISABLE_TIMERQUERY)
|
|
||||||
// and share the handle-based result/delete entries, which must exist even
|
|
||||||
// when the timer-query group above is disabled.
|
|
||||||
funcsTable.GL.BeginOcclusionQuery = BeginOcclusionQuery;
|
|
||||||
funcsTable.GL.EndOcclusionQuery = EndOcclusionQuery;
|
|
||||||
// Real driver primitive counters: the frontend's CPU accounting cannot see a
|
|
||||||
// geometry shader's amplification.
|
|
||||||
funcsTable.GL.BeginXfbPrimitivesQuery = BeginXfbPrimitivesQuery;
|
|
||||||
funcsTable.GL.EndXfbPrimitivesQuery = EndXfbPrimitivesQuery;
|
|
||||||
funcsTable.GL.IsQueryResultAvailable = IsQueryResultAvailable;
|
|
||||||
funcsTable.GL.GetQueryResult64 = GetQueryResult64;
|
|
||||||
funcsTable.GL.DeleteBackendQuery = DeleteBackendQuery;
|
|
||||||
// Transform feedback is captured by the real ES driver rather than
|
|
||||||
// reconstructed from the draw recording, so the frontend has to hand the
|
|
||||||
// span boundaries over.
|
|
||||||
funcsTable.GL.PatchParameteri = DirectGLES::PatchParameteri;
|
|
||||||
funcsTable.GL.BeginTransformFeedback = XfbImpl::BeginTransformFeedback;
|
|
||||||
funcsTable.GL.EndTransformFeedback = XfbImpl::EndTransformFeedback;
|
|
||||||
funcsTable.GL.PauseTransformFeedback = XfbImpl::PauseTransformFeedback;
|
|
||||||
funcsTable.GL.ResumeTransformFeedback = XfbImpl::ResumeTransformFeedback;
|
|
||||||
funcsTable.GL.BindTransformFeedback = XfbImpl::BindTransformFeedback;
|
|
||||||
funcsTable.GL.DeleteTransformFeedback = XfbImpl::DeleteTransformFeedback;
|
|
||||||
funcsTableInitialized = true;
|
funcsTableInitialized = true;
|
||||||
}
|
}
|
||||||
return funcsTable;
|
return funcsTable;
|
||||||
@@ -1028,7 +948,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
return m_dynamicParameters;
|
return m_dynamicParameters;
|
||||||
}
|
}
|
||||||
|
|
||||||
void BackendObject_DirectGLES::ApplyGLESCapabilitiesForTesting(const MG_External::GLESCapabilities& capabilities) {
|
void BackendObject_DirectGLES::ApplyGLESCapabilitiesForTesting(
|
||||||
|
const MG_External::GLESCapabilities& capabilities) {
|
||||||
m_GLESCapabilities = capabilities;
|
m_GLESCapabilities = capabilities;
|
||||||
UpdateDynamicBackendParameters();
|
UpdateDynamicBackendParameters();
|
||||||
}
|
}
|
||||||
@@ -1058,10 +979,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
m_dynamicParameters.MaxIntegerSamples = m_GLESCapabilities.MaxIntegerSamples;
|
m_dynamicParameters.MaxIntegerSamples = m_GLESCapabilities.MaxIntegerSamples;
|
||||||
m_dynamicParameters.MaxSamples = m_GLESCapabilities.MaxSamples;
|
m_dynamicParameters.MaxSamples = m_GLESCapabilities.MaxSamples;
|
||||||
m_dynamicParameters.MaxSampleMaskWords = m_GLESCapabilities.MaxSampleMaskWords;
|
m_dynamicParameters.MaxSampleMaskWords = m_GLESCapabilities.MaxSampleMaskWords;
|
||||||
m_dynamicParameters.MaxPatchVertices = m_GLESCapabilities.MaxPatchVertices;
|
|
||||||
m_dynamicParameters.MaxTessGenLevel = m_GLESCapabilities.MaxTessGenLevel;
|
|
||||||
m_dynamicParameters.MinProgramTextureGatherOffset = m_GLESCapabilities.MinProgramTextureGatherOffset;
|
|
||||||
m_dynamicParameters.MaxProgramTextureGatherOffset = m_GLESCapabilities.MaxProgramTextureGatherOffset;
|
|
||||||
// Clamp the advertised sampler limits the same way the DirectVulkan backend does: per-stage
|
// Clamp the advertised sampler limits the same way the DirectVulkan backend does: per-stage
|
||||||
// GL_MAX_TEXTURE_IMAGE_UNITS must never exceed host-side fixed arrays sized off it (e.g.
|
// GL_MAX_TEXTURE_IMAGE_UNITS must never exceed host-side fixed arrays sized off it (e.g.
|
||||||
// Minecraft's 128-entry Blaze3D GlStateManager.TEXTURES[], iterated by Iris), and the combined
|
// Minecraft's 128-entry Blaze3D GlStateManager.TEXTURES[], iterated by Iris), and the combined
|
||||||
@@ -1089,10 +1006,10 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
m_dynamicParameters.MaxComputeWorkGroupInvocations = m_GLESCapabilities.MaxComputeWorkGroupInvocations;
|
m_dynamicParameters.MaxComputeWorkGroupInvocations = m_GLESCapabilities.MaxComputeWorkGroupInvocations;
|
||||||
m_dynamicParameters.MaxShaderStorageBufferBindings = m_GLESCapabilities.MaxShaderStorageBufferBindings;
|
m_dynamicParameters.MaxShaderStorageBufferBindings = m_GLESCapabilities.MaxShaderStorageBufferBindings;
|
||||||
m_dynamicParameters.MaxTextureBufferSize = m_GLESCapabilities.MaxTextureBufferSize;
|
m_dynamicParameters.MaxTextureBufferSize = m_GLESCapabilities.MaxTextureBufferSize;
|
||||||
m_dynamicParameters.TextureBufferOffsetAlignment = m_GLESCapabilities.TextureBufferOffsetAlignment;
|
|
||||||
m_dynamicParameters.MaxUniformBufferBindings = m_GLESCapabilities.MaxUniformBufferBindings;
|
m_dynamicParameters.MaxUniformBufferBindings = m_GLESCapabilities.MaxUniformBufferBindings;
|
||||||
m_dynamicParameters.MaxUniformBlockSize = m_GLESCapabilities.MaxUniformBlockSize;
|
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 =
|
m_dynamicParameters.MaxImageUnits =
|
||||||
std::max(std::min(m_GLESCapabilities.MaxImageUnits, maxSupportedTextureUnits), 0);
|
std::max(std::min(m_GLESCapabilities.MaxImageUnits, maxSupportedTextureUnits), 0);
|
||||||
m_dynamicParameters.MaxCombinedImageUniforms = std::max(m_GLESCapabilities.MaxCombinedImageUniforms, 0);
|
m_dynamicParameters.MaxCombinedImageUniforms = std::max(m_GLESCapabilities.MaxCombinedImageUniforms, 0);
|
||||||
@@ -1100,40 +1017,14 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
return std::min({std::max(stageLimit, 0), m_dynamicParameters.MaxImageUnits,
|
return std::min({std::max(stageLimit, 0), m_dynamicParameters.MaxImageUnits,
|
||||||
m_dynamicParameters.MaxCombinedImageUniforms});
|
m_dynamicParameters.MaxCombinedImageUniforms});
|
||||||
};
|
};
|
||||||
m_dynamicParameters.MaxVertexImageUniforms = clampStageImageUniforms(m_GLESCapabilities.MaxVertexImageUniforms);
|
m_dynamicParameters.MaxVertexImageUniforms =
|
||||||
|
clampStageImageUniforms(m_GLESCapabilities.MaxVertexImageUniforms);
|
||||||
m_dynamicParameters.MaxGeometryImageUniforms =
|
m_dynamicParameters.MaxGeometryImageUniforms =
|
||||||
clampStageImageUniforms(m_GLESCapabilities.MaxGeometryImageUniforms);
|
clampStageImageUniforms(m_GLESCapabilities.MaxGeometryImageUniforms);
|
||||||
m_dynamicParameters.MaxFragmentImageUniforms =
|
m_dynamicParameters.MaxFragmentImageUniforms =
|
||||||
clampStageImageUniforms(m_GLESCapabilities.MaxFragmentImageUniforms);
|
clampStageImageUniforms(m_GLESCapabilities.MaxFragmentImageUniforms);
|
||||||
m_dynamicParameters.MaxComputeImageUniforms =
|
m_dynamicParameters.MaxComputeImageUniforms =
|
||||||
clampStageImageUniforms(m_GLESCapabilities.MaxComputeImageUniforms);
|
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.
|
|
||||||
// SyncAttachmentObject (Managers.cpp, the glFramebufferTextureLayer branch) routes exactly
|
|
||||||
// five upload targets 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. The cube map array is the one target that also needs
|
|
||||||
// ES-level support before it has any storage to attach.
|
|
||||||
m_dynamicParameters.PerLayerFramebufferAttachmentTargets = 0;
|
|
||||||
if (DirectGLES::g_GLESFuncs.glFramebufferTextureLayer != nullptr) {
|
|
||||||
using DynParams = MG_Backend::DynamicBackendParameters;
|
|
||||||
m_dynamicParameters.PerLayerFramebufferAttachmentTargets |=
|
|
||||||
DynParams::PerLayerFramebufferAttachmentBit(TextureTarget::Texture3D) |
|
|
||||||
DynParams::PerLayerFramebufferAttachmentBit(TextureTarget::Texture1DArray) |
|
|
||||||
DynParams::PerLayerFramebufferAttachmentBit(TextureTarget::Texture2DArray) |
|
|
||||||
DynParams::PerLayerFramebufferAttachmentBit(TextureTarget::Texture2DMultisampleArray);
|
|
||||||
if (m_GLESCapabilities.SupportsTextureCubeMapArray) {
|
|
||||||
m_dynamicParameters.PerLayerFramebufferAttachmentTargets |=
|
|
||||||
DynParams::PerLayerFramebufferAttachmentBit(TextureTarget::TextureCubeMapArray);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Not a driver question and never will be: OpenGL ES has no double-precision vertex format
|
|
||||||
// and ESSL has no fp64 type to consume one with, so a 64-bit vertex attribute has nowhere to
|
|
||||||
// land on this backend regardless of what the driver underneath happens to support.
|
|
||||||
m_dynamicParameters.SupportsFloat64VertexAttributes = false;
|
|
||||||
m_dynamicParameters.MaxDrawBuffers = m_GLESCapabilities.MaxDrawBuffers;
|
m_dynamicParameters.MaxDrawBuffers = m_GLESCapabilities.MaxDrawBuffers;
|
||||||
m_dynamicParameters.MaxColorAttachments = m_GLESCapabilities.MaxColorAttachments;
|
m_dynamicParameters.MaxColorAttachments = m_GLESCapabilities.MaxColorAttachments;
|
||||||
m_dynamicParameters.MaxClipDistances = m_GLESCapabilities.MaxClipDistances;
|
m_dynamicParameters.MaxClipDistances = m_GLESCapabilities.MaxClipDistances;
|
||||||
@@ -1143,29 +1034,13 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
m_dynamicParameters.ViewportBoundsRangeMin = m_GLESCapabilities.ViewportBoundsRangeMin;
|
m_dynamicParameters.ViewportBoundsRangeMin = m_GLESCapabilities.ViewportBoundsRangeMin;
|
||||||
m_dynamicParameters.ViewportBoundsRangeMax = m_GLESCapabilities.ViewportBoundsRangeMax;
|
m_dynamicParameters.ViewportBoundsRangeMax = m_GLESCapabilities.ViewportBoundsRangeMax;
|
||||||
m_dynamicParameters.ViewportSubpixelBits = m_GLESCapabilities.ViewportSubpixelBits;
|
m_dynamicParameters.ViewportSubpixelBits = m_GLESCapabilities.ViewportSubpixelBits;
|
||||||
m_dynamicParameters.MinFragmentInterpolationOffset =
|
|
||||||
std::isfinite(m_GLESCapabilities.MinFragmentInterpolationOffset) &&
|
|
||||||
m_GLESCapabilities.MinFragmentInterpolationOffset <= -0.5f
|
|
||||||
? m_GLESCapabilities.MinFragmentInterpolationOffset
|
|
||||||
: -0.5f;
|
|
||||||
m_dynamicParameters.MaxFragmentInterpolationOffset = 0.4375f;
|
|
||||||
m_dynamicParameters.FragmentInterpolationOffsetBits = 4;
|
|
||||||
if (m_GLESCapabilities.FragmentInterpolationOffsetBits >= 4 &&
|
|
||||||
std::isfinite(m_GLESCapabilities.MaxFragmentInterpolationOffset)) {
|
|
||||||
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.FragmentInterpolationOffsetBits =
|
|
||||||
m_GLESCapabilities.FragmentInterpolationOffsetBits;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
m_dynamicParameters.SupportsWideLines =
|
m_dynamicParameters.SupportsWideLines =
|
||||||
m_GLESCapabilities.AliasedLineWidthRangeMax > 1.0f || m_GLESCapabilities.SmoothLineWidthRangeMax > 1.0f;
|
m_GLESCapabilities.AliasedLineWidthRangeMax > 1.0f || m_GLESCapabilities.SmoothLineWidthRangeMax > 1.0f;
|
||||||
|
|
||||||
const auto containsAny = [](const String& haystack, std::initializer_list<const char*> needles) {
|
const auto containsAny = [](const String& haystack, std::initializer_list<const char*> needles) {
|
||||||
return std::any_of(needles.begin(), needles.end(),
|
return std::any_of(needles.begin(), needles.end(), [&](const char* needle) {
|
||||||
[&](const char* needle) { return haystack.find(needle) != String::npos; });
|
return haystack.find(needle) != String::npos;
|
||||||
|
});
|
||||||
};
|
};
|
||||||
const String vendorAndRenderer =
|
const String vendorAndRenderer =
|
||||||
m_GLESCapabilities.GLESVendorString + " " + m_GLESCapabilities.GLESRendererString;
|
m_GLESCapabilities.GLESVendorString + " " + m_GLESCapabilities.GLESRendererString;
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -59,10 +59,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
GLenum buffer, GLint drawbuffer, const GLfloat* value);
|
GLenum buffer, GLint drawbuffer, const GLfloat* value);
|
||||||
void ClearNamedFramebufferfi(const SharedPtr<MG_State::GLState::FramebufferObject>& framebuffer,
|
void ClearNamedFramebufferfi(const SharedPtr<MG_State::GLState::FramebufferObject>& framebuffer,
|
||||||
GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil);
|
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,
|
void BlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1,
|
||||||
GLint dstY1, GLbitfield mask, GLenum filter);
|
GLint dstY1, GLbitfield mask, GLenum filter);
|
||||||
void BlitNamedFramebuffer(const SharedPtr<MG_State::GLState::FramebufferObject>& readFramebuffer,
|
void BlitNamedFramebuffer(const SharedPtr<MG_State::GLState::FramebufferObject>& readFramebuffer,
|
||||||
@@ -134,16 +130,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
BackendQueryHandle BeginTimeElapsedQuery();
|
BackendQueryHandle BeginTimeElapsedQuery();
|
||||||
void EndTimeElapsedQuery(BackendQueryHandle query);
|
void EndTimeElapsedQuery(BackendQueryHandle query);
|
||||||
BackendQueryHandle QueryCounterTimestamp();
|
BackendQueryHandle QueryCounterTimestamp();
|
||||||
// GL_ANY_SAMPLES_PASSED(_CONSERVATIVE) occlusion queries: core ES3, independent of
|
|
||||||
// GL_EXT_disjoint_timer_query and of MOBILEGL_DISABLE_TIMERQUERY. Results/deletion
|
|
||||||
// flow through GetQueryResult64/DeleteBackendQuery like the timer queries above.
|
|
||||||
BackendQueryHandle BeginOcclusionQuery();
|
|
||||||
void EndOcclusionQuery(BackendQueryHandle query);
|
|
||||||
// GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN / GL_PRIMITIVES_GENERATED, also core ES
|
|
||||||
// (GL_PRIMITIVES_GENERATED from ES 3.2 on). Null when the target is unavailable, in
|
|
||||||
// which case the frontend falls back to counting primitives from the draw calls.
|
|
||||||
BackendQueryHandle BeginXfbPrimitivesQuery(Bool generated);
|
|
||||||
void EndXfbPrimitivesQuery(BackendQueryHandle query);
|
|
||||||
Bool IsQueryResultAvailable(BackendQueryHandle query);
|
Bool IsQueryResultAvailable(BackendQueryHandle query);
|
||||||
// Returns true when a final value landed in *outNanoseconds (a zero for
|
// Returns true when a final value landed in *outNanoseconds (a zero for
|
||||||
// null or stale-generation handles IS final: the frontend may cache it
|
// null or stale-generation handles IS final: the frontend may cache it
|
||||||
@@ -168,24 +154,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
void SetGLESCapabilities(const MG_External::GLESCapabilities& capabilities);
|
void SetGLESCapabilities(const MG_External::GLESCapabilities& capabilities);
|
||||||
void DestroyEGLContext();
|
void DestroyEGLContext();
|
||||||
|
|
||||||
// Transform feedback capture spans, performed by the real ES driver. The
|
|
||||||
// capture set is declared on the backend program at link time; the driver-side
|
|
||||||
// begin is deferred to the first draw of the span (ES needs the capturing
|
|
||||||
// program current and the capture buffers bound), and the end also mirrors the
|
|
||||||
// captured bytes back into the frontend buffer shadows.
|
|
||||||
void PatchParameteri(GLenum pname, GLint value);
|
|
||||||
|
|
||||||
namespace XfbImpl {
|
|
||||||
Bool AreTransformFeedbacksSupported();
|
|
||||||
void BeginTransformFeedback(GLenum primitiveMode);
|
|
||||||
void EndTransformFeedback();
|
|
||||||
void PauseTransformFeedback();
|
|
||||||
void ResumeTransformFeedback();
|
|
||||||
void BindTransformFeedback(GLuint name);
|
|
||||||
void DeleteTransformFeedback(GLuint name);
|
|
||||||
void OnBackendContextDestroyed();
|
|
||||||
} // namespace XfbImpl
|
|
||||||
|
|
||||||
extern MG_External::EGLFunctionsTable g_EGLFuncs;
|
extern MG_External::EGLFunctionsTable g_EGLFuncs;
|
||||||
extern MG_External::GLESFunctionsTable g_GLESFuncs;
|
extern MG_External::GLESFunctionsTable g_GLESFuncs;
|
||||||
extern MG_External::GLESCapabilities g_GLESCapabilities;
|
extern MG_External::GLESCapabilities g_GLESCapabilities;
|
||||||
|
|||||||
@@ -630,32 +630,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
resource->syncedChangeSerial = bufferObject.GetChangeSerial();
|
resource->syncedChangeSerial = bufferObject.GetChangeSerial();
|
||||||
}
|
}
|
||||||
|
|
||||||
// A shader wrote this buffer through a storage/atomic-counter binding, so the ES
|
|
||||||
// driver's copy is ahead of the frontend shadow. Pull the whole thing back so
|
|
||||||
// MapBuffer/GetBufferSubData/CopyBufferSubData see the real results.
|
|
||||||
void Ops_ReadbackFromGpu(BufferObject& bufferObject) {
|
|
||||||
auto* resource = ResourceOf(bufferObject);
|
|
||||||
if (!resource || resource->id == 0 || !resource->storageInitialized) return;
|
|
||||||
if (resource->persistentMapped) return; // shadow already IS the GPU storage
|
|
||||||
if (!CanTouchGLNow() || resource->contextGeneration != g_bufferContextGeneration) return;
|
|
||||||
if (!g_GLESFuncs.glMapBufferRange || !g_GLESFuncs.glUnmapBuffer) return;
|
|
||||||
const SizeT size = std::min<SizeT>(bufferObject.GetSize(), resource->storageSize);
|
|
||||||
if (size == 0) return;
|
|
||||||
|
|
||||||
BindBufferId(TempBufferTarget, resource->id);
|
|
||||||
void* mapped = g_GLESFuncs.glMapBufferRange(TempBufferTarget, 0, static_cast<GLsizeiptr>(size),
|
|
||||||
GL_MAP_READ_BIT);
|
|
||||||
if (mapped == nullptr) {
|
|
||||||
MGLOG_E("Ops_ReadbackFromGpu: glMapBufferRange(read) failed for buffer %u", resource->id);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
bufferObject.WritebackFromBackend({mapped, size}, 0);
|
|
||||||
g_GLESFuncs.glUnmapBuffer(TempBufferTarget);
|
|
||||||
// The shadow now matches the backend byte for byte; without this the next
|
|
||||||
// draw would see a newer change serial and re-upload the readback over it.
|
|
||||||
resource->syncedChangeSerial = bufferObject.GetChangeSerial();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Ops_OnDestroy(SharedPtr<BackendBufferResource>&& resource) {
|
void Ops_OnDestroy(SharedPtr<BackendBufferResource>&& resource) {
|
||||||
if (!resource) return;
|
if (!resource) return;
|
||||||
auto* glesResource = static_cast<GLESBufferResource*>(resource.get());
|
auto* glesResource = static_cast<GLESBufferResource*>(resource.get());
|
||||||
@@ -688,7 +662,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
.FlushMappedRange = Ops_FlushMappedRange,
|
.FlushMappedRange = Ops_FlushMappedRange,
|
||||||
.OnDestroy = Ops_OnDestroy,
|
.OnDestroy = Ops_OnDestroy,
|
||||||
.AcquirePersistentMap = Ops_AcquirePersistentMap,
|
.AcquirePersistentMap = Ops_AcquirePersistentMap,
|
||||||
.ReadbackFromGpu = Ops_ReadbackFromGpu,
|
|
||||||
};
|
};
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
@@ -1289,15 +1262,31 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
const auto& allAttributes = stateVAOObject->GetAllAttributes();
|
const auto& allAttributes = stateVAOObject->GetAllAttributes();
|
||||||
for (Uint attribIndex = 0; attribIndex < allAttributes.size(); ++attribIndex) {
|
for (Uint attribIndex = 0; attribIndex < allAttributes.size(); ++attribIndex) {
|
||||||
const auto& attrib = allAttributes[attribIndex];
|
const auto& attrib = allAttributes[attribIndex];
|
||||||
|
const Uint32 attribBit = 1u << attribIndex;
|
||||||
|
|
||||||
|
// An enabled attrib with neither a buffer object nor a client pointer has no
|
||||||
|
// source; GL tolerates the state (only draws consuming it are undefined), but
|
||||||
|
// Adreno's ES driver memcpys the "client array" from address 0 at draw time
|
||||||
|
// (SIGSEGV). Keep such attribs disabled on the backend VAO and re-enable them
|
||||||
|
// the moment they gain a source - the mask-vs-current compare below triggers
|
||||||
|
// the enable even when only the Buffer/Format versions changed.
|
||||||
|
const Bool unsourceable = attrib.Enabled && !attrib.Buffer && attrib.Offset == 0;
|
||||||
|
const Bool wasForceDisabled = (m_forceDisabledAttribsMask & attribBit) != 0;
|
||||||
|
|
||||||
Bool needsSyncSwitch = allAttributeVersions[attribIndex].SwitchVersion !=
|
Bool needsSyncSwitch = allAttributeVersions[attribIndex].SwitchVersion !=
|
||||||
m_syncedAttributeVersions[attribIndex].SwitchVersion;
|
m_syncedAttributeVersions[attribIndex].SwitchVersion;
|
||||||
if (needsSyncSwitch) {
|
if (needsSyncSwitch || unsourceable != wasForceDisabled) {
|
||||||
if (attrib.Enabled) {
|
if (attrib.Enabled && !unsourceable) {
|
||||||
g_GLESFuncs.glEnableVertexAttribArray(attribIndex);
|
g_GLESFuncs.glEnableVertexAttribArray(attribIndex);
|
||||||
} else {
|
} else {
|
||||||
g_GLESFuncs.glDisableVertexAttribArray(attribIndex);
|
g_GLESFuncs.glDisableVertexAttribArray(attribIndex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (unsourceable) {
|
||||||
|
m_forceDisabledAttribsMask |= attribBit;
|
||||||
|
} else {
|
||||||
|
m_forceDisabledAttribsMask &= ~attribBit;
|
||||||
|
}
|
||||||
|
|
||||||
Bool needsSyncFormat = allAttributeVersions[attribIndex].FormatVersion !=
|
Bool needsSyncFormat = allAttributeVersions[attribIndex].FormatVersion !=
|
||||||
m_syncedAttributeVersions[attribIndex].FormatVersion;
|
m_syncedAttributeVersions[attribIndex].FormatVersion;
|
||||||
@@ -1305,23 +1294,17 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
m_syncedAttributeVersions[attribIndex].BufferVersion;
|
m_syncedAttributeVersions[attribIndex].BufferVersion;
|
||||||
if (!needsSyncFormat && !needsSyncBuffer) continue;
|
if (!needsSyncFormat && !needsSyncBuffer) continue;
|
||||||
|
|
||||||
// Defence in depth. The frontend already declines glVertexAttribLFormat on this
|
if (unsourceable) continue;
|
||||||
// backend (SupportsFloat64VertexAttributes is false - ES has no GL_DOUBLE vertex
|
|
||||||
// format and ESSL has no fp64 type), so IsLong should never arrive here; if it ever
|
// Client-side array with a non-null pointer: the pointer is uploaded and applied
|
||||||
// did, passing GL_DOUBLE to glVertexAttribPointer would only raise GL_INVALID_ENUM on
|
// per draw by SyncClientSideAttributesForDrawArrays.
|
||||||
// the real driver. Disabling rather than merely skipping matters: becoming long bumps
|
if (!attrib.Buffer) continue;
|
||||||
// FormatVersion, not SwitchVersion, so the enable/disable block above will not run
|
|
||||||
// again and an already-enabled array would stay enabled with no pointer and no
|
|
||||||
// ARRAY_BUFFER binding - which ES 3.1+ makes an INVALID_OPERATION at draw.
|
|
||||||
if (attrib.IsLong) {
|
|
||||||
MGLOG_E("DirectGLES: vertex attribute %u is a 64-bit (GL_DOUBLE) array, which this "
|
|
||||||
"backend cannot feed - disabling the array",
|
|
||||||
attribIndex);
|
|
||||||
g_GLESFuncs.glDisableVertexAttribArray(attribIndex);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!BindAttributeBuffer(attrib)) {
|
if (!BindAttributeBuffer(attrib)) {
|
||||||
|
if (attrib.Enabled) {
|
||||||
|
g_GLESFuncs.glDisableVertexAttribArray(attribIndex);
|
||||||
|
m_forceDisabledAttribsMask |= attribBit;
|
||||||
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1382,13 +1365,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Same reason as SyncToBackend: there is no ES vertex format for a 64-bit array, and
|
|
||||||
// this path only ever reaches glVertexAttribPointer/IPointer.
|
|
||||||
if (attrib.IsLong) {
|
|
||||||
g_GLESFuncs.glDisableVertexAttribArray(attribIndex);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto* clientData = reinterpret_cast<const Uint8*>(attrib.Offset);
|
const auto* clientData = reinterpret_cast<const Uint8*>(attrib.Offset);
|
||||||
const SizeT elementSize = GetAttributeByteSize(attrib.Type, attrib.Size, attrib.IsBgra);
|
const SizeT elementSize = GetAttributeByteSize(attrib.Type, attrib.Size, attrib.IsBgra);
|
||||||
if (!clientData || elementSize == 0 || attrib.Size <= 0) {
|
if (!clientData || elementSize == 0 || attrib.Size <= 0) {
|
||||||
@@ -1911,9 +1887,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
break;
|
break;
|
||||||
case TextureTarget::Texture3D:
|
case TextureTarget::Texture3D:
|
||||||
case TextureTarget::Texture2DArray:
|
case TextureTarget::Texture2DArray:
|
||||||
// ES 3.2 has GL_TEXTURE_CUBE_MAP_ARRAY natively and it stores exactly
|
|
||||||
// like a 2D array whose depth is 6 * the cube count.
|
|
||||||
case TextureTarget::TextureCubeMapArray:
|
|
||||||
g_GLESFuncs.glTexImage3D(
|
g_GLESFuncs.glTexImage3D(
|
||||||
glUploadTarget, static_cast<GLint>(level), (GLint)glInternalFormat,
|
glUploadTarget, static_cast<GLint>(level), (GLint)glInternalFormat,
|
||||||
static_cast<GLsizei>(uploadSize.x()), static_cast<GLsizei>(uploadSize.y()),
|
static_cast<GLsizei>(uploadSize.x()), static_cast<GLsizei>(uploadSize.y()),
|
||||||
@@ -1991,7 +1964,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
break;
|
break;
|
||||||
case TextureTarget::Texture3D:
|
case TextureTarget::Texture3D:
|
||||||
case TextureTarget::Texture2DArray:
|
case TextureTarget::Texture2DArray:
|
||||||
case TextureTarget::TextureCubeMapArray:
|
|
||||||
g_GLESFuncs.glTexStorage3D(target, static_cast<GLsizei>(mipmapCount), glInternalFormat,
|
g_GLESFuncs.glTexStorage3D(target, static_cast<GLsizei>(mipmapCount), glInternalFormat,
|
||||||
static_cast<GLsizei>(storageSize.x()),
|
static_cast<GLsizei>(storageSize.x()),
|
||||||
static_cast<GLsizei>(storageSize.y()),
|
static_cast<GLsizei>(storageSize.y()),
|
||||||
@@ -2044,7 +2016,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
break;
|
break;
|
||||||
case TextureTarget::Texture3D:
|
case TextureTarget::Texture3D:
|
||||||
case TextureTarget::Texture2DArray:
|
case TextureTarget::Texture2DArray:
|
||||||
case TextureTarget::TextureCubeMapArray:
|
|
||||||
g_GLESFuncs.glTexSubImage3D(
|
g_GLESFuncs.glTexSubImage3D(
|
||||||
glUploadTarget, static_cast<GLint>(level), 0, 0, 0,
|
glUploadTarget, static_cast<GLint>(level), 0, 0, 0,
|
||||||
static_cast<GLsizei>(uploadSize.x()),
|
static_cast<GLsizei>(uploadSize.x()),
|
||||||
@@ -2108,8 +2079,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case TextureTarget::Texture3D:
|
case TextureTarget::Texture3D:
|
||||||
case TextureTarget::Texture2DArray:
|
case TextureTarget::Texture2DArray: {
|
||||||
case TextureTarget::TextureCubeMapArray: {
|
|
||||||
g_GLESFuncs.glTexImage3D(
|
g_GLESFuncs.glTexImage3D(
|
||||||
glUploadTarget, static_cast<GLint>(level), (GLint)glInternalFormat,
|
glUploadTarget, static_cast<GLint>(level), (GLint)glInternalFormat,
|
||||||
static_cast<GLsizei>(uploadSize.x()),
|
static_cast<GLsizei>(uploadSize.x()),
|
||||||
@@ -2209,9 +2179,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
break;
|
break;
|
||||||
case TextureTarget::Texture3D:
|
case TextureTarget::Texture3D:
|
||||||
case TextureTarget::Texture2DArray:
|
case TextureTarget::Texture2DArray:
|
||||||
// ES 3.2 has GL_TEXTURE_CUBE_MAP_ARRAY natively and it stores exactly
|
|
||||||
// like a 2D array whose depth is 6 * the cube count.
|
|
||||||
case TextureTarget::TextureCubeMapArray:
|
|
||||||
g_GLESFuncs.glTexSubImage3D(glUploadTarget, static_cast<GLint>(level), 0, 0, 0,
|
g_GLESFuncs.glTexSubImage3D(glUploadTarget, static_cast<GLint>(level), 0, 0, 0,
|
||||||
static_cast<GLsizei>(uploadSize.x()),
|
static_cast<GLsizei>(uploadSize.x()),
|
||||||
static_cast<GLsizei>(uploadSize.y()),
|
static_cast<GLsizei>(uploadSize.y()),
|
||||||
@@ -2264,24 +2231,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
"ID: %u, buffer ID: %u, buffer size: %zu, format: %s",
|
"ID: %u, buffer ID: %u, buffer size: %zu, format: %s",
|
||||||
m_backendTextureId, backendId, buffer->GetSize(),
|
m_backendTextureId, backendId, buffer->GetSize(),
|
||||||
MG_Util::ConvertGLEnumToString(glInternalFormat).c_str());
|
MG_Util::ConvertGLEnumToString(glInternalFormat).c_str());
|
||||||
// A texture that names a window of the buffer needs the range form; the
|
g_GLESFuncs.glTexBuffer(GL_TEXTURE_BUFFER, glInternalFormat, backendId);
|
||||||
// whole-buffer forms report offset 0 and the buffer's current size, which
|
|
||||||
// glTexBuffer expresses more directly (and works where the range entry point
|
|
||||||
// is absent).
|
|
||||||
const SizeT rangeOffset = textureBufferObject->GetBufferRangeOffset();
|
|
||||||
const SizeT rangeSize = textureBufferObject->GetBufferRangeSizeInBytes();
|
|
||||||
if (rangeOffset == 0 && rangeSize == buffer->GetSize()) {
|
|
||||||
g_GLESFuncs.glTexBuffer(GL_TEXTURE_BUFFER, glInternalFormat, backendId);
|
|
||||||
} else if (g_GLESFuncs.glTexBufferRange != nullptr) {
|
|
||||||
g_GLESFuncs.glTexBufferRange(GL_TEXTURE_BUFFER, glInternalFormat, backendId,
|
|
||||||
static_cast<GLintptr>(rangeOffset),
|
|
||||||
static_cast<GLsizeiptr>(rangeSize));
|
|
||||||
} else {
|
|
||||||
MGLOG_E("Texture buffer %u names a sub-range but the driver has no "
|
|
||||||
"glTexBufferRange; binding the whole buffer instead",
|
|
||||||
stateTextureObject->GetExternalIndex());
|
|
||||||
g_GLESFuncs.glTexBuffer(GL_TEXTURE_BUFFER, glInternalFormat, backendId);
|
|
||||||
}
|
|
||||||
DebugImpl::ErrorLopper::Loop(
|
DebugImpl::ErrorLopper::Loop(
|
||||||
[file = __FILE__, line = __LINE__, func = __func__, glInternalFormat, backendId](GLenum err) {
|
[file = __FILE__, line = __LINE__, func = __func__, glInternalFormat, backendId](GLenum err) {
|
||||||
MGLOG_D("%s(%s:%d) glTexBuffer(format=%s, buffer=%u) ES error: %s",
|
MGLOG_D("%s(%s:%d) glTexBuffer(format=%s, buffer=%u) ES error: %s",
|
||||||
@@ -2481,19 +2431,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
MGLOG_D("%s(%s:%d) ES error %s", func, file, line, MG_Util::ConvertGLEnumToString(err).c_str());
|
MGLOG_D("%s(%s:%d) ES error %s", func, file, line, MG_Util::ConvertGLEnumToString(err).c_str());
|
||||||
});
|
});
|
||||||
|
|
||||||
// A three-channel format widened to four for a multisample target (see
|
const auto& swizzleParams = stateTextureObject->GetAllSwizzleParams();
|
||||||
// NormalizePixelFormat) gains an alpha channel the frontend format does not have, and
|
|
||||||
// whatever the draw that filled it wrote there is not what GL would report: a format
|
|
||||||
// without alpha reads back as 1.0. Answer the ALPHA swizzle source with ONE so the
|
|
||||||
// promotion stays invisible, composed with the swizzle the application asked for.
|
|
||||||
Vec4<TextureSwizzleParam> swizzleParams = stateTextureObject->GetAllSwizzleParams();
|
|
||||||
if (TextureImpl::BackendTextureFormatAddsAlpha(stateTextureObject->GetFormat(), targetInternal)) {
|
|
||||||
for (SizeT channel = 0; channel < 4; ++channel) {
|
|
||||||
if (swizzleParams[channel] == TextureSwizzleParam::Alpha) {
|
|
||||||
swizzleParams[channel] = TextureSwizzleParam::One;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (swizzleParams != m_cacheSwizzleParams) {
|
if (swizzleParams != m_cacheSwizzleParams) {
|
||||||
#define SYNC_TEX_SWIZZLE_PARAM_IF_CHANGED(func, glEnum) \
|
#define SYNC_TEX_SWIZZLE_PARAM_IF_CHANGED(func, glEnum) \
|
||||||
if (m_cacheSwizzleParams.func != swizzleParams.func) { \
|
if (m_cacheSwizzleParams.func != swizzleParams.func) { \
|
||||||
@@ -2511,10 +2449,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// GL_TEXTURE_BORDER_COLOR needs ES 3.2 or EXT/OES_texture_border_clamp; on a driver
|
if (!isMultisampleTarget && m_cacheBorderColor != stateTextureObject->GetBorderColor()) {
|
||||||
// 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();
|
const auto& borderColor = stateTextureObject->GetBorderColor();
|
||||||
GLfloat borderColorArray[4] = {borderColor.x(), borderColor.y(), borderColor.z(), borderColor.w()};
|
GLfloat borderColorArray[4] = {borderColor.x(), borderColor.y(), borderColor.z(), borderColor.w()};
|
||||||
g_GLESFuncs.glTexParameterfv(target, GL_TEXTURE_BORDER_COLOR, borderColorArray);
|
g_GLESFuncs.glTexParameterfv(target, GL_TEXTURE_BORDER_COLOR, borderColorArray);
|
||||||
@@ -2555,12 +2490,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
#ifdef TRACY_ENABLE
|
#ifdef TRACY_ENABLE
|
||||||
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
||||||
#endif
|
#endif
|
||||||
// Identity until a non-identity draw-buffer array forces a relocation. A framebuffer
|
|
||||||
// that is never draw-bound never runs the recompute, so the table has to start out
|
|
||||||
// matching what the attachment loop will physically do.
|
|
||||||
for (Uint i = 0; i < MAX_COLOR_ATTACHMENT_SLOTS; ++i) {
|
|
||||||
m_backendColorSlots[i] = GL_COLOR_ATTACHMENT0 + i;
|
|
||||||
}
|
|
||||||
g_GLESFuncs.glGenFramebuffers(1, &m_backendFBOId);
|
g_GLESFuncs.glGenFramebuffers(1, &m_backendFBOId);
|
||||||
if (m_backendFBOId == 0) {
|
if (m_backendFBOId == 0) {
|
||||||
MGLOG_E("Failed to generate framebuffer object.");
|
MGLOG_E("Failed to generate framebuffer object.");
|
||||||
@@ -2634,16 +2563,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
std::fill(std::begin(m_frontendDrawBuffers), std::end(m_frontendDrawBuffers),
|
std::fill(std::begin(m_frontendDrawBuffers), std::end(m_frontendDrawBuffers),
|
||||||
FramebufferAttachmentType::Unknown);
|
FramebufferAttachmentType::Unknown);
|
||||||
std::fill(std::begin(m_backendDrawBuffers), std::end(m_backendDrawBuffers), GL_NONE);
|
std::fill(std::begin(m_backendDrawBuffers), std::end(m_backendDrawBuffers), GL_NONE);
|
||||||
// NOTE: this does NOT empty the backend ES framebuffer - m_backendFBOId keeps every
|
|
||||||
// attachment it had, possibly under a non-identity permutation. Declaring the table
|
|
||||||
// identity here is safe only because every attachment version below is invalidated too,
|
|
||||||
// so the next sync re-attaches all non-empty attachments at their identity points AND
|
|
||||||
// (see SyncToBackend's attachment loop) detaches any colour point whose frontend owner
|
|
||||||
// is empty. Without that detach a stale image would survive under a point the table now
|
|
||||||
// claims for a different, empty attachment.
|
|
||||||
for (Uint i = 0; i < MAX_COLOR_ATTACHMENT_SLOTS; ++i) {
|
|
||||||
m_backendColorSlots[i] = GL_COLOR_ATTACHMENT0 + i;
|
|
||||||
}
|
|
||||||
m_frontendReadBuffer = FramebufferAttachmentType::Unknown;
|
m_frontendReadBuffer = FramebufferAttachmentType::Unknown;
|
||||||
m_backendReadBuffer = GL_NONE;
|
m_backendReadBuffer = GL_NONE;
|
||||||
std::fill(m_syncedFrontendAttachmentVersions.begin(), m_syncedFrontendAttachmentVersions.end(),
|
std::fill(m_syncedFrontendAttachmentVersions.begin(), m_syncedFrontendAttachmentVersions.end(),
|
||||||
@@ -2678,8 +2597,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
} else if (const auto uploadTarget = attachmentObject.GetTextureUploadTarget();
|
} else if (const auto uploadTarget = attachmentObject.GetTextureUploadTarget();
|
||||||
uploadTarget == TextureUploadTarget::Texture3D ||
|
uploadTarget == TextureUploadTarget::Texture3D ||
|
||||||
uploadTarget == TextureUploadTarget::Texture2DArray ||
|
uploadTarget == TextureUploadTarget::Texture2DArray ||
|
||||||
uploadTarget == TextureUploadTarget::Texture1DArray ||
|
|
||||||
uploadTarget == TextureUploadTarget::CubeMapArray ||
|
|
||||||
uploadTarget == TextureUploadTarget::Texture2DMultisampleArray) {
|
uploadTarget == TextureUploadTarget::Texture2DMultisampleArray) {
|
||||||
// Single slice/layer of a 3D or array texture: ES has no
|
// Single slice/layer of a 3D or array texture: ES has no
|
||||||
// glFramebufferTexture3D, layers attach via glFramebufferTextureLayer.
|
// glFramebufferTexture3D, layers attach via glFramebufferTextureLayer.
|
||||||
@@ -2788,31 +2705,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Bool IsFixedPointFallbackReadAttachment() {
|
|
||||||
const auto& readFBO =
|
|
||||||
MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Read).GetBoundObject();
|
|
||||||
if (!readFBO) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const auto readBuffer = readFBO->GetReadBuffer();
|
|
||||||
if (readBuffer < FramebufferAttachmentType::Color0 || readBuffer > FramebufferAttachmentType::Color31) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// Any signed-normalized attachment, not just the ones currently substituted:
|
|
||||||
// ES has no GL_CLAMP_READ_COLOR at all, so even a natively stored SNORM buffer
|
|
||||||
// hands back the negative half that desktop GL clamps away.
|
|
||||||
const auto& attachmentObject = readFBO->GetAttachment(readBuffer);
|
|
||||||
if (attachmentObject.IsTexture()) {
|
|
||||||
const auto& textureObject = attachmentObject.GetTexture();
|
|
||||||
return textureObject && IsSnormFormat(textureObject->GetFormat());
|
|
||||||
}
|
|
||||||
if (attachmentObject.IsRenderbuffer()) {
|
|
||||||
const auto& renderbufferObject = attachmentObject.GetRenderbuffer();
|
|
||||||
return renderbufferObject && IsSnormFormat(renderbufferObject->GetInternalFormat());
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void BackendFramebufferObject::SyncReadBufferToBackend(
|
void BackendFramebufferObject::SyncReadBufferToBackend(
|
||||||
const SharedPtr<MG_State::GLState::FramebufferObject>& stateFBOObject) {
|
const SharedPtr<MG_State::GLState::FramebufferObject>& stateFBOObject) {
|
||||||
if (!stateFBOObject) {
|
if (!stateFBOObject) {
|
||||||
@@ -2835,95 +2727,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Bool BackendFramebufferObject::RecomputeBackendColorSlots(
|
|
||||||
const FramebufferObject::FramebufferAttachmentArray& stateDrawBuffers) {
|
|
||||||
// Only the first GL_MAX_COLOR_ATTACHMENTS points exist in the backend. The frontend's own
|
|
||||||
// limit (ValidateColorAttachmentInRange, which reads the clamped
|
|
||||||
// GetDynamicParameters().MaxColorAttachments) is never larger than this raw ES cap, so an
|
|
||||||
// index the frontend accepted is always < slotCount. Indices at or above it can never own
|
|
||||||
// an image and stay on their identity point - never touched, never a GL error.
|
|
||||||
const Uint slotCount =
|
|
||||||
std::min<Uint>(MAX_COLOR_ATTACHMENT_SLOTS,
|
|
||||||
static_cast<Uint>(std::max<Int>(g_GLESCapabilities.MaxColorAttachments, 1)));
|
|
||||||
|
|
||||||
GLenum newSlots[MAX_COLOR_ATTACHMENT_SLOTS];
|
|
||||||
for (Uint i = 0; i < MAX_COLOR_ATTACHMENT_SLOTS; ++i) {
|
|
||||||
newSlots[i] = GL_COLOR_ATTACHMENT0 + i;
|
|
||||||
}
|
|
||||||
Bool assigned[MAX_COLOR_ATTACHMENT_SLOTS] = {false};
|
|
||||||
Bool slotTaken[MAX_COLOR_ATTACHMENT_SLOTS] = {false};
|
|
||||||
|
|
||||||
// 1. ES pins draw-buffer slot s to GL_COLOR_ATTACHMENTs, so an attachment named by draw
|
|
||||||
// buffer slot s has no choice: its image must sit at backend point s. This has to
|
|
||||||
// agree with the compaction the caller just pushed through glDrawBuffers.
|
|
||||||
for (Uint s = 0; s < FramebufferObject::MAX_DRAW_BUFFERS && s < slotCount; ++s) {
|
|
||||||
const auto frontendBuf = stateDrawBuffers[s];
|
|
||||||
if (frontendBuf < FramebufferAttachmentType::Color0 ||
|
|
||||||
frontendBuf > FramebufferAttachmentType::Color31) {
|
|
||||||
continue; // GL_NONE, or a default-framebuffer FRONT/BACK token: never relocated.
|
|
||||||
}
|
|
||||||
const Uint a =
|
|
||||||
static_cast<Uint>(frontendBuf) - static_cast<Uint>(FramebufferAttachmentType::Color0);
|
|
||||||
// Neither guard may ever fire: a duplicate draw buffer is already INVALID_OPERATION
|
|
||||||
// and an out-of-range one is rejected by ValidateColorAttachmentInRange. If one did
|
|
||||||
// fire the table would disagree with the glDrawBuffers the caller already issued,
|
|
||||||
// which is the exact non-injectivity this table exists to remove.
|
|
||||||
MOBILEGL_ASSERT(a < slotCount && !assigned[a],
|
|
||||||
"Draw buffer %u names colour attachment %u which is out of range or duplicated.", s,
|
|
||||||
a);
|
|
||||||
if (a >= slotCount || assigned[a]) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
newSlots[a] = GL_COLOR_ATTACHMENT0 + s;
|
|
||||||
assigned[a] = true;
|
|
||||||
slotTaken[s] = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. Everything else keeps its identity point when that point survived step 1. This is
|
|
||||||
// what makes the ordinary drawBuffers[s] == COLOR_ATTACHMENTs case a strict no-op:
|
|
||||||
// the table stays identity, nothing moves, no attachment is re-issued.
|
|
||||||
for (Uint a = 0; a < slotCount; ++a) {
|
|
||||||
if (assigned[a] || slotTaken[a]) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
newSlots[a] = GL_COLOR_ATTACHMENT0 + a;
|
|
||||||
assigned[a] = true;
|
|
||||||
slotTaken[a] = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. What is left are attachments whose identity point step 1 took away. Park them on the
|
|
||||||
// lowest free point. They are not draw buffers, so nothing is rendered through them;
|
|
||||||
// they only have to stay addressable for glReadBuffer and blits, and the map has to
|
|
||||||
// stay injective so reading one of them cannot land on another's image.
|
|
||||||
for (Uint a = 0; a < slotCount; ++a) {
|
|
||||||
if (assigned[a]) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
for (Uint s = 0; s < slotCount; ++s) {
|
|
||||||
if (!slotTaken[s]) {
|
|
||||||
newSlots[a] = GL_COLOR_ATTACHMENT0 + s;
|
|
||||||
assigned[a] = true;
|
|
||||||
slotTaken[s] = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Bool moved = false;
|
|
||||||
for (Uint a = 0; a < MAX_COLOR_ATTACHMENT_SLOTS; ++a) {
|
|
||||||
if (m_backendColorSlots[a] == newSlots[a]) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
m_backendColorSlots[a] = newSlots[a];
|
|
||||||
moved = true;
|
|
||||||
// This attachment's image now belongs at a different backend point. Its frontend
|
|
||||||
// version has not changed, so the attachment loop would skip it; force it.
|
|
||||||
m_syncedFrontendAttachmentVersions[static_cast<SizeT>(FramebufferAttachmentType::Color0) + a] =
|
|
||||||
static_cast<Uint16>(~0u);
|
|
||||||
}
|
|
||||||
return moved;
|
|
||||||
}
|
|
||||||
|
|
||||||
void BackendFramebufferObject::SyncToBackend(
|
void BackendFramebufferObject::SyncToBackend(
|
||||||
const SharedPtr<MG_State::GLState::FramebufferObject>& stateFBOObject, FramebufferTarget asTarget) {
|
const SharedPtr<MG_State::GLState::FramebufferObject>& stateFBOObject, FramebufferTarget asTarget) {
|
||||||
#ifdef TRACY_ENABLE
|
#ifdef TRACY_ENABLE
|
||||||
@@ -2979,13 +2782,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
nEffectiveBuffers = i + 1;
|
nEffectiveBuffers = i + 1;
|
||||||
}
|
}
|
||||||
g_GLESFuncs.glDrawBuffers(nEffectiveBuffers, m_backendDrawBuffers);
|
g_GLESFuncs.glDrawBuffers(nEffectiveBuffers, m_backendDrawBuffers);
|
||||||
// The line above pinned backend point s to draw-buffer slot s, so the images have to
|
|
||||||
// be moved under those points. Rebuild the whole colour map and, when anything moved,
|
|
||||||
// also drop the read-buffer memo: SyncReadBufferToBackend keys it on the frontend
|
|
||||||
// enum alone, which does not change when the point under it does.
|
|
||||||
if (RecomputeBackendColorSlots(stateDrawBuffers)) {
|
|
||||||
m_frontendReadBuffer = FramebufferAttachmentType::Unknown;
|
|
||||||
}
|
|
||||||
MGLOG_D("DBAPPLY beFbo=%u target=%d n=%d db0=0x%x feDb0=%d", m_backendFBOId, (int)asTarget,
|
MGLOG_D("DBAPPLY beFbo=%u target=%d n=%d db0=0x%x feDb0=%d", m_backendFBOId, (int)asTarget,
|
||||||
nEffectiveBuffers, m_backendDrawBuffers[0], (int)stateDrawBuffers[0]);
|
nEffectiveBuffers, m_backendDrawBuffers[0], (int)stateDrawBuffers[0]);
|
||||||
}
|
}
|
||||||
@@ -3031,22 +2827,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
|
|
||||||
// relevant FRONTEND!!! version should be checked and updated
|
// relevant FRONTEND!!! version should be checked and updated
|
||||||
if (m_syncedFrontendAttachmentVersions[i] != attachmentVersions[i]) {
|
if (m_syncedFrontendAttachmentVersions[i] != attachmentVersions[i]) {
|
||||||
// SyncAttachmentObject only ever attaches: for an empty frontend attachment it
|
|
||||||
// returns true and issues nothing, so the point keeps whatever was there. That is
|
|
||||||
// what makes m_backendColorSlots a permutation of the PHYSICAL layout rather than
|
|
||||||
// a claim about one - a point handed to an attachment with no image would
|
|
||||||
// otherwise still hold the previous owner's image and glReadBuffer would return
|
|
||||||
// it. Bounded by GL_MAX_COLOR_ATTACHMENTS because GL_COLOR_ATTACHMENTn above the
|
|
||||||
// driver's limit is INVALID_ENUM, and restricted to colour points because
|
|
||||||
// FRONT_LEFT/BACK_LEFT and co. are not ES attachment points at all.
|
|
||||||
const Bool isColorPoint =
|
|
||||||
frontendType >= FramebufferAttachmentType::Color0 &&
|
|
||||||
frontendType <= FramebufferAttachmentType::Color31 &&
|
|
||||||
(static_cast<Int>(frontendType) - static_cast<Int>(FramebufferAttachmentType::Color0)) <
|
|
||||||
g_GLESCapabilities.MaxColorAttachments;
|
|
||||||
if (isColorPoint && attachmentObject.IsEmpty() && glBackendAttachment != GL_NONE) {
|
|
||||||
g_GLESFuncs.glFramebufferRenderbuffer(glFBOTarget, glBackendAttachment, GL_RENDERBUFFER, 0);
|
|
||||||
}
|
|
||||||
if (SyncAttachmentObject(glFBOTarget, attachmentObject, glBackendAttachment)) {
|
if (SyncAttachmentObject(glFBOTarget, attachmentObject, glBackendAttachment)) {
|
||||||
m_syncedFrontendAttachmentVersions[i] = attachmentVersions[i];
|
m_syncedFrontendAttachmentVersions[i] = attachmentVersions[i];
|
||||||
}
|
}
|
||||||
@@ -3108,18 +2888,21 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
}
|
}
|
||||||
|
|
||||||
GLenum BackendFramebufferObject::GetBackendAttachmentType(FramebufferAttachmentType frontendAtt) const {
|
GLenum BackendFramebufferObject::GetBackendAttachmentType(FramebufferAttachmentType frontendAtt) const {
|
||||||
// Only colour attachments are ever relocated; depth/stencil, the default framebuffer's
|
GLenum glBackendReadBuffer = GL_NONE;
|
||||||
// FRONT/BACK names and None map straight through.
|
auto it = std::find(m_frontendDrawBuffers, m_frontendDrawBuffers + FramebufferObject::MAX_DRAW_BUFFERS,
|
||||||
if (frontendAtt < FramebufferAttachmentType::Color0 || frontendAtt > FramebufferAttachmentType::Color31) {
|
frontendAtt);
|
||||||
return MG_Util::ConvertFramebufferAttachmentTypeToGLEnum(frontendAtt);
|
Bool notFound = (it == m_frontendDrawBuffers + FramebufferObject::MAX_DRAW_BUFFERS);
|
||||||
|
if (notFound) {
|
||||||
|
MGLOG_D(
|
||||||
|
"%s: frontendAtt not found in draw buffer (probably not remapped), just use the same as frontend",
|
||||||
|
__func__);
|
||||||
|
glBackendReadBuffer = MG_Util::ConvertFramebufferAttachmentTypeToGLEnum(frontendAtt);
|
||||||
|
} else {
|
||||||
|
MGLOG_D("%s: frontendAtt found in draw buffer, keep it consistent as in read buffers", __func__);
|
||||||
|
auto index = std::distance(m_frontendDrawBuffers, it);
|
||||||
|
glBackendReadBuffer = m_backendDrawBuffers[index];
|
||||||
}
|
}
|
||||||
// The table is a permutation of the backend colour points, so this is the one point that
|
return glBackendReadBuffer;
|
||||||
// owns this attachment. Searching the draw-buffer array instead returned the identity
|
|
||||||
// point for every attachment that was not a draw buffer - which is exactly the point a
|
|
||||||
// relocated draw buffer had just taken over, so COLOR_ATTACHMENT0 read back the image of
|
|
||||||
// whatever attachment was last made the draw buffer.
|
|
||||||
const Uint index = static_cast<Uint>(frontendAtt) - static_cast<Uint>(FramebufferAttachmentType::Color0);
|
|
||||||
return m_backendColorSlots[index];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
StateBackendObjectRegistry<MG_State::GLState::FramebufferObject, BackendFramebufferObject>
|
StateBackendObjectRegistry<MG_State::GLState::FramebufferObject, BackendFramebufferObject>
|
||||||
@@ -3424,7 +3207,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
|
|
||||||
namespace PrgramImpl {
|
namespace PrgramImpl {
|
||||||
Uint32 g_snormFallbackClampOutputMask = 0;
|
Uint32 g_snormFallbackClampOutputMask = 0;
|
||||||
Uint g_fragColorBroadcastCount = 1;
|
|
||||||
Uint32 g_unormFallbackClampOutputMask = 0;
|
Uint32 g_unormFallbackClampOutputMask = 0;
|
||||||
Uint g_lastUsedBackendProgramId = 0;
|
Uint g_lastUsedBackendProgramId = 0;
|
||||||
StateBackendObjectRegistry<MG_State::GLState::ProgramObject, BackendProgramObjectImpl> g_backendProgramObjects;
|
StateBackendObjectRegistry<MG_State::GLState::ProgramObject, BackendProgramObjectImpl> g_backendProgramObjects;
|
||||||
@@ -3476,10 +3258,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
|
|
||||||
MGLOG_D("Syncing program to backend. State program ID: %u, Backend ID: %u",
|
MGLOG_D("Syncing program to backend. State program ID: %u, Backend ID: %u",
|
||||||
stateProgramObject->GetExternalIndex(), m_backendProgramId);
|
stateProgramObject->GetExternalIndex(), m_backendProgramId);
|
||||||
m_backendProgramUsable = true;
|
|
||||||
m_snormFallbackClampOutputMask = g_snormFallbackClampOutputMask;
|
m_snormFallbackClampOutputMask = g_snormFallbackClampOutputMask;
|
||||||
m_unormFallbackClampOutputMask = g_unormFallbackClampOutputMask;
|
m_unormFallbackClampOutputMask = g_unormFallbackClampOutputMask;
|
||||||
m_fragColorBroadcastCount = g_fragColorBroadcastCount;
|
|
||||||
|
|
||||||
// Detach all existing shaders
|
// Detach all existing shaders
|
||||||
GLint attachedCount = 0;
|
GLint attachedCount = 0;
|
||||||
@@ -3511,6 +3291,19 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
}
|
}
|
||||||
auto& shaderSpirvs = stateProgramObject->GetGeneratedSpirv();
|
auto& shaderSpirvs = stateProgramObject->GetGeneratedSpirv();
|
||||||
|
|
||||||
|
// Adreno's ESSL compiler mishandles gl_ClipDistance (rejects redeclarations,
|
||||||
|
// miscompiles non-constant-index writes and constant-index gl_in element reads,
|
||||||
|
// crashes on whole-array gl_in reads) and cannot dynamically index the global
|
||||||
|
// const struct[] LUTs SPIRV-Cross likes to emit. Gate the workarounds to
|
||||||
|
// Qualcomm; MOBILEGL_QUIRK_CLIP_DISTANCE overrides the device detection.
|
||||||
|
const MG_Config::QuirkOverride clipDistanceQuirkOverride =
|
||||||
|
MG_Config::Features.ClipDistanceQuirk;
|
||||||
|
const Bool applyClipDistanceQuirk =
|
||||||
|
clipDistanceQuirkOverride == MG_Config::QuirkOverride::ForceOn ||
|
||||||
|
(clipDistanceQuirkOverride == MG_Config::QuirkOverride::Auto &&
|
||||||
|
pActiveBackendObject &&
|
||||||
|
pActiveBackendObject->GetDynamicParameters().GpuVendor == GpuVendorKind::Qualcomm);
|
||||||
|
|
||||||
for (int index = 0; index < attachedShaders.size(); ++index) {
|
for (int index = 0; index < attachedShaders.size(); ++index) {
|
||||||
auto& shader = attachedShaders[index];
|
auto& shader = attachedShaders[index];
|
||||||
GLenum glShaderType = MG_Util::ConvertShaderStageToGLEnum(shader->GetShaderStage());
|
GLenum glShaderType = MG_Util::ConvertShaderStageToGLEnum(shader->GetShaderStage());
|
||||||
@@ -3533,6 +3326,63 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
effectiveSpirv = &loweredSpirv;
|
effectiveSpirv = &loweredSpirv;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GL 3.3 only promises undefined *values* for out-of-bounds array indexing, but
|
||||||
|
// Adreno's ESSL compiler constant-folds a provably out-of-bounds local-array
|
||||||
|
// index into poison that corrupts the whole shader's output. Clamp every
|
||||||
|
// access-chain index to its declared bounds before transpiling.
|
||||||
|
Vector<unsigned int> clampedSpirv;
|
||||||
|
if (MG_Util::ShaderTranspiler::ShaderCompiler::ClampAccessChainIndicesForEssl(*effectiveSpirv,
|
||||||
|
clampedSpirv) &&
|
||||||
|
!clampedSpirv.empty()) {
|
||||||
|
effectiveSpirv = &clampedSpirv;
|
||||||
|
} else {
|
||||||
|
MGLOG_W("ClampAccessChainIndicesForEssl failed, continuing with unclamped SPIR-V.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// SPIRV-Cross emulates 1D samplers as 2D for ES: it widens texelFetch coordinates
|
||||||
|
// to ivec2 but keeps the ConstOffset operand scalar, which is not a valid ESSL
|
||||||
|
// texelFetchOffset overload (Adreno rejects it). Fold the constant offset into the
|
||||||
|
// coordinate instead (texelFetchOffset(t,P,l,o) == texelFetch(t,P+o,l)).
|
||||||
|
Vector<unsigned int> foldedOffsetSpirv;
|
||||||
|
if (MG_Util::ShaderTranspiler::ShaderCompiler::FoldConstOffsetFor1DFetchForEssl(
|
||||||
|
*effectiveSpirv, foldedOffsetSpirv) &&
|
||||||
|
!foldedOffsetSpirv.empty()) {
|
||||||
|
effectiveSpirv = &foldedOffsetSpirv;
|
||||||
|
} else {
|
||||||
|
MGLOG_W("FoldConstOffsetFor1DFetchForEssl failed, continuing with unfolded SPIR-V.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adreno quirk: shadow gl_ClipDistance in Private arrays so the transpiled
|
||||||
|
// ESSL only writes the builtin with literal constant indices (flushed before
|
||||||
|
// EmitVertex/return) and only reads gl_in clip distances through dynamic loop
|
||||||
|
// indices - the shapes this driver compiles correctly. Must run after the
|
||||||
|
// access-chain clamp above so the flush indices stay literal constants.
|
||||||
|
Vector<unsigned int> clipDistanceSpirv;
|
||||||
|
if (applyClipDistanceQuirk &&
|
||||||
|
(glShaderType == GL_VERTEX_SHADER || glShaderType == GL_GEOMETRY_SHADER)) {
|
||||||
|
if (MG_Util::ShaderTranspiler::ShaderCompiler::LowerClipDistanceForEssl(
|
||||||
|
*effectiveSpirv, clipDistanceSpirv) &&
|
||||||
|
!clipDistanceSpirv.empty()) {
|
||||||
|
effectiveSpirv = &clipDistanceSpirv;
|
||||||
|
} else {
|
||||||
|
MGLOG_W("LowerClipDistanceForEssl failed, continuing with unlowered SPIR-V.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adreno quirk: split single constant-composite stores of struct arrays so
|
||||||
|
// SPIRV-Cross does not promote them to global const struct[] LUTs, which this
|
||||||
|
// driver cannot dynamically index ("Cannot offset into the structure").
|
||||||
|
Vector<unsigned int> structLutSpirv;
|
||||||
|
if (applyClipDistanceQuirk) {
|
||||||
|
if (MG_Util::ShaderTranspiler::ShaderCompiler::DefeatConstStructArrayLutForEssl(
|
||||||
|
*effectiveSpirv, structLutSpirv) &&
|
||||||
|
!structLutSpirv.empty()) {
|
||||||
|
effectiveSpirv = &structLutSpirv;
|
||||||
|
} else {
|
||||||
|
MGLOG_W("DefeatConstStructArrayLutForEssl failed, continuing with unsplit SPIR-V.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ESSL stage-matches uniform blocks by member precision, but SPIRV-Cross prints
|
// ESSL stage-matches uniform blocks by member precision, but SPIRV-Cross prints
|
||||||
// a RelaxedPrecision member as explicit "mediump" in the vertex stage and as
|
// a RelaxedPrecision member as explicit "mediump" in the vertex stage and as
|
||||||
// UNQUALIFIED (mediump-by-default) in the fragment stage; after
|
// UNQUALIFIED (mediump-by-default) in the fragment stage; after
|
||||||
@@ -3562,16 +3412,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
effectiveSpirv = &noperspectiveSpirv;
|
effectiveSpirv = &noperspectiveSpirv;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ES has no rectangle sampler, and SPIRV-Cross refuses the whole module rather
|
|
||||||
// than approximating one. The shared pass turns the type into the 2D one and
|
|
||||||
// divides the coordinate of every normalized-coordinate lookup by the texture
|
|
||||||
// size, which is the whole of the difference between the two.
|
|
||||||
Vector<unsigned int> rectLoweredSpirv;
|
|
||||||
if (MG_Util::ShaderTranspiler::ShaderCompiler::LowerRectImages(*effectiveSpirv, rectLoweredSpirv) &&
|
|
||||||
!rectLoweredSpirv.empty()) {
|
|
||||||
effectiveSpirv = &rectLoweredSpirv;
|
|
||||||
}
|
|
||||||
|
|
||||||
MG_Util::ShaderTranspiler::SpvcSession spvcSession(*effectiveSpirv,
|
MG_Util::ShaderTranspiler::SpvcSession spvcSession(*effectiveSpirv,
|
||||||
MG_Util::ShaderTranspiler::SessionUsageBit::Transpile);
|
MG_Util::ShaderTranspiler::SessionUsageBit::Transpile);
|
||||||
|
|
||||||
@@ -3594,7 +3434,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
r.log += spvcSession.GetLastErrorString();
|
r.log += spvcSession.GetLastErrorString();
|
||||||
r.errc = -5;
|
r.errc = -5;
|
||||||
MGLOG_E("%s", r.log.c_str());
|
MGLOG_E("%s", r.log.c_str());
|
||||||
m_backendProgramUsable = false;
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3602,10 +3441,14 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
|
|
||||||
source = RebindImageUniformsToFrontendUnits(std::move(source), stateProgramObject);
|
source = RebindImageUniformsToFrontendUnits(std::move(source), stateProgramObject);
|
||||||
source = RemoveLayoutBinding(source);
|
source = RemoveLayoutBinding(source);
|
||||||
|
if (applyClipDistanceQuirk) {
|
||||||
|
// Adreno rejects the gl_ClipDistance redeclaration SPIRV-Cross still emits
|
||||||
|
// ("reserved built-in name") but accepts plain usage with
|
||||||
|
// GL_EXT_clip_cull_distance required; drop the line, keep the #extension.
|
||||||
|
source = RemoveClipDistanceRedeclaration(source);
|
||||||
|
}
|
||||||
source = ProcessOutColorLocations(source);
|
source = ProcessOutColorLocations(source);
|
||||||
source = ForceFlatIntegerVaryings(source, glShaderType);
|
source = ForceFlatIntegerVaryings(source, glShaderType);
|
||||||
source = BroadcastLegacyFragColor(std::move(source), glShaderType, m_fragColorBroadcastCount);
|
|
||||||
source = EmulateTextureLodBias(source);
|
|
||||||
source = EmulateBaseInstanceInVertexShader(std::move(source), glShaderType);
|
source = EmulateBaseInstanceInVertexShader(std::move(source), glShaderType);
|
||||||
source = PromoteDrawParameterGlobalsToUniforms(std::move(source), glShaderType);
|
source = PromoteDrawParameterGlobalsToUniforms(std::move(source), glShaderType);
|
||||||
source = ForceSupporterOutput(source);
|
source = ForceSupporterOutput(source);
|
||||||
@@ -3636,7 +3479,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
Vector<GLchar> log(logLength);
|
Vector<GLchar> log(logLength);
|
||||||
g_GLESFuncs.glGetShaderInfoLog(backendShaderId, logLength, nullptr, log.data());
|
g_GLESFuncs.glGetShaderInfoLog(backendShaderId, logLength, nullptr, log.data());
|
||||||
MGLOG_E("Shader compilation failed for backend ID %u: %s", backendShaderId, log.data());
|
MGLOG_E("Shader compilation failed for backend ID %u: %s", backendShaderId, log.data());
|
||||||
m_backendProgramUsable = false;
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3646,33 +3488,12 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
MGLOG_D("Processed shader source length: %zu", source.length());
|
MGLOG_D("Processed shader source length: %zu", source.length());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Transform feedback capture runs on the real driver (see XfbImpl in
|
|
||||||
// DirectGLES.cpp), so the capture set has to be declared on the backend
|
|
||||||
// program before it links. SPIRV-Cross keeps user output names verbatim in
|
|
||||||
// the transpiled ESSL (`out vec4 result_0;` stays `result_0`), so the
|
|
||||||
// frontend's requested names carry over unchanged.
|
|
||||||
if (stateProgramObject->GetTransformFeedbackVaryingCount() > 0 &&
|
|
||||||
g_GLESFuncs.glTransformFeedbackVaryings != nullptr) {
|
|
||||||
const auto& xfbVaryings = stateProgramObject->GetTransformFeedbackVaryings();
|
|
||||||
Vector<const GLchar*> xfbNames;
|
|
||||||
xfbNames.reserve(xfbVaryings.size());
|
|
||||||
for (const auto& xfbVarying : xfbVaryings) {
|
|
||||||
xfbNames.push_back(xfbVarying.name.c_str());
|
|
||||||
}
|
|
||||||
MGLOG_D("Declaring %zu transform feedback varyings on program %u", xfbNames.size(),
|
|
||||||
m_backendProgramId);
|
|
||||||
g_GLESFuncs.glTransformFeedbackVaryings(m_backendProgramId, static_cast<GLsizei>(xfbNames.size()),
|
|
||||||
xfbNames.data(),
|
|
||||||
stateProgramObject->GetTransformFeedbackBufferMode());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Link program
|
// Link program
|
||||||
MGLOG_D("Linking program %u", m_backendProgramId);
|
MGLOG_D("Linking program %u", m_backendProgramId);
|
||||||
g_GLESFuncs.glLinkProgram(m_backendProgramId);
|
g_GLESFuncs.glLinkProgram(m_backendProgramId);
|
||||||
|
|
||||||
GLint linkStatus;
|
GLint linkStatus;
|
||||||
g_GLESFuncs.glGetProgramiv(m_backendProgramId, GL_LINK_STATUS, &linkStatus);
|
g_GLESFuncs.glGetProgramiv(m_backendProgramId, GL_LINK_STATUS, &linkStatus);
|
||||||
m_backendProgramUsable = m_backendProgramUsable && linkStatus == GL_TRUE;
|
|
||||||
if (linkStatus != GL_TRUE) {
|
if (linkStatus != GL_TRUE) {
|
||||||
GLint logLength;
|
GLint logLength;
|
||||||
g_GLESFuncs.glGetProgramiv(m_backendProgramId, GL_INFO_LOG_LENGTH, &logLength);
|
g_GLESFuncs.glGetProgramiv(m_backendProgramId, GL_INFO_LOG_LENGTH, &logLength);
|
||||||
@@ -3785,11 +3606,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
binding.backendLocation = backendLoc;
|
binding.backendLocation = backendLoc;
|
||||||
binding.uniformType = uniformType;
|
binding.uniformType = uniformType;
|
||||||
binding.lastAssignedUnit = -1;
|
binding.lastAssignedUnit = -1;
|
||||||
// Present only for the samplers EmulateTextureLodBias actually rewrote; the
|
|
||||||
// pass names it after the sampler, which SPIRV-Cross preserves verbatim.
|
|
||||||
binding.lodBiasLocation =
|
|
||||||
g_GLESFuncs.glGetUniformLocation(m_backendProgramId, (String(LOD_BIAS_UNIFORM_PREFIX) + name).c_str());
|
|
||||||
binding.lastAssignedLodBias = 0.0f;
|
|
||||||
m_samplerUniformBindings.push_back(binding);
|
m_samplerUniformBindings.push_back(binding);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3798,18 +3614,12 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
#ifdef TRACY_ENABLE
|
#ifdef TRACY_ENABLE
|
||||||
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
||||||
#endif
|
#endif
|
||||||
// glUseProgram on a program that did not link is an INVALID_OPERATION and
|
if (g_lastUsedBackendProgramId == m_backendProgramId) {
|
||||||
// leaves the *previous* program current, so the draw would silently render
|
|
||||||
// with an unrelated shader (KHR-GL3x.texture_size_promotion read another
|
|
||||||
// test case's alpha that way once a sampler2DRect stage failed to
|
|
||||||
// transpile). Bind nothing instead: the draw is then a visible no-op.
|
|
||||||
const Uint programToBind = m_backendProgramUsable ? m_backendProgramId : 0;
|
|
||||||
if (g_lastUsedBackendProgramId == programToBind) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
MGLOG_D("Using program %u", programToBind);
|
MGLOG_D("Using program %u", m_backendProgramId);
|
||||||
g_GLESFuncs.glUseProgram(programToBind);
|
g_GLESFuncs.glUseProgram(m_backendProgramId);
|
||||||
g_lastUsedBackendProgramId = programToBind;
|
g_lastUsedBackendProgramId = m_backendProgramId;
|
||||||
}
|
}
|
||||||
|
|
||||||
void BackendProgramObjectImpl::SetBaseInstance(Uint32 baseInstance) const {
|
void BackendProgramObjectImpl::SetBaseInstance(Uint32 baseInstance) const {
|
||||||
@@ -3917,16 +3727,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
}
|
}
|
||||||
m_cacheSamplerParameters.maxAnisotropy = samplerParams.maxAnisotropy;
|
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
|
#undef SYNC_SAMPLER_PARAM_IF_CHANGED
|
||||||
m_isInitialized = true;
|
m_isInitialized = true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -258,6 +258,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
private:
|
private:
|
||||||
Uint m_backendVAOId = 0;
|
Uint m_backendVAOId = 0;
|
||||||
Array<Uint, MG_State::GLState::VertexArrayObject::MAX_VERTEX_ATTRIBS> m_clientAttributeBufferIds;
|
Array<Uint, MG_State::GLState::VertexArrayObject::MAX_VERTEX_ATTRIBS> m_clientAttributeBufferIds;
|
||||||
|
// Attribs the frontend has Enabled but that have no source at all (no buffer object
|
||||||
|
// and NULL client pointer). GL keeps such attribs latently enabled, but Adreno's ES
|
||||||
|
// driver treats them as client arrays and memcpys from address 0 at draw time
|
||||||
|
// (SIGSEGV), so they are kept disabled on the backend VAO until they gain a source.
|
||||||
|
Uint32 m_forceDisabledAttribsMask = 0;
|
||||||
Bool m_isInitialized = false;
|
Bool m_isInitialized = false;
|
||||||
Uint16 m_syncedIndexBufferVersion = 0;
|
Uint16 m_syncedIndexBufferVersion = 0;
|
||||||
Array<MG_State::GLState::VertexAttributeVersion, MG_State::GLState::VertexArrayObject::MAX_VERTEX_ATTRIBS>
|
Array<MG_State::GLState::VertexAttributeVersion, MG_State::GLState::VertexArrayObject::MAX_VERTEX_ATTRIBS>
|
||||||
@@ -270,21 +275,18 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
|
|
||||||
namespace TextureImpl {
|
namespace TextureImpl {
|
||||||
inline Bool IsSupportedTextureTarget(TextureTarget target) {
|
inline Bool IsSupportedTextureTarget(TextureTarget target) {
|
||||||
// Every desktop-only target is stored on an ES one; see MapToBackendTextureTarget.
|
// Rectangle textures need non-normalized sampling ES cannot express; everything else is
|
||||||
(void)target;
|
// either native or emulated (1D -> 2D with height 1, 1D array -> 2D array, see
|
||||||
return true;
|
// MapToBackendTextureTarget). SPIRV-Cross already emits the matching ESSL samplers and
|
||||||
|
// coordinate padding for 1D/1D-array shaders.
|
||||||
|
return target != TextureTarget::TextureRectangle;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ES has none of the desktop-only targets: 1D textures are stored as 2D (height 1), 1D
|
// ES has no 1D targets: 1D textures are stored as 2D (height 1) and 1D arrays as 2D arrays
|
||||||
// arrays as 2D arrays (height 1, layers in depth), and rectangle textures as plain 2D -
|
// (height 1, layers in depth). Must match SPIRV-Cross's ES 1D-as-2D shader emulation.
|
||||||
// they are single-level and already clamp, so only the non-normalized coordinates differ.
|
|
||||||
// Must match the shader-side emulation: SPIRV-Cross handles 1D/1D-array itself, and
|
|
||||||
// ShaderCompiler::LowerRectImages rewrites rectangle images (declining any module
|
|
||||||
// whose lookups are not integer-coordinate, which SPIRV-Cross then still rejects).
|
|
||||||
inline TextureTarget MapToBackendTextureTarget(TextureTarget target) {
|
inline TextureTarget MapToBackendTextureTarget(TextureTarget target) {
|
||||||
switch (target) {
|
switch (target) {
|
||||||
case TextureTarget::Texture1D:
|
case TextureTarget::Texture1D:
|
||||||
case TextureTarget::TextureRectangle:
|
|
||||||
return TextureTarget::Texture2D;
|
return TextureTarget::Texture2D;
|
||||||
case TextureTarget::Texture1DArray:
|
case TextureTarget::Texture1DArray:
|
||||||
return TextureTarget::Texture2DArray;
|
return TextureTarget::Texture2DArray;
|
||||||
@@ -300,7 +302,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
inline GLenum ConvertTextureUploadTargetToBackendGLEnum(TextureUploadTarget uploadTarget) {
|
inline GLenum ConvertTextureUploadTargetToBackendGLEnum(TextureUploadTarget uploadTarget) {
|
||||||
switch (uploadTarget) {
|
switch (uploadTarget) {
|
||||||
case TextureUploadTarget::Texture1D:
|
case TextureUploadTarget::Texture1D:
|
||||||
case TextureUploadTarget::TextureRectangle:
|
|
||||||
return GL_TEXTURE_2D;
|
return GL_TEXTURE_2D;
|
||||||
case TextureUploadTarget::Texture1DArray:
|
case TextureUploadTarget::Texture1DArray:
|
||||||
return GL_TEXTURE_2D_ARRAY;
|
return GL_TEXTURE_2D_ARRAY;
|
||||||
@@ -433,28 +434,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
this array could be provided as data directly to ES `glDrawBuffers` function
|
this array could be provided as data directly to ES `glDrawBuffers` function
|
||||||
*/
|
*/
|
||||||
GLenum m_backendDrawBuffers[MG_State::GLState::FramebufferObject::MAX_DRAW_BUFFERS] = {GL_NONE};
|
GLenum m_backendDrawBuffers[MG_State::GLState::FramebufferObject::MAX_DRAW_BUFFERS] = {GL_NONE};
|
||||||
|
|
||||||
static constexpr Uint MAX_COLOR_ATTACHMENT_SLOTS =
|
|
||||||
static_cast<Uint>(FramebufferAttachmentType::Color31) -
|
|
||||||
static_cast<Uint>(FramebufferAttachmentType::Color0) + 1;
|
|
||||||
/* Where each frontend GL_COLOR_ATTACHMENTn image physically lives in the backend ES
|
|
||||||
framebuffer, as a GL_COLOR_ATTACHMENTm enum. ES only accepts glDrawBuffers bufs[s] ==
|
|
||||||
GL_COLOR_ATTACHMENTs, so a GL draw-buffer slot s naming attachment a forces a's image
|
|
||||||
under backend slot s. This table is the single owner of that decision and is kept a
|
|
||||||
PERMUTATION of the backend colour slots: every other attachment keeps its identity
|
|
||||||
slot when that slot survived, and is parked on the lowest free slot when it did not.
|
|
||||||
Deriving the point per-query from the draw-buffer array instead handed the identity
|
|
||||||
point to any attachment that was not a draw buffer - i.e. exactly the point a
|
|
||||||
relocated draw buffer had just taken over. The permutation is only true of the
|
|
||||||
PHYSICAL framebuffer because the attachment loop detaches a point whose frontend
|
|
||||||
owner is empty; do not remove that detach. */
|
|
||||||
GLenum m_backendColorSlots[MAX_COLOR_ATTACHMENT_SLOTS] = {GL_NONE};
|
|
||||||
/* Rebuild m_backendColorSlots from the frontend draw-buffer array. Returns true when any
|
|
||||||
attachment moved, i.e. when the physical attachments and the memoised read buffer have
|
|
||||||
to be re-applied. */
|
|
||||||
Bool RecomputeBackendColorSlots(
|
|
||||||
const MG_State::GLState::FramebufferObject::FramebufferAttachmentArray& stateDrawBuffers);
|
|
||||||
|
|
||||||
FramebufferAttachmentType m_frontendReadBuffer = FramebufferAttachmentType::Color0;
|
FramebufferAttachmentType m_frontendReadBuffer = FramebufferAttachmentType::Color0;
|
||||||
GLenum m_backendReadBuffer = GL_COLOR_ATTACHMENT0;
|
GLenum m_backendReadBuffer = GL_COLOR_ATTACHMENT0;
|
||||||
|
|
||||||
@@ -464,13 +443,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
|
|
||||||
extern StateBackendObjectRegistry<MG_State::GLState::FramebufferObject, BackendFramebufferObject>
|
extern StateBackendObjectRegistry<MG_State::GLState::FramebufferObject, BackendFramebufferObject>
|
||||||
g_backendFramebufferObjects;
|
g_backendFramebufferObjects;
|
||||||
// True when the read buffer names a fixed-point (norm/snorm) attachment that the
|
|
||||||
// backend actually stores in a floating-point format. GL clamps a read from a
|
|
||||||
// fixed-point colour buffer to [0,1] (GL_CLAMP_READ_COLOR defaults to
|
|
||||||
// GL_FIXED_ONLY); the substituted float storage would not, so the readback path
|
|
||||||
// has to apply the clamp itself.
|
|
||||||
Bool IsFixedPointFallbackReadAttachment();
|
|
||||||
|
|
||||||
extern Array<Uint16, SizeT(FramebufferTarget::FramebufferTargetCount)> g_fboBindVersions;
|
extern Array<Uint16, SizeT(FramebufferTarget::FramebufferTargetCount)> g_fboBindVersions;
|
||||||
// Tracks the bound FBO's object version (bumped on any attachment/drawbuffer change)
|
// Tracks the bound FBO's object version (bumped on any attachment/drawbuffer change)
|
||||||
// per target: re-attaching textures or changing draw buffers on an already-bound FBO
|
// per target: re-attaching textures or changing draw buffers on an already-bound FBO
|
||||||
@@ -609,12 +581,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
Int backendLocation = -1;
|
Int backendLocation = -1;
|
||||||
GLenum uniformType = 0;
|
GLenum uniformType = 0;
|
||||||
Int lastAssignedUnit = -1;
|
Int lastAssignedUnit = -1;
|
||||||
// Location of this sampler's emulated GL_TEXTURE_LOD_BIAS uniform
|
|
||||||
// (PrgramImpl::EmulateTextureLodBias), -1 when the shader has none.
|
|
||||||
// lastAssignedLodBias mirrors the value the program currently holds,
|
|
||||||
// so an unbiased shader issues no per-draw glUniform1f at all.
|
|
||||||
Int lodBiasLocation = -1;
|
|
||||||
Float lastAssignedLodBias = 0.0f;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
BackendProgramObjectImpl();
|
BackendProgramObjectImpl();
|
||||||
@@ -626,14 +592,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
void SetDrawID(Uint32 drawId) const;
|
void SetDrawID(Uint32 drawId) const;
|
||||||
Int GetIndirectParamsBinding() const { return m_indirectParamsBinding; }
|
Int GetIndirectParamsBinding() const { return m_indirectParamsBinding; }
|
||||||
Uint GetBackendProgramId() const { return m_backendProgramId; }
|
Uint GetBackendProgramId() const { return m_backendProgramId; }
|
||||||
// False when the last SyncToBackend could not produce a usable program (a
|
|
||||||
// shader failed to transpile or compile, or the link itself failed). Use()
|
|
||||||
// must not leave the previously bound program current in that case.
|
|
||||||
Bool IsBackendProgramUsable() const { return m_backendProgramUsable; }
|
|
||||||
Uint GetBackendGlobalUBOId() const { return m_backendGlobalUBOId; }
|
Uint GetBackendGlobalUBOId() const { return m_backendGlobalUBOId; }
|
||||||
Uint32 GetSnormFallbackClampOutputMask() const { return m_snormFallbackClampOutputMask; }
|
Uint32 GetSnormFallbackClampOutputMask() const { return m_snormFallbackClampOutputMask; }
|
||||||
Uint32 GetUnormFallbackClampOutputMask() const { return m_unormFallbackClampOutputMask; }
|
Uint32 GetUnormFallbackClampOutputMask() const { return m_unormFallbackClampOutputMask; }
|
||||||
Uint GetFragColorBroadcastCount() const { return m_fragColorBroadcastCount; }
|
|
||||||
|
|
||||||
Bool HasGlobalUboBlock() const { return m_globalUboBackendBlockIndex >= 0; }
|
Bool HasGlobalUboBlock() const { return m_globalUboBackendBlockIndex >= 0; }
|
||||||
const Vector<Int>& GetUniformBlockBackendIndices() const { return m_uniformBlockBackendIndices; }
|
const Vector<Int>& GetUniformBlockBackendIndices() const { return m_uniformBlockBackendIndices; }
|
||||||
@@ -660,11 +621,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
Int m_indirectParamsBinding = -1;
|
Int m_indirectParamsBinding = -1;
|
||||||
Uint32 m_snormFallbackClampOutputMask = 0;
|
Uint32 m_snormFallbackClampOutputMask = 0;
|
||||||
Uint32 m_unormFallbackClampOutputMask = 0;
|
Uint32 m_unormFallbackClampOutputMask = 0;
|
||||||
// Draw buffers a legacy gl_FragColor write has to reach (see
|
|
||||||
// PrgramImpl::BroadcastLegacyFragColor); 1 keeps the plain single-output shader.
|
|
||||||
Uint m_fragColorBroadcastCount = 1;
|
|
||||||
Bool m_isInitialized = false;
|
Bool m_isInitialized = false;
|
||||||
Bool m_backendProgramUsable = false;
|
|
||||||
|
|
||||||
Int m_globalUboBackendBlockIndex = -1;
|
Int m_globalUboBackendBlockIndex = -1;
|
||||||
Int m_globalUboBackendBlockSize = 0;
|
Int m_globalUboBackendBlockSize = 0;
|
||||||
@@ -677,10 +634,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
|
|
||||||
extern Uint32 g_snormFallbackClampOutputMask;
|
extern Uint32 g_snormFallbackClampOutputMask;
|
||||||
extern Uint32 g_unormFallbackClampOutputMask;
|
extern Uint32 g_unormFallbackClampOutputMask;
|
||||||
// Draw buffers the current draw framebuffer enables. Like the clamp masks above it
|
|
||||||
// is framebuffer state that the shader has to be compiled against, so a program
|
|
||||||
// whose snapshot no longer matches is relinked.
|
|
||||||
extern Uint g_fragColorBroadcastCount;
|
|
||||||
// Backend id of the last glUseProgram issued through this backend; lets Use()
|
// Backend id of the last glUseProgram issued through this backend; lets Use()
|
||||||
// skip redundant rebinds. Reset to 0 wherever glUseProgram(0) is issued or the
|
// skip redundant rebinds. Reset to 0 wherever glUseProgram(0) is issued or the
|
||||||
// ES context is recreated.
|
// ES context is recreated.
|
||||||
|
|||||||
@@ -22,9 +22,6 @@
|
|||||||
#include <MG_Util/Math/SmallFloat.h>
|
#include <MG_Util/Math/SmallFloat.h>
|
||||||
|
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <cctype>
|
|
||||||
#include <cstring>
|
|
||||||
#include <regex>
|
|
||||||
|
|
||||||
namespace MobileGL::MG_Backend::DirectGLES {
|
namespace MobileGL::MG_Backend::DirectGLES {
|
||||||
namespace {
|
namespace {
|
||||||
@@ -48,40 +45,15 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
return options;
|
return options;
|
||||||
}
|
}
|
||||||
|
|
||||||
Flags<PixelFormatNormalizeOptionBit>
|
Flags<PixelFormatNormalizeOptionBit> GetRuntimeFallbackNormalizeOptions(GLenum requestedInternalFormat) {
|
||||||
GetRuntimeFallbackNormalizeOptions(GLenum requestedInternalFormat,
|
|
||||||
Flags<PixelFormatNormalizeOptionBit> extraOptions) {
|
|
||||||
using namespace MG_Util::TextureFormatProcessor;
|
using namespace MG_Util::TextureFormatProcessor;
|
||||||
const Flags<PixelFormatNormalizeOptionBit> forcedOptions = GetApplicablePixelFormatNormalizeOptions(
|
const Flags<PixelFormatNormalizeOptionBit> forcedOptions =
|
||||||
requestedInternalFormat, GetForcedPixelFormatNormalizeOptions() | extraOptions);
|
GetApplicablePixelFormatNormalizeOptions(requestedInternalFormat, GetForcedPixelFormatNormalizeOptions());
|
||||||
if (forcedOptions) {
|
if (forcedOptions) {
|
||||||
return forcedOptions;
|
return forcedOptions;
|
||||||
}
|
}
|
||||||
return GetApplicablePixelFormatNormalizeOptions(
|
return GetApplicablePixelFormatNormalizeOptions(requestedInternalFormat,
|
||||||
requestedInternalFormat, GetDriverPixelFormatNormalizeOptions() | extraOptions);
|
GetDriverPixelFormatNormalizeOptions());
|
||||||
}
|
|
||||||
|
|
||||||
// Multisample textures can only ever be rendered into, never uploaded to, so a fallback
|
|
||||||
// format for them has to stay colour-renderable - a three-channel float fallback is a legal
|
|
||||||
// ES texture format but not a legal multisample storage format. Widening to four channels
|
|
||||||
// is safe here precisely because there is no transfer path that would have to expand
|
|
||||||
// three-channel client data, and the alpha the draw writes for a three-channel source is
|
|
||||||
// already the 1.0 the frontend format implies.
|
|
||||||
Bool TargetRequiresRenderableFormat(SizeT targetIndex) {
|
|
||||||
return targetIndex == static_cast<SizeT>(TextureTarget::Texture2DMultisample) ||
|
|
||||||
targetIndex == static_cast<SizeT>(TextureTarget::Texture2DMultisampleArray);
|
|
||||||
}
|
|
||||||
|
|
||||||
Flags<PixelFormatNormalizeOptionBit> GetRenderTargetNormalizeOptions(SizeT targetIndex) {
|
|
||||||
Flags<PixelFormatNormalizeOptionBit> options;
|
|
||||||
if (!TargetRequiresRenderableFormat(targetIndex)) {
|
|
||||||
return options;
|
|
||||||
}
|
|
||||||
options |= PixelFormatNormalizeOptionBit::NoThreeChannelRenderTarget;
|
|
||||||
if (!g_GLESCapabilities.SupportsRenderSnorm || !g_GLESCapabilities.SupportsNorm16Texture) {
|
|
||||||
options |= PixelFormatNormalizeOptionBit::NoSnorm16RenderTarget;
|
|
||||||
}
|
|
||||||
return options;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Bool HasCachedFormatCapability(TextureInternalFormat internalFormat,
|
Bool HasCachedFormatCapability(TextureInternalFormat internalFormat,
|
||||||
@@ -141,8 +113,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
const GLenum requestedInternalFormat = MG_Util::ConvertTextureInternalFormatToGLEnum(internalFormat);
|
const GLenum requestedInternalFormat = MG_Util::ConvertTextureInternalFormatToGLEnum(internalFormat);
|
||||||
Flags<PixelFormatNormalizeOptionBit> options;
|
Flags<PixelFormatNormalizeOptionBit> options;
|
||||||
if (!pActiveBackendObject || ShouldUseCaveatFormat(internalFormat, targetIndex)) {
|
if (!pActiveBackendObject || ShouldUseCaveatFormat(internalFormat, targetIndex)) {
|
||||||
options = GetRuntimeFallbackNormalizeOptions(requestedInternalFormat,
|
options = GetRuntimeFallbackNormalizeOptions(requestedInternalFormat);
|
||||||
GetRenderTargetNormalizeOptions(targetIndex));
|
|
||||||
}
|
}
|
||||||
NormalizePixelFormat(requestedInternalFormat, options, outInternalFormat, outFormat, outType);
|
NormalizePixelFormat(requestedInternalFormat, options, outInternalFormat, outFormat, outType);
|
||||||
}
|
}
|
||||||
@@ -177,22 +148,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
Bool ShouldUseCaveatRenderbufferFormat(TextureInternalFormat internalFormat) {
|
Bool ShouldUseCaveatRenderbufferFormat(TextureInternalFormat internalFormat) {
|
||||||
return ShouldUseCaveatFormat(internalFormat, GetRenderbufferFormatCapabilityTargetIndex());
|
return ShouldUseCaveatFormat(internalFormat, GetRenderbufferFormatCapabilityTargetIndex());
|
||||||
}
|
}
|
||||||
|
|
||||||
Bool BackendTextureFormatAddsAlpha(TextureInternalFormat internalFormat, TextureTarget target) {
|
|
||||||
const SizeT targetIndex =
|
|
||||||
target == TextureTarget::Unknown ? kFormatCapabilityTargetCount : GetFormatCapabilityTargetIndex(target);
|
|
||||||
if (!TargetRequiresRenderableFormat(targetIndex)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (pActiveBackendObject && !ShouldUseCaveatFormat(internalFormat, targetIndex)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const GLenum requestedInternalFormat = MG_Util::ConvertTextureInternalFormatToGLEnum(internalFormat);
|
|
||||||
const Flags<PixelFormatNormalizeOptionBit> options =
|
|
||||||
GetRuntimeFallbackNormalizeOptions(requestedInternalFormat,
|
|
||||||
GetRenderTargetNormalizeOptions(targetIndex));
|
|
||||||
return static_cast<Bool>(options & PixelFormatNormalizeOptionBit::NoThreeChannelRenderTarget);
|
|
||||||
}
|
|
||||||
} // namespace TextureImpl
|
} // namespace TextureImpl
|
||||||
namespace PrgramImpl {
|
namespace PrgramImpl {
|
||||||
String ProcessOutColorLocations(const String& glslCode) {
|
String ProcessOutColorLocations(const String& glslCode) {
|
||||||
@@ -321,55 +276,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
return glslCode;
|
return glslCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
String BroadcastLegacyFragColor(String glslCode, GLenum shaderType, Uint drawBufferCount) {
|
|
||||||
#ifdef TRACY_ENABLE
|
|
||||||
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
|
||||||
#endif
|
|
||||||
// The name is the marker: ShaderSourceProcessor only emits it when the source
|
|
||||||
// wrote gl_FragColor, and such a shader can have no other output.
|
|
||||||
static const char* const kLoweredName = "mg_FragColor";
|
|
||||||
if (shaderType != GL_FRAGMENT_SHADER || drawBufferCount <= 1) {
|
|
||||||
return glslCode;
|
|
||||||
}
|
|
||||||
static const std::regex declRegex(
|
|
||||||
R"(layout\s*\(\s*location\s*=\s*0\s*\)\s*out\s+((?:lowp|mediump|highp)\s+)?vec4\s+mg_FragColor\s*;)");
|
|
||||||
std::smatch declMatch;
|
|
||||||
if (!std::regex_search(glslCode, declMatch, declRegex)) {
|
|
||||||
return glslCode;
|
|
||||||
}
|
|
||||||
const String precision = declMatch[1].matched ? declMatch[1].str() : String();
|
|
||||||
|
|
||||||
String replicaDecls;
|
|
||||||
String replicaCopies;
|
|
||||||
for (Uint location = 1; location < drawBufferCount; ++location) {
|
|
||||||
const String name = String(kLoweredName) + "_" + std::to_string(location);
|
|
||||||
replicaDecls += "\nlayout(location = " + std::to_string(location) + ") out " + precision + "vec4 " +
|
|
||||||
name + ";";
|
|
||||||
replicaCopies += "\n " + name + " = " + kLoweredName + ";";
|
|
||||||
}
|
|
||||||
|
|
||||||
static const std::regex mainRegex(R"(void\s+main\s*\([^)]*\)\s*\{)");
|
|
||||||
std::smatch mainMatch;
|
|
||||||
if (!std::regex_search(glslCode, mainMatch, mainRegex)) {
|
|
||||||
return glslCode;
|
|
||||||
}
|
|
||||||
SizeT bracePos = static_cast<SizeT>(mainMatch.position(0) + mainMatch.length(0) - 1);
|
|
||||||
Int depth = 0;
|
|
||||||
for (SizeT pos = bracePos; pos < glslCode.size(); ++pos) {
|
|
||||||
if (glslCode[pos] == '{') {
|
|
||||||
++depth;
|
|
||||||
} else if (glslCode[pos] == '}') {
|
|
||||||
--depth;
|
|
||||||
if (depth == 0) {
|
|
||||||
glslCode.insert(pos, replicaCopies + "\n");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
glslCode.insert(static_cast<SizeT>(declMatch.position(0)) + declMatch[0].str().size(), replicaDecls);
|
|
||||||
return glslCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
String ForceFlatIntegerVaryings(const String& glslCode, GLenum shaderType) {
|
String ForceFlatIntegerVaryings(const String& glslCode, GLenum shaderType) {
|
||||||
#ifdef TRACY_ENABLE
|
#ifdef TRACY_ENABLE
|
||||||
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
||||||
@@ -437,166 +343,40 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace {
|
String RemoveClipDistanceRedeclaration(const String& glslCode) {
|
||||||
// How a lookup carries its level of detail, and how many arguments it takes
|
|
||||||
// before the optional bias.
|
|
||||||
struct LodLookupForm {
|
|
||||||
const char* name;
|
|
||||||
Int requiredArgs; // arguments before the optional bias (implicit form)
|
|
||||||
Int explicitLodArg; // index of the explicit LOD argument, -1 for implicit
|
|
||||||
};
|
|
||||||
|
|
||||||
// texelFetch* is deliberately absent: an integer fetch names its level directly
|
|
||||||
// and takes no LOD bias. textureGather has no bias either. textureGrad* derives
|
|
||||||
// the LOD from gradients and offers no argument to fold a bias into, so it is
|
|
||||||
// left alone rather than rewritten incorrectly.
|
|
||||||
constexpr LodLookupForm LOD_LOOKUP_FORMS[] = {
|
|
||||||
{"textureProjLodOffset", 0, 2}, {"textureProjOffset", 4, -1}, {"textureProjLod", 0, 2},
|
|
||||||
{"textureLodOffset", 0, 2}, {"textureOffset", 3, -1}, {"textureProj", 2, -1},
|
|
||||||
{"textureLod", 0, 2}, {"texture", 2, -1},
|
|
||||||
};
|
|
||||||
|
|
||||||
// Sampler types with no mip chain, or whose GLSL lookups have no bias overload
|
|
||||||
// at all (the array-shadow forms), so nothing can or should be folded in.
|
|
||||||
Bool IsBiasableSamplerType(const String& samplerType) {
|
|
||||||
if (samplerType.find("MS") != String::npos) return false; // multisample
|
|
||||||
if (samplerType.find("Buffer") != String::npos) return false; // texture buffer
|
|
||||||
if (samplerType.find("Rect") != String::npos) return false; // rectangle: no mips
|
|
||||||
if (samplerType == "sampler2DArrayShadow") return false;
|
|
||||||
if (samplerType == "samplerCubeArrayShadow") return false;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
Bool IsIdentifierChar(char c) { return std::isalnum(static_cast<unsigned char>(c)) || c == '_'; }
|
|
||||||
|
|
||||||
// Byte offsets of the top-level argument separators and of the closing paren,
|
|
||||||
// starting from the '(' at openParen. Empty when the parentheses do not balance.
|
|
||||||
Vector<SizeT> SplitCallArguments(const String& code, SizeT openParen) {
|
|
||||||
Vector<SizeT> marks;
|
|
||||||
Int depth = 0;
|
|
||||||
for (SizeT i = openParen; i < code.size(); ++i) {
|
|
||||||
const char c = code[i];
|
|
||||||
if (c == '(' || c == '[') {
|
|
||||||
++depth;
|
|
||||||
} else if (c == ']') {
|
|
||||||
--depth;
|
|
||||||
} else if (c == ')') {
|
|
||||||
--depth;
|
|
||||||
if (depth == 0) {
|
|
||||||
marks.push_back(i);
|
|
||||||
return marks;
|
|
||||||
}
|
|
||||||
} else if (c == ',' && depth == 1) {
|
|
||||||
marks.push_back(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
String EmulateTextureLodBias(const String& glslCode) {
|
|
||||||
#ifdef TRACY_ENABLE
|
#ifdef TRACY_ENABLE
|
||||||
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
||||||
#endif
|
#endif
|
||||||
if (glslCode.find("sampler") == String::npos || glslCode.find("texture") == String::npos) {
|
// Adreno rejects any redeclaration of gl_ClipDistance/gl_CullDistance ("reserved
|
||||||
return glslCode;
|
// built-in name") even with GL_EXT_clip_cull_distance required, but accepts plain
|
||||||
}
|
// usage of the builtin. Drop the desktop-style redeclaration line SPIRV-Cross
|
||||||
|
// prints; the "#extension GL_EXT_clip_cull_distance : require" line stays.
|
||||||
|
static const std::regex redeclarationRegex(
|
||||||
|
R"(^\s*(?:out|in)\s+(?:(?:high|medium|low)p\s+)?float\s+gl_(?:Clip|Cull)Distance\[[0-9]+\];\s*$)");
|
||||||
|
|
||||||
// Collect the mip-capable sampler uniforms this shader declares.
|
String result;
|
||||||
static const std::regex samplerDeclRegex(
|
result.reserve(glslCode.size());
|
||||||
R"(uniform\s+(?:(?:highp|mediump|lowp)\s+)?([iu]?sampler[A-Za-z0-9]*)\s+([A-Za-z_][A-Za-z0-9_]*)\s*;)");
|
SizeT lineStart = 0;
|
||||||
UnorderedMap<String, String> samplerNames; // name -> bias uniform name
|
Bool firstLine = true;
|
||||||
for (std::sregex_iterator it(glslCode.begin(), glslCode.end(), samplerDeclRegex), end; it != end; ++it) {
|
while (lineStart <= glslCode.size()) {
|
||||||
const String samplerType = (*it)[1].str();
|
SizeT lineEnd = glslCode.find('\n', lineStart);
|
||||||
if (!IsBiasableSamplerType(samplerType)) continue;
|
const Bool lastLine = lineEnd == String::npos;
|
||||||
const String name = (*it)[2].str();
|
String line = glslCode.substr(lineStart, lastLine ? String::npos : lineEnd - lineStart);
|
||||||
samplerNames.emplace(name, String(LOD_BIAS_UNIFORM_PREFIX) + name);
|
|
||||||
}
|
|
||||||
if (samplerNames.empty()) {
|
|
||||||
return glslCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rewrite the lookups. Right-to-left so earlier offsets stay valid, and only for
|
if (!std::regex_match(line, redeclarationRegex)) {
|
||||||
// samplers named directly as the first argument (SPIRV-Cross never produces an
|
if (!firstLine) {
|
||||||
// expression there for ES output, which has no separate sampler objects).
|
result += '\n';
|
||||||
String result = glslCode;
|
}
|
||||||
Vector<String> usedSamplers;
|
result += line;
|
||||||
for (SizeT scan = result.size(); scan-- > 0;) {
|
firstLine = false;
|
||||||
if (result[scan] != 't') continue;
|
}
|
||||||
if (scan > 0 && IsIdentifierChar(result[scan - 1])) continue;
|
if (lastLine) {
|
||||||
|
|
||||||
const LodLookupForm* form = nullptr;
|
|
||||||
SizeT openParen = 0;
|
|
||||||
for (const auto& candidate : LOD_LOOKUP_FORMS) {
|
|
||||||
const SizeT nameLength = std::strlen(candidate.name);
|
|
||||||
if (result.compare(scan, nameLength, candidate.name) != 0) continue;
|
|
||||||
SizeT after = result.find_first_not_of(" \t", scan + nameLength);
|
|
||||||
if (after == String::npos || result[after] != '(') continue;
|
|
||||||
form = &candidate;
|
|
||||||
openParen = after;
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (form == nullptr) continue;
|
lineStart = lineEnd + 1;
|
||||||
|
|
||||||
const Vector<SizeT> marks = SplitCallArguments(result, openParen);
|
|
||||||
if (marks.empty()) continue;
|
|
||||||
const SizeT argCount = marks.size();
|
|
||||||
const SizeT closeParen = marks.back();
|
|
||||||
|
|
||||||
// First argument must be one of our samplers.
|
|
||||||
const SizeT firstArgStart = result.find_first_not_of(" \t", openParen + 1);
|
|
||||||
SizeT firstArgEnd = marks.front();
|
|
||||||
while (firstArgEnd > firstArgStart && (result[firstArgEnd - 1] == ' ' || result[firstArgEnd - 1] == '\t')) {
|
|
||||||
--firstArgEnd;
|
|
||||||
}
|
|
||||||
if (firstArgStart == String::npos || firstArgEnd <= firstArgStart) continue;
|
|
||||||
const String samplerName = result.substr(firstArgStart, firstArgEnd - firstArgStart);
|
|
||||||
const auto samplerIt = samplerNames.find(samplerName);
|
|
||||||
if (samplerIt == samplerNames.end()) continue;
|
|
||||||
|
|
||||||
const String& biasName = samplerIt->second;
|
|
||||||
if (form->explicitLodArg >= 0) {
|
|
||||||
// Explicit LOD: the bias adds to it, as Vulkan does for
|
|
||||||
// OpImageSampleExplicitLod and as the CTS reference expects.
|
|
||||||
const SizeT lodIndex = static_cast<SizeT>(form->explicitLodArg);
|
|
||||||
if (argCount <= lodIndex) continue;
|
|
||||||
const SizeT lodStart = marks[lodIndex - 1] + 1;
|
|
||||||
const SizeT lodEnd = marks[lodIndex];
|
|
||||||
result.insert(lodEnd, String(") + ") + biasName + ")");
|
|
||||||
result.insert(lodStart, "((");
|
|
||||||
} else {
|
|
||||||
const SizeT required = static_cast<SizeT>(form->requiredArgs);
|
|
||||||
if (argCount == required) {
|
|
||||||
result.insert(closeParen, String(", ") + biasName);
|
|
||||||
} else if (argCount == required + 1) {
|
|
||||||
const SizeT biasStart = marks[argCount - 2] + 1;
|
|
||||||
result.insert(closeParen, String(") + ") + biasName + ")");
|
|
||||||
result.insert(biasStart, "((");
|
|
||||||
} else {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
usedSamplers.push_back(samplerName);
|
|
||||||
}
|
|
||||||
if (usedSamplers.empty()) {
|
|
||||||
return glslCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Declare the bias uniforms that were actually referenced, right after the
|
|
||||||
// sampler declaration line they belong to.
|
|
||||||
for (const auto& samplerName : usedSamplers) {
|
|
||||||
const String& biasName = samplerNames[samplerName];
|
|
||||||
if (result.find(String("float ") + biasName + ";") != String::npos) continue;
|
|
||||||
const std::regex declRegex(
|
|
||||||
R"(uniform\s+(?:(?:highp|mediump|lowp)\s+)?[iu]?sampler[A-Za-z0-9]*\s+)" + samplerName + R"(\s*;)");
|
|
||||||
std::smatch match;
|
|
||||||
if (!std::regex_search(result, match, declRegex)) continue;
|
|
||||||
const SizeT declEnd = static_cast<SizeT>(match.position(0)) + match[0].str().size();
|
|
||||||
result.insert(declEnd, String("\nuniform highp float ") + biasName + ";");
|
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace PrgramImpl
|
} // namespace PrgramImpl
|
||||||
|
|
||||||
namespace Utils {
|
namespace Utils {
|
||||||
|
|||||||
@@ -40,11 +40,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
void GenerateRenderbufferFormatInfo(TextureInternalFormat internalFormat, GLenum* outInternalFormat,
|
void GenerateRenderbufferFormatInfo(TextureInternalFormat internalFormat, GLenum* outInternalFormat,
|
||||||
GLenum* outFormat, GLenum* outType);
|
GLenum* outFormat, GLenum* outType);
|
||||||
Bool ShouldUseCaveatTextureFormat(TextureInternalFormat internalFormat, TextureTarget target);
|
Bool ShouldUseCaveatTextureFormat(TextureInternalFormat internalFormat, TextureTarget target);
|
||||||
|
|
||||||
// True when the format the texture is actually created with has an alpha channel the
|
|
||||||
// frontend format does not (the three-channel multisample widening). GL reads such a
|
|
||||||
// channel back as 1.0, so any swizzle source of ALPHA has to be answered with ONE.
|
|
||||||
Bool BackendTextureFormatAddsAlpha(TextureInternalFormat internalFormat, TextureTarget target);
|
|
||||||
Bool ShouldUseCaveatRenderbufferFormat(TextureInternalFormat internalFormat);
|
Bool ShouldUseCaveatRenderbufferFormat(TextureInternalFormat internalFormat);
|
||||||
} // namespace TextureImpl
|
} // namespace TextureImpl
|
||||||
|
|
||||||
@@ -109,26 +104,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
String ClampNormFallbackOutputs(String glslCode, GLenum shaderType, Uint32 snormOutputMask,
|
String ClampNormFallbackOutputs(String glslCode, GLenum shaderType, Uint32 snormOutputMask,
|
||||||
Uint32 unormOutputMask);
|
Uint32 unormOutputMask);
|
||||||
String ForceFlatIntegerVaryings(const String& glslCode, GLenum shaderType);
|
String ForceFlatIntegerVaryings(const String& glslCode, GLenum shaderType);
|
||||||
// Legacy GLSL's gl_FragColor is broadcast to every enabled draw buffer (GL 4.6
|
|
||||||
// 15.2.3), but ShaderSourceProcessor lowers it to the single output mg_FragColor,
|
|
||||||
// which only ever reaches draw buffer 0. Replicates it across `drawBufferCount`
|
|
||||||
// outputs and copies the value into them at the end of main. A no-op for
|
|
||||||
// drawBufferCount <= 1, i.e. for everything but a framebuffer that actually
|
|
||||||
// enables several draw buffers, so the ordinary single-target shader is untouched.
|
|
||||||
String BroadcastLegacyFragColor(String glslCode, GLenum shaderType, Uint drawBufferCount);
|
|
||||||
String RemoveLayoutBinding(const String& glslCode);
|
String RemoveLayoutBinding(const String& glslCode);
|
||||||
// Prefix of the per-sampler float uniform that carries GL_TEXTURE_LOD_BIAS into
|
String RemoveClipDistanceRedeclaration(const String& glslCode);
|
||||||
// the shader (see EmulateTextureLodBias); the suffix is the sampler's own name.
|
|
||||||
constexpr const char* LOD_BIAS_UNIFORM_PREFIX = "mg_lodBias_";
|
|
||||||
// ES has no per-texture/sampler LOD bias at all (GL_TEXTURE_LOD_BIAS is desktop
|
|
||||||
// only; Vulkan spells it VkSamplerCreateInfo::mipLodBias), so it has to reach the
|
|
||||||
// shader as a uniform and be folded into every lookup's level of detail. Declares
|
|
||||||
// one `uniform highp float mg_lodBias_<sampler>;` per mip-capable sampler and adds
|
|
||||||
// it to the bias / explicit-LOD argument of every lookup that takes one. Draws push
|
|
||||||
// the bound texture's (or sampler object's) value into it; a shader whose samplers
|
|
||||||
// all have a zero bias is therefore unaffected. Returns the source unchanged when
|
|
||||||
// there is nothing to rewrite.
|
|
||||||
String EmulateTextureLodBias(const String& glslCode);
|
|
||||||
} // namespace PrgramImpl
|
} // namespace PrgramImpl
|
||||||
|
|
||||||
namespace Utils {
|
namespace Utils {
|
||||||
|
|||||||
@@ -18,7 +18,6 @@
|
|||||||
#include "MG_Util/Texture/TextureFormatProcessor.h"
|
#include "MG_Util/Texture/TextureFormatProcessor.h"
|
||||||
|
|
||||||
#include <Config.h>
|
#include <Config.h>
|
||||||
#include <cmath>
|
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
|
||||||
@@ -41,11 +40,13 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
Bool IsLayeredTarget(TextureTarget target) {
|
Bool IsLayeredTarget(TextureTarget target) {
|
||||||
return target == TextureTarget::Texture3D || target == TextureTarget::Texture1DArray ||
|
return target == TextureTarget::Texture3D || target == TextureTarget::Texture1DArray ||
|
||||||
target == TextureTarget::Texture2DArray || target == TextureTarget::TextureCubeMap ||
|
target == TextureTarget::Texture2DArray || target == TextureTarget::TextureCubeMap ||
|
||||||
target == TextureTarget::TextureCubeMapArray || target == TextureTarget::Texture2DMultisampleArray;
|
target == TextureTarget::TextureCubeMapArray ||
|
||||||
|
target == TextureTarget::Texture2DMultisampleArray;
|
||||||
}
|
}
|
||||||
|
|
||||||
Bool IsMultisampleTarget(TextureTarget target) {
|
Bool IsMultisampleTarget(TextureTarget target) {
|
||||||
return target == TextureTarget::Texture2DMultisample || target == TextureTarget::Texture2DMultisampleArray;
|
return target == TextureTarget::Texture2DMultisample ||
|
||||||
|
target == TextureTarget::Texture2DMultisampleArray;
|
||||||
}
|
}
|
||||||
|
|
||||||
Bool IsTextureBufferTarget(TextureTarget target) {
|
Bool IsTextureBufferTarget(TextureTarget target) {
|
||||||
@@ -57,8 +58,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
GLenum normalizedInternalFormat = glFormat;
|
GLenum normalizedInternalFormat = glFormat;
|
||||||
GLenum imageFormat = GL_RGBA;
|
GLenum imageFormat = GL_RGBA;
|
||||||
GLenum imageType = GL_UNSIGNED_BYTE;
|
GLenum imageType = GL_UNSIGNED_BYTE;
|
||||||
MG_Util::TextureFormatProcessor::NormalizePixelFormat(glFormat, PixelFormatNormalizeOptionBit::None,
|
MG_Util::TextureFormatProcessor::NormalizePixelFormat(
|
||||||
&normalizedInternalFormat, &imageFormat, &imageType);
|
glFormat, PixelFormatNormalizeOptionBit::None, &normalizedInternalFormat, &imageFormat, &imageType);
|
||||||
return imageFormat == GL_RED_INTEGER || imageFormat == GL_RG_INTEGER || imageFormat == GL_RGB_INTEGER ||
|
return imageFormat == GL_RED_INTEGER || imageFormat == GL_RG_INTEGER || imageFormat == GL_RGB_INTEGER ||
|
||||||
imageFormat == GL_RGBA_INTEGER;
|
imageFormat == GL_RGBA_INTEGER;
|
||||||
}
|
}
|
||||||
@@ -79,7 +80,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
return caps;
|
return caps;
|
||||||
}
|
}
|
||||||
|
|
||||||
FormatCapabilityFlags BuildVulkanCaps(TextureInternalFormat logicalFormat, TextureTarget target,
|
FormatCapabilityFlags BuildVulkanCaps(TextureInternalFormat logicalFormat,
|
||||||
|
TextureTarget target,
|
||||||
VkFormatFeatureFlags features) {
|
VkFormatFeatureFlags features) {
|
||||||
FormatCapabilityFlags caps;
|
FormatCapabilityFlags caps;
|
||||||
const Bool isDepth = MG_Util::IsDepthFormatInternalFormat(logicalFormat);
|
const Bool isDepth = MG_Util::IsDepthFormatInternalFormat(logicalFormat);
|
||||||
@@ -98,7 +100,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
const Bool sampled = (features & VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT) != 0;
|
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 linearFilter = (features & VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT) != 0;
|
||||||
const Bool colorRenderable = (features & VK_FORMAT_FEATURE_COLOR_ATTACHMENT_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;
|
const Bool renderable = (isDepth || isStencil) ? depthStencilRenderable : colorRenderable;
|
||||||
|
|
||||||
if (sampled || renderable) {
|
if (sampled || renderable) {
|
||||||
@@ -195,20 +198,21 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
|
|
||||||
Bool HasNewCaveatFormatCaps(FormatCapabilityFlags nativeCaps, FormatCapabilityFlags fallbackCaps) {
|
Bool HasNewCaveatFormatCaps(FormatCapabilityFlags nativeCaps, FormatCapabilityFlags fallbackCaps) {
|
||||||
for (FormatCapability capability : kReportedFormatCapabilities) {
|
for (FormatCapability capability : kReportedFormatCapabilities) {
|
||||||
if (HasFormatCapability(fallbackCaps, capability) && !HasFormatCapability(nativeCaps, capability)) {
|
if (HasFormatCapability(fallbackCaps, capability) &&
|
||||||
|
!HasFormatCapability(nativeCaps, capability)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void LogVulkanFormatCaveat(TextureInternalFormat logicalFormat, SizeT targetIndex,
|
void LogVulkanFormatCaveat(TextureInternalFormat logicalFormat,
|
||||||
|
SizeT targetIndex,
|
||||||
TextureInternalFormat fallbackFormat) {
|
TextureInternalFormat fallbackFormat) {
|
||||||
MGLOG_D(
|
MGLOG_D("Caveat: %s %s not fully supported. Reason: native Vulkan format is not fully supported. Fallback: %s",
|
||||||
"Caveat: %s %s not fully supported. Reason: native Vulkan format is not fully supported. Fallback: %s",
|
GetFormatCapabilityTargetName(targetIndex).c_str(),
|
||||||
GetFormatCapabilityTargetName(targetIndex).c_str(),
|
MG_Util::ConvertTextureInternalFormatToString(logicalFormat).c_str(),
|
||||||
MG_Util::ConvertTextureInternalFormatToString(logicalFormat).c_str(),
|
MG_Util::ConvertTextureInternalFormatToString(fallbackFormat).c_str());
|
||||||
MG_Util::ConvertTextureInternalFormatToString(fallbackFormat).c_str());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Vector<Int> BuildSampleCounts(Int maxSamples) {
|
Vector<Int> BuildSampleCounts(Int maxSamples) {
|
||||||
@@ -252,15 +256,15 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
|
|
||||||
for (SizeT targetIndex = 0; targetIndex < kFormatCapabilityTextureTargetCount; ++targetIndex) {
|
for (SizeT targetIndex = 0; targetIndex < kFormatCapabilityTextureTargetCount; ++targetIndex) {
|
||||||
const auto target = static_cast<TextureTarget>(targetIndex);
|
const auto target = static_cast<TextureTarget>(targetIndex);
|
||||||
const VkFormatFeatureFlags nativeFeatures = IsTextureBufferTarget(target)
|
const VkFormatFeatureFlags nativeFeatures =
|
||||||
? nativeProperties.bufferFeatures
|
IsTextureBufferTarget(target) ? nativeProperties.bufferFeatures
|
||||||
: nativeProperties.optimalTilingFeatures;
|
: nativeProperties.optimalTilingFeatures;
|
||||||
FormatCapabilityFlags nativeCaps = BuildVulkanCaps(logicalFormat, target, nativeFeatures);
|
FormatCapabilityFlags nativeCaps = BuildVulkanCaps(logicalFormat, target, nativeFeatures);
|
||||||
cache.FullCaps[targetIndex][formatIndex] |= nativeCaps;
|
cache.FullCaps[targetIndex][formatIndex] |= nativeCaps;
|
||||||
|
|
||||||
const VkFormatFeatureFlags fallbackFeatures = IsTextureBufferTarget(target)
|
const VkFormatFeatureFlags fallbackFeatures =
|
||||||
? fallbackProperties.bufferFeatures
|
IsTextureBufferTarget(target) ? fallbackProperties.bufferFeatures
|
||||||
: fallbackProperties.optimalTilingFeatures;
|
: fallbackProperties.optimalTilingFeatures;
|
||||||
FormatCapabilityFlags fallbackCaps = BuildVulkanCaps(logicalFormat, target, fallbackFeatures);
|
FormatCapabilityFlags fallbackCaps = BuildVulkanCaps(logicalFormat, target, fallbackFeatures);
|
||||||
if (fallbackFormat != VK_FORMAT_UNDEFINED && fallbackFormat != nativeFormat) {
|
if (fallbackFormat != VK_FORMAT_UNDEFINED && fallbackFormat != nativeFormat) {
|
||||||
cache.CaveatCaps[targetIndex][formatIndex] |= fallbackCaps;
|
cache.CaveatCaps[targetIndex][formatIndex] |= fallbackCaps;
|
||||||
@@ -295,8 +299,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
cache.FullCaps[renderbufferTargetIndex][formatIndex] |= renderbufferCaps;
|
cache.FullCaps[renderbufferTargetIndex][formatIndex] |= renderbufferCaps;
|
||||||
|
|
||||||
if (fallbackFormat != VK_FORMAT_UNDEFINED && fallbackFormat != nativeFormat) {
|
if (fallbackFormat != VK_FORMAT_UNDEFINED && fallbackFormat != nativeFormat) {
|
||||||
FormatCapabilityFlags fallbackRenderbufferCaps = BuildVulkanCaps(
|
FormatCapabilityFlags fallbackRenderbufferCaps =
|
||||||
logicalFormat, TextureTarget::Texture2D, fallbackProperties.optimalTilingFeatures);
|
BuildVulkanCaps(logicalFormat, TextureTarget::Texture2D,
|
||||||
|
fallbackProperties.optimalTilingFeatures);
|
||||||
fallbackRenderbufferCaps &= FormatCapability::Creatable;
|
fallbackRenderbufferCaps &= FormatCapability::Creatable;
|
||||||
if ((fallbackProperties.optimalTilingFeatures &
|
if ((fallbackProperties.optimalTilingFeatures &
|
||||||
(VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT)) !=
|
(VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT)) !=
|
||||||
@@ -305,7 +310,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
fallbackRenderbufferCaps |= FormatCapability::MultisampleRenderbuffer;
|
fallbackRenderbufferCaps |= FormatCapability::MultisampleRenderbuffer;
|
||||||
}
|
}
|
||||||
cache.CaveatCaps[renderbufferTargetIndex][formatIndex] |= fallbackRenderbufferCaps;
|
cache.CaveatCaps[renderbufferTargetIndex][formatIndex] |= fallbackRenderbufferCaps;
|
||||||
if (fallbackLogicalFormat && HasNewCaveatFormatCaps(renderbufferCaps, fallbackRenderbufferCaps)) {
|
if (fallbackLogicalFormat &&
|
||||||
|
HasNewCaveatFormatCaps(renderbufferCaps, fallbackRenderbufferCaps)) {
|
||||||
LogVulkanFormatCaveat(logicalFormat, renderbufferTargetIndex, *fallbackLogicalFormat);
|
LogVulkanFormatCaveat(logicalFormat, renderbufferTargetIndex, *fallbackLogicalFormat);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -322,13 +328,14 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
|
|
||||||
void PopulateFormatCapabilities(VkPhysicalDevice physicalDevice,
|
void PopulateFormatCapabilities(VkPhysicalDevice physicalDevice,
|
||||||
PFN_vkGetPhysicalDeviceFormatProperties getFormatProperties,
|
PFN_vkGetPhysicalDeviceFormatProperties getFormatProperties,
|
||||||
const MG_External::VulkanCapabilities& capabilities, FormatCapabilityCache& cache) {
|
const MG_External::VulkanCapabilities& capabilities,
|
||||||
|
FormatCapabilityCache& cache) {
|
||||||
PopulateFormatCapabilitiesImpl(physicalDevice, getFormatProperties, capabilities, cache);
|
PopulateFormatCapabilitiesImpl(physicalDevice, getFormatProperties, capabilities, cache);
|
||||||
}
|
}
|
||||||
|
|
||||||
BackendObject_DirectVulkan::~BackendObject_DirectVulkan() = default;
|
BackendObject_DirectVulkan::~BackendObject_DirectVulkan() = default;
|
||||||
|
|
||||||
BackendObject_DirectVulkan::BackendObject_DirectVulkan() : m_rendererInfo{GetRendererIdentity()} {}
|
BackendObject_DirectVulkan::BackendObject_DirectVulkan(): m_rendererInfo{GetRendererIdentity()} {}
|
||||||
|
|
||||||
Bool BackendObject_DirectVulkan::InitWindowSurface() {
|
Bool BackendObject_DirectVulkan::InitWindowSurface() {
|
||||||
if (!m_windowHandle.Handle) {
|
if (!m_windowHandle.Handle) {
|
||||||
@@ -402,8 +409,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
MGLOG_E("DirectVulkan backend not initialized");
|
MGLOG_E("DirectVulkan backend not initialized");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!handle.Handle || (handle.Backend != WindowBackend::Android && handle.Backend != WindowBackend::X11 &&
|
if (!handle.Handle || (handle.Backend != WindowBackend::Android &&
|
||||||
handle.Backend != WindowBackend::MetalLayer && handle.Backend != WindowBackend::Win32)) {
|
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");
|
MGLOG_E("DirectVulkan backend only supports Android, X11, CAMetalLayer, and Win32 native windows");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -460,9 +469,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
// treat them as signaled/available with zero results from here on.
|
// treat them as signaled/available with zero results from here on.
|
||||||
BumpRendererGeneration();
|
BumpRendererGeneration();
|
||||||
pVulkanRenderer.reset();
|
pVulkanRenderer.reset();
|
||||||
// The reflection cache is file-scope, not renderer-owned; without this the
|
|
||||||
// deleted programs' reflection strings survive full context teardown.
|
|
||||||
ClearProgramResourceCaches();
|
|
||||||
BackendObject::ReleaseEGLResources();
|
BackendObject::ReleaseEGLResources();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -472,9 +478,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
// treat them as signaled/available with zero results from here on.
|
// treat them as signaled/available with zero results from here on.
|
||||||
BumpRendererGeneration();
|
BumpRendererGeneration();
|
||||||
pVulkanRenderer.reset();
|
pVulkanRenderer.reset();
|
||||||
// The reflection cache is file-scope, not renderer-owned; without this the
|
|
||||||
// deleted programs' reflection strings survive full context teardown.
|
|
||||||
ClearProgramResourceCaches();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const RendererInfo& BackendObject_DirectVulkan::GetRendererInfo() const {
|
const RendererInfo& BackendObject_DirectVulkan::GetRendererInfo() const {
|
||||||
@@ -494,32 +497,32 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
.RendererName = "Magma",
|
.RendererName = "Magma",
|
||||||
.BackendName = "Direct (Vulkan)",
|
.BackendName = "Direct (Vulkan)",
|
||||||
.ExtraVendor = Nullopt,
|
.ExtraVendor = Nullopt,
|
||||||
.RendererGLInfo = {.TargetGLVersion = {4, 0, 0},
|
.RendererGLInfo =
|
||||||
.TargetGLSLVersion = {4, 6, 0},
|
{
|
||||||
// Baseline advertisement (no shader subgroup, no timer queries); a
|
.TargetGLVersion = {3, 3, 0},
|
||||||
// live backend reconciles its copy in UpdateAdvertisedExtensions.
|
.TargetGLSLVersion = {4, 6, 0},
|
||||||
.Extensions = BuildAdvertisedExtensions(false, false, false),
|
// Baseline advertisement (no shader subgroup, no timer queries); a
|
||||||
.IsCompatibilityProfile = false},
|
// live backend reconciles its copy in UpdateAdvertisedExtensions.
|
||||||
|
.Extensions = BuildAdvertisedExtensions(false, false, false),
|
||||||
|
.IsCompatibilityProfile = false
|
||||||
|
},
|
||||||
.StaticBackendCapability = {.AllowVSOnlyPrograms = false}};
|
.StaticBackendCapability = {.AllowVSOnlyPrograms = false}};
|
||||||
return rendererInfo;
|
return rendererInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
Vector<GLExtension> BuildAdvertisedExtensions(Bool shaderSubgroupSupported, Bool timerQueriesSupported,
|
Vector<GLExtension> BuildAdvertisedExtensions(Bool shaderSubgroupSupported, Bool timerQueriesSupported,
|
||||||
Bool anisotropicFilteringSupported) {
|
Bool anisotropicFilteringSupported) {
|
||||||
Vector<GLExtension> extensions = {
|
Vector<GLExtension> extensions = {V_OpenGL30, V_OpenGL31, V_OpenGL32,
|
||||||
V_OpenGL30, V_OpenGL31, V_OpenGL32, V_OpenGL33, V_OpenGL40, E_GL_ARB_draw_buffers_blend,
|
V_OpenGL33, E_GL_ARB_draw_buffers_blend, E_GL_ARB_compute_shader,
|
||||||
E_GL_ARB_compute_shader, E_GL_ARB_shader_storage_buffer_object, E_GL_ARB_shader_image_load_store,
|
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_program_interface_query, E_GL_ARB_framebuffer_object,
|
||||||
E_GL_ARB_indirect_parameters, E_GL_EXT_framebuffer_object, E_GL_ARB_depth_texture, E_GL_ARB_buffer_storage,
|
E_GL_ARB_multi_draw_indirect, E_GL_ARB_indirect_parameters,
|
||||||
E_GL_ARB_texture_storage, E_GL_ARB_texture_storage_multisample, E_GL_ARB_texture_multisample,
|
E_GL_EXT_framebuffer_object, E_GL_ARB_depth_texture, E_GL_ARB_buffer_storage,
|
||||||
E_GL_ARB_clear_texture, E_GL_ARB_direct_state_access, E_GL_ARB_shader_draw_parameters,
|
E_GL_ARB_texture_storage, E_GL_ARB_texture_storage_multisample,
|
||||||
E_GL_ARB_gpu_shader_int64, E_GL_KHR_debug, E_GL_ARB_gpu_shader5, E_GL_ARB_multi_bind,
|
E_GL_ARB_clear_texture, E_GL_ARB_direct_state_access,
|
||||||
E_GL_ARB_shading_language_420pack, E_GL_ARB_vertex_attrib_binding, E_GL_ARB_shader_image_size,
|
E_GL_ARB_shader_draw_parameters, E_GL_ARB_gpu_shader_int64, E_GL_KHR_debug,
|
||||||
E_GL_ARB_explicit_attrib_location,
|
E_GL_ARB_gpu_shader5, E_GL_ARB_multi_bind, E_GL_ARB_shading_language_420pack,
|
||||||
// Advertised with GL_NUM_PROGRAM_BINARY_FORMATS = 0, which the
|
E_GL_ARB_vertex_attrib_binding, E_GL_ARB_shader_image_size};
|
||||||
// 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) {
|
if (shaderSubgroupSupported && !MG_Config::Features.DisableSubgroup) {
|
||||||
extensions.push_back(E_GL_KHR_shader_subgroup);
|
extensions.push_back(E_GL_KHR_shader_subgroup);
|
||||||
}
|
}
|
||||||
@@ -582,8 +585,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
funcsTable.GL.ClearBufferiv = ClearBufferiv;
|
funcsTable.GL.ClearBufferiv = ClearBufferiv;
|
||||||
funcsTable.GL.ClearNamedFramebufferfv = ClearNamedFramebufferfv;
|
funcsTable.GL.ClearNamedFramebufferfv = ClearNamedFramebufferfv;
|
||||||
funcsTable.GL.ClearNamedFramebufferfi = ClearNamedFramebufferfi;
|
funcsTable.GL.ClearNamedFramebufferfi = ClearNamedFramebufferfi;
|
||||||
funcsTable.GL.ClearNamedFramebufferiv = ClearNamedFramebufferiv;
|
|
||||||
funcsTable.GL.ClearNamedFramebufferuiv = ClearNamedFramebufferuiv;
|
|
||||||
funcsTable.GL.BlitFramebuffer = BlitFramebuffer;
|
funcsTable.GL.BlitFramebuffer = BlitFramebuffer;
|
||||||
funcsTable.GL.BlitNamedFramebuffer = BlitNamedFramebuffer;
|
funcsTable.GL.BlitNamedFramebuffer = BlitNamedFramebuffer;
|
||||||
funcsTable.GL.CopyTexImage2D = CopyTexImage2D;
|
funcsTable.GL.CopyTexImage2D = CopyTexImage2D;
|
||||||
@@ -627,15 +628,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
funcsTable.GL.DeleteBackendQuery = DeleteBackendQuery;
|
funcsTable.GL.DeleteBackendQuery = DeleteBackendQuery;
|
||||||
funcsTable.GL.GetGpuTimestampNs = GetGpuTimestampNs;
|
funcsTable.GL.GetGpuTimestampNs = GetGpuTimestampNs;
|
||||||
}
|
}
|
||||||
// Occlusion queries share the handle-based result/delete entries, which must
|
|
||||||
// exist even when timer queries are disabled.
|
|
||||||
funcsTable.GL.BeginOcclusionQuery = BeginOcclusionQuery;
|
|
||||||
funcsTable.GL.EndOcclusionQuery = EndOcclusionQuery;
|
|
||||||
funcsTable.GL.BeginXfbPrimitivesQuery = BeginXfbPrimitivesQuery;
|
|
||||||
funcsTable.GL.EndXfbPrimitivesQuery = EndXfbPrimitivesQuery;
|
|
||||||
funcsTable.GL.IsQueryResultAvailable = IsQueryResultAvailable;
|
|
||||||
funcsTable.GL.GetQueryResult64 = GetQueryResult64;
|
|
||||||
funcsTable.GL.DeleteBackendQuery = DeleteBackendQuery;
|
|
||||||
funcsTableInitialized = true;
|
funcsTableInitialized = true;
|
||||||
}
|
}
|
||||||
return funcsTable;
|
return funcsTable;
|
||||||
@@ -716,7 +708,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
// rather than a maximum the sampler manager will never apply.
|
// rather than a maximum the sampler manager will never apply.
|
||||||
m_dynamicParameters.MaxTextureMaxAnisotropy =
|
m_dynamicParameters.MaxTextureMaxAnisotropy =
|
||||||
(pVulkanRenderer && pVulkanRenderer->IsSamplerAnisotropySupported()) ? m_vulkanCaps.MaxSamplerAnisotropy
|
(pVulkanRenderer && pVulkanRenderer->IsSamplerAnisotropySupported()) ? m_vulkanCaps.MaxSamplerAnisotropy
|
||||||
: 1.0f;
|
: 1.0f;
|
||||||
m_dynamicParameters.SmoothLineWidthRangeMin = m_vulkanCaps.SmoothLineWidthRangeMin;
|
m_dynamicParameters.SmoothLineWidthRangeMin = m_vulkanCaps.SmoothLineWidthRangeMin;
|
||||||
m_dynamicParameters.SmoothLineWidthRangeMax = m_vulkanCaps.SmoothLineWidthRangeMax;
|
m_dynamicParameters.SmoothLineWidthRangeMax = m_vulkanCaps.SmoothLineWidthRangeMax;
|
||||||
m_dynamicParameters.SmoothLineWidthGranularity = m_vulkanCaps.SmoothLineWidthGranularity;
|
m_dynamicParameters.SmoothLineWidthGranularity = m_vulkanCaps.SmoothLineWidthGranularity;
|
||||||
@@ -737,7 +729,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
m_dynamicParameters.MaxIntegerSamples = m_vulkanCaps.MaxIntegerSamples;
|
m_dynamicParameters.MaxIntegerSamples = m_vulkanCaps.MaxIntegerSamples;
|
||||||
m_dynamicParameters.MaxSamples = m_vulkanCaps.MaxSamples;
|
m_dynamicParameters.MaxSamples = m_vulkanCaps.MaxSamples;
|
||||||
m_dynamicParameters.MaxSampleMaskWords = m_vulkanCaps.MaxSampleMaskWords;
|
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
|
// 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
|
// 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
|
// combined array capacity (192) still advertises 192 per stage. Host code treats this value as
|
||||||
@@ -747,7 +740,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
// limits while keeping the combined limit at our texture-unit array capacity.
|
// limits while keeping the combined limit at our texture-unit array capacity.
|
||||||
constexpr Int maxPerStageTextureUnits =
|
constexpr Int maxPerStageTextureUnits =
|
||||||
static_cast<Int>(MG_State::GLState::TextureState::MAX_PER_STAGE_TEXTURE_IMAGE_UNITS);
|
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 =
|
m_dynamicParameters.MaxVertexTextureImageUnits =
|
||||||
std::min(m_vulkanCaps.MaxVertexTextureImageUnits, maxPerStageTextureUnits);
|
std::min(m_vulkanCaps.MaxVertexTextureImageUnits, maxPerStageTextureUnits);
|
||||||
m_dynamicParameters.MaxComputeTextureImageUnits =
|
m_dynamicParameters.MaxComputeTextureImageUnits =
|
||||||
@@ -756,18 +750,19 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
std::min(m_vulkanCaps.MaxCombinedTextureImageUnits, maxSupportedTextureUnits);
|
std::min(m_vulkanCaps.MaxCombinedTextureImageUnits, maxSupportedTextureUnits);
|
||||||
// Never advertise more attributes than the state layer can store: the current-value array and
|
// 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.
|
// the Uint32 attribute masks the draw path passes around are both bounded by MAX_VERTEX_ATTRIBS.
|
||||||
m_dynamicParameters.MaxVertexAttribs = std::min(
|
m_dynamicParameters.MaxVertexAttribs =
|
||||||
m_vulkanCaps.MaxVertexAttribs, static_cast<Int>(MG_State::GLState::VertexArrayObject::MAX_VERTEX_ATTRIBS));
|
std::min(m_vulkanCaps.MaxVertexAttribs,
|
||||||
|
static_cast<Int>(MG_State::GLState::VertexArrayObject::MAX_VERTEX_ATTRIBS));
|
||||||
m_dynamicParameters.MaxComputeShaderStorageBlocks = m_vulkanCaps.MaxComputeShaderStorageBlocks;
|
m_dynamicParameters.MaxComputeShaderStorageBlocks = m_vulkanCaps.MaxComputeShaderStorageBlocks;
|
||||||
m_dynamicParameters.MaxCombinedShaderStorageBlocks = m_vulkanCaps.MaxCombinedShaderStorageBlocks;
|
m_dynamicParameters.MaxCombinedShaderStorageBlocks = m_vulkanCaps.MaxCombinedShaderStorageBlocks;
|
||||||
m_dynamicParameters.MaxComputeUniformBlocks = m_vulkanCaps.MaxComputeUniformBlocks;
|
m_dynamicParameters.MaxComputeUniformBlocks = m_vulkanCaps.MaxComputeUniformBlocks;
|
||||||
m_dynamicParameters.MaxComputeWorkGroupInvocations = m_vulkanCaps.MaxComputeWorkGroupInvocations;
|
m_dynamicParameters.MaxComputeWorkGroupInvocations = m_vulkanCaps.MaxComputeWorkGroupInvocations;
|
||||||
m_dynamicParameters.MaxShaderStorageBufferBindings = m_vulkanCaps.MaxShaderStorageBufferBindings;
|
m_dynamicParameters.MaxShaderStorageBufferBindings = m_vulkanCaps.MaxShaderStorageBufferBindings;
|
||||||
m_dynamicParameters.MaxTextureBufferSize = m_vulkanCaps.MaxTextureBufferSize;
|
m_dynamicParameters.MaxTextureBufferSize = m_vulkanCaps.MaxTextureBufferSize;
|
||||||
m_dynamicParameters.TextureBufferOffsetAlignment = m_vulkanCaps.TextureBufferOffsetAlignment;
|
|
||||||
m_dynamicParameters.MaxUniformBufferBindings = m_vulkanCaps.MaxUniformBufferBindings;
|
m_dynamicParameters.MaxUniformBufferBindings = m_vulkanCaps.MaxUniformBufferBindings;
|
||||||
m_dynamicParameters.MaxUniformBlockSize = m_vulkanCaps.MaxUniformBlockSize;
|
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);
|
m_dynamicParameters.MaxCombinedImageUniforms = std::max(m_vulkanCaps.MaxCombinedImageUniforms, 0);
|
||||||
const Int maxPerStageImageUniforms =
|
const Int maxPerStageImageUniforms =
|
||||||
std::min(m_dynamicParameters.MaxImageUnits, m_dynamicParameters.MaxCombinedImageUniforms);
|
std::min(m_dynamicParameters.MaxImageUnits, m_dynamicParameters.MaxCombinedImageUniforms);
|
||||||
@@ -784,7 +779,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
m_vulkanCaps.SupportsFragmentStoresAndAtomics ? maxPerStageImageUniforms : 0;
|
m_vulkanCaps.SupportsFragmentStoresAndAtomics ? maxPerStageImageUniforms : 0;
|
||||||
m_dynamicParameters.MaxComputeImageUniforms =
|
m_dynamicParameters.MaxComputeImageUniforms =
|
||||||
std::min(std::max(m_vulkanCaps.MaxComputeImageUniforms, 0), maxPerStageImageUniforms);
|
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.MaxDrawBuffers = std::min(m_vulkanCaps.MaxDrawBuffers, maxSupportedDrawBuffers);
|
||||||
m_dynamicParameters.MaxColorAttachments = std::min(m_vulkanCaps.MaxColorAttachments, maxSupportedDrawBuffers);
|
m_dynamicParameters.MaxColorAttachments = std::min(m_vulkanCaps.MaxColorAttachments, maxSupportedDrawBuffers);
|
||||||
m_dynamicParameters.MaxClipDistances = m_vulkanCaps.MaxClipDistances;
|
m_dynamicParameters.MaxClipDistances = m_vulkanCaps.MaxClipDistances;
|
||||||
@@ -794,53 +790,13 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
m_dynamicParameters.ViewportBoundsRangeMin = m_vulkanCaps.ViewportBoundsRangeMin;
|
m_dynamicParameters.ViewportBoundsRangeMin = m_vulkanCaps.ViewportBoundsRangeMin;
|
||||||
m_dynamicParameters.ViewportBoundsRangeMax = m_vulkanCaps.ViewportBoundsRangeMax;
|
m_dynamicParameters.ViewportBoundsRangeMax = m_vulkanCaps.ViewportBoundsRangeMax;
|
||||||
m_dynamicParameters.ViewportSubpixelBits = m_vulkanCaps.ViewportSubpixelBits;
|
m_dynamicParameters.ViewportSubpixelBits = m_vulkanCaps.ViewportSubpixelBits;
|
||||||
m_dynamicParameters.MinFragmentInterpolationOffset =
|
|
||||||
std::isfinite(m_vulkanCaps.MinFragmentInterpolationOffset) &&
|
|
||||||
m_vulkanCaps.MinFragmentInterpolationOffset <= -0.5f
|
|
||||||
? m_vulkanCaps.MinFragmentInterpolationOffset
|
|
||||||
: -0.5f;
|
|
||||||
m_dynamicParameters.MaxFragmentInterpolationOffset = 0.4375f;
|
|
||||||
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);
|
|
||||||
if (m_vulkanCaps.MaxFragmentInterpolationOffset >= requiredMaxOffset) {
|
|
||||||
m_dynamicParameters.MaxFragmentInterpolationOffset = m_vulkanCaps.MaxFragmentInterpolationOffset;
|
|
||||||
m_dynamicParameters.FragmentInterpolationOffsetBits = m_vulkanCaps.FragmentInterpolationOffsetBits;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
m_dynamicParameters.SupportsWideLines = m_vulkanCaps.SupportsWideLines;
|
m_dynamicParameters.SupportsWideLines = m_vulkanCaps.SupportsWideLines;
|
||||||
// A 2D or 2D multisample array texture is a VK_IMAGE_TYPE_2D image whose GL depth IS its
|
|
||||||
// arrayLayers, so a GL layer is a Vulkan array layer with nothing to translate.
|
|
||||||
// ResolveAttachmentBaseArrayLayer already passes the attachment's layer through. The other
|
|
||||||
// layered targets are declared separately as their own machinery lands.
|
|
||||||
{
|
|
||||||
using DynParams = MG_Backend::DynamicBackendParameters;
|
|
||||||
m_dynamicParameters.PerLayerFramebufferAttachmentTargets |=
|
|
||||||
DynParams::PerLayerFramebufferAttachmentBit(TextureTarget::Texture2DArray) |
|
|
||||||
DynParams::PerLayerFramebufferAttachmentBit(TextureTarget::Texture2DMultisampleArray);
|
|
||||||
// A cube map array is one 2D image with arrayLayers = 6 * cubeCount, so a GL layer is a
|
|
||||||
// Vulkan array layer here too - but the image cannot be created without imageCubeArray.
|
|
||||||
// A 3D texture's GL layer is a z slice, which only a 2D view over a 2D-array-compatible
|
|
||||||
// image can name. Optimistic: a format that refuses the flag is caught at image creation
|
|
||||||
// and declines the slice view there, which the clear path handles as a soft miss.
|
|
||||||
if (m_vulkanCaps.Supports2DArrayCompatible3DImages) {
|
|
||||||
m_dynamicParameters.PerLayerFramebufferAttachmentTargets |=
|
|
||||||
DynParams::PerLayerFramebufferAttachmentBit(TextureTarget::Texture3D);
|
|
||||||
}
|
|
||||||
if (m_vulkanCaps.SupportsImageCubeArray) {
|
|
||||||
m_dynamicParameters.PerLayerFramebufferAttachmentTargets |=
|
|
||||||
DynParams::PerLayerFramebufferAttachmentBit(TextureTarget::TextureCubeMapArray);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
m_dynamicParameters.SupportsFloat64VertexAttributes = m_vulkanCaps.SupportsShaderFloat64;
|
|
||||||
m_dynamicParameters.MaxShaderStorageBlockSize =
|
m_dynamicParameters.MaxShaderStorageBlockSize =
|
||||||
std::min(m_vulkanCaps.MaxShaderStorageBlockSize, kMaxAdvertisedShaderStorageBlockSize);
|
std::min(m_vulkanCaps.MaxShaderStorageBlockSize, kMaxAdvertisedShaderStorageBlockSize);
|
||||||
if (m_vulkanCaps.SupportsShaderSubgroup) {
|
if (m_vulkanCaps.SupportsShaderSubgroup) {
|
||||||
m_dynamicParameters.SubgroupSize = m_vulkanCaps.SubgroupSize;
|
m_dynamicParameters.SubgroupSize = m_vulkanCaps.SubgroupSize;
|
||||||
m_dynamicParameters.SubgroupSupportedStages = mapShaderStages(m_vulkanCaps.SubgroupSupportedStages);
|
m_dynamicParameters.SubgroupSupportedStages = mapShaderStages(m_vulkanCaps.SubgroupSupportedStages);
|
||||||
m_dynamicParameters.SubgroupSupportedFeatures =
|
m_dynamicParameters.SubgroupSupportedFeatures = mapSubgroupFeatures(m_vulkanCaps.SubgroupSupportedOperations);
|
||||||
mapSubgroupFeatures(m_vulkanCaps.SubgroupSupportedOperations);
|
|
||||||
m_dynamicParameters.SubgroupQuadOperationsInAllStages = m_vulkanCaps.SubgroupQuadOperationsInAllStages;
|
m_dynamicParameters.SubgroupQuadOperationsInAllStages = m_vulkanCaps.SubgroupQuadOperationsInAllStages;
|
||||||
} else {
|
} else {
|
||||||
m_dynamicParameters.SubgroupSize = 0;
|
m_dynamicParameters.SubgroupSize = 0;
|
||||||
@@ -850,7 +806,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
}
|
}
|
||||||
if (m_dynamicParameters.MaxShaderStorageBlockSize != m_vulkanCaps.MaxShaderStorageBlockSize) {
|
if (m_dynamicParameters.MaxShaderStorageBlockSize != m_vulkanCaps.MaxShaderStorageBlockSize) {
|
||||||
MGLOG_I("DirectVulkan: clamped GL_MAX_SHADER_STORAGE_BLOCK_SIZE from %zu to %zu",
|
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) {
|
switch (m_vulkanCaps.VendorId) {
|
||||||
case 0x5143u: // VK_VENDOR_ID: Qualcomm
|
case 0x5143u: // VK_VENDOR_ID: Qualcomm
|
||||||
|
|||||||
@@ -61,12 +61,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
};
|
};
|
||||||
|
|
||||||
struct ProgramResourceCache {
|
struct ProgramResourceCache {
|
||||||
// Lifetime id of the program the cached reflection belongs to. GL names are
|
|
||||||
// recycled (IndexGenerator hands freed indices straight back), and a
|
|
||||||
// recreated program's backendStateVersion restarts at the same small values,
|
|
||||||
// so the version alone can collide; the never-reused lifetime id makes the
|
|
||||||
// slot's ownership unambiguous.
|
|
||||||
Uint64 programLifetimeId = 0;
|
|
||||||
Uint32 backendStateVersion = 0;
|
Uint32 backendStateVersion = 0;
|
||||||
Vector<StorageBlockResource> storageBlocks;
|
Vector<StorageBlockResource> storageBlocks;
|
||||||
Vector<BufferVariableResource> bufferVariables;
|
Vector<BufferVariableResource> bufferVariables;
|
||||||
@@ -88,11 +82,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
Uint32 baseInstance = 0;
|
Uint32 baseInstance = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Keyed by GL program name so the freed-name reuse in IndexGenerator bounds the
|
|
||||||
// map at the peak-simultaneous-program high-water mark; each slot's ownership is
|
|
||||||
// checked against the program's lifetime id before it is served (see
|
|
||||||
// GetProgramResourceCache). Cleared wholesale at EGL teardown via
|
|
||||||
// ClearProgramResourceCaches.
|
|
||||||
UnorderedMap<GLuint, ProgramResourceCache> g_programResourceCaches;
|
UnorderedMap<GLuint, ProgramResourceCache> g_programResourceCaches;
|
||||||
|
|
||||||
void ClearReadPixelsOutput(GLsizei width, GLsizei height, GLenum format, GLenum type, void* pixels) {
|
void ClearReadPixelsOutput(GLsizei width, GLsizei height, GLenum format, GLenum type, void* pixels) {
|
||||||
@@ -153,19 +142,13 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
|
|
||||||
ProgramResourceCache& GetProgramResourceCache(const MG_State::GLState::ProgramObject& program) {
|
ProgramResourceCache& GetProgramResourceCache(const MG_State::GLState::ProgramObject& program) {
|
||||||
auto& cache = g_programResourceCaches[program.GetExternalIndex()];
|
auto& cache = g_programResourceCaches[program.GetExternalIndex()];
|
||||||
const Uint64 programLifetimeId = program.GetLifetimeId();
|
|
||||||
const Uint32 backendStateVersion = program.GetBackendStateVersion();
|
const Uint32 backendStateVersion = program.GetBackendStateVersion();
|
||||||
// The lifetime id must match too: a new program that reuses a deleted
|
if (cache.backendStateVersion == backendStateVersion &&
|
||||||
// program's name and happens to land on the same backendStateVersion (both
|
|
||||||
// count from zero) would otherwise be served the dead program's reflection.
|
|
||||||
if (cache.programLifetimeId == programLifetimeId &&
|
|
||||||
cache.backendStateVersion == backendStateVersion &&
|
|
||||||
(!cache.storageBlocks.empty() || !cache.bufferVariables.empty())) {
|
(!cache.storageBlocks.empty() || !cache.bufferVariables.empty())) {
|
||||||
return cache;
|
return cache;
|
||||||
}
|
}
|
||||||
|
|
||||||
cache = {};
|
cache = {};
|
||||||
cache.programLifetimeId = programLifetimeId;
|
|
||||||
cache.backendStateVersion = backendStateVersion;
|
cache.backendStateVersion = backendStateVersion;
|
||||||
|
|
||||||
Vector<SpvReflectShaderModule> modules;
|
Vector<SpvReflectShaderModule> modules;
|
||||||
@@ -383,15 +366,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
void ClearProgramResourceCaches() {
|
|
||||||
// Called from EGL teardown while the backend's m_eglStateMutex is held; GL
|
|
||||||
// calls are serialized in this codebase (contexts migrate threads but never
|
|
||||||
// run concurrently), so no other thread can be inside the unsynchronized map.
|
|
||||||
// Live programs in another context self-heal: their entry rebuilds from the
|
|
||||||
// retained generated SPIR-V on the next resource query.
|
|
||||||
g_programResourceCaches.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
GLuint GetShaderStorageBlockIndex(const MG_State::GLState::ProgramObject& program, const String& name) {
|
GLuint GetShaderStorageBlockIndex(const MG_State::GLState::ProgramObject& program, const String& name) {
|
||||||
auto& cache = GetProgramResourceCache(program);
|
auto& cache = GetProgramResourceCache(program);
|
||||||
const auto it = std::find_if(cache.storageBlocks.begin(), cache.storageBlocks.end(),
|
const auto it = std::find_if(cache.storageBlocks.begin(), cache.storageBlocks.end(),
|
||||||
@@ -440,20 +414,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
pVulkanRenderer->ClearNamedFramebufferfv(framebuffer, buffer, drawbuffer, value);
|
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,
|
void ClearNamedFramebufferfi(const SharedPtr<MG_State::GLState::FramebufferObject>& framebuffer, GLenum buffer,
|
||||||
GLint drawbuffer, GLfloat depth, GLint stencil) {
|
GLint drawbuffer, GLfloat depth, GLint stencil) {
|
||||||
MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::ClearNamedFramebufferfi called with null VulkanRenderer");
|
MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::ClearNamedFramebufferfi called with null VulkanRenderer");
|
||||||
@@ -1264,76 +1224,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
pVulkanRenderer->Clear(mask);
|
pVulkanRenderer->Clear(mask);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Vulkan has no LINE_LOOP topology; rewrite the draw as an indexed LINE_STRIP
|
|
||||||
// whose synthesized index list revisits the first vertex at the end.
|
|
||||||
static void DrawLineLoopAsIndexedStrip(const Vector<Uint32>& closedIndices, GLint basevertex) {
|
|
||||||
DrawIndexedCmd payload{};
|
|
||||||
payload.mode = GL_LINE_STRIP;
|
|
||||||
payload.indexBufferView.indexType = GL_UNSIGNED_INT;
|
|
||||||
payload.indexBufferView.indexByteOffset = reinterpret_cast<SizeT>(closedIndices.data());
|
|
||||||
payload.indexBufferView.indexByteSize = closedIndices.size() * sizeof(Uint32);
|
|
||||||
payload.indexBufferView.forceClientMemory = true;
|
|
||||||
payload.params.indexCount = static_cast<Uint32>(closedIndices.size());
|
|
||||||
payload.params.instanceCount = 1;
|
|
||||||
payload.params.vertexOffset = basevertex;
|
|
||||||
pVulkanRenderer->DrawElements(payload);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Resolve a DrawElements index list (bound element-array buffer or client
|
|
||||||
// memory) into uint32 values with the loop-closing first index appended.
|
|
||||||
static Bool BuildClosedLineLoopIndices(GLsizei count, GLenum type, const void* indices,
|
|
||||||
Vector<Uint32>& outIndices) {
|
|
||||||
const SizeT indexSize = MG_Util::GetGLTypeSize(type);
|
|
||||||
if (indexSize == 0 || count < 2) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const Uint8* indexBytes = nullptr;
|
|
||||||
const auto& vao = *MG_State::pGLContext->GetBoundVertexArray();
|
|
||||||
const auto& indexBufferShared = vao.GetIndexBufferBindingSlot().GetBoundObject();
|
|
||||||
if (indexBufferShared != nullptr) {
|
|
||||||
const SizeT offset = reinterpret_cast<SizeT>(indices);
|
|
||||||
const SizeT bufferSize = indexBufferShared->GetSize();
|
|
||||||
if (indexBufferShared->MappedData() == nullptr || offset > bufferSize ||
|
|
||||||
static_cast<SizeT>(count) * indexSize > bufferSize - offset) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
indexBufferShared->SyncPersistentMappedRange();
|
|
||||||
indexBytes = indexBufferShared->MappedData() + offset;
|
|
||||||
} else {
|
|
||||||
indexBytes = static_cast<const Uint8*>(indices);
|
|
||||||
if (indexBytes == nullptr) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
outIndices.resize(static_cast<SizeT>(count) + 1);
|
|
||||||
for (GLsizei i = 0; i < count; ++i) {
|
|
||||||
switch (indexSize) {
|
|
||||||
case 1: outIndices[i] = indexBytes[i]; break;
|
|
||||||
case 2: outIndices[i] = reinterpret_cast<const Uint16*>(indexBytes)[i]; break;
|
|
||||||
default: outIndices[i] = reinterpret_cast<const Uint32*>(indexBytes)[i]; break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
outIndices[count] = outIndices[0];
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void DrawArrays(GLenum mode, GLint first, GLsizei count) {
|
void DrawArrays(GLenum mode, GLint first, GLsizei count) {
|
||||||
MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::DrawArrays called with null VulkanRenderer");
|
MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::DrawArrays called with null VulkanRenderer");
|
||||||
MOBILEGL_ASSERT(MG_State::pGLContext, "DirectVulkan::DrawArrays called with null GL context");
|
MOBILEGL_ASSERT(MG_State::pGLContext, "DirectVulkan::DrawArrays called with null GL context");
|
||||||
|
|
||||||
if (mode == GL_LINE_LOOP) {
|
|
||||||
if (count < 2) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Vector<Uint32> closedIndices(static_cast<SizeT>(count) + 1);
|
|
||||||
for (GLsizei i = 0; i < count; ++i) {
|
|
||||||
closedIndices[i] = static_cast<Uint32>(first + i);
|
|
||||||
}
|
|
||||||
closedIndices[count] = static_cast<Uint32>(first);
|
|
||||||
DrawLineLoopAsIndexedStrip(closedIndices, 0);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
DrawCmd payload{};
|
DrawCmd payload{};
|
||||||
payload.mode = mode;
|
payload.mode = mode;
|
||||||
payload.params.firstVertex = first;
|
payload.params.firstVertex = first;
|
||||||
@@ -1346,14 +1240,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::DrawElements called with null VulkanRenderer");
|
MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::DrawElements called with null VulkanRenderer");
|
||||||
MOBILEGL_ASSERT(MG_State::pGLContext, "DirectVulkan::DrawElements called with null GL context");
|
MOBILEGL_ASSERT(MG_State::pGLContext, "DirectVulkan::DrawElements called with null GL context");
|
||||||
|
|
||||||
if (mode == GL_LINE_LOOP) {
|
|
||||||
Vector<Uint32> closedIndices;
|
|
||||||
if (BuildClosedLineLoopIndices(count, type, indices, closedIndices)) {
|
|
||||||
DrawLineLoopAsIndexedStrip(closedIndices, 0);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
DrawIndexedCmd payload{};
|
DrawIndexedCmd payload{};
|
||||||
payload.mode = mode;
|
payload.mode = mode;
|
||||||
payload.indexBufferView.indexType = type;
|
payload.indexBufferView.indexType = type;
|
||||||
@@ -1422,13 +1308,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
void DrawElementsBaseVertex(GLenum mode, GLsizei count, GLenum type, const GLvoid* indices, GLint basevertex) {
|
void DrawElementsBaseVertex(GLenum mode, GLsizei count, GLenum type, const GLvoid* indices, GLint basevertex) {
|
||||||
MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::DrawElementsBaseVertex called with null VulkanRenderer");
|
MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::DrawElementsBaseVertex called with null VulkanRenderer");
|
||||||
MOBILEGL_ASSERT(MG_State::pGLContext, "DirectVulkan::DrawElementsBaseVertex called with null GL context");
|
MOBILEGL_ASSERT(MG_State::pGLContext, "DirectVulkan::DrawElementsBaseVertex called with null GL context");
|
||||||
if (mode == GL_LINE_LOOP) {
|
|
||||||
Vector<Uint32> closedIndices;
|
|
||||||
if (BuildClosedLineLoopIndices(count, type, indices, closedIndices)) {
|
|
||||||
DrawLineLoopAsIndexedStrip(closedIndices, basevertex);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
DrawIndexedCmd payload{};
|
DrawIndexedCmd payload{};
|
||||||
payload.mode = mode;
|
payload.mode = mode;
|
||||||
payload.indexBufferView.indexType = type;
|
payload.indexBufferView.indexType = type;
|
||||||
@@ -1578,12 +1457,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
// records are shared (SharedPtr) with the owning pool's pending list,
|
// records are shared (SharedPtr) with the owning pool's pending list,
|
||||||
// so deleting the query while results are still in flight is safe.
|
// so deleting the query while results are still in flight is safe.
|
||||||
struct VulkanTimerQuery {
|
struct VulkanTimerQuery {
|
||||||
enum class Kind : Uint8 { Timer, Occlusion, XfbWritten, XfbGenerated };
|
|
||||||
Kind kind = Kind::Timer;
|
|
||||||
SharedPtr<VkTimerQueryManager::TimestampRecord> begin;
|
SharedPtr<VkTimerQueryManager::TimestampRecord> begin;
|
||||||
SharedPtr<VkTimerQueryManager::TimestampRecord> end;
|
SharedPtr<VkTimerQueryManager::TimestampRecord> end;
|
||||||
// Kind::Occlusion - pool slots recorded between Begin/End; summed at result time.
|
|
||||||
Vector<Uint32> occlusionSlots;
|
|
||||||
// Renderer generation the records were written under (see
|
// Renderer generation the records were written under (see
|
||||||
// g_rendererGeneration). A stale generation resolves as available
|
// g_rendererGeneration). A stale generation resolves as available
|
||||||
// with a final zero result: the records' pool indices and frame
|
// with a final zero result: the records' pool indices and frame
|
||||||
@@ -1592,12 +1467,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
// (and, via the SharedPtrs, the records), never pool slots, so
|
// (and, via the SharedPtrs, the records), never pool slots, so
|
||||||
// stale queries are always safe to delete.
|
// stale queries are always safe to delete.
|
||||||
Uint64 rendererGeneration = 0;
|
Uint64 rendererGeneration = 0;
|
||||||
// Kind::XfbGenerated - the frontend's paused-draw primitive counter when the
|
|
||||||
// query began. VK_QUERY_TYPE_TRANSFORM_FEEDBACK_STREAM_EXT counts only what the
|
|
||||||
// capture saw, so a draw made while the span was paused is invisible to it -
|
|
||||||
// but GL_PRIMITIVES_GENERATED counts what the last vertex processing stage
|
|
||||||
// emitted regardless. The delta closes that gap at result time.
|
|
||||||
Uint64 pausedPrimitiveSnapshot = 0;
|
|
||||||
};
|
};
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
@@ -1679,30 +1548,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
// ever be produced, so resolve with a final 0.
|
// ever be produced, so resolve with a final 0.
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (query->kind == VulkanTimerQuery::Kind::Occlusion) {
|
|
||||||
Uint64 samples = 0;
|
|
||||||
if (!pVulkanRenderer->ResolveOcclusionQueryResult(query->occlusionSlots, samples)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
query->occlusionSlots.clear(); // slots are recycled by the resolve
|
|
||||||
*outNanoseconds = samples;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (query->kind == VulkanTimerQuery::Kind::XfbWritten ||
|
|
||||||
query->kind == VulkanTimerQuery::Kind::XfbGenerated) {
|
|
||||||
Uint64 primitives = 0;
|
|
||||||
if (!pVulkanRenderer->ResolveXfbQueryResult(query->occlusionSlots,
|
|
||||||
query->kind == VulkanTimerQuery::Kind::XfbGenerated,
|
|
||||||
primitives)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (query->kind == VulkanTimerQuery::Kind::XfbGenerated && MG_State::pGLContext != nullptr) {
|
|
||||||
primitives += MG_State::pGLContext->GetTransformFeedbackPausedPrimitiveCounter() -
|
|
||||||
query->pausedPrimitiveSnapshot;
|
|
||||||
}
|
|
||||||
*outNanoseconds = primitives;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// With wait, mirrors ClientWaitSync: a query ended this frame cannot
|
// With wait, mirrors ClientWaitSync: a query ended this frame cannot
|
||||||
// complete until Present submits the commands, so the wait refuses to
|
// complete until Present submits the commands, so the wait refuses to
|
||||||
// block on the current unsubmitted serial. Returning false keeps the
|
// block on the current unsubmitted serial. Returning false keeps the
|
||||||
@@ -1735,49 +1580,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
delete static_cast<VulkanTimerQuery*>(handle);
|
delete static_cast<VulkanTimerQuery*>(handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
BackendQueryHandle BeginXfbPrimitivesQuery(Bool generated) {
|
|
||||||
MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::BeginXfbPrimitivesQuery called with null VulkanRenderer");
|
|
||||||
if (!pVulkanRenderer->StartXfbQueryCapture(generated ? 1u : 0u)) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
auto* query = new VulkanTimerQuery{};
|
|
||||||
query->kind = generated ? VulkanTimerQuery::Kind::XfbGenerated : VulkanTimerQuery::Kind::XfbWritten;
|
|
||||||
query->rendererGeneration = GetRendererGeneration();
|
|
||||||
query->pausedPrimitiveSnapshot =
|
|
||||||
MG_State::pGLContext ? MG_State::pGLContext->GetTransformFeedbackPausedPrimitiveCounter() : 0;
|
|
||||||
return query;
|
|
||||||
}
|
|
||||||
|
|
||||||
void EndXfbPrimitivesQuery(BackendQueryHandle handle) {
|
|
||||||
auto* query = static_cast<VulkanTimerQuery*>(handle);
|
|
||||||
MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::EndXfbPrimitivesQuery called with null VulkanRenderer");
|
|
||||||
if (query == nullptr || query->rendererGeneration != GetRendererGeneration()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
pVulkanRenderer->StopXfbQueryCapture(
|
|
||||||
query->kind == VulkanTimerQuery::Kind::XfbGenerated ? 1u : 0u, query->occlusionSlots);
|
|
||||||
}
|
|
||||||
|
|
||||||
BackendQueryHandle BeginOcclusionQuery() {
|
|
||||||
MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::BeginOcclusionQuery called with null VulkanRenderer");
|
|
||||||
if (!pVulkanRenderer->StartOcclusionQueryCapture()) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
auto* query = new VulkanTimerQuery{};
|
|
||||||
query->kind = VulkanTimerQuery::Kind::Occlusion;
|
|
||||||
query->rendererGeneration = GetRendererGeneration();
|
|
||||||
return query;
|
|
||||||
}
|
|
||||||
|
|
||||||
void EndOcclusionQuery(BackendQueryHandle handle) {
|
|
||||||
auto* query = static_cast<VulkanTimerQuery*>(handle);
|
|
||||||
MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::EndOcclusionQuery called with null VulkanRenderer");
|
|
||||||
if (query == nullptr || query->rendererGeneration != GetRendererGeneration()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
pVulkanRenderer->StopOcclusionQueryCapture(query->occlusionSlots);
|
|
||||||
}
|
|
||||||
|
|
||||||
Int64 GetGpuTimestampNs() {
|
Int64 GetGpuTimestampNs() {
|
||||||
// Vulkan cannot synchronously sample the GPU clock: timestamps only
|
// Vulkan cannot synchronously sample the GPU clock: timestamps only
|
||||||
// exist as vkCmdWriteTimestamp results read back later, and
|
// exist as vkCmdWriteTimestamp results read back later, and
|
||||||
|
|||||||
@@ -23,22 +23,12 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
Uint64 GetRendererGeneration();
|
Uint64 GetRendererGeneration();
|
||||||
void BumpRendererGeneration();
|
void BumpRendererGeneration();
|
||||||
|
|
||||||
// Drops every cached program-resource reflection entry (CPU-side strings/vectors
|
|
||||||
// only, no Vulkan handles). Called at EGL teardown next to the renderer reset;
|
|
||||||
// safe because GL calls are serialized in this codebase, and any still-live
|
|
||||||
// program rebuilds its entry from the retained generated SPIR-V on demand.
|
|
||||||
void ClearProgramResourceCaches();
|
|
||||||
|
|
||||||
void ClearBufferfi(GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil);
|
void ClearBufferfi(GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil);
|
||||||
void ClearBufferfv(GLenum buffer, GLint drawbuffer, const GLfloat* value);
|
void ClearBufferfv(GLenum buffer, GLint drawbuffer, const GLfloat* value);
|
||||||
void ClearBufferuiv(GLenum buffer, GLint drawbuffer, const GLuint* value);
|
void ClearBufferuiv(GLenum buffer, GLint drawbuffer, const GLuint* value);
|
||||||
void ClearBufferiv(GLenum buffer, GLint drawbuffer, const GLint* value);
|
void ClearBufferiv(GLenum buffer, GLint drawbuffer, const GLint* value);
|
||||||
void ClearNamedFramebufferfv(const SharedPtr<MG_State::GLState::FramebufferObject>& framebuffer, GLenum buffer,
|
void ClearNamedFramebufferfv(const SharedPtr<MG_State::GLState::FramebufferObject>& framebuffer, GLenum buffer,
|
||||||
GLint drawbuffer, const GLfloat* value);
|
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,
|
void ClearNamedFramebufferfi(const SharedPtr<MG_State::GLState::FramebufferObject>& framebuffer, GLenum buffer,
|
||||||
GLint drawbuffer, GLfloat depth, GLint stencil);
|
GLint drawbuffer, GLfloat depth, GLint stencil);
|
||||||
void Clear(GLbitfield mask);
|
void Clear(GLbitfield mask);
|
||||||
@@ -127,10 +117,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
// only while a live renderer exists whose device can actually time.
|
// only while a live renderer exists whose device can actually time.
|
||||||
Bool IsTimerQuerySupported();
|
Bool IsTimerQuerySupported();
|
||||||
BackendQueryHandle BeginTimeElapsedQuery();
|
BackendQueryHandle BeginTimeElapsedQuery();
|
||||||
BackendQueryHandle BeginXfbPrimitivesQuery(Bool generated);
|
|
||||||
void EndXfbPrimitivesQuery(BackendQueryHandle query);
|
|
||||||
BackendQueryHandle BeginOcclusionQuery();
|
|
||||||
void EndOcclusionQuery(BackendQueryHandle query);
|
|
||||||
void EndTimeElapsedQuery(BackendQueryHandle query);
|
void EndTimeElapsedQuery(BackendQueryHandle query);
|
||||||
BackendQueryHandle QueryCounterTimestamp();
|
BackendQueryHandle QueryCounterTimestamp();
|
||||||
Bool IsQueryResultAvailable(BackendQueryHandle query);
|
Bool IsQueryResultAvailable(BackendQueryHandle query);
|
||||||
|
|||||||
@@ -16,19 +16,18 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
m_device = device;
|
m_device = device;
|
||||||
m_commandPool = commandPool;
|
m_commandPool = commandPool;
|
||||||
|
|
||||||
Vector<VkCommandBuffer> commandBuffers(frameCount * 2, VK_NULL_HANDLE);
|
Vector<VkCommandBuffer> commandBuffers(frameCount, VK_NULL_HANDLE);
|
||||||
VkCommandBufferAllocateInfo allocInfo{};
|
VkCommandBufferAllocateInfo allocInfo{};
|
||||||
allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
|
allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
|
||||||
allocInfo.commandPool = commandPool;
|
allocInfo.commandPool = commandPool;
|
||||||
allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
|
allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
|
||||||
allocInfo.commandBufferCount = frameCount * 2;
|
allocInfo.commandBufferCount = frameCount;
|
||||||
VkResult result = vkAllocateCommandBuffers(device, &allocInfo, commandBuffers.data());
|
VkResult result = vkAllocateCommandBuffers(device, &allocInfo, commandBuffers.data());
|
||||||
if (result != VK_SUCCESS) {
|
if (result != VK_SUCCESS) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
for (Uint32 i = 0; i < frameCount; ++i) {
|
for (Uint32 i = 0; i < frameCount; ++i) {
|
||||||
m_frames[i].commandBuffer = commandBuffers[i];
|
m_frames[i].commandBuffer = commandBuffers[i];
|
||||||
m_frames[i].preCommandBuffer = commandBuffers[frameCount + i];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
VkSemaphoreCreateInfo semaphoreInfo{VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO};
|
VkSemaphoreCreateInfo semaphoreInfo{VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO};
|
||||||
@@ -48,10 +47,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
|
|
||||||
void FrameContext::Destroy(VkDevice device, VkCommandPool commandPool) {
|
void FrameContext::Destroy(VkDevice device, VkCommandPool commandPool) {
|
||||||
const Uint32 frameCount = static_cast<Uint32>(m_frames.size());
|
const Uint32 frameCount = static_cast<Uint32>(m_frames.size());
|
||||||
Vector<VkCommandBuffer> commandBuffers(frameCount * 2, VK_NULL_HANDLE);
|
Vector<VkCommandBuffer> commandBuffers(frameCount, VK_NULL_HANDLE);
|
||||||
for (Uint32 i = 0; i < frameCount; ++i) {
|
for (Uint32 i = 0; i < frameCount; ++i) {
|
||||||
commandBuffers[i] = m_frames[i].commandBuffer;
|
commandBuffers[i] = m_frames[i].commandBuffer;
|
||||||
commandBuffers[frameCount + i] = m_frames[i].preCommandBuffer;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (Uint32 i = 0; i < frameCount; ++i) {
|
for (Uint32 i = 0; i < frameCount; ++i) {
|
||||||
@@ -62,7 +60,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
for (auto& frame : m_frames) {
|
for (auto& frame : m_frames) {
|
||||||
FreeRetiredCommandBuffers(frame);
|
FreeRetiredCommandBuffers(frame);
|
||||||
}
|
}
|
||||||
vkFreeCommandBuffers(device, commandPool, frameCount * 2, commandBuffers.data());
|
vkFreeCommandBuffers(device, commandPool, frameCount, commandBuffers.data());
|
||||||
}
|
}
|
||||||
m_frames.clear();
|
m_frames.clear();
|
||||||
currentFrameIndex = 0;
|
currentFrameIndex = 0;
|
||||||
@@ -89,8 +87,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
currentFrameIndex = (currentFrameIndex + 1) % static_cast<Uint32>(m_frames.size());
|
currentFrameIndex = (currentFrameIndex + 1) % static_cast<Uint32>(m_frames.size());
|
||||||
GetCurrent().isCommandRecording = false;
|
GetCurrent().isCommandRecording = false;
|
||||||
GetCurrent().hasCommandBufferRecorded = false;
|
GetCurrent().hasCommandBufferRecorded = false;
|
||||||
GetCurrent().isPreCommandRecording = false;
|
|
||||||
GetCurrent().hasPreCommandBufferRecorded = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
VkCommandBuffer& FrameContext::BeginCommandRecording(VkCommandBufferUsageFlags flags,
|
VkCommandBuffer& FrameContext::BeginCommandRecording(VkCommandBufferUsageFlags flags,
|
||||||
@@ -122,41 +118,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
frame.hasCommandBufferRecorded = true;
|
frame.hasCommandBufferRecorded = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
VkCommandBuffer FrameContext::BeginPreCommandRecording() {
|
|
||||||
auto& frame = GetCurrent();
|
|
||||||
if (frame.isPreCommandRecording) {
|
|
||||||
return frame.preCommandBuffer;
|
|
||||||
}
|
|
||||||
MOBILEGL_ASSERT(!frame.hasPreCommandBufferRecorded,
|
|
||||||
"BeginPreCommandRecording: a recorded pre stream is still awaiting submission");
|
|
||||||
VK_VERIFY(vkResetCommandBuffer(frame.preCommandBuffer, 0), "BeginPreCommandRecording, vkResetCommandBuffer");
|
|
||||||
VkCommandBufferBeginInfo beginInfo{};
|
|
||||||
beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
|
|
||||||
VK_VERIFY(vkBeginCommandBuffer(frame.preCommandBuffer, &beginInfo),
|
|
||||||
"BeginPreCommandRecording, vkBeginCommandBuffer");
|
|
||||||
frame.isPreCommandRecording = true;
|
|
||||||
return frame.preCommandBuffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
void FrameContext::EndPreCommandRecordingIfOpen() {
|
|
||||||
auto& frame = GetCurrent();
|
|
||||||
if (!frame.isPreCommandRecording) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
VK_VERIFY(vkEndCommandBuffer(frame.preCommandBuffer), "EndPreCommandRecordingIfOpen, vkEndCommandBuffer");
|
|
||||||
frame.isPreCommandRecording = false;
|
|
||||||
frame.hasPreCommandBufferRecorded = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void FrameContext::AbandonPreCommandRecording() {
|
|
||||||
auto& frame = GetCurrent();
|
|
||||||
if (frame.isPreCommandRecording) {
|
|
||||||
VK_VERIFY(vkEndCommandBuffer(frame.preCommandBuffer), "AbandonPreCommandRecording, vkEndCommandBuffer");
|
|
||||||
}
|
|
||||||
frame.isPreCommandRecording = false;
|
|
||||||
frame.hasPreCommandBufferRecorded = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
VkResult FrameContext::InitializeSwapchainSemaphores(VkDevice device, Uint32 swapchainImageCount) {
|
VkResult FrameContext::InitializeSwapchainSemaphores(VkDevice device, Uint32 swapchainImageCount) {
|
||||||
DestroySwapchainSemaphores(device);
|
DestroySwapchainSemaphores(device);
|
||||||
if (swapchainImageCount == 0) {
|
if (swapchainImageCount == 0) {
|
||||||
@@ -189,30 +150,12 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
|
|
||||||
Bool FrameContext::TransitionToPresent(VkImage image, VkImageLayout oldLayout, VkImageLayout presentLayout) {
|
Bool FrameContext::TransitionToPresent(VkImage image, VkImageLayout oldLayout, VkImageLayout presentLayout) {
|
||||||
auto& frame = GetCurrent();
|
auto& frame = GetCurrent();
|
||||||
if (oldLayout == presentLayout || oldLayout == VK_IMAGE_LAYOUT_SHARED_PRESENT_KHR) {
|
if (frame.hasCommandBufferRecorded || frame.isCommandRecording || oldLayout == presentLayout ||
|
||||||
|
oldLayout == VK_IMAGE_LAYOUT_SHARED_PRESENT_KHR) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// The barrier belongs in the frame's own recording. Bailing out because
|
auto& commandBuffer = BeginCommandRecording();
|
||||||
// something was already recorded (the previous behaviour) dropped the
|
|
||||||
// transition entirely for every frame that never ran a default-framebuffer
|
|
||||||
// render pass - the only other thing that carries the image to
|
|
||||||
// PRESENT_SRC_KHR, via that pass's finalLayout - so the swapchain image was
|
|
||||||
// handed to the WSI still in the layout it was acquired in.
|
|
||||||
// A closed-but-unsubmitted buffer can only come from a submit that already
|
|
||||||
// failed (SubmitPendingCommandBuffer leaves the flag set on error), and
|
|
||||||
// appending to it is illegal while reopening would reset the frame's own
|
|
||||||
// commands away. The device is gone on that path anyway - stay silent-safe
|
|
||||||
// rather than trade a lost device for a barrier into a closed buffer.
|
|
||||||
if (frame.hasCommandBufferRecorded) {
|
|
||||||
MGLOG_E("TransitionToPresent: command buffer already closed; skipping the present barrier");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reopening a recording here would vkResetCommandBuffer this frame's own
|
|
||||||
// commands away, so append to the open one and let the caller close it.
|
|
||||||
const Bool openedRecording = !frame.isCommandRecording;
|
|
||||||
VkCommandBuffer commandBuffer = openedRecording ? BeginCommandRecording() : frame.commandBuffer;
|
|
||||||
|
|
||||||
VkImageMemoryBarrier presentBarrier{};
|
VkImageMemoryBarrier presentBarrier{};
|
||||||
presentBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
|
presentBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
|
||||||
@@ -231,9 +174,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
vkCmdPipelineBarrier(commandBuffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, 0, 0,
|
vkCmdPipelineBarrier(commandBuffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, 0, 0,
|
||||||
nullptr, 0, nullptr, 1, &presentBarrier);
|
nullptr, 0, nullptr, 1, &presentBarrier);
|
||||||
|
|
||||||
if (openedRecording) {
|
EndCommandRecording();
|
||||||
EndCommandRecording();
|
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -241,27 +182,17 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
Uint32 swapchainImageIndex) const {
|
Uint32 swapchainImageIndex) const {
|
||||||
const auto& frame = GetCurrent();
|
const auto& frame = GetCurrent();
|
||||||
MOBILEGL_ASSERT(!frame.isCommandRecording, "GetSubmitInfo called while command buffer recording is still active");
|
MOBILEGL_ASSERT(!frame.isCommandRecording, "GetSubmitInfo called while command buffer recording is still active");
|
||||||
MOBILEGL_ASSERT(!frame.isPreCommandRecording,
|
|
||||||
"GetSubmitInfo called while the pre-pass stream is still recording");
|
|
||||||
AssertValidSwapchainImageIndex(swapchainImageIndex);
|
AssertValidSwapchainImageIndex(swapchainImageIndex);
|
||||||
SubmitInfoPacket packet{};
|
SubmitInfoPacket packet{};
|
||||||
packet.waitSemaphore = frame.imageAvailableSemaphore;
|
packet.waitSemaphore = frame.imageAvailableSemaphore;
|
||||||
packet.signalSemaphore = m_swapchainImageRenderFinishedSemaphores[swapchainImageIndex];
|
packet.signalSemaphore = m_swapchainImageRenderFinishedSemaphores[swapchainImageIndex];
|
||||||
|
packet.commandBuffer = frame.commandBuffer;
|
||||||
Uint32 commandBufferCount = 0;
|
|
||||||
// The pre-pass stream executes strictly before the frame's commands.
|
|
||||||
if (frame.hasPreCommandBufferRecorded) {
|
|
||||||
packet.commandBuffers[commandBufferCount++] = frame.preCommandBuffer;
|
|
||||||
}
|
|
||||||
if (shouldSubmitCommandBuffer) {
|
|
||||||
packet.commandBuffers[commandBufferCount++] = frame.commandBuffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
packet.submitInfo.waitSemaphoreCount = frame.imageAvailableSemaphoreConsumed ? 0U : 1U;
|
packet.submitInfo.waitSemaphoreCount = frame.imageAvailableSemaphoreConsumed ? 0U : 1U;
|
||||||
packet.submitInfo.pWaitSemaphores = frame.imageAvailableSemaphoreConsumed ? nullptr : &packet.waitSemaphore;
|
packet.submitInfo.pWaitSemaphores = frame.imageAvailableSemaphoreConsumed ? nullptr : &packet.waitSemaphore;
|
||||||
packet.submitInfo.pWaitDstStageMask = frame.imageAvailableSemaphoreConsumed ? nullptr : &packet.waitDstStageMask;
|
packet.submitInfo.pWaitDstStageMask = frame.imageAvailableSemaphoreConsumed ? nullptr : &packet.waitDstStageMask;
|
||||||
packet.submitInfo.commandBufferCount = commandBufferCount;
|
packet.submitInfo.commandBufferCount = shouldSubmitCommandBuffer ? 1U : 0U;
|
||||||
packet.submitInfo.pCommandBuffers = commandBufferCount > 0 ? packet.commandBuffers : nullptr;
|
packet.submitInfo.pCommandBuffers = shouldSubmitCommandBuffer ? &packet.commandBuffer : nullptr;
|
||||||
packet.submitInfo.signalSemaphoreCount = 1;
|
packet.submitInfo.signalSemaphoreCount = 1;
|
||||||
packet.submitInfo.pSignalSemaphores = &packet.signalSemaphore;
|
packet.submitInfo.pSignalSemaphores = &packet.signalSemaphore;
|
||||||
return packet;
|
return packet;
|
||||||
@@ -296,21 +227,12 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
|
|
||||||
result = vkAcquireNextImageKHR(device, swapchain, timeout, frame.imageAvailableSemaphore, acquireFence,
|
result = vkAcquireNextImageKHR(device, swapchain, timeout, frame.imageAvailableSemaphore, acquireFence,
|
||||||
&outImageIndex);
|
&outImageIndex);
|
||||||
// VK_SUBOPTIMAL_KHR is a success code: an image *was* acquired and
|
if (result != VK_SUCCESS) {
|
||||||
// imageAvailableSemaphore *will* be signaled. Bailing out on it skipped both
|
|
||||||
// the consumed-flag reset (leaving a stale "already consumed", so the next
|
|
||||||
// submit never waited on the pending signal) and the fence reset (leaving
|
|
||||||
// the slot's fence signaled for the next submit to reuse). Only a genuine
|
|
||||||
// failure - VK_ERROR_OUT_OF_DATE_KHR and friends, where nothing is acquired
|
|
||||||
// and nothing is signaled - skips the bookkeeping.
|
|
||||||
if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR) {
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
frame.imageAvailableSemaphoreConsumed = false;
|
frame.imageAvailableSemaphoreConsumed = false;
|
||||||
const VkResult resetResult = vkResetFences(device, 1, &frame.imageInFlightFence);
|
return vkResetFences(device, 1, &frame.imageInFlightFence);
|
||||||
// Hand the acquire's own code back so the caller can schedule a rebuild.
|
|
||||||
return resetResult == VK_SUCCESS ? result : resetResult;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Uint32 FrameContext::GetCurrentFrameIndex() const {
|
Uint32 FrameContext::GetCurrentFrameIndex() const {
|
||||||
@@ -325,14 +247,12 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
m_recordingObserver = observer;
|
m_recordingObserver = observer;
|
||||||
}
|
}
|
||||||
|
|
||||||
VkResult FrameContext::RetireCurrentCommandBuffer(Bool retirePreCommandBuffer) {
|
VkResult FrameContext::RetireCurrentCommandBuffer() {
|
||||||
MOBILEGL_ASSERT(m_device != VK_NULL_HANDLE && m_commandPool != VK_NULL_HANDLE,
|
MOBILEGL_ASSERT(m_device != VK_NULL_HANDLE && m_commandPool != VK_NULL_HANDLE,
|
||||||
"RetireCurrentCommandBuffer requires an initialized FrameContext");
|
"RetireCurrentCommandBuffer requires an initialized FrameContext");
|
||||||
auto& frame = GetCurrent();
|
auto& frame = GetCurrent();
|
||||||
MOBILEGL_ASSERT(!frame.isCommandRecording,
|
MOBILEGL_ASSERT(!frame.isCommandRecording,
|
||||||
"RetireCurrentCommandBuffer called while the command buffer is still recording");
|
"RetireCurrentCommandBuffer called while the command buffer is still recording");
|
||||||
MOBILEGL_ASSERT(!frame.isPreCommandRecording,
|
|
||||||
"RetireCurrentCommandBuffer called while the pre-pass stream is still recording");
|
|
||||||
|
|
||||||
VkCommandBufferAllocateInfo allocInfo{};
|
VkCommandBufferAllocateInfo allocInfo{};
|
||||||
allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
|
allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
|
||||||
@@ -340,23 +260,11 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
|
allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
|
||||||
allocInfo.commandBufferCount = 1;
|
allocInfo.commandBufferCount = 1;
|
||||||
VkCommandBuffer replacement = VK_NULL_HANDLE;
|
VkCommandBuffer replacement = VK_NULL_HANDLE;
|
||||||
VkResult result = vkAllocateCommandBuffers(m_device, &allocInfo, &replacement);
|
const VkResult result = vkAllocateCommandBuffers(m_device, &allocInfo, &replacement);
|
||||||
if (result != VK_SUCCESS) {
|
if (result != VK_SUCCESS) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
if (retirePreCommandBuffer) {
|
frame.retiredCommandBuffers.push_back(frame.commandBuffer);
|
||||||
VkCommandBuffer preReplacement = VK_NULL_HANDLE;
|
|
||||||
result = vkAllocateCommandBuffers(m_device, &allocInfo, &preReplacement);
|
|
||||||
if (result != VK_SUCCESS) {
|
|
||||||
vkFreeCommandBuffers(m_device, m_commandPool, 1, &replacement);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
frame.retiredCommandBuffers.push_back({frame.preCommandBuffer, frame.lastSubmitIndex});
|
|
||||||
frame.preCommandBuffer = preReplacement;
|
|
||||||
}
|
|
||||||
// lastSubmitIndex was just written by the renderer for the submission
|
|
||||||
// that carried this command buffer.
|
|
||||||
frame.retiredCommandBuffers.push_back({frame.commandBuffer, frame.lastSubmitIndex});
|
|
||||||
frame.commandBuffer = replacement;
|
frame.commandBuffer = replacement;
|
||||||
return VK_SUCCESS;
|
return VK_SUCCESS;
|
||||||
}
|
}
|
||||||
@@ -366,40 +274,12 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (m_device != VK_NULL_HANDLE && m_commandPool != VK_NULL_HANDLE) {
|
if (m_device != VK_NULL_HANDLE && m_commandPool != VK_NULL_HANDLE) {
|
||||||
for (const auto& retired : frame.retiredCommandBuffers) {
|
vkFreeCommandBuffers(m_device, m_commandPool, static_cast<Uint32>(frame.retiredCommandBuffers.size()),
|
||||||
vkFreeCommandBuffers(m_device, m_commandPool, 1, &retired.commandBuffer);
|
frame.retiredCommandBuffers.data());
|
||||||
}
|
|
||||||
}
|
}
|
||||||
frame.retiredCommandBuffers.clear();
|
frame.retiredCommandBuffers.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
void FrameContext::FreeRetiredCommandBuffersCompletedUpTo(Uint64 completedSubmitIndex) {
|
|
||||||
if (m_device == VK_NULL_HANDLE || m_commandPool == VK_NULL_HANDLE) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
for (auto& frame : m_frames) {
|
|
||||||
// Retired buffers are appended in submit order, so the completed
|
|
||||||
// ones form a prefix.
|
|
||||||
SizeT completedCount = 0;
|
|
||||||
while (completedCount < frame.retiredCommandBuffers.size() &&
|
|
||||||
frame.retiredCommandBuffers[completedCount].submitIndex <= completedSubmitIndex) {
|
|
||||||
vkFreeCommandBuffers(m_device, m_commandPool, 1,
|
|
||||||
&frame.retiredCommandBuffers[completedCount].commandBuffer);
|
|
||||||
++completedCount;
|
|
||||||
}
|
|
||||||
if (completedCount > 0) {
|
|
||||||
frame.retiredCommandBuffers.erase(frame.retiredCommandBuffers.begin(),
|
|
||||||
frame.retiredCommandBuffers.begin() + completedCount);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void FrameContext::FreeAllRetiredCommandBuffers() {
|
|
||||||
for (auto& frame : m_frames) {
|
|
||||||
FreeRetiredCommandBuffers(frame);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void FrameContext::AssertValidFrameIndex(Uint32 frameIndex) const {
|
void FrameContext::AssertValidFrameIndex(Uint32 frameIndex) const {
|
||||||
MOBILEGL_ASSERT(frameIndex < m_frames.size(), "FrameContext index out of range");
|
MOBILEGL_ASSERT(frameIndex < m_frames.size(), "FrameContext index out of range");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,9 +29,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
VkPipelineStageFlags waitDstStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
|
VkPipelineStageFlags waitDstStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
|
||||||
VkSemaphore waitSemaphore = VK_NULL_HANDLE;
|
VkSemaphore waitSemaphore = VK_NULL_HANDLE;
|
||||||
VkSemaphore signalSemaphore = VK_NULL_HANDLE;
|
VkSemaphore signalSemaphore = VK_NULL_HANDLE;
|
||||||
// [0] = pre-pass command buffer (when recorded), then the frame
|
VkCommandBuffer commandBuffer = VK_NULL_HANDLE;
|
||||||
// command buffer; submitInfo.pCommandBuffers points here.
|
|
||||||
VkCommandBuffer commandBuffers[2] = {VK_NULL_HANDLE, VK_NULL_HANDLE};
|
|
||||||
VkSubmitInfo submitInfo{VK_STRUCTURE_TYPE_SUBMIT_INFO};
|
VkSubmitInfo submitInfo{VK_STRUCTURE_TYPE_SUBMIT_INFO};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -42,35 +40,17 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
VkPresentInfoKHR presentInfo{VK_STRUCTURE_TYPE_PRESENT_INFO_KHR};
|
VkPresentInfoKHR presentInfo{VK_STRUCTURE_TYPE_PRESENT_INFO_KHR};
|
||||||
};
|
};
|
||||||
|
|
||||||
// A command buffer submitted mid-frame (FlushPendingCommands), tagged
|
|
||||||
// with the submit-tracker index it was submitted under so it can be
|
|
||||||
// freed as soon as that submission is observed complete - without
|
|
||||||
// waiting for the slot's fence to be waited again (present-less flush
|
|
||||||
// loops never wait it).
|
|
||||||
struct RetiredCommandBuffer {
|
|
||||||
VkCommandBuffer commandBuffer = VK_NULL_HANDLE;
|
|
||||||
Uint64 submitIndex = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct FrameData {
|
struct FrameData {
|
||||||
VkCommandBuffer commandBuffer = VK_NULL_HANDLE;
|
VkCommandBuffer commandBuffer = VK_NULL_HANDLE;
|
||||||
// Pre-pass work stream: out-of-pass commands (deferred clear
|
|
||||||
// materialization, sampled-layout transitions) for resources the
|
|
||||||
// frame's recording has not touched yet. Submitted immediately
|
|
||||||
// BEFORE commandBuffer in the same vkQueueSubmit, so recording
|
|
||||||
// into it never has to split the frame's active render pass.
|
|
||||||
VkCommandBuffer preCommandBuffer = VK_NULL_HANDLE;
|
|
||||||
VkSemaphore imageAvailableSemaphore = VK_NULL_HANDLE;
|
VkSemaphore imageAvailableSemaphore = VK_NULL_HANDLE;
|
||||||
VkFence imageInFlightFence = VK_NULL_HANDLE;
|
VkFence imageInFlightFence = VK_NULL_HANDLE;
|
||||||
Bool isCommandRecording = false;
|
Bool isCommandRecording = false;
|
||||||
Bool hasCommandBufferRecorded = false;
|
Bool hasCommandBufferRecorded = false;
|
||||||
Bool isPreCommandRecording = false;
|
|
||||||
Bool hasPreCommandBufferRecorded = false;
|
|
||||||
Bool imageAvailableSemaphoreConsumed = false;
|
Bool imageAvailableSemaphoreConsumed = false;
|
||||||
// Command buffers submitted mid-frame (FlushPendingCommands),
|
// Command buffers submitted mid-frame (FlushPendingCommands) whose
|
||||||
// appended in submit order; freed once their submission is known
|
// execution is only known complete once this slot's fence has been
|
||||||
// complete (fence wait or completion poll).
|
// waited again; freed at that point.
|
||||||
Vector<RetiredCommandBuffer> retiredCommandBuffers;
|
Vector<VkCommandBuffer> retiredCommandBuffers;
|
||||||
// Submit-tracker index of this slot's most recent queue submission
|
// Submit-tracker index of this slot's most recent queue submission
|
||||||
// (written by the renderer at submit time).
|
// (written by the renderer at submit time).
|
||||||
Uint64 lastSubmitIndex = 0;
|
Uint64 lastSubmitIndex = 0;
|
||||||
@@ -87,14 +67,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
VkCommandBuffer& BeginCommandRecording(VkCommandBufferUsageFlags flags = 0,
|
VkCommandBuffer& BeginCommandRecording(VkCommandBufferUsageFlags flags = 0,
|
||||||
const VkCommandBufferInheritanceInfo* pInheritanceInfo = nullptr);
|
const VkCommandBufferInheritanceInfo* pInheritanceInfo = nullptr);
|
||||||
void EndCommandRecording();
|
void EndCommandRecording();
|
||||||
// Lazily opens the pre-pass work stream (see FrameData::preCommandBuffer).
|
|
||||||
VkCommandBuffer BeginPreCommandRecording();
|
|
||||||
// Closes the pre stream if open, marking it for submission ahead of the
|
|
||||||
// frame command buffer. Safe to call when it never opened.
|
|
||||||
void EndPreCommandRecordingIfOpen();
|
|
||||||
// Drops an in-progress or recorded-but-unsubmitted pre stream (dropped
|
|
||||||
// frame recordings, swapchain recreation).
|
|
||||||
void AbandonPreCommandRecording();
|
|
||||||
VkResult InitializeSwapchainSemaphores(VkDevice device, Uint32 swapchainImageCount);
|
VkResult InitializeSwapchainSemaphores(VkDevice device, Uint32 swapchainImageCount);
|
||||||
void DestroySwapchainSemaphores(VkDevice device);
|
void DestroySwapchainSemaphores(VkDevice device);
|
||||||
Bool TransitionToPresent(VkImage image, VkImageLayout oldLayout,
|
Bool TransitionToPresent(VkImage image, VkImageLayout oldLayout,
|
||||||
@@ -107,18 +79,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
// Parks the current (already ended and submitted) command buffer on the
|
// Parks the current (already ended and submitted) command buffer on the
|
||||||
// slot's retired list and installs a freshly allocated one, so recording
|
// slot's retired list and installs a freshly allocated one, so recording
|
||||||
// can restart while the submitted buffer is still executing. Retired
|
// can restart while the submitted buffer is still executing. Retired
|
||||||
// buffers are freed after the slot's fence is next waited, or as soon
|
// buffers are freed after the slot's fence is next waited.
|
||||||
// as their submission is observed complete.
|
VkResult RetireCurrentCommandBuffer();
|
||||||
VkResult RetireCurrentCommandBuffer(Bool retirePreCommandBuffer = false);
|
|
||||||
|
|
||||||
// Frees every retired command buffer whose tagged submission index is
|
|
||||||
// known complete. Driven by the renderer's submit tracker on completion
|
|
||||||
// events (fence waits and non-blocking polls), so present-less flush
|
|
||||||
// loops reclaim their buffers without any extra wait.
|
|
||||||
void FreeRetiredCommandBuffersCompletedUpTo(Uint64 completedSubmitIndex);
|
|
||||||
// Frees every slot's retired command buffers. Only valid when the
|
|
||||||
// caller has proven every queue submission complete.
|
|
||||||
void FreeAllRetiredCommandBuffers();
|
|
||||||
|
|
||||||
Uint32 GetCurrentFrameIndex() const;
|
Uint32 GetCurrentFrameIndex() const;
|
||||||
Uint32 GetFrameCount() const;
|
Uint32 GetFrameCount() const;
|
||||||
|
|||||||
@@ -8,7 +8,6 @@
|
|||||||
|
|
||||||
#include "PipelineFactory.h"
|
#include "PipelineFactory.h"
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
|
|
||||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||||
static const char* PrimitiveTopologyToString(VkPrimitiveTopology topology) {
|
static const char* PrimitiveTopologyToString(VkPrimitiveTopology topology) {
|
||||||
@@ -205,12 +204,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.topology, sizeof(payload.topology)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.topology, sizeof(payload.topology)));
|
||||||
XXHASH_VERIFY(
|
XXHASH_VERIFY(
|
||||||
XXH64_update(m_hashState, &payload.primitiveRestartEnable, sizeof(payload.primitiveRestartEnable)));
|
XXH64_update(m_hashState, &payload.primitiveRestartEnable, sizeof(payload.primitiveRestartEnable)));
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.patchControlPoints, sizeof(payload.patchControlPoints)));
|
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.polygonMode, sizeof(payload.polygonMode)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.polygonMode, sizeof(payload.polygonMode)));
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.cullMode, sizeof(payload.cullMode)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.cullMode, sizeof(payload.cullMode)));
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.frontFace, sizeof(payload.frontFace)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.frontFace, sizeof(payload.frontFace)));
|
||||||
XXHASH_VERIFY(
|
|
||||||
XXH64_update(m_hashState, &payload.provokingVertexMode, sizeof(payload.provokingVertexMode)));
|
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.depthTestEnable, sizeof(payload.depthTestEnable)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.depthTestEnable, sizeof(payload.depthTestEnable)));
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.depthWriteEnable, sizeof(payload.depthWriteEnable)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.depthWriteEnable, sizeof(payload.depthWriteEnable)));
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.depthBiasEnable, sizeof(payload.depthBiasEnable)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.depthBiasEnable, sizeof(payload.depthBiasEnable)));
|
||||||
@@ -247,108 +243,23 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
const HashType hash = ComputeHash(payload);
|
const HashType hash = ComputeHash(payload);
|
||||||
auto it = m_cache.find(hash);
|
auto it = m_cache.find(hash);
|
||||||
if (it != m_cache.end()) {
|
if (it != m_cache.end()) {
|
||||||
it->second.lastUsedFrame = m_frameCounter;
|
return it->second;
|
||||||
return it->second.pipeline;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
VkPipeline pipeline = CreatePipeline(payload);
|
VkPipeline pipeline = CreatePipeline(payload);
|
||||||
m_cache.emplace(hash, PipelineCacheEntry{pipeline, payload.programHash, payload.renderPass,
|
m_cache.emplace(hash, pipeline);
|
||||||
m_frameCounter});
|
|
||||||
return pipeline;
|
return pipeline;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PipelineFactory::DestroyAll() {
|
void PipelineFactory::DestroyAll() {
|
||||||
for (auto& pair : m_cache) {
|
for (auto& pair : m_cache) {
|
||||||
if (pair.second.pipeline != VK_NULL_HANDLE) {
|
if (pair.second != VK_NULL_HANDLE) {
|
||||||
vkDestroyPipeline(m_device, pair.second.pipeline, nullptr);
|
vkDestroyPipeline(m_device, pair.second, nullptr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
m_cache.clear();
|
m_cache.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
Uint32 PipelineFactory::OnFrameBoundary() {
|
|
||||||
++m_frameCounter;
|
|
||||||
|
|
||||||
// Sweep cadence and retire age mirror VkRenderPassManager::OnPresent: an entry
|
|
||||||
// idle for more than kRetireAgeFrames frame boundaries cannot be referenced by
|
|
||||||
// any in-flight command buffer (frames-in-flight <= MOBILEGL_MAGMA_FRAMESINFLIGHT),
|
|
||||||
// so immediate vkDestroyPipeline is safe. The caller must drop its "last
|
|
||||||
// pipeline" memo when this returns non-zero: the memo can return a cached
|
|
||||||
// handle without touching this cache, so an evicted pipeline may still be
|
|
||||||
// memoized (present-less flush loops never reset the memo per frame).
|
|
||||||
constexpr Uint64 kSweepInterval = 256;
|
|
||||||
constexpr Uint64 kRetireAgeFrames = 1024;
|
|
||||||
if ((m_frameCounter % kSweepInterval) != 0) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
Uint32 evicted = 0;
|
|
||||||
for (auto it = m_cache.begin(); it != m_cache.end();) {
|
|
||||||
if (m_frameCounter - it->second.lastUsedFrame > kRetireAgeFrames) {
|
|
||||||
if (it->second.pipeline != VK_NULL_HANDLE) {
|
|
||||||
vkDestroyPipeline(m_device, it->second.pipeline, nullptr);
|
|
||||||
}
|
|
||||||
it = m_cache.erase(it);
|
|
||||||
++evicted;
|
|
||||||
} else {
|
|
||||||
++it;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (evicted > 0) {
|
|
||||||
MGLOG_D("PipelineFactory::OnFrameBoundary: evicted %u idle pipelines (%zu remain)", evicted,
|
|
||||||
m_cache.size());
|
|
||||||
}
|
|
||||||
return evicted;
|
|
||||||
}
|
|
||||||
|
|
||||||
Uint32 PipelineFactory::EvictByRenderPasses(const Vector<VkRenderPass>& renderPasses) {
|
|
||||||
if (renderPasses.empty() || m_cache.empty()) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
// Sorted-batch membership test keeps a mass eviction (shader-pack switch,
|
|
||||||
// dimension exit) at one O(cache * log batch) scan instead of one full scan
|
|
||||||
// per dying pass.
|
|
||||||
Vector<VkRenderPass> sortedPasses = renderPasses;
|
|
||||||
std::sort(sortedPasses.begin(), sortedPasses.end());
|
|
||||||
Uint32 evicted = 0;
|
|
||||||
for (auto it = m_cache.begin(); it != m_cache.end();) {
|
|
||||||
if (std::binary_search(sortedPasses.begin(), sortedPasses.end(), it->second.renderPass)) {
|
|
||||||
if (it->second.pipeline != VK_NULL_HANDLE) {
|
|
||||||
vkDestroyPipeline(m_device, it->second.pipeline, nullptr);
|
|
||||||
}
|
|
||||||
it = m_cache.erase(it);
|
|
||||||
++evicted;
|
|
||||||
} else {
|
|
||||||
++it;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (evicted > 0) {
|
|
||||||
MGLOG_D("PipelineFactory::EvictByRenderPasses: evicted %u pipelines for %zu destroyed render passes",
|
|
||||||
evicted, sortedPasses.size());
|
|
||||||
}
|
|
||||||
return evicted;
|
|
||||||
}
|
|
||||||
|
|
||||||
Uint32 PipelineFactory::EvictByProgramHash(HashType programHash) {
|
|
||||||
Uint32 evicted = 0;
|
|
||||||
for (auto it = m_cache.begin(); it != m_cache.end();) {
|
|
||||||
if (it->second.programHash == programHash) {
|
|
||||||
if (it->second.pipeline != VK_NULL_HANDLE) {
|
|
||||||
vkDestroyPipeline(m_device, it->second.pipeline, nullptr);
|
|
||||||
}
|
|
||||||
it = m_cache.erase(it);
|
|
||||||
++evicted;
|
|
||||||
} else {
|
|
||||||
++it;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (evicted > 0) {
|
|
||||||
MGLOG_D("PipelineFactory::EvictByProgramHash: evicted %u pipelines for program hash 0x%llx",
|
|
||||||
evicted, static_cast<unsigned long long>(programHash));
|
|
||||||
}
|
|
||||||
return evicted;
|
|
||||||
}
|
|
||||||
|
|
||||||
VkPipeline PipelineFactory::CreatePipeline(const PipelineCreatePayload& payload) const {
|
VkPipeline PipelineFactory::CreatePipeline(const PipelineCreatePayload& payload) const {
|
||||||
MOBILEGL_ASSERT(payload.stages != nullptr && !payload.stages->empty(), "PipelineFactory: stages are empty");
|
MOBILEGL_ASSERT(payload.stages != nullptr && !payload.stages->empty(), "PipelineFactory: stages are empty");
|
||||||
MOBILEGL_ASSERT(payload.vertexInputState != nullptr, "PipelineFactory: vertexInputState is null");
|
MOBILEGL_ASSERT(payload.vertexInputState != nullptr, "PipelineFactory: vertexInputState is null");
|
||||||
@@ -383,11 +294,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
ia.topology = payload.topology;
|
ia.topology = payload.topology;
|
||||||
ia.primitiveRestartEnable = payload.primitiveRestartEnable ? VK_TRUE : VK_FALSE;
|
ia.primitiveRestartEnable = payload.primitiveRestartEnable ? VK_TRUE : VK_FALSE;
|
||||||
|
|
||||||
// Only a patch topology has a tessellation stage to configure; leaving the pointer null
|
|
||||||
// otherwise is what the spec expects.
|
|
||||||
VkPipelineTessellationStateCreateInfo tessellation{VK_STRUCTURE_TYPE_PIPELINE_TESSELLATION_STATE_CREATE_INFO};
|
|
||||||
tessellation.patchControlPoints = payload.patchControlPoints;
|
|
||||||
|
|
||||||
VkPipelineViewportStateCreateInfo vpci{VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO};
|
VkPipelineViewportStateCreateInfo vpci{VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO};
|
||||||
vpci.viewportCount = 1;
|
vpci.viewportCount = 1;
|
||||||
vpci.scissorCount = 1;
|
vpci.scissorCount = 1;
|
||||||
@@ -399,17 +305,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
raster.depthBiasEnable = payload.depthBiasEnable ? VK_TRUE : VK_FALSE;
|
raster.depthBiasEnable = payload.depthBiasEnable ? VK_TRUE : VK_FALSE;
|
||||||
raster.rasterizerDiscardEnable = payload.rasterizerDiscardEnable ? VK_TRUE : VK_FALSE;
|
raster.rasterizerDiscardEnable = payload.rasterizerDiscardEnable ? VK_TRUE : VK_FALSE;
|
||||||
raster.lineWidth = 1.0f;
|
raster.lineWidth = 1.0f;
|
||||||
// Only chain the struct when the mode is not Vulkan's implicit default: a device without
|
|
||||||
// VK_EXT_provoking_vertex enabled must never see this pNext entry, and the renderer's
|
|
||||||
// selector already collapses to FIRST in exactly that case - so a device without the
|
|
||||||
// extension produces a byte-identical VkGraphicsPipelineCreateInfo to before.
|
|
||||||
VkPipelineRasterizationProvokingVertexStateCreateInfoEXT provokingVertexState{
|
|
||||||
VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_PROVOKING_VERTEX_STATE_CREATE_INFO_EXT};
|
|
||||||
if (payload.provokingVertexMode != VK_PROVOKING_VERTEX_MODE_FIRST_VERTEX_EXT) {
|
|
||||||
provokingVertexState.provokingVertexMode = payload.provokingVertexMode;
|
|
||||||
provokingVertexState.pNext = raster.pNext;
|
|
||||||
raster.pNext = &provokingVertexState;
|
|
||||||
}
|
|
||||||
|
|
||||||
VkPipelineMultisampleStateCreateInfo ms{VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO};
|
VkPipelineMultisampleStateCreateInfo ms{VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO};
|
||||||
ms.rasterizationSamples = payload.rasterizationSamples;
|
ms.rasterizationSamples = payload.rasterizationSamples;
|
||||||
@@ -463,8 +358,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
gpi.pStages = payload.stages->data();
|
gpi.pStages = payload.stages->data();
|
||||||
gpi.pVertexInputState = payload.vertexInputState;
|
gpi.pVertexInputState = payload.vertexInputState;
|
||||||
gpi.pInputAssemblyState = &ia;
|
gpi.pInputAssemblyState = &ia;
|
||||||
gpi.pTessellationState =
|
|
||||||
payload.topology == VK_PRIMITIVE_TOPOLOGY_PATCH_LIST ? &tessellation : nullptr;
|
|
||||||
gpi.pViewportState = &vpci;
|
gpi.pViewportState = &vpci;
|
||||||
gpi.pRasterizationState = &raster;
|
gpi.pRasterizationState = &raster;
|
||||||
gpi.pMultisampleState = &ms;
|
gpi.pMultisampleState = &ms;
|
||||||
|
|||||||
@@ -30,16 +30,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
Uint32 subpass = 0;
|
Uint32 subpass = 0;
|
||||||
VkPrimitiveTopology topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
|
VkPrimitiveTopology topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
|
||||||
Bool primitiveRestartEnable = false;
|
Bool primitiveRestartEnable = false;
|
||||||
// GL_PATCH_VERTICES; only read for a PATCH_LIST topology.
|
|
||||||
Uint32 patchControlPoints = 3;
|
|
||||||
VkPolygonMode polygonMode = VK_POLYGON_MODE_FILL;
|
VkPolygonMode polygonMode = VK_POLYGON_MODE_FILL;
|
||||||
VkCullModeFlags cullMode = VK_CULL_MODE_BACK_BIT;
|
VkCullModeFlags cullMode = VK_CULL_MODE_BACK_BIT;
|
||||||
VkFrontFace frontFace = VK_FRONT_FACE_CLOCKWISE;
|
VkFrontFace frontFace = VK_FRONT_FACE_CLOCKWISE;
|
||||||
// GL's provoking vertex, baked into the pipeline (VK_EXT_provoking_vertex). It selects
|
|
||||||
// which vertex a flat varying takes AND the vertex order transform feedback records for
|
|
||||||
// strips/fans, so it is part of the pipeline's identity, not dynamic state. Defaults to
|
|
||||||
// Vulkan's own convention, which is what a device without the extension gets.
|
|
||||||
VkProvokingVertexModeEXT provokingVertexMode = VK_PROVOKING_VERTEX_MODE_FIRST_VERTEX_EXT;
|
|
||||||
Bool depthTestEnable = false;
|
Bool depthTestEnable = false;
|
||||||
Bool depthWriteEnable = false;
|
Bool depthWriteEnable = false;
|
||||||
Bool depthBiasEnable = false;
|
Bool depthBiasEnable = false;
|
||||||
@@ -72,26 +65,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
VkPipeline GetOrCreatePipeline(const PipelineCreatePayload& payload);
|
VkPipeline GetOrCreatePipeline(const PipelineCreatePayload& payload);
|
||||||
void DestroyAll();
|
void DestroyAll();
|
||||||
|
|
||||||
// Frame boundary hook: ages the pipeline cache and destroys long-unused entries
|
|
||||||
// (their command buffers retired many frames ago), mirroring
|
|
||||||
// VkRenderPassManager::OnPresent's sweep. Returns the number of pipelines
|
|
||||||
// destroyed so the caller can drop any memoized VkPipeline handle.
|
|
||||||
Uint32 OnFrameBoundary();
|
|
||||||
// Destroys every cached pipeline hashed on one of `renderPasses`. Only safe
|
|
||||||
// when the caller guarantees GPU idleness for them - the render-pass manager
|
|
||||||
// calls this (via the renderer) for passes its own >1024-boundary-idle sweep
|
|
||||||
// just evicted, and a pipeline hashed on those handles is only ever bound by
|
|
||||||
// draws that also hit the render-pass entries. Also closes the handle-recycling
|
|
||||||
// hazard: a recycled VkRenderPass value must never serve a stale pipeline.
|
|
||||||
// Batched: one cache scan regardless of how many passes died in the sweep.
|
|
||||||
// Returns the number destroyed (callers invalidate memos when non-zero).
|
|
||||||
Uint32 EvictByRenderPasses(const Vector<VkRenderPass>& renderPasses);
|
|
||||||
// Destroys every cached pipeline built from the program with content hash
|
|
||||||
// `programHash`. Called from the ProgramFactory eviction path, which proves the
|
|
||||||
// same >1024-boundary idleness (the program's pipelines are only bound by draws
|
|
||||||
// that stamp its factory entry). Returns the number destroyed.
|
|
||||||
Uint32 EvictByProgramHash(HashType programHash);
|
|
||||||
|
|
||||||
// Driver quirk: suppress depth writes on accumulation-blended pipelines. Multi-pass
|
// Driver quirk: suppress depth writes on accumulation-blended pipelines. Multi-pass
|
||||||
// depth-equality rendering (a blended prepass writes depth that later passes re-test
|
// depth-equality rendering (a blended prepass writes depth that later passes re-test
|
||||||
// with an equality-inclusive compare on the re-rasterized geometry) requires
|
// with an equality-inclusive compare on the re-rasterized geometry) requires
|
||||||
@@ -114,26 +87,12 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
static Bool ShouldSuppressDepthWrite(const PipelineCreatePayload& payload);
|
static Bool ShouldSuppressDepthWrite(const PipelineCreatePayload& payload);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct PipelineCacheEntry {
|
|
||||||
VkPipeline pipeline = VK_NULL_HANDLE;
|
|
||||||
// The hashed inputs the eviction paths key on: programHash ties the entry to
|
|
||||||
// its ProgramFactory entry, renderPass records the exact handle the hash
|
|
||||||
// folded in (the hash is one-way, so targeted eviction needs them verbatim).
|
|
||||||
HashType programHash = 0;
|
|
||||||
VkRenderPass renderPass = VK_NULL_HANDLE;
|
|
||||||
// Frame-boundary counter value of the last GetOrCreatePipeline hit; drives
|
|
||||||
// cache eviction (see OnFrameBoundary).
|
|
||||||
Uint64 lastUsedFrame = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
VkPipeline CreatePipeline(const PipelineCreatePayload& payload) const;
|
VkPipeline CreatePipeline(const PipelineCreatePayload& payload) const;
|
||||||
|
|
||||||
VkDevice m_device = VK_NULL_HANDLE;
|
VkDevice m_device = VK_NULL_HANDLE;
|
||||||
const VulkanRendererConfig& m_config;
|
const VulkanRendererConfig& m_config;
|
||||||
VkPipelineCache m_pipelineCache = VK_NULL_HANDLE;
|
VkPipelineCache m_pipelineCache = VK_NULL_HANDLE;
|
||||||
UnorderedMap<HashType, PipelineCacheEntry> m_cache;
|
UnorderedMap<HashType, VkPipeline> m_cache;
|
||||||
// Monotonic frame-boundary counter (bumped in OnFrameBoundary) for cache aging.
|
|
||||||
Uint64 m_frameCounter = 0;
|
|
||||||
static inline XXH64_state_t* m_hashState = XXH64_createState();
|
static inline XXH64_state_t* m_hashState = XXH64_createState();
|
||||||
static inline Bool s_suppressBlendedDepthWrite = false;
|
static inline Bool s_suppressBlendedDepthWrite = false;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -923,403 +923,11 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
ProgramFactory::CompileOptionFlags m_transformFlags;
|
ProgramFactory::CompileOptionFlags m_transformFlags;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Decorates the module's captured varyings for VK_EXT_transform_feedback:
|
|
||||||
// user outputs get XfbBuffer/XfbStride/Offset directly; a captured
|
|
||||||
// gl_Position (a gl_PerVertex member) is mirrored into a dedicated output
|
|
||||||
// variable copied before every OpReturn, BEFORE the position fixup runs,
|
|
||||||
// so the captured value is the shader's own (pre-remap) gl_Position.
|
|
||||||
class XfbCaptureDecoratePass final : public spvtools::opt::Pass {
|
|
||||||
public:
|
|
||||||
struct CapturedVarying {
|
|
||||||
std::string name;
|
|
||||||
Uint32 bufferIndex = 0;
|
|
||||||
Uint32 offsetBytes = 0;
|
|
||||||
};
|
|
||||||
const char* name() const override { return "mobilegl-xfb-capture-decorate"; }
|
|
||||||
XfbCaptureDecoratePass(Vector<CapturedVarying> varyings, Vector<Uint32> strides)
|
|
||||||
: m_varyings(Move(varyings)), m_strides(Move(strides)) {}
|
|
||||||
|
|
||||||
Status Process() override {
|
|
||||||
using namespace spvtools::opt;
|
|
||||||
if (m_varyings.empty()) return Status::SuccessWithoutChange;
|
|
||||||
|
|
||||||
auto entryPointIter = get_module()->entry_points().begin();
|
|
||||||
if (entryPointIter == get_module()->entry_points().end()) return Status::SuccessWithoutChange;
|
|
||||||
spvtools::opt::Instruction* entryPoint = &*entryPointIter;
|
|
||||||
const Uint32 entryFunctionId = entryPoint->GetSingleWordInOperand(1);
|
|
||||||
|
|
||||||
// Name -> result id map from the debug section.
|
|
||||||
std::unordered_map<std::string, Uint32> idsByName;
|
|
||||||
for (auto& debugInst : get_module()->debugs2()) {
|
|
||||||
if (debugInst.opcode() != spv::Op::OpName) continue;
|
|
||||||
idsByName[debugInst.GetInOperand(1).AsString()] = debugInst.GetSingleWordInOperand(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto* decorationManager = context()->get_decoration_mgr();
|
|
||||||
const auto decorateForXfb = [&](Uint32 targetId, Uint32 bufferIndex, Uint32 offsetBytes) {
|
|
||||||
const Uint32 stride = bufferIndex < m_strides.size() ? m_strides[bufferIndex] : 0;
|
|
||||||
decorationManager->AddDecorationVal(targetId, static_cast<Uint32>(spv::Decoration::XfbBuffer),
|
|
||||||
bufferIndex);
|
|
||||||
decorationManager->AddDecorationVal(targetId, static_cast<Uint32>(spv::Decoration::XfbStride),
|
|
||||||
stride);
|
|
||||||
decorationManager->AddDecorationVal(targetId, static_cast<Uint32>(spv::Decoration::Offset),
|
|
||||||
offsetBytes);
|
|
||||||
};
|
|
||||||
|
|
||||||
Bool modified = false;
|
|
||||||
Bool needsPositionMirror = false;
|
|
||||||
Uint32 positionBufferIndex = 0;
|
|
||||||
Uint32 positionOffset = 0;
|
|
||||||
for (const auto& varying : m_varyings) {
|
|
||||||
if (varying.name == "gl_Position") {
|
|
||||||
needsPositionMirror = true;
|
|
||||||
positionBufferIndex = varying.bufferIndex;
|
|
||||||
positionOffset = varying.offsetBytes;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const auto idIt = idsByName.find(varying.name);
|
|
||||||
if (idIt == idsByName.end()) {
|
|
||||||
MGLOG_E("XfbCaptureDecoratePass: no SPIR-V variable named '%s'", varying.name.c_str());
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
decorateForXfb(idIt->second, varying.bufferIndex, varying.offsetBytes);
|
|
||||||
modified = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (needsPositionMirror) {
|
|
||||||
modified |= MirrorPositionForCapture(entryFunctionId, *entryPoint, positionBufferIndex,
|
|
||||||
positionOffset, decorateForXfb);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!modified) return Status::SuccessWithoutChange;
|
|
||||||
|
|
||||||
context()->AddCapability(spv::Capability::TransformFeedback);
|
|
||||||
{
|
|
||||||
auto executionMode = MakeUnique<spvtools::opt::Instruction>(
|
|
||||||
context(), spv::Op::OpExecutionMode, 0, 0,
|
|
||||||
std::initializer_list<spvtools::opt::Operand>{
|
|
||||||
{SPV_OPERAND_TYPE_ID, {entryPoint->GetSingleWordInOperand(1)}},
|
|
||||||
{SPV_OPERAND_TYPE_EXECUTION_MODE, {static_cast<Uint32>(spv::ExecutionMode::Xfb)}}});
|
|
||||||
get_module()->AddExecutionMode(Move(executionMode));
|
|
||||||
}
|
|
||||||
context()->InvalidateAnalysesExceptFor(spvtools::opt::IRContext::kAnalysisNone);
|
|
||||||
return Status::SuccessWithChange;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
template <typename DecorateFn>
|
|
||||||
Bool MirrorPositionForCapture(Uint32 entryFunctionId, spvtools::opt::Instruction& entryPoint,
|
|
||||||
Uint32 bufferIndex, Uint32 offsetBytes, const DecorateFn& decorateForXfb) {
|
|
||||||
const Uint32 entryPointModel = entryPoint.GetSingleWordInOperand(0);
|
|
||||||
using namespace spvtools::opt;
|
|
||||||
PositionTargetInfo target{};
|
|
||||||
if (!FindPositionTarget(context(), &target)) {
|
|
||||||
MGLOG_E("XfbCaptureDecoratePass: gl_Position capture requested but no position output found");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!target.isMember) {
|
|
||||||
// Standalone gl_Position variable: decorate it directly.
|
|
||||||
decorateForXfb(target.variableId, bufferIndex, offsetBytes);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto* typeManager = context()->get_type_mgr();
|
|
||||||
const Uint32 mirrorPointerTypeId =
|
|
||||||
typeManager->FindPointerToType(target.vectorTypeId, spv::StorageClass::Output);
|
|
||||||
if (mirrorPointerTypeId == 0) return false;
|
|
||||||
|
|
||||||
const Uint32 mirrorVariableId = context()->TakeNextId();
|
|
||||||
auto mirrorVariable = MakeUnique<Instruction>(
|
|
||||||
context(), spv::Op::OpVariable, mirrorPointerTypeId, mirrorVariableId,
|
|
||||||
std::initializer_list<Operand>{
|
|
||||||
{SPV_OPERAND_TYPE_STORAGE_CLASS, {static_cast<Uint32>(spv::StorageClass::Output)}}});
|
|
||||||
get_module()->AddGlobalValue(Move(mirrorVariable));
|
|
||||||
|
|
||||||
// A free output location: past every explicitly decorated output.
|
|
||||||
Uint32 mirrorLocation = 0;
|
|
||||||
for (auto& annotation : get_module()->annotations()) {
|
|
||||||
if (annotation.opcode() != spv::Op::OpDecorate ||
|
|
||||||
annotation.GetSingleWordInOperand(1) != static_cast<Uint32>(spv::Decoration::Location)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
mirrorLocation = std::max(mirrorLocation, annotation.GetSingleWordInOperand(2) + 1);
|
|
||||||
}
|
|
||||||
auto* decorationManager = context()->get_decoration_mgr();
|
|
||||||
decorationManager->AddDecorationVal(mirrorVariableId,
|
|
||||||
static_cast<Uint32>(spv::Decoration::Location), mirrorLocation);
|
|
||||||
decorateForXfb(mirrorVariableId, bufferIndex, offsetBytes);
|
|
||||||
entryPoint.AddOperand({SPV_OPERAND_TYPE_ID, {mirrorVariableId}});
|
|
||||||
|
|
||||||
auto* function = context()->GetFunction(entryFunctionId);
|
|
||||||
if (function == nullptr) return false;
|
|
||||||
const auto model = static_cast<spv::ExecutionModel>(entryPointModel);
|
|
||||||
Bool injected = false;
|
|
||||||
for (auto& block : *function) {
|
|
||||||
for (auto instIter = block.begin(); instIter != block.end(); ++instIter) {
|
|
||||||
// Geometry stages capture per emitted vertex; other stages at return.
|
|
||||||
const Bool isInjectionSite =
|
|
||||||
model == spv::ExecutionModel::Geometry
|
|
||||||
? instIter->opcode() == spv::Op::OpEmitVertex
|
|
||||||
: instIter->opcode() == spv::Op::OpReturn;
|
|
||||||
if (!isInjectionSite) continue;
|
|
||||||
InstructionBuilder builder(context(), &*instIter, IRContext::kAnalysisNone);
|
|
||||||
const Uint32 memberIndexId = builder.GetUintConstantId(target.memberIndex);
|
|
||||||
auto* access =
|
|
||||||
builder.AddAccessChain(target.vectorPtrTypeId, target.variableId, {memberIndexId});
|
|
||||||
if (access == nullptr) return injected;
|
|
||||||
auto* value = builder.AddLoad(target.vectorTypeId, access->result_id());
|
|
||||||
if (value == nullptr) return injected;
|
|
||||||
builder.AddStore(mirrorVariableId, value->result_id());
|
|
||||||
injected = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return injected;
|
|
||||||
}
|
|
||||||
|
|
||||||
Vector<CapturedVarying> m_varyings;
|
|
||||||
Vector<Uint32> m_strides;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Adreno 650 (driver 512.502) faults the GPU on an implicit-LOD sample of a full-screen
|
|
||||||
// colour render target: the texture unit's derivative path reads outside the image's
|
|
||||||
// allocation even though the sampler clamps LOD to 0 and the mapping is 1:1. MobileGL's
|
|
||||||
// own default-framebuffer blit shader works around it with textureLod, but an
|
|
||||||
// application's shader (Minecraft's blit.fsh is `texture(InSampler, texCoord)`) cannot be
|
|
||||||
// edited - so rewrite the sample at the SPIR-V level instead.
|
|
||||||
//
|
|
||||||
// The rewrite is only requested for draws whose every sampler binding is clamped to one
|
|
||||||
// mip level, where explicit LOD 0 is exactly what the implicit form must already produce:
|
|
||||||
// lambda' = clamp(lambda + bias, minLod, maxLod) with minLod = maxLod = 0. Bias and MinLod
|
|
||||||
// operands are therefore dropped rather than translated.
|
|
||||||
class ForceExplicitLod0SamplePass final : public spvtools::opt::Pass {
|
|
||||||
public:
|
|
||||||
const char* name() const override { return "force-explicit-lod0-sample"; }
|
|
||||||
|
|
||||||
Status Process() override {
|
|
||||||
Bool isFragment = false;
|
|
||||||
for (auto& entryPoint : get_module()->entry_points()) {
|
|
||||||
if (entryPoint.opcode() != spv::Op::OpEntryPoint) continue;
|
|
||||||
if (static_cast<spv::ExecutionModel>(entryPoint.GetSingleWordInOperand(0)) ==
|
|
||||||
spv::ExecutionModel::Fragment) {
|
|
||||||
isFragment = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!isFragment) return Status::SuccessWithoutChange;
|
|
||||||
|
|
||||||
// Plan first, mutate second. Materializing the LOD constant is itself a module
|
|
||||||
// change, so it must not happen unless at least one rewrite is going to follow -
|
|
||||||
// otherwise the pass would grow the binary while reporting SuccessWithoutChange.
|
|
||||||
Vector<RewritePlan> plans;
|
|
||||||
for (auto& function : *get_module()) {
|
|
||||||
for (auto& block : function) {
|
|
||||||
for (auto& inst : block) {
|
|
||||||
RewritePlan plan{};
|
|
||||||
if (PlanRewrite(&inst, plan)) plans.push_back(Move(plan));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (plans.empty()) return Status::SuccessWithoutChange;
|
|
||||||
|
|
||||||
const Uint32 zeroId = GetFloatZeroId();
|
|
||||||
if (zeroId == 0) return Status::SuccessWithoutChange;
|
|
||||||
|
|
||||||
for (auto& plan : plans) {
|
|
||||||
plan.operands.push_back({SPV_OPERAND_TYPE_ID, {zeroId}});
|
|
||||||
for (auto& operand : plan.trailingOperands) {
|
|
||||||
plan.operands.push_back(operand);
|
|
||||||
}
|
|
||||||
plan.instruction->SetOpcode(plan.opcode);
|
|
||||||
plan.instruction->SetInOperands(Move(plan.operands));
|
|
||||||
}
|
|
||||||
// Opcodes and operand lists changed underneath every cached analysis.
|
|
||||||
context()->InvalidateAnalysesExceptFor(spvtools::opt::IRContext::kAnalysisNone);
|
|
||||||
return Status::SuccessWithChange;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
struct RewritePlan {
|
|
||||||
spvtools::opt::Instruction* instruction = nullptr;
|
|
||||||
spv::Op opcode = spv::Op::OpNop;
|
|
||||||
// Everything up to and including the Image Operands mask; the Lod id and the
|
|
||||||
// trailing operand values are appended once the constant exists.
|
|
||||||
Vector<spvtools::opt::Operand> operands;
|
|
||||||
Vector<spvtools::opt::Operand> trailingOperands;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Image Operands bits that may accompany an implicit-LOD sample, in the canonical
|
|
||||||
// ascending order SPIR-V requires the operand values to appear in.
|
|
||||||
static constexpr Uint32 kBias = 0x1;
|
|
||||||
static constexpr Uint32 kLod = 0x2;
|
|
||||||
static constexpr Uint32 kGrad = 0x4;
|
|
||||||
static constexpr Uint32 kConstOffset = 0x8;
|
|
||||||
static constexpr Uint32 kOffset = 0x10;
|
|
||||||
static constexpr Uint32 kConstOffsets = 0x20;
|
|
||||||
static constexpr Uint32 kSample = 0x40;
|
|
||||||
static constexpr Uint32 kMinLod = 0x80;
|
|
||||||
static constexpr Uint32 kKnownMask = 0xFF;
|
|
||||||
|
|
||||||
Uint32 GetFloatZeroId() {
|
|
||||||
// Reuse a 32-bit float type already in the module; a shader that samples always has
|
|
||||||
// one, and looking it up avoids depending on type-creation API details.
|
|
||||||
Uint32 floatTypeId = 0;
|
|
||||||
for (auto& inst : get_module()->types_values()) {
|
|
||||||
if (inst.opcode() == spv::Op::OpTypeFloat && inst.NumInOperands() >= 1 &&
|
|
||||||
inst.GetSingleWordInOperand(0) == 32) {
|
|
||||||
floatTypeId = inst.result_id();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (floatTypeId == 0) return 0;
|
|
||||||
|
|
||||||
const auto* floatType = context()->get_type_mgr()->GetType(floatTypeId);
|
|
||||||
if (floatType == nullptr) return 0;
|
|
||||||
const auto zeroBits = std::bit_cast<Uint32>(0.0f);
|
|
||||||
const auto* zeroConst = context()->get_constant_mgr()->GetConstant(floatType, {zeroBits});
|
|
||||||
if (zeroConst == nullptr) return 0;
|
|
||||||
auto* zeroInst = context()->get_constant_mgr()->GetDefiningInstruction(zeroConst);
|
|
||||||
return zeroInst != nullptr ? zeroInst->result_id() : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static Bool MapOpcode(spv::Op op, spv::Op& outOpcode, Uint32& outFixedOperandCount) {
|
|
||||||
switch (op) {
|
|
||||||
case spv::Op::OpImageSampleImplicitLod:
|
|
||||||
outOpcode = spv::Op::OpImageSampleExplicitLod;
|
|
||||||
outFixedOperandCount = 2; // sampled image, coordinate
|
|
||||||
return true;
|
|
||||||
case spv::Op::OpImageSampleProjImplicitLod:
|
|
||||||
outOpcode = spv::Op::OpImageSampleProjExplicitLod;
|
|
||||||
outFixedOperandCount = 2;
|
|
||||||
return true;
|
|
||||||
case spv::Op::OpImageSampleDrefImplicitLod:
|
|
||||||
outOpcode = spv::Op::OpImageSampleDrefExplicitLod;
|
|
||||||
outFixedOperandCount = 3; // sampled image, coordinate, Dref
|
|
||||||
return true;
|
|
||||||
case spv::Op::OpImageSampleProjDrefImplicitLod:
|
|
||||||
outOpcode = spv::Op::OpImageSampleProjDrefExplicitLod;
|
|
||||||
outFixedOperandCount = 3;
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static Bool PlanRewrite(spvtools::opt::Instruction* inst, RewritePlan& outPlan) {
|
|
||||||
spv::Op newOpcode = spv::Op::OpNop;
|
|
||||||
Uint32 fixedCount = 0;
|
|
||||||
if (!MapOpcode(inst->opcode(), newOpcode, fixedCount)) return false;
|
|
||||||
if (inst->NumInOperands() < fixedCount) return false;
|
|
||||||
|
|
||||||
Uint32 mask = 0;
|
|
||||||
Uint32 next = fixedCount;
|
|
||||||
if (inst->NumInOperands() > fixedCount) {
|
|
||||||
mask = inst->GetSingleWordInOperand(fixedCount);
|
|
||||||
next = fixedCount + 1;
|
|
||||||
}
|
|
||||||
// An operand this pass does not model would be silently reordered or dropped, and
|
|
||||||
// Grad cannot legally accompany an implicit-LOD sample: leave such an instruction be.
|
|
||||||
if ((mask & ~kKnownMask) != 0 || (mask & kGrad) != 0) return false;
|
|
||||||
|
|
||||||
Vector<spvtools::opt::Operand> fixedOperands;
|
|
||||||
fixedOperands.reserve(fixedCount + 1);
|
|
||||||
for (Uint32 i = 0; i < fixedCount; ++i) {
|
|
||||||
fixedOperands.push_back(inst->GetInOperand(i));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Collect the surviving operand values in the same ascending-bit order they were
|
|
||||||
// encoded in, so the rebuilt list stays canonical.
|
|
||||||
Uint32 keptMask = kLod;
|
|
||||||
Vector<spvtools::opt::Operand> keptOperands;
|
|
||||||
static constexpr Uint32 kOrderedBits[] = {kBias, kLod, kGrad, kConstOffset,
|
|
||||||
kOffset, kConstOffsets, kSample, kMinLod};
|
|
||||||
for (const Uint32 bit : kOrderedBits) {
|
|
||||||
if ((mask & bit) == 0) continue;
|
|
||||||
if (next >= inst->NumInOperands()) return false;
|
|
||||||
const spvtools::opt::Operand value = inst->GetInOperand(next++);
|
|
||||||
// Bias and MinLod only shift a lambda that is already clamped to 0, and any
|
|
||||||
// original Lod is replaced by the constant the caller appends.
|
|
||||||
if (bit == kBias || bit == kMinLod || bit == kLod) continue;
|
|
||||||
keptMask |= bit;
|
|
||||||
keptOperands.push_back(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
fixedOperands.push_back({SPV_OPERAND_TYPE_IMAGE, {keptMask}});
|
|
||||||
outPlan.instruction = inst;
|
|
||||||
outPlan.opcode = newOpcode;
|
|
||||||
outPlan.operands = Move(fixedOperands);
|
|
||||||
outPlan.trailingOperands = Move(keptOperands);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
spvtools::Optimizer::PassToken CreateForceExplicitLod0SamplePass() {
|
|
||||||
return spvtools::Optimizer::PassToken(MakeUnique<ForceExplicitLod0SamplePass>());
|
|
||||||
}
|
|
||||||
|
|
||||||
Bool TransformSpirvForExplicitLod0Sampling(const Vector<Uint>& input, Vector<Uint>& output) {
|
|
||||||
if (input.empty()) {
|
|
||||||
output.clear();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
spvtools::Optimizer optimizer(SPV_ENV_VULKAN_1_3);
|
|
||||||
spvtools::OptimizerOptions options;
|
|
||||||
// Matches the position-fix pass: this build of spirv-tools asserts rather than
|
|
||||||
// reporting, so validation stays off in the shipping path.
|
|
||||||
options.set_run_validator(false);
|
|
||||||
optimizer.SetMessageConsumer([](spv_message_level_t, const char*, const spv_position_t&,
|
|
||||||
const char* message) {
|
|
||||||
MGLOG_E("Vulkan: explicit-LOD0 pass: %s", message != nullptr ? message : "");
|
|
||||||
});
|
|
||||||
optimizer.RegisterPass(CreateForceExplicitLod0SamplePass());
|
|
||||||
|
|
||||||
const Bool success = optimizer.Run(input.data(), input.size(), &output, options);
|
|
||||||
if (!success) {
|
|
||||||
MGLOG_E("Vulkan: explicit-LOD0 sampling pass failed; keeping the original module");
|
|
||||||
output = input;
|
|
||||||
}
|
|
||||||
return success;
|
|
||||||
}
|
|
||||||
|
|
||||||
spvtools::Optimizer::PassToken CreateGlToVulkanPositionFixPass(
|
spvtools::Optimizer::PassToken CreateGlToVulkanPositionFixPass(
|
||||||
ProgramFactory::CompileOptionFlags transformFlags) {
|
ProgramFactory::CompileOptionFlags transformFlags) {
|
||||||
return spvtools::Optimizer::PassToken(MakeUnique<GlToVulkanPositionFixPass>(transformFlags));
|
return spvtools::Optimizer::PassToken(MakeUnique<GlToVulkanPositionFixPass>(transformFlags));
|
||||||
}
|
}
|
||||||
|
|
||||||
Bool TransformSpirvForXfbCapture(const Vector<Uint>& input, Vector<Uint>& output,
|
|
||||||
const MG_State::GLState::ProgramObject& program) {
|
|
||||||
if (input.empty()) {
|
|
||||||
output.clear();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
Vector<XfbCaptureDecoratePass::CapturedVarying> varyings;
|
|
||||||
varyings.reserve(program.GetTransformFeedbackVaryingCount());
|
|
||||||
for (const auto& varying : program.GetTransformFeedbackVaryings()) {
|
|
||||||
varyings.push_back({varying.name, varying.bufferIndex, varying.offsetBytes});
|
|
||||||
}
|
|
||||||
Vector<Uint32> strides;
|
|
||||||
strides.reserve(program.GetTransformFeedbackBufferCount());
|
|
||||||
for (SizeT i = 0; i < program.GetTransformFeedbackBufferCount(); ++i) {
|
|
||||||
strides.push_back(program.GetTransformFeedbackStride(static_cast<Uint32>(i)));
|
|
||||||
}
|
|
||||||
|
|
||||||
spvtools::Optimizer optimizer(SPV_ENV_VULKAN_1_3);
|
|
||||||
spvtools::OptimizerOptions options;
|
|
||||||
options.set_run_validator(false);
|
|
||||||
optimizer.SetMessageConsumer([](spv_message_level_t, const char*, const spv_position_t&,
|
|
||||||
const char* message) {
|
|
||||||
MGLOG_E("Vulkan: xfb capture pass: %s", message != nullptr ? message : "");
|
|
||||||
});
|
|
||||||
optimizer.RegisterPass(spvtools::Optimizer::PassToken(
|
|
||||||
MakeUnique<XfbCaptureDecoratePass>(Move(varyings), Move(strides))));
|
|
||||||
|
|
||||||
const Bool success = optimizer.Run(input.data(), input.size(), &output, options);
|
|
||||||
if (!success) {
|
|
||||||
MGLOG_E("Vulkan: xfb capture decoration pass failed; keeping the original module");
|
|
||||||
output = input;
|
|
||||||
}
|
|
||||||
return success;
|
|
||||||
}
|
|
||||||
|
|
||||||
Bool TransformSpirvForVulkanPositionFix(const Vector<Uint>& input, Vector<Uint>& output,
|
Bool TransformSpirvForVulkanPositionFix(const Vector<Uint>& input, Vector<Uint>& output,
|
||||||
ProgramFactory::CompileOptionFlags transformFlags) {
|
ProgramFactory::CompileOptionFlags transformFlags) {
|
||||||
if (input.empty()) {
|
if (input.empty()) {
|
||||||
@@ -1761,25 +1369,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
XXHASH_VERIFY(XXH64_update(m_hashState, &binding, sizeof(binding)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &binding, sizeof(binding)));
|
||||||
}
|
}
|
||||||
|
|
||||||
// The transform feedback capture layout is baked into the modules by
|
|
||||||
// XfbCaptureDecoratePass rather than coming from the SPIR-V, so it has to be part of
|
|
||||||
// the key: two programs can share every shader and still capture differently, which
|
|
||||||
// is exactly what changing the buffer mode does (glTransformFeedbackVaryings with the
|
|
||||||
// same varyings but GL_SEPARATE_ATTRIBS instead of GL_INTERLEAVED_ATTRIBS). Only
|
|
||||||
// hashed for a capturing compile, so nothing else changes key.
|
|
||||||
if (flags & CompileOptionBit::XfbCapture) {
|
|
||||||
for (const auto& varying : program.GetTransformFeedbackVaryings()) {
|
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState, varying.name.data(), varying.name.size()));
|
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState, &varying.bufferIndex, sizeof(varying.bufferIndex)));
|
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState, &varying.offsetBytes, sizeof(varying.offsetBytes)));
|
|
||||||
}
|
|
||||||
const SizeT bufferCount = program.GetTransformFeedbackBufferCount();
|
|
||||||
for (SizeT i = 0; i < bufferCount; ++i) {
|
|
||||||
const Uint32 stride = program.GetTransformFeedbackStride(static_cast<Uint32>(i));
|
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState, &stride, sizeof(stride)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
HashType hash = XXH64_digest(m_hashState);
|
HashType hash = XXH64_digest(m_hashState);
|
||||||
return hash;
|
return hash;
|
||||||
}
|
}
|
||||||
@@ -2348,16 +1937,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
pipelineLayoutInfo.pSetLayouts = &entry.descriptorSetLayout;
|
pipelineLayoutInfo.pSetLayouts = &entry.descriptorSetLayout;
|
||||||
VK_VERIFY(vkCreatePipelineLayout(m_device, &pipelineLayoutInfo, nullptr, &entry.pipelineLayout),
|
VK_VERIFY(vkCreatePipelineLayout(m_device, &pipelineLayoutInfo, nullptr, &entry.pipelineLayout),
|
||||||
"ProgramFactory::ReflectLayout, vkCreatePipelineLayout");
|
"ProgramFactory::ReflectLayout, vkCreatePipelineLayout");
|
||||||
|
|
||||||
// Built here rather than where bindingKinds is sized: at that point the vector is only
|
|
||||||
// zero-initialised and the kinds are assigned further down, so a list built there would be
|
|
||||||
// empty. Ascending by construction because the index walks upward.
|
|
||||||
entry.activeBindings.clear();
|
|
||||||
for (Uint32 binding = 0; binding < static_cast<Uint32>(entry.bindingKinds.size()); ++binding) {
|
|
||||||
if (entry.bindingKinds[binding] != DescriptorBindingKind::None) {
|
|
||||||
entry.activeBindings.push_back(binding);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const ProgramFactory::VkProgramObject& ProgramFactory::GetOrCreateProgram(
|
const ProgramFactory::VkProgramObject& ProgramFactory::GetOrCreateProgram(
|
||||||
@@ -2371,17 +1950,11 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
}
|
}
|
||||||
auto it = m_cache.find(hash);
|
auto it = m_cache.find(hash);
|
||||||
if (it != m_cache.end()) {
|
if (it != m_cache.end()) {
|
||||||
// Every draw/dispatch funnels through this lookup (the renderer memos only
|
|
||||||
// skip re-hashing, never the factory lookup), so an actively-used entry is
|
|
||||||
// stamped at least once per frame boundary and can never be aged out while
|
|
||||||
// any in-flight command buffer still references it.
|
|
||||||
it->second.lastUsedFrame = m_frameCounter;
|
|
||||||
return it->second;
|
return it->second;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto& entry = m_cache[hash];
|
auto& entry = m_cache[hash];
|
||||||
entry.hash = hash;
|
entry.hash = hash;
|
||||||
entry.lastUsedFrame = m_frameCounter;
|
|
||||||
auto& shaders = program.GetAttachedShaders();
|
auto& shaders = program.GetAttachedShaders();
|
||||||
auto& spirv = program.GetGeneratedSpirv();
|
auto& spirv = program.GetGeneratedSpirv();
|
||||||
Vector<Vector<Uint>> moduleSpirvs(spirv.size());
|
Vector<Vector<Uint>> moduleSpirvs(spirv.size());
|
||||||
@@ -2394,40 +1967,11 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
|
|
||||||
// Apply position fixup if needed
|
// Apply position fixup if needed
|
||||||
if (fixupStage != ShaderStage::Unknown && shaders[i] && shaders[i]->GetShaderStage() == fixupStage) {
|
if (fixupStage != ShaderStage::Unknown && shaders[i] && shaders[i]->GetShaderStage() == fixupStage) {
|
||||||
const Vector<Uint>* fixupInput = &spv;
|
TransformSpirvForVulkanPositionFix(spv, moduleSpirvs[i], flags);
|
||||||
Vector<Uint> xfbSpirv;
|
|
||||||
if ((flags & ProgramFactory::CompileOptionBit::XfbCapture) &&
|
|
||||||
program.GetTransformFeedbackVaryingCount() > 0) {
|
|
||||||
// Decorate BEFORE the position fixup so a captured gl_Position
|
|
||||||
// mirror copies the shader's own (pre-remap) value.
|
|
||||||
if (TransformSpirvForXfbCapture(spv, xfbSpirv, program)) {
|
|
||||||
fixupInput = &xfbSpirv;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
TransformSpirvForVulkanPositionFix(*fixupInput, moduleSpirvs[i], flags);
|
|
||||||
} else {
|
} else {
|
||||||
moduleSpirvs[i] = spv;
|
moduleSpirvs[i] = spv;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((flags & ProgramFactory::CompileOptionBit::ExplicitLod0Sampling) && shaders[i] &&
|
|
||||||
shaders[i]->GetShaderStage() == ShaderStage::Fragment) {
|
|
||||||
Vector<Uint> explicitLodSpirv;
|
|
||||||
if (TransformSpirvForExplicitLod0Sampling(moduleSpirvs[i], explicitLodSpirv)) {
|
|
||||||
moduleSpirvs[i] = Move(explicitLodSpirv);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Vulkan's SPIR-V environment has no rectangle image dimension, so a
|
|
||||||
// GL_TEXTURE_RECTANGLE lookup has to become the 2D one the texture is really
|
|
||||||
// stored as - which addresses [0,1] where the application addressed texels.
|
|
||||||
{
|
|
||||||
Vector<Uint> rectLoweredSpirv;
|
|
||||||
if (MG_Util::ShaderTranspiler::ShaderCompiler::LowerRectImages(moduleSpirvs[i], rectLoweredSpirv) &&
|
|
||||||
!rectLoweredSpirv.empty()) {
|
|
||||||
moduleSpirvs[i] = Move(rectLoweredSpirv);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GL apps depend on cross-program position invariance for multi-pass equality
|
// GL apps depend on cross-program position invariance for multi-pass equality
|
||||||
// depth tests (MC 26.3's OIT re-draws the cloud geometry with GEQUAL against the
|
// depth tests (MC 26.3's OIT re-draws the cloud geometry with GEQUAL against the
|
||||||
// depth its own first pass wrote); decorate Position outputs Invariant so
|
// depth its own first pass wrote); decorate Position outputs Invariant so
|
||||||
@@ -2468,33 +2012,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// A 64-bit vertex input has to arrive as its 32-bit word pair: VK_FORMAT_R64*_SFLOAT is
|
|
||||||
// optional and lavapipe advertises none of them at all. The pass is unconditional so it
|
|
||||||
// always agrees with the Float64 case in VertexInputStateFactory::ToVkVertexFormat, and
|
|
||||||
// ReflectVertexInputs below then sees an ordinary uvec2/uvec4 input.
|
|
||||||
//
|
|
||||||
// Failure here is not recoverable and must not be swallowed: ToVkVertexFormat has already
|
|
||||||
// committed to R32G32{,B32A32}_UINT for the attribute, so a module still declaring
|
|
||||||
// `in double` would reconcile to Unknown and build a pipeline with a UINT format under a
|
|
||||||
// double input - garbage with no diagnostic anywhere.
|
|
||||||
if (shaders[i] && shaders[i]->GetShaderStage() == ShaderStage::Vertex) {
|
|
||||||
Vector<Uint> packedSpirv;
|
|
||||||
const Bool packOk = MG_Util::ShaderTranspiler::ShaderCompiler::PackDoubleVertexInputsForVulkan(
|
|
||||||
moduleSpirvs[i], packedSpirv);
|
|
||||||
MOBILEGL_ASSERT(packOk,
|
|
||||||
"ProgramFactory: 64-bit vertex input packing failed for program %u; the "
|
|
||||||
"vertex-input format and the shader input type now disagree",
|
|
||||||
program.GetExternalIndex());
|
|
||||||
if (packOk) {
|
|
||||||
moduleSpirvs[i] = std::move(packedSpirv);
|
|
||||||
} else {
|
|
||||||
MGLOG_E("ProgramFactory: failed to pack 64-bit vertex inputs for program %u; "
|
|
||||||
"double-typed vertex attributes will be fetched as uint32 words and not "
|
|
||||||
"reinterpreted",
|
|
||||||
program.GetExternalIndex());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// When Vulkan can legally access storage images without a statically declared
|
// When Vulkan can legally access storage images without a statically declared
|
||||||
// format, let GL's glBindImageTexture format select the runtime image view. This
|
// format, let GL's glBindImageTexture format select the runtime image view. This
|
||||||
// provides desktop-driver-compatible behavior for packs such as iterationRP, whose
|
// provides desktop-driver-compatible behavior for packs such as iterationRP, whose
|
||||||
@@ -2551,42 +2068,4 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
|
|
||||||
return entry;
|
return entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ProgramFactory::OnFrameBoundary() {
|
|
||||||
++m_frameCounter;
|
|
||||||
|
|
||||||
// Sweep cadence and retire age mirror VkRenderPassManager::OnPresent: an entry
|
|
||||||
// idle for more than kRetireAgeFrames frame boundaries cannot be referenced by
|
|
||||||
// any in-flight command buffer (frames-in-flight <= MOBILEGL_MAGMA_FRAMESINFLIGHT),
|
|
||||||
// so its shader modules and layouts are destroyed immediately - no deferred-
|
|
||||||
// destroy machinery needed. Eviction is content-based, never tied to
|
|
||||||
// glDeleteProgram: the cache is content-hash-shared across GL programs, so a
|
|
||||||
// delete-driven erase could free an entry another live program still resolves.
|
|
||||||
// An evicted entry self-heals - the frontend program keeps its generated
|
|
||||||
// SPIR-V, so the next GetOrCreateProgram rebuilds it (this also covers the
|
|
||||||
// renderer's internal blit/depth-mipmap programs).
|
|
||||||
constexpr Uint64 kSweepInterval = 256;
|
|
||||||
constexpr Uint64 kRetireAgeFrames = 1024;
|
|
||||||
if ((m_frameCounter % kSweepInterval) != 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto it = m_cache.begin(); it != m_cache.end();) {
|
|
||||||
if (m_frameCounter - it->second.lastUsedFrame > kRetireAgeFrames) {
|
|
||||||
const HashType hash = it->first;
|
|
||||||
const VkDescriptorSetLayout descriptorSetLayout = it->second.descriptorSetLayout;
|
|
||||||
MGLOG_D("ProgramFactory::OnFrameBoundary: evicting idle program entry hash=0x%llx",
|
|
||||||
static_cast<unsigned long long>(hash));
|
|
||||||
// erase runs ~VkProgramObject (modules/layouts destroyed); notify after
|
|
||||||
// so an observer never observes a half-destroyed entry through a lookup.
|
|
||||||
// Observers only need the handle values to purge their keyed caches.
|
|
||||||
it = m_cache.erase(it);
|
|
||||||
if (m_evictionObserver != nullptr) {
|
|
||||||
m_evictionObserver->OnProgramEvicted(hash, descriptorSetLayout);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
++it;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||||
|
|||||||
@@ -42,16 +42,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
SurfaceRotate90 = 1 << 2,
|
SurfaceRotate90 = 1 << 2,
|
||||||
SurfaceRotate180 = 1 << 3,
|
SurfaceRotate180 = 1 << 3,
|
||||||
SurfaceRotate270 = 1 << 4,
|
SurfaceRotate270 = 1 << 4,
|
||||||
// Rewrites the fragment stage's implicit-LOD image samples to explicit LOD 0.
|
|
||||||
// Only ever set for a draw whose every sampler binding is clamped to a single mip
|
|
||||||
// level, which makes the two forms produce identical texels (the implicit lambda is
|
|
||||||
// clamped into [minLod, maxLod] = [0, 0] regardless of derivatives or bias).
|
|
||||||
ExplicitLod0Sampling = 1 << 5,
|
|
||||||
// Decorates the last vertex-processing stage's captured varyings with
|
|
||||||
// XfbBuffer/XfbStride/Offset (VK_EXT_transform_feedback). Set only for draws
|
|
||||||
// recorded while GL transform feedback is active, so plain draws keep the
|
|
||||||
// undecorated variant.
|
|
||||||
XfbCapture = 1 << 6,
|
|
||||||
};
|
};
|
||||||
using CompileOptionFlags = Flags<CompileOptionBit>;
|
using CompileOptionFlags = Flags<CompileOptionBit>;
|
||||||
using HashType = Uint64;
|
using HashType = Uint64;
|
||||||
@@ -67,12 +57,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
VkDescriptorSetLayout descriptorSetLayout = VK_NULL_HANDLE;
|
VkDescriptorSetLayout descriptorSetLayout = VK_NULL_HANDLE;
|
||||||
VkPipelineLayout pipelineLayout = VK_NULL_HANDLE;
|
VkPipelineLayout pipelineLayout = VK_NULL_HANDLE;
|
||||||
Vector<DescriptorBindingKind> bindingKinds;
|
Vector<DescriptorBindingKind> bindingKinds;
|
||||||
// The bindings this program actually declares, ascending. bindingKinds is sized to the
|
|
||||||
// 256-binding cap while a real GL program uses 1-8, so the per-draw descriptor walk was
|
|
||||||
// scanning 256 slots to find a handful. MUST stay ascending: Vulkan consumes
|
|
||||||
// pDynamicOffsets in binding order and the writer pushes them in iteration order, so an
|
|
||||||
// unordered list would silently mis-pair dynamic offsets with their uniform blocks.
|
|
||||||
Vector<Uint32> activeBindings;
|
|
||||||
Vector<Uint32> dynamicBindings;
|
Vector<Uint32> dynamicBindings;
|
||||||
Vector<Int> uniformBlockIndexByBinding;
|
Vector<Int> uniformBlockIndexByBinding;
|
||||||
// Descriptor count per binding (1 except for UBO instance arrays, which occupy one
|
// Descriptor count per binding (1 except for UBO instance arrays, which occupy one
|
||||||
@@ -104,9 +88,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
// gl_FragDepth); shader-computed depth is immune to the cross-pipeline
|
// gl_FragDepth); shader-computed depth is immune to the cross-pipeline
|
||||||
// position-invariance quirk (see PipelineFactory::ShouldSuppressDepthWrite).
|
// position-invariance quirk (see PipelineFactory::ShouldSuppressDepthWrite).
|
||||||
Bool fragmentReplacesDepth = false;
|
Bool fragmentReplacesDepth = false;
|
||||||
// Frame-boundary counter value of the last GetOrCreateProgram hit; drives
|
|
||||||
// cache eviction (see OnFrameBoundary).
|
|
||||||
Uint64 lastUsedFrame = 0;
|
|
||||||
|
|
||||||
static inline VkDevice s_device = VK_NULL_HANDLE;
|
static inline VkDevice s_device = VK_NULL_HANDLE;
|
||||||
|
|
||||||
@@ -120,7 +101,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
descriptorSetLayout = other.descriptorSetLayout;
|
descriptorSetLayout = other.descriptorSetLayout;
|
||||||
pipelineLayout = other.pipelineLayout;
|
pipelineLayout = other.pipelineLayout;
|
||||||
bindingKinds = std::move(other.bindingKinds);
|
bindingKinds = std::move(other.bindingKinds);
|
||||||
activeBindings = std::move(other.activeBindings);
|
|
||||||
dynamicBindings = std::move(other.dynamicBindings);
|
dynamicBindings = std::move(other.dynamicBindings);
|
||||||
uniformBlockIndexByBinding = std::move(other.uniformBlockIndexByBinding);
|
uniformBlockIndexByBinding = std::move(other.uniformBlockIndexByBinding);
|
||||||
bindingDescriptorCounts = std::move(other.bindingDescriptorCounts);
|
bindingDescriptorCounts = std::move(other.bindingDescriptorCounts);
|
||||||
@@ -144,7 +124,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
producerOutputComponentCount = other.producerOutputComponentCount;
|
producerOutputComponentCount = other.producerOutputComponentCount;
|
||||||
fragmentInputComponentCount = other.fragmentInputComponentCount;
|
fragmentInputComponentCount = other.fragmentInputComponentCount;
|
||||||
fragmentReplacesDepth = other.fragmentReplacesDepth;
|
fragmentReplacesDepth = other.fragmentReplacesDepth;
|
||||||
lastUsedFrame = other.lastUsedFrame;
|
|
||||||
other.hash = 0;
|
other.hash = 0;
|
||||||
other.descriptorSetLayout = VK_NULL_HANDLE;
|
other.descriptorSetLayout = VK_NULL_HANDLE;
|
||||||
other.pipelineLayout = VK_NULL_HANDLE;
|
other.pipelineLayout = VK_NULL_HANDLE;
|
||||||
@@ -156,7 +135,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
other.producerOutputComponentCount = 0;
|
other.producerOutputComponentCount = 0;
|
||||||
other.fragmentInputComponentCount = 0;
|
other.fragmentInputComponentCount = 0;
|
||||||
other.fragmentReplacesDepth = false;
|
other.fragmentReplacesDepth = false;
|
||||||
other.lastUsedFrame = 0;
|
|
||||||
}
|
}
|
||||||
VkProgramObject& operator=(VkProgramObject&& other) noexcept {
|
VkProgramObject& operator=(VkProgramObject&& other) noexcept {
|
||||||
if (this == &other) {
|
if (this == &other) {
|
||||||
@@ -169,7 +147,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
descriptorSetLayout = other.descriptorSetLayout;
|
descriptorSetLayout = other.descriptorSetLayout;
|
||||||
pipelineLayout = other.pipelineLayout;
|
pipelineLayout = other.pipelineLayout;
|
||||||
bindingKinds = std::move(other.bindingKinds);
|
bindingKinds = std::move(other.bindingKinds);
|
||||||
activeBindings = std::move(other.activeBindings);
|
|
||||||
dynamicBindings = std::move(other.dynamicBindings);
|
dynamicBindings = std::move(other.dynamicBindings);
|
||||||
uniformBlockIndexByBinding = std::move(other.uniformBlockIndexByBinding);
|
uniformBlockIndexByBinding = std::move(other.uniformBlockIndexByBinding);
|
||||||
bindingDescriptorCounts = std::move(other.bindingDescriptorCounts);
|
bindingDescriptorCounts = std::move(other.bindingDescriptorCounts);
|
||||||
@@ -193,7 +170,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
producerOutputComponentCount = other.producerOutputComponentCount;
|
producerOutputComponentCount = other.producerOutputComponentCount;
|
||||||
fragmentInputComponentCount = other.fragmentInputComponentCount;
|
fragmentInputComponentCount = other.fragmentInputComponentCount;
|
||||||
fragmentReplacesDepth = other.fragmentReplacesDepth;
|
fragmentReplacesDepth = other.fragmentReplacesDepth;
|
||||||
lastUsedFrame = other.lastUsedFrame;
|
|
||||||
other.hash = 0;
|
other.hash = 0;
|
||||||
other.descriptorSetLayout = VK_NULL_HANDLE;
|
other.descriptorSetLayout = VK_NULL_HANDLE;
|
||||||
other.pipelineLayout = VK_NULL_HANDLE;
|
other.pipelineLayout = VK_NULL_HANDLE;
|
||||||
@@ -205,7 +181,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
other.producerOutputComponentCount = 0;
|
other.producerOutputComponentCount = 0;
|
||||||
other.fragmentInputComponentCount = 0;
|
other.fragmentInputComponentCount = 0;
|
||||||
other.fragmentReplacesDepth = false;
|
other.fragmentReplacesDepth = false;
|
||||||
other.lastUsedFrame = 0;
|
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -235,18 +210,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Notified when the OnFrameBoundary sweep destroys an aged-out cache entry,
|
|
||||||
// carrying the entry's content hash and the VkDescriptorSetLayout it owned.
|
|
||||||
// Dependent caches (compute pipelines, PipelineFactory entries, UniformManager's
|
|
||||||
// per-layout descriptor sets) must purge in the same step: after vkDestroy the
|
|
||||||
// layout handle value may be recycled for an unrelated layout, and the program
|
|
||||||
// hash may be re-inserted by a later rebuild of the same content.
|
|
||||||
class IEvictionObserver {
|
|
||||||
public:
|
|
||||||
virtual ~IEvictionObserver() = default;
|
|
||||||
virtual void OnProgramEvicted(HashType programHash, VkDescriptorSetLayout descriptorSetLayout) = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
explicit ProgramFactory(VkDevice device, const VulkanRendererConfig& config, Uint32 maxBindings = 16,
|
explicit ProgramFactory(VkDevice device, const VulkanRendererConfig& config, Uint32 maxBindings = 16,
|
||||||
Bool shaderDrawParametersEnabled = false,
|
Bool shaderDrawParametersEnabled = false,
|
||||||
Bool unformattedFloatStorageImagesEnabled = false)
|
Bool unformattedFloatStorageImagesEnabled = false)
|
||||||
@@ -262,13 +225,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
const VkProgramObject& GetOrCreateProgram(
|
const VkProgramObject& GetOrCreateProgram(
|
||||||
const MG_State::GLState::ProgramObject& program, CompileOptionFlags flags);
|
const MG_State::GLState::ProgramObject& program, CompileOptionFlags flags);
|
||||||
|
|
||||||
// Observer may be null (no notifications). Not owned.
|
|
||||||
void SetEvictionObserver(IEvictionObserver* observer) { m_evictionObserver = observer; }
|
|
||||||
// Frame boundary hook: ages the program cache and evicts long-unused entries
|
|
||||||
// (their command buffers retired many frames ago), mirroring
|
|
||||||
// VkRenderPassManager::OnPresent's sweep.
|
|
||||||
void OnFrameBoundary();
|
|
||||||
|
|
||||||
static VkShaderStageFlagBits ToVkStage(ShaderStage stage);
|
static VkShaderStageFlagBits ToVkStage(ShaderStage stage);
|
||||||
static VkFormat ConvertSpirvImageFormatToVkFormat(SpvImageFormat format);
|
static VkFormat ConvertSpirvImageFormatToVkFormat(SpvImageFormat format);
|
||||||
static SamplerNumericDomain UniformTypeToSamplerNumericDomain(GLenum glType);
|
static SamplerNumericDomain UniformTypeToSamplerNumericDomain(GLenum glType);
|
||||||
@@ -310,9 +266,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
// shaderStorageImageReadWithoutFormat and shaderStorageImageWriteWithoutFormat.
|
// shaderStorageImageReadWithoutFormat and shaderStorageImageWriteWithoutFormat.
|
||||||
Bool m_unformattedFloatStorageImagesEnabled = false;
|
Bool m_unformattedFloatStorageImagesEnabled = false;
|
||||||
mutable ProgramLookupCache m_lastLookup;
|
mutable ProgramLookupCache m_lastLookup;
|
||||||
// Monotonic frame-boundary counter (bumped in OnFrameBoundary) for cache aging.
|
|
||||||
Uint64 m_frameCounter = 0;
|
|
||||||
IEvictionObserver* m_evictionObserver = nullptr;
|
|
||||||
static inline XXH64_state_t* m_hashState = XXH64_createState();
|
static inline XXH64_state_t* m_hashState = XXH64_createState();
|
||||||
};
|
};
|
||||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||||
|
|||||||
@@ -247,11 +247,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
|
|
||||||
m_surfaceFormat = {createInfo.imageFormat, createInfo.imageColorSpace};
|
m_surfaceFormat = {createInfo.imageFormat, createInfo.imageColorSpace};
|
||||||
m_extent = createInfo.imageExtent;
|
m_extent = createInfo.imageExtent;
|
||||||
// The surface-space extent this swapchain was built from, i.e. before the
|
|
||||||
// quarter-turn swap above. Out-of-date checks must compare in THIS space: comparing a
|
|
||||||
// freshly queried currentExtent against the swapped m_extent flips axes every rotation
|
|
||||||
// and makes the comparison alternate forever.
|
|
||||||
m_surfaceExtent = defaultFramebufferExtent;
|
|
||||||
m_preTransform = createInfo.preTransform;
|
m_preTransform = createInfo.preTransform;
|
||||||
|
|
||||||
VK_VERIFY(vkCreateSwapchainKHR(device, &createInfo, nullptr, &m_swapchain));
|
VK_VERIFY(vkCreateSwapchainKHR(device, &createInfo, nullptr, &m_swapchain));
|
||||||
@@ -262,9 +257,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
m_images.resize(imageCount, VK_NULL_HANDLE);
|
m_images.resize(imageCount, VK_NULL_HANDLE);
|
||||||
VK_VERIFY(vkGetSwapchainImagesKHR(device, m_swapchain, &imageCount, m_images.data()));
|
VK_VERIFY(vkGetSwapchainImagesKHR(device, m_swapchain, &imageCount, m_images.data()));
|
||||||
m_imageLayouts.assign(imageCount, VK_IMAGE_LAYOUT_UNDEFINED);
|
m_imageLayouts.assign(imageCount, VK_IMAGE_LAYOUT_UNDEFINED);
|
||||||
// Fresh swapchain images hold garbage until a render pass stores into them.
|
|
||||||
m_imageContentDefined.assign(imageCount, false);
|
|
||||||
m_depthStencilContentDefined.assign(imageCount, false);
|
|
||||||
|
|
||||||
CreateImageViews(device);
|
CreateImageViews(device);
|
||||||
CreateDepthStencilResources(device, physicalDevice);
|
CreateDepthStencilResources(device, physicalDevice);
|
||||||
@@ -436,39 +428,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
|
|
||||||
m_images.clear();
|
m_images.clear();
|
||||||
m_imageLayouts.clear();
|
m_imageLayouts.clear();
|
||||||
m_imageContentDefined.clear();
|
|
||||||
m_depthStencilContentDefined.clear();
|
|
||||||
m_preTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
|
m_preTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
|
||||||
}
|
}
|
||||||
|
|
||||||
Bool SwapchainObject::IsImageContentDefined(Uint32 index) const {
|
|
||||||
MOBILEGL_ASSERT(index < m_imageContentDefined.size(), "Swapchain image content index out of range");
|
|
||||||
return m_imageContentDefined[index];
|
|
||||||
}
|
|
||||||
|
|
||||||
void SwapchainObject::SetImageContentDefined(Uint32 index, Bool defined) {
|
|
||||||
MOBILEGL_ASSERT(index < m_imageContentDefined.size(), "Swapchain image content index out of range");
|
|
||||||
m_imageContentDefined[index] = defined;
|
|
||||||
}
|
|
||||||
|
|
||||||
Bool SwapchainObject::IsDepthStencilContentDefined(Uint32 index) const {
|
|
||||||
MOBILEGL_ASSERT(index < m_depthStencilContentDefined.size(),
|
|
||||||
"Swapchain depth/stencil content index out of range");
|
|
||||||
return m_depthStencilContentDefined[index];
|
|
||||||
}
|
|
||||||
|
|
||||||
void SwapchainObject::SetDepthStencilContentDefined(Uint32 index, Bool defined) {
|
|
||||||
MOBILEGL_ASSERT(index < m_depthStencilContentDefined.size(),
|
|
||||||
"Swapchain depth/stencil content index out of range");
|
|
||||||
m_depthStencilContentDefined[index] = defined;
|
|
||||||
}
|
|
||||||
|
|
||||||
void SwapchainObject::SetAllDepthStencilContentUndefined() {
|
|
||||||
for (SizeT i = 0; i < m_depthStencilContentDefined.size(); ++i) {
|
|
||||||
m_depthStencilContentDefined[i] = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
VkImage SwapchainObject::GetImage(Uint32 index) const {
|
VkImage SwapchainObject::GetImage(Uint32 index) const {
|
||||||
MOBILEGL_ASSERT(index < m_images.size(), "Swapchain image index out of range");
|
MOBILEGL_ASSERT(index < m_images.size(), "Swapchain image index out of range");
|
||||||
return m_images[index];
|
return m_images[index];
|
||||||
|
|||||||
@@ -35,9 +35,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
VkSwapchainKHR GetHandle() const { return m_swapchain; }
|
VkSwapchainKHR GetHandle() const { return m_swapchain; }
|
||||||
const VkSurfaceFormatKHR& GetSurfaceFormat() const { return m_surfaceFormat; }
|
const VkSurfaceFormatKHR& GetSurfaceFormat() const { return m_surfaceFormat; }
|
||||||
VkExtent2D GetExtent() const { return m_extent; }
|
VkExtent2D GetExtent() const { return m_extent; }
|
||||||
// Surface-space extent (before the pre-rotation quarter-turn swap) this swapchain was
|
|
||||||
// created from - the value to compare a freshly queried currentExtent against.
|
|
||||||
VkExtent2D GetSurfaceExtent() const { return m_surfaceExtent; }
|
|
||||||
VkSurfaceTransformFlagBitsKHR GetPreTransform() const { return m_preTransform; }
|
VkSurfaceTransformFlagBitsKHR GetPreTransform() const { return m_preTransform; }
|
||||||
const Vector<VkImage>& GetImages() const { return m_images; }
|
const Vector<VkImage>& GetImages() const { return m_images; }
|
||||||
const Vector<VkImageView>& GetImageViews() const { return m_imageViews; }
|
const Vector<VkImageView>& GetImageViews() const { return m_imageViews; }
|
||||||
@@ -52,21 +49,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
void SetImageLayout(Uint32 index, VkImageLayout layout);
|
void SetImageLayout(Uint32 index, VkImageLayout layout);
|
||||||
SizeT GetImageCount() const { return m_images.size(); }
|
SizeT GetImageCount() const { return m_images.size(); }
|
||||||
|
|
||||||
// EGL content-validity tracking for the default framebuffer. A color
|
|
||||||
// buffer's content is undefined once its image has been presented
|
|
||||||
// (EGL_BUFFER_DESTROYED swap behaviour, the implementation default),
|
|
||||||
// and every ancillary (depth/stencil) buffer's content is undefined
|
|
||||||
// after ANY swap regardless of swap behaviour (EGL 1.5 §3.10.1). The
|
|
||||||
// render-pass manager turns an undefined attachment's tile load into
|
|
||||||
// LOAD_OP_DONT_CARE. Flags start false (a fresh swapchain image holds
|
|
||||||
// garbage) and a render pass storing into an attachment sets it back
|
|
||||||
// to defined.
|
|
||||||
Bool IsImageContentDefined(Uint32 index) const;
|
|
||||||
void SetImageContentDefined(Uint32 index, Bool defined);
|
|
||||||
Bool IsDepthStencilContentDefined(Uint32 index) const;
|
|
||||||
void SetDepthStencilContentDefined(Uint32 index, Bool defined);
|
|
||||||
void SetAllDepthStencilContentUndefined();
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void CreateImageViews(VkDevice device);
|
void CreateImageViews(VkDevice device);
|
||||||
void CreateDepthStencilResources(VkDevice device, VkPhysicalDevice physicalDevice);
|
void CreateDepthStencilResources(VkDevice device, VkPhysicalDevice physicalDevice);
|
||||||
@@ -81,7 +63,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
VkSwapchainKHR m_swapchain = VK_NULL_HANDLE;
|
VkSwapchainKHR m_swapchain = VK_NULL_HANDLE;
|
||||||
VkSurfaceFormatKHR m_surfaceFormat{};
|
VkSurfaceFormatKHR m_surfaceFormat{};
|
||||||
VkExtent2D m_extent{};
|
VkExtent2D m_extent{};
|
||||||
VkExtent2D m_surfaceExtent{};
|
|
||||||
VkSurfaceTransformFlagBitsKHR m_preTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
|
VkSurfaceTransformFlagBitsKHR m_preTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
|
||||||
Vector<VkImage> m_images;
|
Vector<VkImage> m_images;
|
||||||
Vector<VkImageView> m_imageViews;
|
Vector<VkImageView> m_imageViews;
|
||||||
@@ -92,7 +73,5 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
Vector<VkDeviceMemory> m_depthStencilImageMemories;
|
Vector<VkDeviceMemory> m_depthStencilImageMemories;
|
||||||
Vector<VkImageView> m_depthStencilImageViews;
|
Vector<VkImageView> m_depthStencilImageViews;
|
||||||
Vector<VkImageLayout> m_depthStencilImageLayouts;
|
Vector<VkImageLayout> m_depthStencilImageLayouts;
|
||||||
Vector<Bool> m_imageContentDefined;
|
|
||||||
Vector<Bool> m_depthStencilContentDefined;
|
|
||||||
};
|
};
|
||||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||||
|
|||||||
@@ -18,7 +18,6 @@
|
|||||||
#include "MG_Util/Converters/MGToVk/TextureEnumConverter.h"
|
#include "MG_Util/Converters/MGToVk/TextureEnumConverter.h"
|
||||||
#include "MG_Util/Metrics/TextureMetrics.h"
|
#include "MG_Util/Metrics/TextureMetrics.h"
|
||||||
#include <Config.h>
|
#include <Config.h>
|
||||||
#include <algorithm>
|
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
@@ -205,7 +204,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
// The frame's descriptor sets are recycled above, so last frame's reuse target
|
// The frame's descriptor sets are recycled above, so last frame's reuse target
|
||||||
// is gone: start the per-draw descriptor-reuse cache fresh this frame.
|
// is gone: start the per-draw descriptor-reuse cache fresh this frame.
|
||||||
m_hasLastDescriptor = false;
|
m_hasLastDescriptor = false;
|
||||||
m_lastBindValid = false;
|
|
||||||
// Re-fingerprint the bound sampler set fresh this frame so any GL object address
|
// Re-fingerprint the bound sampler set fresh this frame so any GL object address
|
||||||
// reuse cannot outlive a single frame (see SamplerResolveMemo).
|
// reuse cannot outlive a single frame (see SamplerResolveMemo).
|
||||||
for (auto& memo : m_samplerResolveMemo) {
|
for (auto& memo : m_samplerResolveMemo) {
|
||||||
@@ -213,40 +211,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void UniformManager::OnDescriptorSetLayoutDestroyed(VkDescriptorSetLayout descriptorSetLayout) {
|
|
||||||
SizeT purgedSets = 0;
|
|
||||||
for (auto& frame : m_frames) {
|
|
||||||
const auto it = frame.descriptorSetCacheByLayout.find(descriptorSetLayout);
|
|
||||||
if (it == frame.descriptorSetCacheByLayout.end()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// Free the sets back to their pools and credit the bucket accounting, so
|
|
||||||
// program churn recycles pool capacity instead of abandoning the slots.
|
|
||||||
// GPU-safe: the layout only dies after >1024 idle frame boundaries, so no
|
|
||||||
// in-flight command buffer references these sets.
|
|
||||||
for (const auto& cached : it->second.sets) {
|
|
||||||
if (cached.set == VK_NULL_HANDLE) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
vkFreeDescriptorSets(m_device, cached.pool, 1, &cached.set);
|
|
||||||
const auto bucket = std::find_if(
|
|
||||||
frame.descriptorPools.begin(), frame.descriptorPools.end(),
|
|
||||||
[&cached](const DescriptorPoolBucket& candidate) { return candidate.handle == cached.pool; });
|
|
||||||
if (bucket != frame.descriptorPools.end() && bucket->allocatedSets > 0) {
|
|
||||||
--bucket->allocatedSets;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
purgedSets += it->second.sets.size();
|
|
||||||
frame.descriptorSetCacheByLayout.erase(it);
|
|
||||||
}
|
|
||||||
if (purgedSets > 0) {
|
|
||||||
// The per-draw reuse memo folds the layout handle into its signature; drop
|
|
||||||
// it so a recycled handle value cannot revive a purged set mid-frame.
|
|
||||||
m_hasLastDescriptor = false;
|
|
||||||
MGLOG_D("UniformDescriptorBinder: freed %zu descriptor sets for destroyed layout", purgedSets);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Bool UniformManager::ResolveSamplerDescriptor(VkCommandBuffer commandBuffer,
|
Bool UniformManager::ResolveSamplerDescriptor(VkCommandBuffer commandBuffer,
|
||||||
const MG_State::GLState::ProgramObject& program,
|
const MG_State::GLState::ProgramObject& program,
|
||||||
const ProgramFactory::VkProgramObject& programObj,
|
const ProgramFactory::VkProgramObject& programObj,
|
||||||
@@ -266,24 +230,12 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
const auto& samplerOverride = textureUnit.GetSamplerObject();
|
const auto& samplerOverride = textureUnit.GetSamplerObject();
|
||||||
const auto preferredTarget = programObj.samplerTextureTargetByBinding[binding];
|
const auto preferredTarget = programObj.samplerTextureTargetByBinding[binding];
|
||||||
SharedPtr<MG_State::GLState::ITextureObject> fallbackHolder;
|
SharedPtr<MG_State::GLState::ITextureObject> fallbackHolder;
|
||||||
// A texture that fails the completeness rules for the filter in effect reads
|
|
||||||
// (0, 0, 0, 1), which is exactly what the fallback texture holds - so it takes the
|
|
||||||
// same route as a sampler with nothing bound.
|
|
||||||
if (texture != nullptr &&
|
|
||||||
MG_State::GLState::SamplesAsIncompleteTexture(
|
|
||||||
texture, samplerOverride ? samplerOverride.get() : texture->GetSamplerObject().get())) {
|
|
||||||
texture = nullptr;
|
|
||||||
}
|
|
||||||
if (texture == nullptr) {
|
if (texture == nullptr) {
|
||||||
fallbackHolder = GetFallbackTexture(preferredTarget);
|
fallbackHolder = GetFallbackTexture(preferredTarget);
|
||||||
texture = fallbackHolder.get();
|
texture = fallbackHolder.get();
|
||||||
if (texture == nullptr) {
|
MOBILEGL_ASSERT(texture != nullptr,
|
||||||
MGLOG_E("ResolveSamplerDescriptor: no fallback texture available for binding=%u ('%s') "
|
"ResolveSamplerDescriptor: no fallback texture available for binding=%u location=%d unit=%d target=%d",
|
||||||
"location=%d unit=%d target=%d",
|
binding, location, unit, static_cast<Int>(preferredTarget));
|
||||||
binding, programObj.samplerNameByBinding[binding].c_str(), location, unit,
|
|
||||||
static_cast<Int>(preferredTarget));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
MGLOG_W(
|
MGLOG_W(
|
||||||
"ResolveSamplerDescriptor: using fallback texture for unbound sampler binding=%u ('%s') location=%d unit=%d target=%d",
|
"ResolveSamplerDescriptor: using fallback texture for unbound sampler binding=%u ('%s') location=%d unit=%d target=%d",
|
||||||
binding, programObj.samplerNameByBinding[binding].c_str(), location, unit,
|
binding, programObj.samplerNameByBinding[binding].c_str(), location, unit,
|
||||||
@@ -398,28 +350,24 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
const Uint16 samplerVersion = samplerToUse->GetVersion();
|
const Uint16 samplerVersion = samplerToUse->GetVersion();
|
||||||
const Uint64 textureLifetimeId = texture->GetLifetimeId();
|
const Uint64 textureLifetimeId = texture->GetLifetimeId();
|
||||||
const Uint16 textureParamsVersion = texture->GetTextureParamsVersion();
|
const Uint16 textureParamsVersion = texture->GetTextureParamsVersion();
|
||||||
// The sampler's LOD clamp depends on how many levels the sampled view exposes, and that
|
|
||||||
// follows uploads as well as GL parameters - so it belongs in the memo key too.
|
|
||||||
const Uint32 viewLevelCount = resource->sampledLevelCount;
|
|
||||||
if (memo.valid && memo.samplerLifetimeId == samplerLifetimeId && memo.samplerVersion == samplerVersion &&
|
if (memo.valid && memo.samplerLifetimeId == samplerLifetimeId && memo.samplerVersion == samplerVersion &&
|
||||||
memo.textureLifetimeId == textureLifetimeId && memo.textureParamsVersion == textureParamsVersion &&
|
memo.textureLifetimeId == textureLifetimeId && memo.textureParamsVersion == textureParamsVersion &&
|
||||||
memo.forceNearestFiltering == forceNearestFiltering && memo.viewLevelCount == viewLevelCount) {
|
memo.forceNearestFiltering == forceNearestFiltering) {
|
||||||
resolvedSampler = memo.sampler;
|
resolvedSampler = memo.sampler;
|
||||||
} else {
|
} else {
|
||||||
resolvedSampler = m_samplerManager->GetOrCreateSampler(*samplerToUse, *texture,
|
resolvedSampler =
|
||||||
forceNearestFiltering, viewLevelCount);
|
m_samplerManager->GetOrCreateSampler(*samplerToUse, *texture, forceNearestFiltering);
|
||||||
memo.samplerLifetimeId = samplerLifetimeId;
|
memo.samplerLifetimeId = samplerLifetimeId;
|
||||||
memo.samplerVersion = samplerVersion;
|
memo.samplerVersion = samplerVersion;
|
||||||
memo.textureLifetimeId = textureLifetimeId;
|
memo.textureLifetimeId = textureLifetimeId;
|
||||||
memo.textureParamsVersion = textureParamsVersion;
|
memo.textureParamsVersion = textureParamsVersion;
|
||||||
memo.forceNearestFiltering = forceNearestFiltering;
|
memo.forceNearestFiltering = forceNearestFiltering;
|
||||||
memo.viewLevelCount = viewLevelCount;
|
|
||||||
memo.sampler = resolvedSampler;
|
memo.sampler = resolvedSampler;
|
||||||
memo.valid = true;
|
memo.valid = true;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
resolvedSampler = m_samplerManager->GetOrCreateSampler(*samplerToUse, *texture, forceNearestFiltering,
|
resolvedSampler =
|
||||||
resource->sampledLevelCount);
|
m_samplerManager->GetOrCreateSampler(*samplerToUse, *texture, forceNearestFiltering);
|
||||||
}
|
}
|
||||||
outImageInfo = {
|
outImageInfo = {
|
||||||
.sampler = resolvedSampler,
|
.sampler = resolvedSampler,
|
||||||
@@ -460,48 +408,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
return outImageInfo.sampler != VK_NULL_HANDLE;
|
return outImageInfo.sampler != VK_NULL_HANDLE;
|
||||||
}
|
}
|
||||||
|
|
||||||
Bool UniformManager::ProgramSamplesOnlySingleLevelTextures(
|
|
||||||
const MG_State::GLState::ProgramObject& program, const ProgramFactory::VkProgramObject& programObj) {
|
|
||||||
Bool sawSampler = false;
|
|
||||||
for (Uint32 binding = 0; binding < programObj.bindingKinds.size(); ++binding) {
|
|
||||||
if (programObj.bindingKinds[binding] != ProgramFactory::DescriptorBindingKind::CombinedImageSampler) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const auto* texture = ResolveSamplerTextureRaw(program, programObj, binding);
|
|
||||||
if (texture == nullptr) return false;
|
|
||||||
const auto& levelRange = texture->GetLevelRange();
|
|
||||||
if (levelRange.x() != levelRange.y()) return false;
|
|
||||||
|
|
||||||
// An explicit-LOD sample is a single filtered tap, so it also gives up anisotropic
|
|
||||||
// filtering - which a single-level view can still have. Resolve the sampler exactly
|
|
||||||
// the way ResolveSamplerDescriptor does and bail if anisotropy would apply.
|
|
||||||
const Int location = programObj.samplerUniformLocationByBinding[binding];
|
|
||||||
const Int unit = ResolveSamplerUnitIndex(program, location, binding);
|
|
||||||
const auto& samplerOverride = MG_State::pGLContext->GetTextureUnitObject(unit).GetSamplerObject();
|
|
||||||
const auto* effectiveSampler =
|
|
||||||
samplerOverride ? samplerOverride.get() : texture->GetSamplerObject().get();
|
|
||||||
if (effectiveSampler == nullptr) return false;
|
|
||||||
if (effectiveSampler->GetMaxAnisotropy() > 1.0f &&
|
|
||||||
effectiveSampler->GetMinFilter() == SamplerFilterMode::Linear &&
|
|
||||||
effectiveSampler->GetMagFilter() == SamplerFilterMode::Linear) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// An explicit LOD 0 makes lambda exactly 0, which is the magnification side of the
|
|
||||||
// min/mag decision. That only matches the implicit form when lambda could not have been
|
|
||||||
// positive anyway (the LOD clamp already pins it at or below 0), or when the two
|
|
||||||
// filters are the same and the choice cannot be observed.
|
|
||||||
const Float effectiveMaxLod = effectiveSampler->GetMipmapMode() == SamplerMipmapMode::None
|
|
||||||
? 0.0f
|
|
||||||
: effectiveSampler->GetMaxLod();
|
|
||||||
if (effectiveMaxLod > 0.0f && effectiveSampler->GetMinFilter() != effectiveSampler->GetMagFilter()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
sawSampler = true;
|
|
||||||
}
|
|
||||||
return sawSampler;
|
|
||||||
}
|
|
||||||
|
|
||||||
Bool UniformManager::ResolveSamplerTexture(const MG_State::GLState::ProgramObject& program,
|
Bool UniformManager::ResolveSamplerTexture(const MG_State::GLState::ProgramObject& program,
|
||||||
const ProgramFactory::VkProgramObject& programObj, Uint32 binding,
|
const ProgramFactory::VkProgramObject& programObj, Uint32 binding,
|
||||||
SharedPtr<MG_State::GLState::ITextureObject>& outTexture) {
|
SharedPtr<MG_State::GLState::ITextureObject>& outTexture) {
|
||||||
@@ -604,11 +510,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
|
|
||||||
const VkDeviceSize texelSize =
|
const VkDeviceSize texelSize =
|
||||||
static_cast<VkDeviceSize>(MG_Util::GetSizedInternalFormatSizeInBytes(internalFormat));
|
static_cast<VkDeviceSize>(MG_Util::GetSizedInternalFormatSizeInBytes(internalFormat));
|
||||||
// glTextureBufferRange addresses a window of the buffer, not all of it; the whole-buffer
|
VkDeviceSize viewRange = slice.size;
|
||||||
// forms report the buffer's current size here, so both go through the same clamp.
|
|
||||||
const VkDeviceSize rangeOffset = static_cast<VkDeviceSize>(textureBuffer->GetBufferRangeOffset());
|
|
||||||
const VkDeviceSize rangeSize = static_cast<VkDeviceSize>(textureBuffer->GetBufferRangeSizeInBytes());
|
|
||||||
VkDeviceSize viewRange = std::min(rangeSize, slice.size > rangeOffset ? slice.size - rangeOffset : 0);
|
|
||||||
if (texelSize > 0) {
|
if (texelSize > 0) {
|
||||||
viewRange = (viewRange / texelSize) * texelSize;
|
viewRange = (viewRange / texelSize) * texelSize;
|
||||||
}
|
}
|
||||||
@@ -621,7 +523,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
viewInfo.sType = VK_STRUCTURE_TYPE_BUFFER_VIEW_CREATE_INFO;
|
viewInfo.sType = VK_STRUCTURE_TYPE_BUFFER_VIEW_CREATE_INFO;
|
||||||
viewInfo.buffer = slice.buffer;
|
viewInfo.buffer = slice.buffer;
|
||||||
viewInfo.format = vkFormat;
|
viewInfo.format = vkFormat;
|
||||||
viewInfo.offset = slice.offset + rangeOffset;
|
viewInfo.offset = slice.offset;
|
||||||
viewInfo.range = viewRange;
|
viewInfo.range = viewRange;
|
||||||
|
|
||||||
VkBufferView bufferView = VK_NULL_HANDLE;
|
VkBufferView bufferView = VK_NULL_HANDLE;
|
||||||
@@ -666,14 +568,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// The shader may write this buffer, and those writes land in GPU memory behind the
|
|
||||||
// frontend's CPU shadow - which is what MapBuffer and GetBufferSubData read.
|
|
||||||
// Host-visible coherent GPU residency makes the shadow BE that memory, so the
|
|
||||||
// results are visible without a readback path, exactly as for a capture buffer.
|
|
||||||
bufferObject->EnsureGpuResidentStorage();
|
|
||||||
// ... and the read that follows has to wait for this draw or dispatch to retire.
|
|
||||||
bufferObject->MarkGpuWritten();
|
|
||||||
|
|
||||||
BufferSlice slice{};
|
BufferSlice slice{};
|
||||||
if (!m_bufferManager->AcquireResidentSlice(BufferKind::ShaderStorage, bufferObject, slice) || !slice.IsValid()) {
|
if (!m_bufferManager->AcquireResidentSlice(BufferKind::ShaderStorage, bufferObject, slice) || !slice.IsValid()) {
|
||||||
MGLOG_E("ResolveStorageBufferDescriptor: failed to sync GL buffer %u for block '%s'",
|
MGLOG_E("ResolveStorageBufferDescriptor: failed to sync GL buffer %u for block '%s'",
|
||||||
@@ -776,27 +670,15 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SharedPtr<MG_State::GLState::ITextureObject> UniformManager::GetFallbackTexture(TextureTarget target) const {
|
SharedPtr<MG_State::GLState::ITextureObject> UniformManager::GetFallbackTexture(TextureTarget target) const {
|
||||||
// The fallback is a single-sampled 2D image, so it can only stand in for a sampler that
|
MOBILEGL_ASSERT(target == TextureTarget::Texture2D || target == TextureTarget::TextureRectangle,
|
||||||
// would accept one. A multisample sampler in particular cannot: its descriptor demands a
|
"UniformManager::GetFallbackTexture: unsupported fallback target=%d",
|
||||||
// multisample view, and handing it this one is invalid Vulkan, not a degraded picture.
|
static_cast<Int>(target));
|
||||||
// Report that there is no fallback and let the caller decline the draw - aborting the
|
|
||||||
// process over an unbound sampler is never the right answer.
|
|
||||||
if (target != TextureTarget::Texture2D && target != TextureTarget::TextureRectangle) {
|
|
||||||
MGLOG_E("UniformManager::GetFallbackTexture: no fallback exists for target=%d",
|
|
||||||
static_cast<Int>(target));
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_fallbackTexture2D == nullptr) {
|
if (m_fallbackTexture2D == nullptr) {
|
||||||
auto fallbackTexture = MakeShared<MG_State::GLState::TextureObject2D>(kFallbackTexture2DExternalIndex);
|
auto fallbackTexture = MakeShared<MG_State::GLState::TextureObject2D>(kFallbackTexture2DExternalIndex);
|
||||||
fallbackTexture->SetInternalFormat(TextureInternalFormat::RGBA8);
|
fallbackTexture->SetInternalFormat(TextureInternalFormat::RGBA8);
|
||||||
fallbackTexture->AllocateStorage(TextureUploadTarget::Texture2D, 0,
|
fallbackTexture->AllocateStorage(TextureUploadTarget::Texture2D, 0,
|
||||||
{.texelSize = {1, 1, 1}, .byteSize = 4});
|
{.texelSize = {1, 1, 1}, .byteSize = 4});
|
||||||
// (0, 0, 0, 1): what GL reads from a texture that is not complete, and the only
|
|
||||||
// sensible answer for a sampler with nothing bound.
|
|
||||||
static Uint8 kOpaqueBlackTexel[4] = {0, 0, 0, 255};
|
|
||||||
fallbackTexture->UpdateMipmapSubData(TextureUploadTarget::Texture2D, 0,
|
|
||||||
{kOpaqueBlackTexel, sizeof(kOpaqueBlackTexel)});
|
|
||||||
fallbackTexture->MarkStorageDirty(TextureUploadTarget::Texture2D, 0, true);
|
fallbackTexture->MarkStorageDirty(TextureUploadTarget::Texture2D, 0, true);
|
||||||
m_fallbackTexture2D = fallbackTexture;
|
m_fallbackTexture2D = fallbackTexture;
|
||||||
}
|
}
|
||||||
@@ -1008,17 +890,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sized from what a real program declares, not from the 256-binding cap. A GL program's
|
const Uint64 descriptorCount64 = static_cast<Uint64>(maxSets) * static_cast<Uint64>(m_maxBindings);
|
||||||
// single descriptor set holds the bindings shader reflection found - typically 2 to 8 - so
|
|
||||||
// scaling by m_maxBindings declared 5 x 64 x 256 = 81,920 descriptors per pool and 245,760
|
|
||||||
// across the three frames in flight, which drivers that reserve backing store proportional
|
|
||||||
// to the declared count pay for at init. An outlier program is absorbed by the existing
|
|
||||||
// VK_ERROR_OUT_OF_POOL_MEMORY -> GrowFrameDescriptorPool path: pool sizes are aggregate
|
|
||||||
// budgets rather than per-set limits, and vkAllocateDescriptorSets is spec-required to
|
|
||||||
// report that error rather than fail hard.
|
|
||||||
static constexpr Uint32 kEstimatedBindingsPerSet = 8;
|
|
||||||
const Uint64 descriptorCount64 =
|
|
||||||
static_cast<Uint64>(maxSets) * static_cast<Uint64>(std::min(m_maxBindings, kEstimatedBindingsPerSet));
|
|
||||||
if (descriptorCount64 > static_cast<Uint64>(std::numeric_limits<Uint32>::max())) {
|
if (descriptorCount64 > static_cast<Uint64>(std::numeric_limits<Uint32>::max())) {
|
||||||
MGLOG_E("UniformDescriptorBinder::CreateDescriptorPool failed: descriptorCount overflow");
|
MGLOG_E("UniformDescriptorBinder::CreateDescriptorPool failed: descriptorCount overflow");
|
||||||
return false;
|
return false;
|
||||||
@@ -1039,11 +911,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
|
|
||||||
VkDescriptorPoolCreateInfo poolInfo{};
|
VkDescriptorPoolCreateInfo poolInfo{};
|
||||||
poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
|
poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
|
||||||
// FREE_DESCRIPTOR_SET_BIT lets a destroyed layout's cached sets be freed back
|
|
||||||
// (OnDescriptorSetLayoutDestroyed) so program churn recycles pool capacity.
|
|
||||||
// The cost is on set allocation only, which happens when a layout's per-frame
|
|
||||||
// cache grows - never on the per-draw reuse path.
|
|
||||||
poolInfo.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT;
|
|
||||||
poolInfo.maxSets = maxSets;
|
poolInfo.maxSets = maxSets;
|
||||||
poolInfo.poolSizeCount = static_cast<Uint32>(std::size(poolSizes));
|
poolInfo.poolSizeCount = static_cast<Uint32>(std::size(poolSizes));
|
||||||
poolInfo.pPoolSizes = poolSizes;
|
poolInfo.pPoolSizes = poolSizes;
|
||||||
@@ -1123,7 +990,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
auto& frame = m_frames[frameIndex];
|
auto& frame = m_frames[frameIndex];
|
||||||
auto& cache = frame.descriptorSetCacheByLayout[programObj.descriptorSetLayout];
|
auto& cache = frame.descriptorSetCacheByLayout[programObj.descriptorSetLayout];
|
||||||
if (cache.cursor < cache.sets.size()) {
|
if (cache.cursor < cache.sets.size()) {
|
||||||
outDescriptorSet = cache.sets[cache.cursor++].set;
|
outDescriptorSet = cache.sets[cache.cursor++];
|
||||||
} else {
|
} else {
|
||||||
VkResult allocResult = AllocateDescriptorSetsFromActivePool(frameIndex, programObj, outDescriptorSet);
|
VkResult allocResult = AllocateDescriptorSetsFromActivePool(frameIndex, programObj, outDescriptorSet);
|
||||||
if (allocResult == VK_ERROR_OUT_OF_POOL_MEMORY || allocResult == VK_ERROR_FRAGMENTED_POOL) {
|
if (allocResult == VK_ERROR_OUT_OF_POOL_MEMORY || allocResult == VK_ERROR_FRAGMENTED_POOL) {
|
||||||
@@ -1137,9 +1004,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
return allocResult;
|
return allocResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
// The successful allocation came from the bucket the alloc helper left
|
cache.sets.push_back(outDescriptorSet);
|
||||||
// active; record it so a layout-destroyed purge can free the set back.
|
|
||||||
cache.sets.push_back({outDescriptorSet, frame.descriptorPools[frame.activeDescriptorPoolIndex].handle});
|
|
||||||
++cache.cursor;
|
++cache.cursor;
|
||||||
MGLOG_D("UniformDescriptorBinder: cached descriptor set count for frame=%u grew to %zu", frameIndex,
|
MGLOG_D("UniformDescriptorBinder: cached descriptor set count for frame=%u grew to %zu", frameIndex,
|
||||||
cache.sets.size());
|
cache.sets.size());
|
||||||
@@ -1197,13 +1062,13 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
texelBufferViews.reserve(m_maxBindings);
|
texelBufferViews.reserve(m_maxBindings);
|
||||||
dynamicOffsets.reserve(programObj.dynamicBindings.size() + uboArrayExtra);
|
dynamicOffsets.reserve(programObj.dynamicBindings.size() + uboArrayExtra);
|
||||||
|
|
||||||
// Iterate only the bindings this program declares. The old walk covered all 256 slots of
|
const Uint32 bindingCount =
|
||||||
// bindingKinds on every draw to find the 1-8 a real program uses.
|
std::min<Uint32>(m_maxBindings, static_cast<Uint32>(programObj.bindingKinds.size()));
|
||||||
for (const Uint32 binding : programObj.activeBindings) {
|
for (Uint32 binding = 0; binding < bindingCount; ++binding) {
|
||||||
if (binding >= m_maxBindings) {
|
|
||||||
break; // ascending, so nothing past the cap can follow
|
|
||||||
}
|
|
||||||
const auto kind = programObj.bindingKinds[binding];
|
const auto kind = programObj.bindingKinds[binding];
|
||||||
|
if (kind == ProgramFactory::DescriptorBindingKind::None) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
VkWriteDescriptorSet write{};
|
VkWriteDescriptorSet write{};
|
||||||
write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
|
write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
|
||||||
@@ -1237,47 +1102,16 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
bufferInfo.range = ubo.range;
|
bufferInfo.range = ubo.range;
|
||||||
dynOffset = static_cast<Uint32>(ubo.dynamicOffset);
|
dynOffset = static_cast<Uint32>(ubo.dynamicOffset);
|
||||||
} else {
|
} else {
|
||||||
// Global-UBO slice reuse (see GlobalUboSliceMemo): unchanged
|
BufferSlice slice{};
|
||||||
// uniform bytes re-use the slice already uploaded this frame.
|
if (!m_bufferManager->UploadTransient(BufferKind::Uniform, frameIndex, ubo.payload,
|
||||||
const Bool isGlobalUbo =
|
ubo.payloadSize, m_minDynamicOffsetAlignment, slice)) {
|
||||||
programObj.globalUboBinding == static_cast<Int>(binding) && element == 0;
|
MOBILEGL_ASSERT(false, "UniformDescriptorBinder::BindProgramUniformBuffers failed: UBO upload failed on binding %u element %u",
|
||||||
const Uint64 uboFrameSerial = m_bufferManager->GetFrameSerial();
|
binding, element);
|
||||||
const Uint64 uboProgramLifetimeId = program.GetLifetimeId();
|
return false;
|
||||||
const Uint32 uboContentVersion = program.GetUBOContentVersion();
|
|
||||||
Bool reusedSlice = false;
|
|
||||||
if (isGlobalUbo) {
|
|
||||||
for (const auto& memo : m_globalUboMemo) {
|
|
||||||
if (memo.buffer != VK_NULL_HANDLE &&
|
|
||||||
memo.programLifetimeId == uboProgramLifetimeId &&
|
|
||||||
memo.frameSerial == uboFrameSerial &&
|
|
||||||
memo.uboContentVersion == uboContentVersion &&
|
|
||||||
memo.range == static_cast<VkDeviceSize>(ubo.payloadSize)) {
|
|
||||||
bufferInfo.buffer = memo.buffer;
|
|
||||||
bufferInfo.range = memo.range;
|
|
||||||
dynOffset = static_cast<Uint32>(memo.offset);
|
|
||||||
reusedSlice = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!reusedSlice) {
|
|
||||||
BufferSlice slice{};
|
|
||||||
if (!m_bufferManager->UploadTransient(BufferKind::Uniform, frameIndex, ubo.payload,
|
|
||||||
ubo.payloadSize, m_minDynamicOffsetAlignment, slice)) {
|
|
||||||
MOBILEGL_ASSERT(false, "UniformDescriptorBinder::BindProgramUniformBuffers failed: UBO upload failed on binding %u element %u",
|
|
||||||
binding, element);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
bufferInfo.buffer = slice.buffer;
|
|
||||||
bufferInfo.range = ubo.payloadSize;
|
|
||||||
dynOffset = static_cast<Uint32>(slice.offset);
|
|
||||||
if (isGlobalUbo) {
|
|
||||||
m_globalUboMemo[m_globalUboMemoNext] = GlobalUboSliceMemo{
|
|
||||||
uboProgramLifetimeId, uboFrameSerial, uboContentVersion,
|
|
||||||
slice.buffer, slice.offset, static_cast<VkDeviceSize>(ubo.payloadSize)};
|
|
||||||
m_globalUboMemoNext = (m_globalUboMemoNext + 1) % kGlobalUboMemoSize;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
bufferInfo.buffer = slice.buffer;
|
||||||
|
bufferInfo.range = ubo.payloadSize;
|
||||||
|
dynOffset = static_cast<Uint32>(slice.offset);
|
||||||
}
|
}
|
||||||
bufferInfos.push_back(bufferInfo);
|
bufferInfos.push_back(bufferInfo);
|
||||||
// Dynamic offsets are consumed in binding order, then array element order,
|
// Dynamic offsets are consumed in binding order, then array element order,
|
||||||
@@ -1416,34 +1250,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
m_hasLastDescriptor = cacheable;
|
m_hasLastDescriptor = cacheable;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Skip the driver call when this exact binding is already live on the
|
vkCmdBindDescriptorSets(commandBuffer, bindPoint, programObj.pipelineLayout, 0, 1,
|
||||||
// command buffer (see the bind-dedup shadow in the header).
|
&descriptorSet, static_cast<Uint32>(dynamicOffsets.size()), dynamicOffsets.data());
|
||||||
const Uint32 offsetCount = static_cast<Uint32>(dynamicOffsets.size());
|
|
||||||
Bool identicalBind = m_lastBindValid && m_lastBindSet == descriptorSet &&
|
|
||||||
m_lastBindLayout == programObj.pipelineLayout && m_lastBindPoint == bindPoint &&
|
|
||||||
m_lastBindOffsetCount == offsetCount && offsetCount <= kMaxShadowedDynamicOffsets;
|
|
||||||
if (identicalBind) {
|
|
||||||
for (Uint32 i = 0; i < offsetCount; ++i) {
|
|
||||||
if (m_lastBindOffsets[i] != dynamicOffsets[i]) {
|
|
||||||
identicalBind = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!identicalBind) {
|
|
||||||
vkCmdBindDescriptorSets(commandBuffer, bindPoint, programObj.pipelineLayout, 0, 1,
|
|
||||||
&descriptorSet, offsetCount, dynamicOffsets.data());
|
|
||||||
if (offsetCount <= kMaxShadowedDynamicOffsets) {
|
|
||||||
m_lastBindValid = true;
|
|
||||||
m_lastBindSet = descriptorSet;
|
|
||||||
m_lastBindLayout = programObj.pipelineLayout;
|
|
||||||
m_lastBindPoint = bindPoint;
|
|
||||||
m_lastBindOffsetCount = offsetCount;
|
|
||||||
std::copy_n(dynamicOffsets.data(), offsetCount, m_lastBindOffsets);
|
|
||||||
} else {
|
|
||||||
m_lastBindValid = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||||
|
|||||||
@@ -39,20 +39,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
void Shutdown();
|
void Shutdown();
|
||||||
|
|
||||||
void BeginFrame(Uint32 frameIndex);
|
void BeginFrame(Uint32 frameIndex);
|
||||||
// A command buffer (re)began recording: descriptor bindings recorded into
|
|
||||||
// the previous buffer do not carry over, so drop the bind-dedup shadow.
|
|
||||||
void OnCommandBufferBoundary() { m_lastBindValid = false; }
|
|
||||||
// A ProgramFactory eviction just destroyed this layout: purge every frame
|
|
||||||
// slot's cached descriptor sets for it, so a recycled handle value can never
|
|
||||||
// stale-hit sets written for the dead layout's bindings. The sets are
|
|
||||||
// vkFreeDescriptorSets'd back to their pools (created with
|
|
||||||
// FREE_DESCRIPTOR_SET_BIT) and the pool accounting is credited, so program
|
|
||||||
// churn recycles pool capacity instead of abandoning it. GPU-safe: the layout
|
|
||||||
// only dies after >1024 idle frame boundaries, so no in-flight command buffer
|
|
||||||
// references its sets. This is the only eviction path for the per-layout
|
|
||||||
// caches - a live layout's entry must never be purged (its sets would be
|
|
||||||
// unreachable pool slots), so there is deliberately no age-based sweep here.
|
|
||||||
void OnDescriptorSetLayoutDestroyed(VkDescriptorSetLayout descriptorSetLayout);
|
|
||||||
Bool CollectSampledTextures(const MG_State::GLState::ProgramObject& program,
|
Bool CollectSampledTextures(const MG_State::GLState::ProgramObject& program,
|
||||||
const ProgramFactory::VkProgramObject& programObj,
|
const ProgramFactory::VkProgramObject& programObj,
|
||||||
Vector<MG_State::GLState::ITextureObject*>& outTextures);
|
Vector<MG_State::GLState::ITextureObject*>& outTextures);
|
||||||
@@ -72,16 +58,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
static VkFormat ResolveStorageImageViewFormat(VkFormat reflectedFormat, GLenum bindingFormat,
|
static VkFormat ResolveStorageImageViewFormat(VkFormat reflectedFormat, GLenum bindingFormat,
|
||||||
VkFormat resourceFormat, Bool useBindingFormat);
|
VkFormat resourceFormat, Bool useBindingFormat);
|
||||||
|
|
||||||
// True when the program reads at least one sampler and every one of them is bound to a
|
|
||||||
// texture whose GL level range is a single level. Such a sampler resolves to
|
|
||||||
// minLod = maxLod = 0 (see VkSamplerManager::GetOrCreateSampler), so an implicit-LOD sample
|
|
||||||
// and an explicit LOD 0 sample must read the same texel - which is what makes the
|
|
||||||
// ExplicitLod0Sampling SPIR-V rewrite safe to request. Deliberately conservative: it reads
|
|
||||||
// only GL state, so a texture that ends up single-level for another reason (one uploaded
|
|
||||||
// level under a wide level range) merely misses the rewrite.
|
|
||||||
static Bool ProgramSamplesOnlySingleLevelTextures(const MG_State::GLState::ProgramObject& program,
|
|
||||||
const ProgramFactory::VkProgramObject& programObj);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct DescriptorPoolBucket {
|
struct DescriptorPoolBucket {
|
||||||
VkDescriptorPool handle = VK_NULL_HANDLE;
|
VkDescriptorPool handle = VK_NULL_HANDLE;
|
||||||
@@ -89,16 +65,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
Uint32 allocatedSets = 0;
|
Uint32 allocatedSets = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
// A cached descriptor set together with the pool it was allocated from, so a
|
|
||||||
// layout-destroyed purge can vkFreeDescriptorSets it back and credit the
|
|
||||||
// owning bucket's accounting.
|
|
||||||
struct CachedDescriptorSet {
|
|
||||||
VkDescriptorSet set = VK_NULL_HANDLE;
|
|
||||||
VkDescriptorPool pool = VK_NULL_HANDLE;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct DescriptorSetCacheEntry {
|
struct DescriptorSetCacheEntry {
|
||||||
Vector<CachedDescriptorSet> sets;
|
Vector<VkDescriptorSet> sets;
|
||||||
Uint32 cursor = 0;
|
Uint32 cursor = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -188,35 +156,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
Uint64 m_lastDescriptorSignature = 0;
|
Uint64 m_lastDescriptorSignature = 0;
|
||||||
Bool m_hasLastDescriptor = false;
|
Bool m_hasLastDescriptor = false;
|
||||||
|
|
||||||
// vkCmdBindDescriptorSets dedup: consecutive draws with a static uniform
|
|
||||||
// block resolve to the same set AND the same dynamic offsets, so the
|
|
||||||
// driver call can be skipped outright. Command-buffer-scope state; reset
|
|
||||||
// via OnCommandBufferBoundary whenever a recording (re)begins. Keyed on
|
|
||||||
// layout+bind point, so a pipeline-layout switch always rebinds.
|
|
||||||
static constexpr Uint32 kMaxShadowedDynamicOffsets = 8;
|
|
||||||
Bool m_lastBindValid = false;
|
|
||||||
VkDescriptorSet m_lastBindSet = VK_NULL_HANDLE;
|
|
||||||
VkPipelineLayout m_lastBindLayout = VK_NULL_HANDLE;
|
|
||||||
VkPipelineBindPoint m_lastBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
|
|
||||||
Uint32 m_lastBindOffsetCount = 0;
|
|
||||||
Uint32 m_lastBindOffsets[kMaxShadowedDynamicOffsets] = {};
|
|
||||||
|
|
||||||
// Global-UBO transient-slice reuse: MC leaves the default uniform block
|
|
||||||
// untouched across long GUI/terrain runs, so the per-draw re-upload of
|
|
||||||
// the same bytes can reuse the slice uploaded earlier THIS frame (frame
|
|
||||||
// serial guards arena recycling; the content version guards writes).
|
|
||||||
struct GlobalUboSliceMemo {
|
|
||||||
Uint64 programLifetimeId = 0;
|
|
||||||
Uint64 frameSerial = 0;
|
|
||||||
Uint32 uboContentVersion = 0;
|
|
||||||
VkBuffer buffer = VK_NULL_HANDLE;
|
|
||||||
VkDeviceSize offset = 0;
|
|
||||||
VkDeviceSize range = 0;
|
|
||||||
};
|
|
||||||
static constexpr Uint32 kGlobalUboMemoSize = 4;
|
|
||||||
GlobalUboSliceMemo m_globalUboMemo[kGlobalUboMemoSize];
|
|
||||||
Uint32 m_globalUboMemoNext = 0;
|
|
||||||
|
|
||||||
// Per-binding fast path over VkSamplerManager's content-hashed sampler cache, which
|
// Per-binding fast path over VkSamplerManager's content-hashed sampler cache, which
|
||||||
// stays the source of truth: its key hashes all sampler+texture state, so two distinct
|
// stays the source of truth: its key hashes all sampler+texture state, so two distinct
|
||||||
// sampler objects with identical state still resolve to one VkSampler. This memo only
|
// sampler objects with identical state still resolve to one VkSampler. This memo only
|
||||||
@@ -233,7 +172,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
Uint64 samplerLifetimeId = 0;
|
Uint64 samplerLifetimeId = 0;
|
||||||
Uint64 textureLifetimeId = 0;
|
Uint64 textureLifetimeId = 0;
|
||||||
VkSampler sampler = VK_NULL_HANDLE;
|
VkSampler sampler = VK_NULL_HANDLE;
|
||||||
Uint32 viewLevelCount = 0;
|
|
||||||
Uint16 samplerVersion = 0;
|
Uint16 samplerVersion = 0;
|
||||||
Uint16 textureParamsVersion = 0;
|
Uint16 textureParamsVersion = 0;
|
||||||
Bool forceNearestFiltering = false;
|
Bool forceNearestFiltering = false;
|
||||||
|
|||||||
@@ -29,17 +29,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
XXHASH_VERIFY(XXH64_update(m_hashState, &attr.Stride, sizeof(attr.Stride)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &attr.Stride, sizeof(attr.Stride)));
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState, &attr.Offset, sizeof(attr.Offset)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &attr.Offset, sizeof(attr.Offset)));
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState, &attr.IsInteger, sizeof(attr.IsInteger)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &attr.IsInteger, sizeof(attr.IsInteger)));
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState, &attr.IsLong, sizeof(attr.IsLong)));
|
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState, &attr.IsBgra, sizeof(attr.IsBgra)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &attr.IsBgra, sizeof(attr.IsBgra)));
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState, &attr.Divisor, sizeof(attr.Divisor)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &attr.Divisor, sizeof(attr.Divisor)));
|
||||||
|
|
||||||
// The buffer's heap address is an identity component of the key: a freed
|
|
||||||
// buffer's reused address can alias an old cache entry, but only under a
|
|
||||||
// byte-identical attribute layout - and the entry payload is a pure function
|
|
||||||
// of the hashed inputs, with the draw path re-resolving bindingBufferKeys
|
|
||||||
// against the live VAO attribute pointers, so an aliased hit returns exactly
|
|
||||||
// what a rebuild would. Address drift only grows the map; the OnFrameBoundary
|
|
||||||
// aging sweep bounds that.
|
|
||||||
const SizeT bufferKey = reinterpret_cast<SizeT>(attr.Buffer.get());
|
const SizeT bufferKey = reinterpret_cast<SizeT>(attr.Buffer.get());
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState, &bufferKey, sizeof(bufferKey)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &bufferKey, sizeof(bufferKey)));
|
||||||
}
|
}
|
||||||
@@ -59,27 +51,14 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
|
|
||||||
const VertexInputStateFactory::BackendVertexInputState& VertexInputStateFactory::GetOrCreateVertexInputState(
|
const VertexInputStateFactory::BackendVertexInputState& VertexInputStateFactory::GetOrCreateVertexInputState(
|
||||||
const MG_State::GLState::VertexArrayObject& vao) {
|
const MG_State::GLState::VertexArrayObject& vao) {
|
||||||
// Per-draw fast path: the VAO carries a pointer to its resolved entry,
|
return GetOrCreateVertexInputState(vao, GetOrComputeHash(vao));
|
||||||
// valid while its config version and the cache's eviction epoch both
|
|
||||||
// match - no re-hash, no map lookup.
|
|
||||||
const void* memoState = nullptr;
|
|
||||||
Uint64 memoEpoch = 0;
|
|
||||||
if (vao.GetBackendStateMemo(memoState, memoEpoch) && memoEpoch == m_evictionEpoch) {
|
|
||||||
const auto* entry = static_cast<const BackendVertexInputState*>(memoState);
|
|
||||||
entry->lastUsedFrameBoundary = m_frameBoundaryCounter;
|
|
||||||
return *entry;
|
|
||||||
}
|
|
||||||
const BackendVertexInputState& entry = GetOrCreateVertexInputState(vao, GetOrComputeHash(vao));
|
|
||||||
vao.SetBackendStateMemo(&entry, m_evictionEpoch);
|
|
||||||
return entry;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const VertexInputStateFactory::BackendVertexInputState& VertexInputStateFactory::GetOrCreateVertexInputState(
|
const VertexInputStateFactory::BackendVertexInputState& VertexInputStateFactory::GetOrCreateVertexInputState(
|
||||||
const MG_State::GLState::VertexArrayObject& vao, HashType hash) {
|
const MG_State::GLState::VertexArrayObject& vao, HashType hash) {
|
||||||
auto it = m_cache.find(hash);
|
auto it = m_cache.find(hash);
|
||||||
if (it != m_cache.end()) {
|
if (it != m_cache.end()) {
|
||||||
it->second->lastUsedFrameBoundary = m_frameBoundaryCounter;
|
return it->second;
|
||||||
return *it->second;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
VertexInputStateBuilder builder;
|
VertexInputStateBuilder builder;
|
||||||
@@ -88,7 +67,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
Vector<Uint32> bindingAttributeLocations;
|
Vector<Uint32> bindingAttributeLocations;
|
||||||
Vector<Bool> bindingUsesClientMemory;
|
Vector<Bool> bindingUsesClientMemory;
|
||||||
Vector<VertexStreamConversion> bindingConversions;
|
Vector<VertexStreamConversion> bindingConversions;
|
||||||
Vector<VkVertexInputBindingDivisorDescriptionEXT> bindingDivisors;
|
|
||||||
Uint32 unsupportedAttribMask = 0;
|
Uint32 unsupportedAttribMask = 0;
|
||||||
|
|
||||||
for (Uint32 location = 0; location < MG_State::GLState::VertexArrayObject::MAX_VERTEX_ATTRIBS; ++location) {
|
for (Uint32 location = 0; location < MG_State::GLState::VertexArrayObject::MAX_VERTEX_ATTRIBS; ++location) {
|
||||||
@@ -98,7 +76,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const VkFormat sourceVkFormat =
|
const VkFormat sourceVkFormat =
|
||||||
ToVkVertexFormat(attr.Type, attr.Size, attr.Normalized, attr.IsInteger, attr.IsBgra, attr.IsLong);
|
ToVkVertexFormat(attr.Type, attr.Size, attr.Normalized, attr.IsInteger, attr.IsBgra);
|
||||||
if (sourceVkFormat == VK_FORMAT_UNDEFINED) {
|
if (sourceVkFormat == VK_FORMAT_UNDEFINED) {
|
||||||
MGLOG_E("Unsupported vertex attribute layout (location=%u, type=%s, size=%d): the array is "
|
MGLOG_E("Unsupported vertex attribute layout (location=%u, type=%s, size=%d): the array is "
|
||||||
"enabled but cannot be mapped to a VkFormat",
|
"enabled but cannot be mapped to a VkFormat",
|
||||||
@@ -182,51 +160,14 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
bindingConversions.push_back(conversion);
|
bindingConversions.push_back(conversion);
|
||||||
builder.AddBinding(binding, stride, inputRate);
|
builder.AddBinding(binding, stride, inputRate);
|
||||||
builder.AddAttribute(location, binding, vkFormat, 0);
|
builder.AddAttribute(location, binding, vkFormat, 0);
|
||||||
// Divisor 1 is what VK_VERTEX_INPUT_RATE_INSTANCE already means; only anything
|
|
||||||
// else needs the extension to say it.
|
|
||||||
if (inputRate == VK_VERTEX_INPUT_RATE_INSTANCE && attr.Divisor != 1) {
|
|
||||||
bindingDivisors.push_back({binding, static_cast<Uint32>(attr.Divisor)});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto& state = builder.Build();
|
const auto& state = builder.Build();
|
||||||
|
|
||||||
auto& slot = m_cache[hash];
|
auto& entry = m_cache[hash];
|
||||||
if (!slot) {
|
|
||||||
slot = MakeUnique<BackendVertexInputState>();
|
|
||||||
}
|
|
||||||
BackendVertexInputState& entry = *slot;
|
|
||||||
entry.hash = hash;
|
entry.hash = hash;
|
||||||
entry.lastUsedFrameBoundary = m_frameBoundaryCounter;
|
|
||||||
entry.bindingDivisors = Move(bindingDivisors);
|
|
||||||
entry.bindings = builder.GetBindings();
|
entry.bindings = builder.GetBindings();
|
||||||
entry.attributes = builder.GetAttributes();
|
entry.attributes = builder.GetAttributes();
|
||||||
// See the layoutHash declaration: hash only the resolved layout, never
|
|
||||||
// buffer identities, so identical layouts across VAOs/buffers agree.
|
|
||||||
XXHASH_VERIFY(XXH64_reset(m_hashState, 0));
|
|
||||||
for (const auto& binding : entry.bindings) {
|
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState, &binding.binding, sizeof(binding.binding)));
|
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState, &binding.stride, sizeof(binding.stride)));
|
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState, &binding.inputRate, sizeof(binding.inputRate)));
|
|
||||||
}
|
|
||||||
for (const auto& attribute : entry.attributes) {
|
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState, &attribute.location, sizeof(attribute.location)));
|
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState, &attribute.binding, sizeof(attribute.binding)));
|
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState, &attribute.format, sizeof(attribute.format)));
|
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState, &attribute.offset, sizeof(attribute.offset)));
|
|
||||||
}
|
|
||||||
for (const auto& divisor : entry.bindingDivisors) {
|
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState, &divisor.binding, sizeof(divisor.binding)));
|
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState, &divisor.divisor, sizeof(divisor.divisor)));
|
|
||||||
}
|
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState, &unsupportedAttribMask, sizeof(unsupportedAttribMask)));
|
|
||||||
entry.layoutHash = XXH64_digest(m_hashState);
|
|
||||||
entry.attributeLocationMask = 0;
|
|
||||||
for (const auto& attribute : entry.attributes) {
|
|
||||||
if (attribute.location < 32u) {
|
|
||||||
entry.attributeLocationMask |= (1u << attribute.location);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
entry.bindingBufferKeys = std::move(bindingBufferKeys);
|
entry.bindingBufferKeys = std::move(bindingBufferKeys);
|
||||||
entry.bindingBaseOffsets = std::move(bindingBaseOffsets);
|
entry.bindingBaseOffsets = std::move(bindingBaseOffsets);
|
||||||
entry.bindingAttributeLocations = std::move(bindingAttributeLocations);
|
entry.bindingAttributeLocations = std::move(bindingAttributeLocations);
|
||||||
@@ -236,45 +177,11 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
entry.state = state;
|
entry.state = state;
|
||||||
entry.state.pVertexBindingDescriptions = entry.bindings.empty() ? nullptr : entry.bindings.data();
|
entry.state.pVertexBindingDescriptions = entry.bindings.empty() ? nullptr : entry.bindings.data();
|
||||||
entry.state.pVertexAttributeDescriptions = entry.attributes.empty() ? nullptr : entry.attributes.data();
|
entry.state.pVertexAttributeDescriptions = entry.attributes.empty() ? nullptr : entry.attributes.data();
|
||||||
if (!entry.bindingDivisors.empty()) {
|
|
||||||
entry.divisorState.vertexBindingDivisorCount = static_cast<Uint32>(entry.bindingDivisors.size());
|
|
||||||
entry.divisorState.pVertexBindingDivisors = entry.bindingDivisors.data();
|
|
||||||
entry.state.pNext = &entry.divisorState;
|
|
||||||
} else {
|
|
||||||
entry.state.pNext = nullptr;
|
|
||||||
}
|
|
||||||
return entry;
|
return entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
void VertexInputStateFactory::OnFrameBoundary() {
|
|
||||||
++m_frameBoundaryCounter;
|
|
||||||
|
|
||||||
// Sweep occasionally; evict entries whose last hit is far in the past.
|
|
||||||
// Erasure happens only here, never mid-frame: the draw path holds a
|
|
||||||
// reference into the current entry across its setup, and unordered_map
|
|
||||||
// erase would invalidate it. Entries are CPU-side only, so no GPU-idle
|
|
||||||
// proof is needed; an evicted entry that is used again is simply rebuilt
|
|
||||||
// from the VAO state (same hash, same content).
|
|
||||||
constexpr Uint64 kSweepInterval = 256;
|
|
||||||
constexpr Uint64 kRetireAgeBoundaries = 1024;
|
|
||||||
if ((m_frameBoundaryCounter % kSweepInterval) != 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto it = m_cache.begin(); it != m_cache.end();) {
|
|
||||||
if (m_frameBoundaryCounter - it->second->lastUsedFrameBoundary > kRetireAgeBoundaries) {
|
|
||||||
it = m_cache.erase(it);
|
|
||||||
// Invalidate every VAO's state-pointer memo: the erased node's
|
|
||||||
// address may be reused by a future insert.
|
|
||||||
++m_evictionEpoch;
|
|
||||||
} else {
|
|
||||||
++it;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
VkFormat VertexInputStateFactory::ToVkVertexFormat(DataType type, Int size, Bool normalized, Bool isInteger,
|
VkFormat VertexInputStateFactory::ToVkVertexFormat(DataType type, Int size, Bool normalized, Bool isInteger,
|
||||||
Bool isBgra, Bool isLong) {
|
Bool isBgra) {
|
||||||
if (isBgra) {
|
if (isBgra) {
|
||||||
// GL_BGRA: four reversed-order components, always normalized (enforced at validation), only
|
// GL_BGRA: four reversed-order components, always normalized (enforced at validation), only
|
||||||
// legal with GL_UNSIGNED_BYTE or a 2_10_10_10 type. The reversed VkFormats put the
|
// legal with GL_UNSIGNED_BYTE or a 2_10_10_10 type. The reversed VkFormats put the
|
||||||
@@ -299,22 +206,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
case DataType::Int2101010Rev:
|
case DataType::Int2101010Rev:
|
||||||
if (isInteger || size != 4) return VK_FORMAT_UNDEFINED;
|
if (isInteger || size != 4) return VK_FORMAT_UNDEFINED;
|
||||||
return normalized ? VK_FORMAT_A2B10G10R10_SNORM_PACK32 : VK_FORMAT_A2B10G10R10_SSCALED_PACK32;
|
return normalized ? VK_FORMAT_A2B10G10R10_SNORM_PACK32 : VK_FORMAT_A2B10G10R10_SSCALED_PACK32;
|
||||||
case DataType::Float64:
|
|
||||||
// A 64-bit attribute is fetched as its 32-bit word pair and bitcast back to double in the
|
|
||||||
// shader (PackDoubleVertexInputsPass does the shader half). That is bit-exact and, unlike
|
|
||||||
// VK_FORMAT_R64*_SFLOAT, needs no format capability: lavapipe reports bufferFeatures = 0
|
|
||||||
// for every R64 float format, so a native 64-bit vertex fetch is simply unavailable there
|
|
||||||
// while shaderFloat64 is not. Both halves key off nothing but the attribute being long,
|
|
||||||
// so they always agree without extra plumbing.
|
|
||||||
if (!isLong || isInteger || normalized) return VK_FORMAT_UNDEFINED;
|
|
||||||
switch (size) {
|
|
||||||
case 1: return VK_FORMAT_R32G32_UINT;
|
|
||||||
case 2: return VK_FORMAT_R32G32B32A32_UINT;
|
|
||||||
// A dvec3/dvec4 input is 6/8 uint32 components: no single VkFormat, and GL spreads it
|
|
||||||
// over two attribute locations, which the location-per-VAO-index model here does not
|
|
||||||
// express. Declined rather than fetched wrong.
|
|
||||||
default: return VK_FORMAT_UNDEFINED;
|
|
||||||
}
|
|
||||||
case DataType::Float32:
|
case DataType::Float32:
|
||||||
switch (size) {
|
switch (size) {
|
||||||
case 1: return VK_FORMAT_R32_SFLOAT;
|
case 1: return VK_FORMAT_R32_SFLOAT;
|
||||||
|
|||||||
@@ -27,18 +27,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
|
|
||||||
struct BackendVertexInputState {
|
struct BackendVertexInputState {
|
||||||
HashType hash = 0;
|
HashType hash = 0;
|
||||||
// Hash of the resolved Vulkan vertex layout only (bindings, attributes,
|
|
||||||
// unsupported mask) - NO buffer identities. `hash` mixes buffer heap
|
|
||||||
// addresses so per-chunk VBOs mint a fresh identity per buffer; keying
|
|
||||||
// pipelines on that minted one VkPipeline per chunk section for an
|
|
||||||
// identical layout, defeating pipeline reuse and the per-draw memo.
|
|
||||||
// Pipelines depend only on the layout, so they key on this instead.
|
|
||||||
HashType layoutHash = 0;
|
|
||||||
// Frame boundary of the last cache hit; entries idle past the
|
|
||||||
// OnFrameBoundary retirement age are evicted (CPU heap only).
|
|
||||||
// Mutable: the VAO's state-pointer memo fast path stamps it through
|
|
||||||
// a const entry reference.
|
|
||||||
mutable Uint64 lastUsedFrameBoundary = 0;
|
|
||||||
Vector<VkVertexInputBindingDescription> bindings;
|
Vector<VkVertexInputBindingDescription> bindings;
|
||||||
Vector<VkVertexInputAttributeDescription> attributes;
|
Vector<VkVertexInputAttributeDescription> attributes;
|
||||||
Vector<SizeT> bindingBufferKeys;
|
Vector<SizeT> bindingBufferKeys;
|
||||||
@@ -50,17 +38,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
// absent from `attributes`, so without this mask the draw path cannot tell them apart from
|
// absent from `attributes`, so without this mask the draw path cannot tell them apart from
|
||||||
// a genuinely disabled array and would silently feed the shader the current attribute value.
|
// a genuinely disabled array and would silently feed the shader the current attribute value.
|
||||||
Uint32 unsupportedAttribMask = 0;
|
Uint32 unsupportedAttribMask = 0;
|
||||||
// Bitmask of `attributes[i].location` - the draw path needs it up to
|
|
||||||
// three times per draw, so it is baked once at build time.
|
|
||||||
Uint32 attributeLocationMask = 0;
|
|
||||||
// Per-binding glVertexAttribDivisor values other than 1. Vulkan's instance input
|
|
||||||
// rate advances once per instance and nothing else, so anything else has to be
|
|
||||||
// stated through VK_EXT_vertex_attribute_divisor. Empty when every instanced
|
|
||||||
// binding uses divisor 1, which is what the plain input rate already means.
|
|
||||||
Vector<VkVertexInputBindingDivisorDescriptionEXT> bindingDivisors;
|
|
||||||
VkPipelineVertexInputDivisorStateCreateInfoEXT divisorState{
|
|
||||||
VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_DIVISOR_STATE_CREATE_INFO_EXT
|
|
||||||
};
|
|
||||||
VkPipelineVertexInputStateCreateInfo state{
|
VkPipelineVertexInputStateCreateInfo state{
|
||||||
VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO
|
VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO
|
||||||
};
|
};
|
||||||
@@ -78,14 +55,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
const BackendVertexInputState& GetOrCreateVertexInputState(
|
const BackendVertexInputState& GetOrCreateVertexInputState(
|
||||||
const MG_State::GLState::VertexArrayObject& vao, HashType hash);
|
const MG_State::GLState::VertexArrayObject& vao, HashType hash);
|
||||||
const BackendVertexInputState& GetOrCreateVertexInputState(const MG_State::GLState::VertexArrayObject& vao);
|
const BackendVertexInputState& GetOrCreateVertexInputState(const MG_State::GLState::VertexArrayObject& vao);
|
||||||
// Frame boundary hook: ages the cache and evicts entries not hit for many
|
|
||||||
// frames. The key mixes buffer heap addresses, so buffer/VAO churn keeps
|
|
||||||
// minting fresh keys; without eviction the map grows for the whole session.
|
|
||||||
// Entries hold no Vulkan handles (pipeline creation copies the descriptions)
|
|
||||||
// and the draw path's entry reference never spans a frame boundary, so
|
|
||||||
// eviction here needs no GPU-idle proof. Self-gated: one counter bump and
|
|
||||||
// compare except on sweep boundaries.
|
|
||||||
void OnFrameBoundary();
|
|
||||||
static SizeT GetComponentSize(DataType type);
|
static SizeT GetComponentSize(DataType type);
|
||||||
// Tightly-packed byte size of one vertex element for this attribute: componentSize * size for
|
// Tightly-packed byte size of one vertex element for this attribute: componentSize * size for
|
||||||
// normal types, and 4 (one packed word) for the 2_10_10_10 types and GL_BGRA. Returns 0 for
|
// normal types, and 4 (one packed word) for the 2_10_10_10 types and GL_BGRA. Returns 0 for
|
||||||
@@ -93,27 +62,14 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
static SizeT GetAttributeByteSize(DataType type, Int size, Bool isBgra);
|
static SizeT GetAttributeByteSize(DataType type, Int size, Bool isBgra);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static VkFormat ToVkVertexFormat(DataType type, Int size, Bool normalized, Bool isInteger, Bool isBgra = false,
|
static VkFormat ToVkVertexFormat(DataType type, Int size, Bool normalized, Bool isInteger, Bool isBgra = false);
|
||||||
Bool isLong = false);
|
|
||||||
static Bool IsScaledIntegerVertexFormat(VkFormat format);
|
static Bool IsScaledIntegerVertexFormat(VkFormat format);
|
||||||
static VkFormat ToFloat32VertexFormat(Int componentCount);
|
static VkFormat ToFloat32VertexFormat(Int componentCount);
|
||||||
Bool SupportsVertexBufferFormat(VkFormat format) const;
|
Bool SupportsVertexBufferFormat(VkFormat format) const;
|
||||||
|
|
||||||
const VulkanRendererConfig& m_config;
|
const VulkanRendererConfig& m_config;
|
||||||
VkPhysicalDevice m_physicalDevice = VK_NULL_HANDLE;
|
VkPhysicalDevice m_physicalDevice = VK_NULL_HANDLE;
|
||||||
// Values are heap-allocated: FastSTL::unordered_map is open-addressing,
|
UnorderedMap<HashType, BackendVertexInputState> m_cache;
|
||||||
// so INSERT invalidates references to stored values. The draw path (and
|
|
||||||
// the VAOs' state-pointer memos) hold entry pointers across inserts;
|
|
||||||
// only the unique_ptr cell moves, never the pointee.
|
|
||||||
UnorderedMap<HashType, UniquePtr<BackendVertexInputState>> m_cache;
|
|
||||||
// Monotonic frame-boundary counter (bumped in OnFrameBoundary) for cache aging.
|
|
||||||
Uint64 m_frameBoundaryCounter = 0;
|
|
||||||
// Bumped whenever any cache entry is erased. VAOs memo a raw pointer to
|
|
||||||
// their heap-allocated entry (stable across map insert/rehash by
|
|
||||||
// construction); a memo is honored only while its recorded epoch
|
|
||||||
// matches, so an evicted entry can never be dereferenced through a
|
|
||||||
// stale memo.
|
|
||||||
Uint64 m_evictionEpoch = 1;
|
|
||||||
static inline XXH64_state_t* m_hashState = XXH64_createState();
|
static inline XXH64_state_t* m_hashState = XXH64_createState();
|
||||||
};
|
};
|
||||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||||
|
|||||||
@@ -7,8 +7,6 @@
|
|||||||
// End of Source File Header
|
// End of Source File Header
|
||||||
|
|
||||||
#include "VkBufferManager.h"
|
#include "VkBufferManager.h"
|
||||||
#include "../DirectVulkan.h"
|
|
||||||
#include "VulkanRenderer.h"
|
|
||||||
|
|
||||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||||
namespace {
|
namespace {
|
||||||
@@ -24,10 +22,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT |
|
VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT |
|
||||||
VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT | VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT |
|
VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT | VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT |
|
||||||
VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
|
VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
|
||||||
// Appended to kPersistentBackedUsage when VK_EXT_transform_feedback is enabled
|
|
||||||
// (see VkBufferManagerInitInfo::transformFeedbackUsageEnabled).
|
|
||||||
constexpr VkBufferUsageFlags kTransformFeedbackUsage =
|
|
||||||
VK_BUFFER_USAGE_TRANSFORM_FEEDBACK_BUFFER_BIT_EXT;
|
|
||||||
// The app writes into the persistent map with no explicit flush, so its memory must
|
// The app writes into the persistent map with no explicit flush, so its memory must
|
||||||
// be host-coherent (Adreno host-visible memory is; requiring it keeps us portable).
|
// be host-coherent (Adreno host-visible memory is; requiring it keeps us portable).
|
||||||
constexpr VkMemoryPropertyFlags kPersistentBackedRequiredFlags =
|
constexpr VkMemoryPropertyFlags kPersistentBackedRequiredFlags =
|
||||||
@@ -59,18 +53,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// The CPU is about to read a buffer a shader wrote. Its bytes live in coherent
|
|
||||||
// host-visible GPU storage (EnsureGpuResidentStorage adopts it when the buffer is
|
|
||||||
// bound as a shader storage buffer), so nothing needs copying - but coherence only
|
|
||||||
// says the writes are visible once they have happened, so the work has to retire
|
|
||||||
// first.
|
|
||||||
void Ops_ReadbackFromGpu(BufferObject& bufferObject) {
|
|
||||||
(void)bufferObject;
|
|
||||||
if (pVulkanRenderer) {
|
|
||||||
pVulkanRenderer->FinishPendingGpuWork();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void* Ops_AcquirePersistentMap(BufferObject& bufferObject) {
|
void* Ops_AcquirePersistentMap(BufferObject& bufferObject) {
|
||||||
if (g_activeBufferManager) {
|
if (g_activeBufferManager) {
|
||||||
return g_activeBufferManager->AcquirePersistentMap(bufferObject);
|
return g_activeBufferManager->AcquirePersistentMap(bufferObject);
|
||||||
@@ -94,7 +76,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
.FlushMappedRange = Ops_FlushMappedRange,
|
.FlushMappedRange = Ops_FlushMappedRange,
|
||||||
.OnDestroy = Ops_OnDestroy,
|
.OnDestroy = Ops_OnDestroy,
|
||||||
.AcquirePersistentMap = Ops_AcquirePersistentMap,
|
.AcquirePersistentMap = Ops_AcquirePersistentMap,
|
||||||
.ReadbackFromGpu = Ops_ReadbackFromGpu,
|
|
||||||
};
|
};
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
@@ -160,15 +141,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
m_transientUploadArena.BeginFrame(frameIndex);
|
m_transientUploadArena.BeginFrame(frameIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
void VkBufferManager::CollectAllDeferredReleases() {
|
|
||||||
for (Uint32 frameIndex = 0; frameIndex < m_deferredBufferReleases.size(); ++frameIndex) {
|
|
||||||
CollectDeferredReleases(frameIndex);
|
|
||||||
}
|
|
||||||
for (Uint32 frameIndex = 0; frameIndex < m_transientUploadArena.GetFrameCount(); ++frameIndex) {
|
|
||||||
m_transientUploadArena.CollectDeferredReleases(frameIndex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void VkBufferManager::NotifyDeviceIdle() {
|
void VkBufferManager::NotifyDeviceIdle() {
|
||||||
// Everything submitted so far has completed. Work recorded for the
|
// Everything submitted so far has completed. Work recorded for the
|
||||||
// current frame has not been submitted yet, so the current serial
|
// current frame has not been submitted yet, so the current serial
|
||||||
@@ -242,15 +214,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void VkBufferManager::TrackLiveResource(const SharedPtr<VkBufferResource>& resource) {
|
void VkBufferManager::TrackLiveResource(const SharedPtr<VkBufferResource>& resource) {
|
||||||
// Sweep on a doubling watermark rather than on every insert past the threshold. The old
|
if (m_liveResources.size() >= kLiveResourcePruneThreshold) {
|
||||||
// form walked the whole vector for each new buffer once the list passed 256, and when the
|
|
||||||
// buffers are all live the walk removes nothing and the list grows by one - so creating N
|
|
||||||
// live buffers cost ~N^2/2 expired() checks. Reclamation semantics are unchanged: the sweep
|
|
||||||
// still removes exactly the expired entries, just less often and with the same bound on how
|
|
||||||
// much dead weight can accumulate (at most as many entries as were live at the last sweep).
|
|
||||||
if (m_liveResources.size() >= std::max<SizeT>(kLiveResourcePruneThreshold, 2 * m_liveResourcesLastPruned)) {
|
|
||||||
std::erase_if(m_liveResources, [](const WeakPtr<VkBufferResource>& weak) { return weak.expired(); });
|
std::erase_if(m_liveResources, [](const WeakPtr<VkBufferResource>& weak) { return weak.expired(); });
|
||||||
m_liveResourcesLastPruned = m_liveResources.size();
|
|
||||||
}
|
}
|
||||||
m_liveResources.push_back(resource);
|
m_liveResources.push_back(resource);
|
||||||
}
|
}
|
||||||
@@ -491,10 +456,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
// it from the current shadow - MappedData() is still the shadow here because the
|
// it from the current shadow - MappedData() is still the shadow here because the
|
||||||
// frontend adopts (and drops) the shadow only after this returns.
|
// frontend adopts (and drops) the shadow only after this returns.
|
||||||
DeferRelease(std::move(resource->buffer));
|
DeferRelease(std::move(resource->buffer));
|
||||||
const VkBufferUsageFlags persistentUsage =
|
if (!CreateResidentStorage(*resource, size, kPersistentBackedUsage, kPersistentBackedRequiredFlags)) {
|
||||||
kPersistentBackedUsage |
|
|
||||||
(m_initInfo.transformFeedbackUsageEnabled ? kTransformFeedbackUsage : 0);
|
|
||||||
if (!CreateResidentStorage(*resource, size, persistentUsage, kPersistentBackedRequiredFlags)) {
|
|
||||||
resource->persistentMapped = false;
|
resource->persistentMapped = false;
|
||||||
resource->storageSize = 0;
|
resource->storageSize = 0;
|
||||||
resource->usageFlags = 0;
|
resource->usageFlags = 0;
|
||||||
@@ -568,16 +530,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
auto resource = GetOrCreateResource(bufferObject);
|
auto resource = GetOrCreateResource(bufferObject);
|
||||||
bufferObject->SyncPersistentMappedRange();
|
bufferObject->SyncPersistentMappedRange();
|
||||||
|
|
||||||
// A persistently mapped resource's storage IS the application's copy of the bytes -
|
|
||||||
// the frontend adopted it in place of the shadow and hands out pointers into it, and
|
|
||||||
// a shader can have written bytes the shadow never saw (a transform feedback
|
|
||||||
// capture). Streaming a second copy would feed this draw the stale shadow, and the
|
|
||||||
// downgrade below would release the storage the application still points at,
|
|
||||||
// breaking the "never recreated" promise AcquirePersistentMap makes.
|
|
||||||
if (resource->persistentMapped) {
|
|
||||||
return AcquireResidentSlice(kind, bufferObject, outSlice);
|
|
||||||
}
|
|
||||||
|
|
||||||
const VkDeviceSize size = static_cast<VkDeviceSize>(bufferObject->GetSize());
|
const VkDeviceSize size = static_cast<VkDeviceSize>(bufferObject->GetSize());
|
||||||
if (size == 0) {
|
if (size == 0) {
|
||||||
MGLOG_E("VkBufferManager::AcquireStreamedSlice failed: buffer size is zero");
|
MGLOG_E("VkBufferManager::AcquireStreamedSlice failed: buffer size is zero");
|
||||||
|
|||||||
@@ -31,9 +31,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
VmaMemoryUsage transientMemoryUsage = VMA_MEMORY_USAGE_AUTO;
|
VmaMemoryUsage transientMemoryUsage = VMA_MEMORY_USAGE_AUTO;
|
||||||
VmaAllocationCreateFlags transientAllocationFlags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT;
|
VmaAllocationCreateFlags transientAllocationFlags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT;
|
||||||
Bool transientPersistentMapping = false;
|
Bool transientPersistentMapping = false;
|
||||||
// VK_EXT_transform_feedback is enabled: persistent-map storage additionally
|
|
||||||
// carries the transform feedback usage so capture targets can bind directly.
|
|
||||||
Bool transformFeedbackUsageEnabled = false;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// The DirectVulkan storage behind one frontend buffer (pipe_resource analogue).
|
// The DirectVulkan storage behind one frontend buffer (pipe_resource analogue).
|
||||||
@@ -80,11 +77,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
// Recreate all per-frame transient arenas
|
// Recreate all per-frame transient arenas
|
||||||
Bool RecreateTransientArenas(Uint32 frameCount);
|
Bool RecreateTransientArenas(Uint32 frameCount);
|
||||||
void BeginFrame(Uint32 frameIndex);
|
void BeginFrame(Uint32 frameIndex);
|
||||||
// Drains every frame slot's deferred buffer/resource releases (and the
|
|
||||||
// transient arena's parked superseded blocks). Only valid when the
|
|
||||||
// caller has proven every queue submission complete; used by the
|
|
||||||
// present-less frame-boundary drain.
|
|
||||||
void CollectAllDeferredReleases();
|
|
||||||
// All previously submitted GPU work has completed (vkDeviceWaitIdle).
|
// All previously submitted GPU work has completed (vkDeviceWaitIdle).
|
||||||
void NotifyDeviceIdle();
|
void NotifyDeviceIdle();
|
||||||
// A frame slot's submission fence has been waited: every serial up to
|
// A frame slot's submission fence has been waited: every serial up to
|
||||||
@@ -154,8 +146,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
Vector<Vector<VkBufferObject>> m_deferredBufferReleases;
|
Vector<Vector<VkBufferObject>> m_deferredBufferReleases;
|
||||||
Vector<Vector<SharedPtr<VkBufferResource>>> m_deferredResourceReleases;
|
Vector<Vector<SharedPtr<VkBufferResource>>> m_deferredResourceReleases;
|
||||||
Vector<WeakPtr<VkBufferResource>> m_liveResources;
|
Vector<WeakPtr<VkBufferResource>> m_liveResources;
|
||||||
// Size m_liveResources had just after the last sweep; the next sweep waits for it to double.
|
|
||||||
SizeT m_liveResourcesLastPruned = 0;
|
|
||||||
Uint32 m_currentFrameIndex = 0;
|
Uint32 m_currentFrameIndex = 0;
|
||||||
Uint64 m_frameSerial = 1;
|
Uint64 m_frameSerial = 1;
|
||||||
Uint64 m_completedSerialFloor = 0;
|
Uint64 m_completedSerialFloor = 0;
|
||||||
|
|||||||
@@ -8,75 +8,15 @@
|
|||||||
|
|
||||||
#include "VkClearManager.h"
|
#include "VkClearManager.h"
|
||||||
|
|
||||||
#include "MG_State/GLState/Core.h"
|
|
||||||
#include "MG_Util/Converters/MGToStr/FramebufferEnumConverter.h"
|
#include "MG_Util/Converters/MGToStr/FramebufferEnumConverter.h"
|
||||||
#include "MG_Util/Converters/MGToStr/TextureEnumConverter.h"
|
#include "MG_Util/Converters/MGToStr/TextureEnumConverter.h"
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <cmath>
|
|
||||||
|
|
||||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||||
static Bool IsCubeMapFaceUploadTarget(TextureUploadTarget target) {
|
static Bool IsCubeMapFaceUploadTarget(TextureUploadTarget target) {
|
||||||
return target >= TextureUploadTarget::CubeMapPositiveX &&
|
return target >= TextureUploadTarget::CubeMapPositiveX &&
|
||||||
target <= TextureUploadTarget::CubeMapNegativeZ;
|
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) {
|
static Bool PendingClearMatchesTextureIdentity(const PendingClearKey& key, const TextureIdentity& identity) {
|
||||||
return key.texture == identity.texture && key.textureLifetimeId == identity.lifetimeId;
|
return key.texture == identity.texture && key.textureLifetimeId == identity.lifetimeId;
|
||||||
}
|
}
|
||||||
@@ -153,7 +93,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
const std::lock_guard<std::mutex> lock(m_mutex);
|
const std::lock_guard<std::mutex> lock(m_mutex);
|
||||||
m_pendingClears.clear();
|
m_pendingClears.clear();
|
||||||
m_aliveObjects.clear();
|
m_aliveObjects.clear();
|
||||||
m_pendingCount.store(static_cast<Uint32>(m_pendingClears.size()), std::memory_order_relaxed);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TextureIdentity VkClearManager::MakeTextureIdentity(MG_State::GLState::ITextureObject* texture) {
|
TextureIdentity VkClearManager::MakeTextureIdentity(MG_State::GLState::ITextureObject* texture) {
|
||||||
@@ -188,7 +127,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
m_pendingClears.erase(key);
|
m_pendingClears.erase(key);
|
||||||
}
|
}
|
||||||
m_aliveObjects.erase(identity);
|
m_aliveObjects.erase(identity);
|
||||||
m_pendingCount.store(static_cast<Uint32>(m_pendingClears.size()), std::memory_order_relaxed);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Bool VkClearManager::LockTextureIdentityLocked(const TextureIdentity& identity,
|
Bool VkClearManager::LockTextureIdentityLocked(const TextureIdentity& identity,
|
||||||
@@ -283,7 +221,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
m_aliveObjects[MakeTextureIdentity(texture.get())] = texture;
|
m_aliveObjects[MakeTextureIdentity(texture.get())] = texture;
|
||||||
auto& pending = m_pendingClears[key];
|
auto& pending = m_pendingClears[key];
|
||||||
MergeClearPayload(pending, clearPayload);
|
MergeClearPayload(pending, clearPayload);
|
||||||
m_pendingCount.store(static_cast<Uint32>(m_pendingClears.size()), std::memory_order_relaxed);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void VkClearManager::QueueClear(const ClearAttachmentPayload& clearPayload,
|
void VkClearManager::QueueClear(const ClearAttachmentPayload& clearPayload,
|
||||||
@@ -301,7 +238,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
m_aliveObjects[MakeTextureIdentity(texture.get())] = texture;
|
m_aliveObjects[MakeTextureIdentity(texture.get())] = texture;
|
||||||
auto& pending = m_pendingClears[key];
|
auto& pending = m_pendingClears[key];
|
||||||
MergeClearPayload(pending, clearPayload);
|
MergeClearPayload(pending, clearPayload);
|
||||||
m_pendingCount.store(static_cast<Uint32>(m_pendingClears.size()), std::memory_order_relaxed);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Bool VkClearManager::HasPendingClear(MG_State::GLState::ITextureObject* texture) {
|
Bool VkClearManager::HasPendingClear(MG_State::GLState::ITextureObject* texture) {
|
||||||
@@ -309,10 +245,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_pendingCount.load(std::memory_order_relaxed) == 0) {
|
|
||||||
return false; // per-draw hot path: nothing pending anywhere
|
|
||||||
}
|
|
||||||
|
|
||||||
const Uint64 lifetimeId = texture->GetLifetimeId();
|
const Uint64 lifetimeId = texture->GetLifetimeId();
|
||||||
const std::lock_guard<std::mutex> lock(m_mutex);
|
const std::lock_guard<std::mutex> lock(m_mutex);
|
||||||
for (auto it = m_pendingClears.begin(); it != m_pendingClears.end(); ++it) {
|
for (auto it = m_pendingClears.begin(); it != m_pendingClears.end(); ++it) {
|
||||||
@@ -328,9 +260,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
if (key.texture == nullptr) {
|
if (key.texture == nullptr) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (m_pendingCount.load(std::memory_order_relaxed) == 0) {
|
|
||||||
return false; // per-draw hot path: nothing pending anywhere
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::lock_guard<std::mutex> lock(m_mutex);
|
const std::lock_guard<std::mutex> lock(m_mutex);
|
||||||
if (m_pendingClears.find(key) == m_pendingClears.end()) {
|
if (m_pendingClears.find(key) == m_pendingClears.end()) {
|
||||||
@@ -358,9 +287,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
if (key.texture == nullptr) {
|
if (key.texture == nullptr) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (m_pendingCount.load(std::memory_order_relaxed) == 0) {
|
|
||||||
return false; // per-draw hot path: nothing pending anywhere
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::lock_guard<std::mutex> lock(m_mutex);
|
const std::lock_guard<std::mutex> lock(m_mutex);
|
||||||
if (!LockTextureLocked(key, outTexture)) {
|
if (!LockTextureLocked(key, outTexture)) {
|
||||||
@@ -399,9 +325,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
if (texture == nullptr) {
|
if (texture == nullptr) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (m_pendingCount.load(std::memory_order_relaxed) == 0) {
|
|
||||||
return false; // per-draw hot path: nothing pending anywhere
|
|
||||||
}
|
|
||||||
|
|
||||||
const Uint64 lifetimeId = texture->GetLifetimeId();
|
const Uint64 lifetimeId = texture->GetLifetimeId();
|
||||||
const std::lock_guard<std::mutex> lock(m_mutex);
|
const std::lock_guard<std::mutex> lock(m_mutex);
|
||||||
@@ -422,9 +345,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_pendingCount.load(std::memory_order_relaxed) == 0) {
|
|
||||||
return; // per-draw hot path: nothing pending anywhere
|
|
||||||
}
|
|
||||||
const TextureIdentity identity = MakeTextureIdentity(texture);
|
const TextureIdentity identity = MakeTextureIdentity(texture);
|
||||||
MGLOG_D("%s: Pop all pending clears for texture %d", __func__, texture->GetExternalIndex());
|
MGLOG_D("%s: Pop all pending clears for texture %d", __func__, texture->GetExternalIndex());
|
||||||
const std::lock_guard<std::mutex> lock(m_mutex);
|
const std::lock_guard<std::mutex> lock(m_mutex);
|
||||||
@@ -441,7 +361,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
auto it = m_pendingClears.find(key);
|
auto it = m_pendingClears.find(key);
|
||||||
if (it != m_pendingClears.end()) {
|
if (it != m_pendingClears.end()) {
|
||||||
m_pendingClears.erase(it);
|
m_pendingClears.erase(it);
|
||||||
m_pendingCount.store(static_cast<Uint32>(m_pendingClears.size()), std::memory_order_relaxed);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,6 @@
|
|||||||
#include "MG_Util/Math/VectorTypes.h"
|
#include "MG_Util/Math/VectorTypes.h"
|
||||||
|
|
||||||
#include <Includes.h>
|
#include <Includes.h>
|
||||||
#include <atomic>
|
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
|
||||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||||
@@ -24,41 +23,13 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
Uint32 stencil{};
|
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 {
|
struct ClearAttachmentPayload {
|
||||||
GLbitfield mask = 0;
|
GLbitfield mask = 0;
|
||||||
FloatVec4 color = FloatVec4(0.0f, 0.0f, 0.0f, 0.0f);
|
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;
|
Float depth = 1.0f;
|
||||||
Uint32 stencil = 0;
|
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 {
|
struct PendingClearKey {
|
||||||
MG_State::GLState::ITextureObject* texture = nullptr;
|
MG_State::GLState::ITextureObject* texture = nullptr;
|
||||||
Uint64 textureLifetimeId = 0;
|
Uint64 textureLifetimeId = 0;
|
||||||
@@ -149,19 +120,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
SharedPtr<MG_State::GLState::ITextureObject>& outTexture);
|
SharedPtr<MG_State::GLState::ITextureObject>& outTexture);
|
||||||
|
|
||||||
Uint8 m_gcCounter = 0;
|
Uint8 m_gcCounter = 0;
|
||||||
public:
|
|
||||||
// Lock-free probe for the consecutive-draw fast path: any pending clear
|
|
||||||
// forces the full SetupDraw path (which materializes/consumes it).
|
|
||||||
Bool HasAnyPendingClears() const { return m_pendingCount.load(std::memory_order_relaxed) != 0; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
mutable std::mutex m_mutex;
|
mutable std::mutex m_mutex;
|
||||||
// Lock-free mirror of m_pendingClears.size(), maintained under m_mutex
|
|
||||||
// by every mutation. The per-draw probes (HasPendingClear/GetPending*)
|
|
||||||
// read it before taking the lock: during draw batches the pending set
|
|
||||||
// is almost always empty, so this turns several locked map probes per
|
|
||||||
// draw into one relaxed load.
|
|
||||||
std::atomic<Uint32> m_pendingCount{0};
|
|
||||||
std::unordered_map<PendingClearKey, ClearAttachmentPayload, PendingClearKeyHash> m_pendingClears;
|
std::unordered_map<PendingClearKey, ClearAttachmentPayload, PendingClearKeyHash> m_pendingClears;
|
||||||
std::unordered_map<TextureIdentity, WeakPtr<MG_State::GLState::ITextureObject>, TextureIdentityHash> m_aliveObjects;
|
std::unordered_map<TextureIdentity, WeakPtr<MG_State::GLState::ITextureObject>, TextureIdentityHash> m_aliveObjects;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -16,21 +16,31 @@
|
|||||||
|
|
||||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||||
static Bool TryResolveSampleCountFlagBits(Int requestedSamples, VkSampleCountFlagBits& outSampleCount) {
|
static Bool TryResolveSampleCountFlagBits(Int requestedSamples, VkSampleCountFlagBits& outSampleCount) {
|
||||||
// GL promises "at least the requested samples", so a non-power-of-two
|
switch (requestedSamples <= 0 ? 1 : requestedSamples) {
|
||||||
// request (legal in GL, e.g. 3) rounds up to the next Vulkan bit.
|
case 1:
|
||||||
if (requestedSamples <= 1) {
|
|
||||||
outSampleCount = VK_SAMPLE_COUNT_1_BIT;
|
outSampleCount = VK_SAMPLE_COUNT_1_BIT;
|
||||||
return true;
|
return true;
|
||||||
}
|
case 2:
|
||||||
if (requestedSamples > 64) {
|
outSampleCount = VK_SAMPLE_COUNT_2_BIT;
|
||||||
|
return true;
|
||||||
|
case 4:
|
||||||
|
outSampleCount = VK_SAMPLE_COUNT_4_BIT;
|
||||||
|
return true;
|
||||||
|
case 8:
|
||||||
|
outSampleCount = VK_SAMPLE_COUNT_8_BIT;
|
||||||
|
return true;
|
||||||
|
case 16:
|
||||||
|
outSampleCount = VK_SAMPLE_COUNT_16_BIT;
|
||||||
|
return true;
|
||||||
|
case 32:
|
||||||
|
outSampleCount = VK_SAMPLE_COUNT_32_BIT;
|
||||||
|
return true;
|
||||||
|
case 64:
|
||||||
|
outSampleCount = VK_SAMPLE_COUNT_64_BIT;
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
Uint32 bit = 1;
|
|
||||||
while (bit < static_cast<Uint32>(requestedSamples)) {
|
|
||||||
bit <<= 1;
|
|
||||||
}
|
|
||||||
outSampleCount = static_cast<VkSampleCountFlagBits>(bit);
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static VkImageAspectFlags ResolveImageAspectMaskForFormat(VkFormat format) {
|
static VkImageAspectFlags ResolveImageAspectMaskForFormat(VkFormat format) {
|
||||||
@@ -50,11 +60,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static Bool ColorFormatLacksAlpha(const MG_State::GLState::ITextureObject* texture) {
|
static Float ResolveColorClearAlpha(const MG_State::GLState::ITextureObject* texture, Float requestedAlpha) {
|
||||||
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) {
|
if (texture != nullptr && MG_Util::GetBaseInternalFormatComponentCount(texture->GetFormat()) == 3) {
|
||||||
return 1.0f;
|
return 1.0f;
|
||||||
}
|
}
|
||||||
@@ -87,20 +93,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
static VkImageViewType ResolveAttachmentViewType(
|
static VkImageViewType ResolveAttachmentViewType(
|
||||||
const MG_State::GLState::FramebufferAttachmentObject& attachment,
|
const MG_State::GLState::FramebufferAttachmentObject& attachment,
|
||||||
const VkTextureManager::TextureResource& resource) {
|
const VkTextureManager::TextureResource& resource) {
|
||||||
if (attachment.IsLayered()) {
|
return !attachment.IsLayered() && IsCubeMapFaceUploadTarget(attachment.GetTextureUploadTarget()) ?
|
||||||
return resource.viewType;
|
VK_IMAGE_VIEW_TYPE_2D :
|
||||||
}
|
resource.viewType;
|
||||||
// A non-layered attachment names ONE layer, so the view over it is a plain 2D view whatever
|
|
||||||
// the image's own view type is. The cube-face upload targets always meant this; a cube map
|
|
||||||
// array attached through glFramebufferTextureLayer means it too, and a CUBE_ARRAY view over
|
|
||||||
// a single layer is not a legal attachment. The CUBE arm is inert today - no frontend path
|
|
||||||
// produces a non-layered cube attachment without a face upload target - and is kept for
|
|
||||||
// symmetry with CUBE_ARRAY.
|
|
||||||
if (IsCubeMapFaceUploadTarget(attachment.GetTextureUploadTarget()) ||
|
|
||||||
resource.viewType == VK_IMAGE_VIEW_TYPE_CUBE_ARRAY || resource.viewType == VK_IMAGE_VIEW_TYPE_CUBE) {
|
|
||||||
return VK_IMAGE_VIEW_TYPE_2D;
|
|
||||||
}
|
|
||||||
return resource.viewType;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static MG_State::GLState::ITextureObject* ResolveCompleteColorAttachmentTexture(
|
static MG_State::GLState::ITextureObject* ResolveCompleteColorAttachmentTexture(
|
||||||
@@ -171,9 +166,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
if (view != VK_NULL_HANDLE) {
|
if (view != VK_NULL_HANDLE) {
|
||||||
vkDestroyImageView(device, view, nullptr);
|
vkDestroyImageView(device, view, nullptr);
|
||||||
}
|
}
|
||||||
if (unormTwinView != VK_NULL_HANDLE) {
|
|
||||||
vkDestroyImageView(device, unormTwinView, nullptr);
|
|
||||||
}
|
|
||||||
if (image != VK_NULL_HANDLE && allocation != nullptr) {
|
if (image != VK_NULL_HANDLE && allocation != nullptr) {
|
||||||
vmaDestroyImage(allocator, image, allocation);
|
vmaDestroyImage(allocator, image, allocation);
|
||||||
}
|
}
|
||||||
@@ -181,7 +173,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
image = VK_NULL_HANDLE;
|
image = VK_NULL_HANDLE;
|
||||||
allocation = nullptr;
|
allocation = nullptr;
|
||||||
view = VK_NULL_HANDLE;
|
view = VK_NULL_HANDLE;
|
||||||
unormTwinView = VK_NULL_HANDLE;
|
|
||||||
layout = VK_IMAGE_LAYOUT_UNDEFINED;
|
layout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||||
format = VK_FORMAT_UNDEFINED;
|
format = VK_FORMAT_UNDEFINED;
|
||||||
aspect = VK_IMAGE_ASPECT_NONE;
|
aspect = VK_IMAGE_ASPECT_NONE;
|
||||||
@@ -189,7 +180,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
sampleCount = VK_SAMPLE_COUNT_1_BIT;
|
sampleCount = VK_SAMPLE_COUNT_1_BIT;
|
||||||
internalFormat = TextureInternalFormat::Unknown;
|
internalFormat = TextureInternalFormat::Unknown;
|
||||||
samples = 0;
|
samples = 0;
|
||||||
deadSinceFrame = kNeverObservedDead;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
VkRenderPassManager::VkRenderPassManager(VkDevice device,
|
VkRenderPassManager::VkRenderPassManager(VkDevice device,
|
||||||
@@ -216,7 +206,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
resource.Destroy(m_device, m_allocator);
|
resource.Destroy(m_device, m_allocator);
|
||||||
}
|
}
|
||||||
m_renderbufferResources.clear();
|
m_renderbufferResources.clear();
|
||||||
CollectDeferredRenderbufferReleases(/*destroyAll=*/true); // caller guarantees device idle
|
|
||||||
m_pendingRenderbufferClears.clear();
|
m_pendingRenderbufferClears.clear();
|
||||||
RenderPassEntry::s_textureResourcesScratch.clear();
|
RenderPassEntry::s_textureResourcesScratch.clear();
|
||||||
s_activeRenderPass = {};
|
s_activeRenderPass = {};
|
||||||
@@ -224,80 +213,22 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
m_rpFastValid = false;
|
m_rpFastValid = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Uint64 VkRenderPassManager::RetireAgeFrames() const {
|
|
||||||
// MaxFramesInFlight + 2 covers the frame ring plus one boundary for the
|
|
||||||
// recording-to-submit gap and one because OnPresent runs ahead of Present's
|
|
||||||
// fence wait; the floor of 8 keeps a margin over the default ring of 3 while
|
|
||||||
// still releasing multi-MB attachment memory promptly (the render-pass cache's
|
|
||||||
// 1024-frame retirement would pin it for no additional safety).
|
|
||||||
return std::max<Uint64>(8, static_cast<Uint64>(m_config.MaxFramesInFlight) + 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
void VkRenderPassManager::DeferRenderbufferBackingRelease(RenderbufferResource& resource) {
|
|
||||||
// The superseded backing may still be referenced by in-flight command buffers
|
|
||||||
// (glRenderbufferStorage can respecify a renderbuffer drawn this very frame),
|
|
||||||
// so it is parked and destroyed only after RetireAgeFrames() boundaries.
|
|
||||||
if (resource.image == VK_NULL_HANDLE && resource.view == VK_NULL_HANDLE) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
m_deferredRenderbufferReleases.push_back(
|
|
||||||
{resource.image, resource.allocation, resource.view, resource.unormTwinView, m_frameCounter});
|
|
||||||
resource.image = VK_NULL_HANDLE;
|
|
||||||
resource.allocation = nullptr;
|
|
||||||
resource.view = VK_NULL_HANDLE;
|
|
||||||
resource.unormTwinView = VK_NULL_HANDLE;
|
|
||||||
}
|
|
||||||
|
|
||||||
void VkRenderPassManager::CollectDeferredRenderbufferReleases(Bool destroyAll) {
|
|
||||||
if (m_deferredRenderbufferReleases.empty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const Uint64 retireAgeFrames = RetireAgeFrames();
|
|
||||||
std::erase_if(m_deferredRenderbufferReleases, [&](DeferredRenderbufferRelease& release) {
|
|
||||||
if (!destroyAll && m_frameCounter - release.deferredAtFrame < retireAgeFrames) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (release.view != VK_NULL_HANDLE) {
|
|
||||||
vkDestroyImageView(m_device, release.view, nullptr);
|
|
||||||
}
|
|
||||||
if (release.unormTwinView != VK_NULL_HANDLE) {
|
|
||||||
vkDestroyImageView(m_device, release.unormTwinView, nullptr);
|
|
||||||
}
|
|
||||||
if (release.image != VK_NULL_HANDLE) {
|
|
||||||
vmaDestroyImage(m_allocator, release.image, release.allocation);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void VkRenderPassManager::CollectRenderbufferGarbage() {
|
void VkRenderPassManager::CollectRenderbufferGarbage() {
|
||||||
// Two-phase reclamation: a dead renderbuffer's VkImage may still be referenced by
|
Vector<MG_State::GLState::RenderbufferObject*> deadRenderbuffers;
|
||||||
// command buffers submitted up to frames-in-flight frames ago (it was legally
|
deadRenderbuffers.reserve(m_renderbufferResources.size());
|
||||||
// attached and drawn right up to its deletion), so the first observation of an
|
for (auto& [renderbuffer, resource] : m_renderbufferResources) {
|
||||||
// expired weak reference only stamps the current frame counter; Destroy runs once
|
|
||||||
// enough frame boundaries have passed that the stamping frame's submission fence
|
|
||||||
// has provably been waited (see RetireAgeFrames).
|
|
||||||
const Uint64 retireAgeFrames = RetireAgeFrames();
|
|
||||||
for (auto it = m_renderbufferResources.begin(); it != m_renderbufferResources.end();) {
|
|
||||||
auto& resource = it->second;
|
|
||||||
const auto liveRenderbuffer = resource.renderbuffer.lock();
|
const auto liveRenderbuffer = resource.renderbuffer.lock();
|
||||||
if (liveRenderbuffer && liveRenderbuffer.get() == it->first) {
|
if (!liveRenderbuffer || liveRenderbuffer.get() != renderbuffer) {
|
||||||
resource.deadSinceFrame = RenderbufferResource::kNeverObservedDead;
|
deadRenderbuffers.emplace_back(renderbuffer);
|
||||||
++it;
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
if (resource.deadSinceFrame == RenderbufferResource::kNeverObservedDead) {
|
}
|
||||||
resource.deadSinceFrame = m_frameCounter;
|
for (auto* renderbuffer : deadRenderbuffers) {
|
||||||
++it;
|
auto resourceIt = m_renderbufferResources.find(renderbuffer);
|
||||||
continue;
|
if (resourceIt != m_renderbufferResources.end()) {
|
||||||
|
resourceIt->second.Destroy(m_device, m_allocator);
|
||||||
|
m_renderbufferResources.erase(resourceIt);
|
||||||
}
|
}
|
||||||
if (m_frameCounter - resource.deadSinceFrame < retireAgeFrames) {
|
m_pendingRenderbufferClears.erase(renderbuffer);
|
||||||
++it;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
m_pendingRenderbufferClears.erase(it->first);
|
|
||||||
resource.Destroy(m_device, m_allocator);
|
|
||||||
it = m_renderbufferResources.erase(it);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -318,47 +249,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const auto internalFormat = renderbuffer->GetInternalFormat();
|
const auto internalFormat = renderbuffer->GetInternalFormat();
|
||||||
// Three-channel color formats widen to their RGBA twin exactly like textures do
|
const VkFormat format = MG_Util::ConvertTextureInternalFormatToVkEnum(internalFormat);
|
||||||
// (VkTextureManager::ResolveTextureFormatInfo): blits/resolves between a
|
|
||||||
// renderbuffer and a texture of the same GL format then see one VkFormat.
|
|
||||||
const VkFormat format = [&]() -> VkFormat {
|
|
||||||
switch (internalFormat) {
|
|
||||||
case TextureInternalFormat::RGB:
|
|
||||||
case TextureInternalFormat::RGB8:
|
|
||||||
case TextureInternalFormat::R3G3B2:
|
|
||||||
case TextureInternalFormat::RGB4:
|
|
||||||
case TextureInternalFormat::RGB5:
|
|
||||||
return VK_FORMAT_R8G8B8A8_UNORM;
|
|
||||||
case TextureInternalFormat::SRGB8:
|
|
||||||
return VK_FORMAT_R8G8B8A8_SRGB;
|
|
||||||
case TextureInternalFormat::RGB8Snorm:
|
|
||||||
return VK_FORMAT_R8G8B8A8_SNORM;
|
|
||||||
case TextureInternalFormat::RGB10:
|
|
||||||
case TextureInternalFormat::RGB12:
|
|
||||||
case TextureInternalFormat::RGB16:
|
|
||||||
return VK_FORMAT_R16G16B16A16_UNORM;
|
|
||||||
case TextureInternalFormat::RGB16Snorm:
|
|
||||||
return VK_FORMAT_R16G16B16A16_SNORM;
|
|
||||||
case TextureInternalFormat::RGB16F:
|
|
||||||
return VK_FORMAT_R16G16B16A16_SFLOAT;
|
|
||||||
case TextureInternalFormat::RGB32F:
|
|
||||||
return VK_FORMAT_R32G32B32A32_SFLOAT;
|
|
||||||
case TextureInternalFormat::RGB8I:
|
|
||||||
return VK_FORMAT_R8G8B8A8_SINT;
|
|
||||||
case TextureInternalFormat::RGB8UI:
|
|
||||||
return VK_FORMAT_R8G8B8A8_UINT;
|
|
||||||
case TextureInternalFormat::RGB16I:
|
|
||||||
return VK_FORMAT_R16G16B16A16_SINT;
|
|
||||||
case TextureInternalFormat::RGB16UI:
|
|
||||||
return VK_FORMAT_R16G16B16A16_UINT;
|
|
||||||
case TextureInternalFormat::RGB32I:
|
|
||||||
return VK_FORMAT_R32G32B32A32_SINT;
|
|
||||||
case TextureInternalFormat::RGB32UI:
|
|
||||||
return VK_FORMAT_R32G32B32A32_UINT;
|
|
||||||
default:
|
|
||||||
return MG_Util::ConvertTextureInternalFormatToVkEnum(internalFormat);
|
|
||||||
}
|
|
||||||
}();
|
|
||||||
const VkImageAspectFlags aspect = ResolveImageAspectMaskForFormat(format);
|
const VkImageAspectFlags aspect = ResolveImageAspectMaskForFormat(format);
|
||||||
// Renderbuffers are never sampled (GL has no way to bind one to a sampler), so the
|
// Renderbuffers are never sampled (GL has no way to bind one to a sampler), so the
|
||||||
// usage set is attachment + transfer: transfer covers readback (vkCmdCopyImageToBuffer),
|
// usage set is attachment + transfer: transfer covers readback (vkCmdCopyImageToBuffer),
|
||||||
@@ -368,46 +259,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
: VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT) |
|
: VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT) |
|
||||||
VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
|
VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
|
||||||
|
|
||||||
// GL allows the implementation to allocate more samples than requested
|
|
||||||
// (glRenderbufferStorageMultisample only promises "at least"), and devices
|
|
||||||
// like llvmpipe expose 1x/4x but not 2x. Round the request up to the
|
|
||||||
// nearest supported count for this format.
|
|
||||||
if (renderbuffer->GetSamples() > 0) {
|
|
||||||
auto supportedIt = m_attachmentSampleCountsByFormat.find(format);
|
|
||||||
if (supportedIt == m_attachmentSampleCountsByFormat.end()) {
|
|
||||||
VkImageFormatProperties formatProperties{};
|
|
||||||
VkSampleCountFlags supported = VK_SAMPLE_COUNT_1_BIT;
|
|
||||||
if (vkGetPhysicalDeviceImageFormatProperties(m_physicalDevice, format, VK_IMAGE_TYPE_2D,
|
|
||||||
VK_IMAGE_TILING_OPTIMAL, imageUsage, 0,
|
|
||||||
&formatProperties) == VK_SUCCESS) {
|
|
||||||
supported = formatProperties.sampleCounts;
|
|
||||||
}
|
|
||||||
supportedIt = m_attachmentSampleCountsByFormat.emplace(format, supported).first;
|
|
||||||
}
|
|
||||||
const VkSampleCountFlags supported = supportedIt->second;
|
|
||||||
if ((supported & sampleCount) == 0) {
|
|
||||||
// Smallest supported count above the request, else the largest below it.
|
|
||||||
Uint32 rounded = 0;
|
|
||||||
for (Uint32 bit = static_cast<Uint32>(sampleCount) << 1; bit <= VK_SAMPLE_COUNT_64_BIT; bit <<= 1) {
|
|
||||||
if ((supported & bit) != 0) {
|
|
||||||
rounded = bit;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (rounded == 0) {
|
|
||||||
for (Uint32 bit = static_cast<Uint32>(sampleCount) >> 1; bit != 0; bit >>= 1) {
|
|
||||||
if ((supported & bit) != 0) {
|
|
||||||
rounded = bit;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (rounded != 0) {
|
|
||||||
sampleCount = static_cast<VkSampleCountFlagBits>(rounded);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
auto& resource = m_renderbufferResources[renderbuffer.get()];
|
auto& resource = m_renderbufferResources[renderbuffer.get()];
|
||||||
const Bool needsCreate =
|
const Bool needsCreate =
|
||||||
resource.image == VK_NULL_HANDLE ||
|
resource.image == VK_NULL_HANDLE ||
|
||||||
@@ -419,15 +270,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
resource.samples != renderbuffer->GetSamples();
|
resource.samples != renderbuffer->GetSamples();
|
||||||
if (!needsCreate) {
|
if (!needsCreate) {
|
||||||
resource.renderbuffer = renderbuffer;
|
resource.renderbuffer = renderbuffer;
|
||||||
// A new renderbuffer at a recycled address may adopt a compatible entry that
|
|
||||||
// was already stamped dead; it is alive again, so cancel the aging.
|
|
||||||
resource.deadSinceFrame = RenderbufferResource::kNeverObservedDead;
|
|
||||||
return &resource;
|
return &resource;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Respecify: park the old backing for aged destruction instead of destroying
|
|
||||||
// inline - it may still be referenced by in-flight command buffers.
|
|
||||||
DeferRenderbufferBackingRelease(resource);
|
|
||||||
resource.Destroy(m_device, m_allocator);
|
resource.Destroy(m_device, m_allocator);
|
||||||
resource.renderbuffer = renderbuffer;
|
resource.renderbuffer = renderbuffer;
|
||||||
|
|
||||||
@@ -445,12 +290,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
imageInfo.usage = imageUsage;
|
imageInfo.usage = imageUsage;
|
||||||
imageInfo.samples = sampleCount;
|
imageInfo.samples = sampleCount;
|
||||||
imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
||||||
// sRGB renderbuffers attach through their UNORM twin while GL_FRAMEBUFFER_SRGB
|
|
||||||
// is disabled, which needs a format-reinterpreting second view.
|
|
||||||
const Bool hasUnormTwin = ResolveSrgbAttachmentWriteFormat(format, false) != format;
|
|
||||||
if (hasUnormTwin) {
|
|
||||||
imageInfo.flags |= VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT;
|
|
||||||
}
|
|
||||||
|
|
||||||
VkImageFormatProperties imageFormatProperties{};
|
VkImageFormatProperties imageFormatProperties{};
|
||||||
const VkResult imageFormatResult = vkGetPhysicalDeviceImageFormatProperties(
|
const VkResult imageFormatResult = vkGetPhysicalDeviceImageFormatProperties(
|
||||||
@@ -485,11 +324,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
viewInfo.subresourceRange.layerCount = 1;
|
viewInfo.subresourceRange.layerCount = 1;
|
||||||
VK_VERIFY(vkCreateImageView(m_device, &viewInfo, nullptr, &resource.view),
|
VK_VERIFY(vkCreateImageView(m_device, &viewInfo, nullptr, &resource.view),
|
||||||
"vkCreateImageView(renderbuffer)");
|
"vkCreateImageView(renderbuffer)");
|
||||||
if (hasUnormTwin) {
|
|
||||||
viewInfo.format = ResolveSrgbAttachmentWriteFormat(format, false);
|
|
||||||
VK_VERIFY(vkCreateImageView(m_device, &viewInfo, nullptr, &resource.unormTwinView),
|
|
||||||
"vkCreateImageView(renderbuffer unorm twin)");
|
|
||||||
}
|
|
||||||
|
|
||||||
resource.layout = VK_IMAGE_LAYOUT_UNDEFINED;
|
resource.layout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||||
resource.format = format;
|
resource.format = format;
|
||||||
@@ -541,13 +375,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
pending.renderbuffer = renderbuffer;
|
pending.renderbuffer = renderbuffer;
|
||||||
pending.payload.mask |= clearPayload.mask;
|
pending.payload.mask |= clearPayload.mask;
|
||||||
if ((clearPayload.mask & GL_COLOR_BUFFER_BIT) != 0) {
|
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.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) {
|
if ((clearPayload.mask & GL_DEPTH_BUFFER_BIT) != 0) {
|
||||||
pending.payload.depth = clearPayload.depth;
|
pending.payload.depth = clearPayload.depth;
|
||||||
@@ -592,18 +420,12 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
}
|
}
|
||||||
|
|
||||||
VkRenderPassManager::HashType VkRenderPassManager::ComputeHash(
|
VkRenderPassManager::HashType VkRenderPassManager::ComputeHash(
|
||||||
const MG_State::GLState::FramebufferObject& fbo, Uint32 swapchainImageIndex, Bool includePendingClear,
|
const MG_State::GLState::FramebufferObject& fbo, Uint32 swapchainImageIndex, Bool includePendingClear) {
|
||||||
Bool includeDefaultFboDepthStencil) {
|
|
||||||
XXHASH_VERIFY(XXH64_reset(m_hashState, m_config.CacheVersion));
|
XXHASH_VERIFY(XXH64_reset(m_hashState, m_config.CacheVersion));
|
||||||
const Bool isDefaultFbo = fbo.IsDefaultFramebuffer();
|
const Bool isDefaultFbo = fbo.IsDefaultFramebuffer();
|
||||||
if (isDefaultFbo) {
|
if (isDefaultFbo) {
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState, &swapchainImageIndex, sizeof(swapchainImageIndex)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &swapchainImageIndex, sizeof(swapchainImageIndex)));
|
||||||
}
|
}
|
||||||
// sRGB attachments switch between their sRGB and UNORM-twin views with this
|
|
||||||
// capability (ResolveSrgbAttachmentWriteFormat), changing the render pass formats.
|
|
||||||
const Bool framebufferSrgbEnabled =
|
|
||||||
MG_State::pGLContext->IsCapabilityEnabled(MobileGL::CapabilityInput::FramebufferSrgb);
|
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState, &framebufferSrgbEnabled, sizeof(framebufferSrgbEnabled)));
|
|
||||||
auto& drawBuffers = fbo.GetDrawBuffers();
|
auto& drawBuffers = fbo.GetDrawBuffers();
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState, drawBuffers.data(), drawBuffers.size() * sizeof(drawBuffers[0])));
|
XXHASH_VERIFY(XXH64_update(m_hashState, drawBuffers.data(), drawBuffers.size() * sizeof(drawBuffers[0])));
|
||||||
auto readBuffer = fbo.GetReadBuffer();
|
auto readBuffer = fbo.GetReadBuffer();
|
||||||
@@ -677,17 +499,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
attachment <= FramebufferAttachmentType::BackRight);
|
attachment <= FramebufferAttachmentType::BackRight);
|
||||||
if (isDefaultColorAttachment) {
|
if (isDefaultColorAttachment) {
|
||||||
currentLayout = m_swapchainObject.GetImageLayout(swapchainImageIndex);
|
currentLayout = m_swapchainObject.GetImageLayout(swapchainImageIndex);
|
||||||
// Content validity feeds the attachment's loadOp (see the
|
|
||||||
// creation path), so it must key the cache as well.
|
|
||||||
if (!m_swapchainObject.IsImageContentDefined(swapchainImageIndex)) {
|
|
||||||
currentLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
|
||||||
}
|
|
||||||
} else if (attachment == FramebufferAttachmentType::Depth ||
|
} else if (attachment == FramebufferAttachmentType::Depth ||
|
||||||
attachment == FramebufferAttachmentType::Stencil) {
|
attachment == FramebufferAttachmentType::Stencil) {
|
||||||
currentLayout = m_swapchainObject.GetDepthStencilImageLayout(swapchainImageIndex);
|
currentLayout = m_swapchainObject.GetDepthStencilImageLayout(swapchainImageIndex);
|
||||||
if (!m_swapchainObject.IsDepthStencilContentDefined(swapchainImageIndex)) {
|
|
||||||
currentLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
auto* textureResource = m_textureManager.SyncTextureAndGetDescriptor(*texture);
|
auto* textureResource = m_textureManager.SyncTextureAndGetDescriptor(*texture);
|
||||||
@@ -742,49 +556,14 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
combineFramebufferAttachmentObjHash(drawbuf);
|
combineFramebufferAttachmentObjHash(drawbuf);
|
||||||
}
|
}
|
||||||
|
|
||||||
// The depth-less default-FBO flavor omits the depth/stencil attachment
|
combineFramebufferAttachmentObjHash(FramebufferAttachmentType::Depth);
|
||||||
// entirely, so it must hash differently from the depth-full flavor.
|
combineFramebufferAttachmentObjHash(FramebufferAttachmentType::Stencil);
|
||||||
const Bool depthStencilIncluded = !isDefaultFbo || includeDefaultFboDepthStencil;
|
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState, &depthStencilIncluded, sizeof(depthStencilIncluded)));
|
|
||||||
if (depthStencilIncluded) {
|
|
||||||
combineFramebufferAttachmentObjHash(FramebufferAttachmentType::Depth);
|
|
||||||
combineFramebufferAttachmentObjHash(FramebufferAttachmentType::Stencil);
|
|
||||||
}
|
|
||||||
|
|
||||||
return XXH64_digest(m_hashState);
|
return XXH64_digest(m_hashState);
|
||||||
}
|
}
|
||||||
|
|
||||||
RenderPassEntry& VkRenderPassManager::GetOrCreateRenderPass(const MG_State::GLState::FramebufferObject& fbo,
|
RenderPassEntry& VkRenderPassManager::GetOrCreateRenderPass(const MG_State::GLState::FramebufferObject& fbo,
|
||||||
Uint32 swapchainImageIndex,
|
Uint32 swapchainImageIndex) {
|
||||||
Bool drawUsesDepthStencil) {
|
|
||||||
// Resolve the default-FBO depth flavor (see the header comment): keep the
|
|
||||||
// depth attachment when the caller needs it, when a depth/stencil clear is
|
|
||||||
// pending, or when the active pass already carries it (escalate-only, so
|
|
||||||
// alternating depth-less draws never split an established depth pass).
|
|
||||||
Bool includeDefaultFboDepthStencil = true;
|
|
||||||
if (fbo.IsDefaultFramebuffer()) {
|
|
||||||
Bool activeDefaultHasDepthStencil = false;
|
|
||||||
if (const auto* active = GetActiveRenderPass()) {
|
|
||||||
Bool activeIsSwapchainPass = false;
|
|
||||||
Bool activeHasSwapchainDepthStencil = false;
|
|
||||||
for (const auto& tracked : active->trackedAttachmentLayouts) {
|
|
||||||
activeIsSwapchainPass |= tracked.target == TrackedAttachmentTarget::SwapchainColor;
|
|
||||||
activeHasSwapchainDepthStencil |=
|
|
||||||
tracked.target == TrackedAttachmentTarget::SwapchainDepthStencil;
|
|
||||||
}
|
|
||||||
activeDefaultHasDepthStencil = activeIsSwapchainPass && activeHasSwapchainDepthStencil;
|
|
||||||
}
|
|
||||||
const auto& defaultDepthAtt = fbo.GetAttachment(FramebufferAttachmentType::Depth);
|
|
||||||
const auto& defaultStencilAtt = fbo.GetAttachment(FramebufferAttachmentType::Stencil);
|
|
||||||
const Bool pendingDepthStencilClear =
|
|
||||||
(defaultDepthAtt.IsTexture() && m_clearManager.HasPendingClear(defaultDepthAtt)) ||
|
|
||||||
HasPendingRenderbufferClear(defaultDepthAtt) ||
|
|
||||||
(defaultStencilAtt.IsTexture() && m_clearManager.HasPendingClear(defaultStencilAtt)) ||
|
|
||||||
HasPendingRenderbufferClear(defaultStencilAtt);
|
|
||||||
includeDefaultFboDepthStencil =
|
|
||||||
drawUsesDepthStencil || activeDefaultHasDepthStencil || pendingDepthStencilClear;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto hasPendingClearOnFramebuffer = [&]() -> Bool {
|
auto hasPendingClearOnFramebuffer = [&]() -> Bool {
|
||||||
const auto& drawBuffers = fbo.GetDrawBuffers();
|
const auto& drawBuffers = fbo.GetDrawBuffers();
|
||||||
for (auto attachment : drawBuffers) {
|
for (auto attachment : drawBuffers) {
|
||||||
@@ -834,7 +613,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
m_rpFastFboVersion == fbo.GetObjectVersion() && m_rpFastSwapchainIndex == swapchainImageIndex &&
|
m_rpFastFboVersion == fbo.GetObjectVersion() && m_rpFastSwapchainIndex == swapchainImageIndex &&
|
||||||
m_rpFastTexEpoch == m_textureManager.GetTextureImageEpoch() &&
|
m_rpFastTexEpoch == m_textureManager.GetTextureImageEpoch() &&
|
||||||
m_rpFastRbEpoch == m_renderbufferImageEpoch &&
|
m_rpFastRbEpoch == m_renderbufferImageEpoch &&
|
||||||
(!fbo.IsDefaultFramebuffer() || m_rpFastHadDepthStencil == includeDefaultFboDepthStencil) &&
|
|
||||||
m_rpFastRenderPassHash == activeRenderPass->hash && !hasPendingClearOnFramebuffer()) {
|
m_rpFastRenderPassHash == activeRenderPass->hash && !hasPendingClearOnFramebuffer()) {
|
||||||
auto activeIt = m_renderPasses.find(activeRenderPass->hash);
|
auto activeIt = m_renderPasses.find(activeRenderPass->hash);
|
||||||
if (activeIt != m_renderPasses.end()) {
|
if (activeIt != m_renderPasses.end()) {
|
||||||
@@ -843,7 +621,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto compatibilityHash = ComputeHash(fbo, swapchainImageIndex, false, includeDefaultFboDepthStencil);
|
auto compatibilityHash = ComputeHash(fbo, swapchainImageIndex, false);
|
||||||
if (activeRenderPass != nullptr &&
|
if (activeRenderPass != nullptr &&
|
||||||
activeRenderPass->CompatibleWith(compatibilityHash) &&
|
activeRenderPass->CompatibleWith(compatibilityHash) &&
|
||||||
!hasPendingClearOnFramebuffer()) {
|
!hasPendingClearOnFramebuffer()) {
|
||||||
@@ -860,11 +638,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
m_rpFastTexEpoch = m_textureManager.GetTextureImageEpoch();
|
m_rpFastTexEpoch = m_textureManager.GetTextureImageEpoch();
|
||||||
m_rpFastRbEpoch = m_renderbufferImageEpoch;
|
m_rpFastRbEpoch = m_renderbufferImageEpoch;
|
||||||
m_rpFastRenderPassHash = activeRenderPass->hash;
|
m_rpFastRenderPassHash = activeRenderPass->hash;
|
||||||
m_rpFastHadDepthStencil = activeIt->second.hasDepthStencilAttachment;
|
|
||||||
activeIt->second.lastUsedFrame = m_frameCounter;
|
activeIt->second.lastUsedFrame = m_frameCounter;
|
||||||
return activeIt->second;
|
return activeIt->second;
|
||||||
}
|
}
|
||||||
auto hash = ComputeHash(fbo, swapchainImageIndex, true, includeDefaultFboDepthStencil);
|
auto hash = ComputeHash(fbo, swapchainImageIndex, true);
|
||||||
auto it = m_renderPasses.find(hash);
|
auto it = m_renderPasses.find(hash);
|
||||||
if (it != m_renderPasses.end()) {
|
if (it != m_renderPasses.end()) {
|
||||||
it->second.lastUsedFrame = m_frameCounter;
|
it->second.lastUsedFrame = m_frameCounter;
|
||||||
@@ -945,16 +722,14 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
if (rbHasClear &&
|
if (rbHasClear &&
|
||||||
MG_Util::GetBaseInternalFormatComponentCount(renderbuffer->GetInternalFormat()) == 3) {
|
MG_Util::GetBaseInternalFormatComponentCount(renderbuffer->GetInternalFormat()) == 3) {
|
||||||
// RGB renderbuffers are backed by an RGBA image; the missing alpha reads as 1.
|
// RGB renderbuffers are backed by an RGBA image; the missing alpha reads as 1.
|
||||||
ForceOpaqueClearAlpha(rbClearPayload);
|
rbClearPayload.color =
|
||||||
|
FloatVec4(rbClearPayload.color.x(), rbClearPayload.color.y(),
|
||||||
|
rbClearPayload.color.z(), 1.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
const VkImageLayout trackedRbLayout = rbResource->layout;
|
const VkImageLayout trackedRbLayout = rbResource->layout;
|
||||||
const Bool rbFramebufferSrgb =
|
|
||||||
MG_State::pGLContext->IsCapabilityEnabled(MobileGL::CapabilityInput::FramebufferSrgb);
|
|
||||||
const VkFormat rbAttachmentFormat =
|
|
||||||
ResolveSrgbAttachmentWriteFormat(rbResource->format, rbFramebufferSrgb);
|
|
||||||
rbDesc.flags = 0;
|
rbDesc.flags = 0;
|
||||||
rbDesc.format = rbAttachmentFormat;
|
rbDesc.format = rbResource->format;
|
||||||
rbDesc.samples = rbResource->sampleCount;
|
rbDesc.samples = rbResource->sampleCount;
|
||||||
rbDesc.loadOp = rbHasClear ? VK_ATTACHMENT_LOAD_OP_CLEAR :
|
rbDesc.loadOp = rbHasClear ? VK_ATTACHMENT_LOAD_OP_CLEAR :
|
||||||
(trackedRbLayout == VK_IMAGE_LAYOUT_UNDEFINED ? VK_ATTACHMENT_LOAD_OP_DONT_CARE
|
(trackedRbLayout == VK_IMAGE_LAYOUT_UNDEFINED ? VK_ATTACHMENT_LOAD_OP_DONT_CARE
|
||||||
@@ -989,8 +764,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
.finalLayout = rbDesc.finalLayout,
|
.finalLayout = rbDesc.finalLayout,
|
||||||
});
|
});
|
||||||
textureResources.emplace_back(nullptr);
|
textureResources.emplace_back(nullptr);
|
||||||
attachmentViews.emplace_back(rbAttachmentFormat != rbResource->format ? rbResource->unormTwinView
|
attachmentViews.emplace_back(rbResource->view);
|
||||||
: rbResource->view);
|
|
||||||
MOBILEGL_ASSERT(attachmentViews.back() != VK_NULL_HANDLE,
|
MOBILEGL_ASSERT(attachmentViews.back() != VK_NULL_HANDLE,
|
||||||
"GetOrCreateRenderPass: renderbuffer view missing at color attachment %d", i);
|
"GetOrCreateRenderPass: renderbuffer view missing at color attachment %d", i);
|
||||||
|
|
||||||
@@ -1059,13 +833,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
MOBILEGL_ASSERT(swapchainImageIndex < swapchainViews.size(),
|
MOBILEGL_ASSERT(swapchainImageIndex < swapchainViews.size(),
|
||||||
"GetOrCreateRenderPass: swapchain image index out of range");
|
"GetOrCreateRenderPass: swapchain image index out of range");
|
||||||
trackedColorLayout = m_swapchainObject.GetImageLayout(swapchainImageIndex);
|
trackedColorLayout = m_swapchainObject.GetImageLayout(swapchainImageIndex);
|
||||||
// EGL: a presented color buffer's content is undefined when its
|
|
||||||
// image comes back around (EGL_BUFFER_DESTROYED, the default
|
|
||||||
// swap behaviour) - skip the tile load instead of reloading
|
|
||||||
// stale pixels nobody may rely on.
|
|
||||||
if (!hasClear && !m_swapchainObject.IsImageContentDefined(swapchainImageIndex)) {
|
|
||||||
trackedColorLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
|
||||||
}
|
|
||||||
trackedAttachmentLayouts.emplace_back(TrackedAttachmentLayoutInfo {
|
trackedAttachmentLayouts.emplace_back(TrackedAttachmentLayoutInfo {
|
||||||
.target = TrackedAttachmentTarget::SwapchainColor,
|
.target = TrackedAttachmentTarget::SwapchainColor,
|
||||||
.swapchainImageIndex = swapchainImageIndex,
|
.swapchainImageIndex = swapchainImageIndex,
|
||||||
@@ -1079,15 +846,12 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
MOBILEGL_ASSERT(textureResource,
|
MOBILEGL_ASSERT(textureResource,
|
||||||
"GetOrCreateRenderPass: SyncTextureAndGetDescriptor failed at color attachment %d", i);
|
"GetOrCreateRenderPass: SyncTextureAndGetDescriptor failed at color attachment %d", i);
|
||||||
textureResources.emplace_back(textureResource);
|
textureResources.emplace_back(textureResource);
|
||||||
desc.format = ResolveSrgbAttachmentWriteFormat(
|
desc.format = textureResource->format;
|
||||||
textureResource->format,
|
|
||||||
MG_State::pGLContext->IsCapabilityEnabled(MobileGL::CapabilityInput::FramebufferSrgb));
|
|
||||||
attachmentSampleCount = textureResource->sampleCount;
|
attachmentSampleCount = textureResource->sampleCount;
|
||||||
trackedColorLayout = textureResource->layout;
|
trackedColorLayout = textureResource->layout;
|
||||||
trackedAttachmentLayouts.emplace_back(TrackedAttachmentLayoutInfo {
|
trackedAttachmentLayouts.emplace_back(TrackedAttachmentLayoutInfo {
|
||||||
.target = TrackedAttachmentTarget::Texture,
|
.target = TrackedAttachmentTarget::Texture,
|
||||||
.texture = att.GetTexture(),
|
.texture = att.GetTexture(),
|
||||||
.textureRaw = att.GetTexture().get(),
|
|
||||||
.textureMipLevel = attachmentMipLevel,
|
.textureMipLevel = attachmentMipLevel,
|
||||||
.finalLayout = desc.finalLayout,
|
.finalLayout = desc.finalLayout,
|
||||||
});
|
});
|
||||||
@@ -1151,12 +915,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
};
|
};
|
||||||
const auto* selectedDepthStencilAttachment = isUsableDepthStencilAttachment(depthAtt) ? &depthAtt :
|
const auto* selectedDepthStencilAttachment = isUsableDepthStencilAttachment(depthAtt) ? &depthAtt :
|
||||||
(isUsableDepthStencilAttachment(stencilAtt) ? &stencilAtt : nullptr);
|
(isUsableDepthStencilAttachment(stencilAtt) ? &stencilAtt : nullptr);
|
||||||
// Depth-less default-FBO flavor: nothing in this pass touches depth/stencil
|
|
||||||
// and their content is undefined anyway (EGL swap), so drop the attachment
|
|
||||||
// and its whole tile load + store.
|
|
||||||
if (isDefaultFbo && !includeDefaultFboDepthStencil) {
|
|
||||||
selectedDepthStencilAttachment = nullptr;
|
|
||||||
}
|
|
||||||
const Bool hasDistinctDepthAndStencilAttachments =
|
const Bool hasDistinctDepthAndStencilAttachments =
|
||||||
isUsableDepthStencilAttachment(depthAtt) && isUsableDepthStencilAttachment(stencilAtt) &&
|
isUsableDepthStencilAttachment(depthAtt) && isUsableDepthStencilAttachment(stencilAtt) &&
|
||||||
!sameDepthStencilAttachmentObject(depthAtt, stencilAtt);
|
!sameDepthStencilAttachmentObject(depthAtt, stencilAtt);
|
||||||
@@ -1175,12 +933,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
VkImageLayout trackedDepthLayout = isDefaultFbo ?
|
VkImageLayout trackedDepthLayout = isDefaultFbo ?
|
||||||
m_swapchainObject.GetDepthStencilImageLayout(swapchainImageIndex) :
|
m_swapchainObject.GetDepthStencilImageLayout(swapchainImageIndex) :
|
||||||
VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
|
VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
|
||||||
// EGL 1.5 §3.10.1: every ancillary (depth/stencil) buffer's content is
|
|
||||||
// undefined after a swap, so the first default-FBO pass of a frame can
|
|
||||||
// skip the depth/stencil tile load outright.
|
|
||||||
if (isDefaultFbo && !m_swapchainObject.IsDepthStencilContentDefined(swapchainImageIndex)) {
|
|
||||||
trackedDepthLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
|
||||||
}
|
|
||||||
depthAttachmentDescription.flags = 0;
|
depthAttachmentDescription.flags = 0;
|
||||||
VkSampleCountFlagBits depthAttachmentSampleCount = VK_SAMPLE_COUNT_1_BIT;
|
VkSampleCountFlagBits depthAttachmentSampleCount = VK_SAMPLE_COUNT_1_BIT;
|
||||||
Int depthAttachmentId = 0;
|
Int depthAttachmentId = 0;
|
||||||
@@ -1264,7 +1016,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
trackedAttachmentLayouts.emplace_back(TrackedAttachmentLayoutInfo {
|
trackedAttachmentLayouts.emplace_back(TrackedAttachmentLayoutInfo {
|
||||||
.target = TrackedAttachmentTarget::Texture,
|
.target = TrackedAttachmentTarget::Texture,
|
||||||
.texture = selectedDepthStencilAttachment->GetTexture(),
|
.texture = selectedDepthStencilAttachment->GetTexture(),
|
||||||
.textureRaw = selectedDepthStencilAttachment->GetTexture().get(),
|
|
||||||
.textureMipLevel = attachmentMipLevel,
|
.textureMipLevel = attachmentMipLevel,
|
||||||
.finalLayout = depthAttachmentDescription.finalLayout,
|
.finalLayout = depthAttachmentDescription.finalLayout,
|
||||||
});
|
});
|
||||||
@@ -1310,22 +1061,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
}
|
}
|
||||||
const Bool hasDepthStencilAttachment = depthAttachmentRef.attachment != VK_ATTACHMENT_UNUSED;
|
const Bool hasDepthStencilAttachment = depthAttachmentRef.attachment != VK_ATTACHMENT_UNUSED;
|
||||||
|
|
||||||
// Declare only the used colour-reference span. The GL draw-buffer array
|
|
||||||
// always spans 8 slots, so passes used to declare colorAttachmentCount=8
|
|
||||||
// with trailing VK_ATTACHMENT_UNUSED holes - and Adreno configures its
|
|
||||||
// per-pixel render-backend/export path from the DECLARED count, so every
|
|
||||||
// fragment of every pass paid the 8-target export cost (measured on
|
|
||||||
// Adreno 650 / MC 26.2: 11.9 -> 7.5 ms of GPU time per frame, with the
|
|
||||||
// single-quad swapchain blit pass alone dropping 1.26 -> 0.40 ms).
|
|
||||||
// Interior GL_NONE holes keep their slots so fragment-output locations
|
|
||||||
// still line up; a fragment output at a location past the trimmed count
|
|
||||||
// is discarded, which is exactly GL's semantic for writing to a draw
|
|
||||||
// buffer set to GL_NONE.
|
|
||||||
while (!colorAttachmentRefs.empty() &&
|
|
||||||
colorAttachmentRefs.back().attachment == VK_ATTACHMENT_UNUSED) {
|
|
||||||
colorAttachmentRefs.pop_back();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Subpass
|
// Subpass
|
||||||
VkSubpassDescription subpassDesc;
|
VkSubpassDescription subpassDesc;
|
||||||
subpassDesc.flags = 0;
|
subpassDesc.flags = 0;
|
||||||
@@ -1443,14 +1178,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
void VkRenderPassManager::OnPresent() {
|
void VkRenderPassManager::OnPresent() {
|
||||||
++m_frameCounter;
|
++m_frameCounter;
|
||||||
|
|
||||||
// Runs every frame boundary, ahead of the render-pass sweep gate below: the walk
|
|
||||||
// is O(#renderbuffer resources) — single digits in practice — and per-frame
|
|
||||||
// invocation keeps dead-resource reclaim latency at the aging bound instead of
|
|
||||||
// coupling it to renderbuffer *use* (the GetOrCreateRenderbufferResource call
|
|
||||||
// site never runs again once an app stops using renderbuffers).
|
|
||||||
CollectRenderbufferGarbage();
|
|
||||||
CollectDeferredRenderbufferReleases(/*destroyAll=*/false);
|
|
||||||
|
|
||||||
// Sweep occasionally; evict entries whose last use is far past every
|
// Sweep occasionally; evict entries whose last use is far past every
|
||||||
// in-flight frame so their VkRenderPass/VkFramebuffer can be destroyed
|
// in-flight frame so their VkRenderPass/VkFramebuffer can be destroyed
|
||||||
// safely (RenderPassEntry's destructor releases the handles).
|
// safely (RenderPassEntry's destructor releases the handles).
|
||||||
@@ -1460,12 +1187,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Collect the dying handles and notify once after the loop: pipelines hashed
|
|
||||||
// on them share the entries' >kRetireAgeFrames idleness (they are only bound
|
|
||||||
// by draws that hit those entries), so the observer may destroy them
|
|
||||||
// immediately - and a single batched notification costs one pipeline-cache
|
|
||||||
// scan instead of one per evicted pass.
|
|
||||||
Vector<VkRenderPass> destroyedRenderPasses;
|
|
||||||
const Uint64 activeHash = s_hasActiveRenderPass ? s_activeRenderPass.hash : 0;
|
const Uint64 activeHash = s_hasActiveRenderPass ? s_activeRenderPass.hash : 0;
|
||||||
for (auto it = m_renderPasses.begin(); it != m_renderPasses.end();) {
|
for (auto it = m_renderPasses.begin(); it != m_renderPasses.end();) {
|
||||||
const Bool isActive = s_hasActiveRenderPass && it->first == activeHash;
|
const Bool isActive = s_hasActiveRenderPass && it->first == activeHash;
|
||||||
@@ -1473,15 +1194,11 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
if (m_rpFastValid && m_rpFastRenderPassHash == it->first) {
|
if (m_rpFastValid && m_rpFastRenderPassHash == it->first) {
|
||||||
m_rpFastValid = false;
|
m_rpFastValid = false;
|
||||||
}
|
}
|
||||||
destroyedRenderPasses.push_back(it->second.renderPass);
|
|
||||||
it = m_renderPasses.erase(it);
|
it = m_renderPasses.erase(it);
|
||||||
} else {
|
} else {
|
||||||
++it;
|
++it;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!destroyedRenderPasses.empty() && m_evictionObserver != nullptr) {
|
|
||||||
m_evictionObserver->OnRenderPassesDestroyed(destroyedRenderPasses);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Bool VkRenderPassManager::BeginRenderPass(VkCommandBuffer commandBuffer, RenderPassEntry& renderPassEntry) {
|
Bool VkRenderPassManager::BeginRenderPass(VkCommandBuffer commandBuffer, RenderPassEntry& renderPassEntry) {
|
||||||
@@ -1515,8 +1232,12 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ((clearPayload.mask & GL_COLOR_BUFFER_BIT) != 0) {
|
if ((clearPayload.mask & GL_COLOR_BUFFER_BIT) != 0) {
|
||||||
clearValues[pending.attachmentIndex].color =
|
clearValues[pending.attachmentIndex].color = {
|
||||||
MakeVkClearColorValue(clearPayload, ColorFormatLacksAlpha(liveTexture.get()));
|
clearPayload.color.x(),
|
||||||
|
clearPayload.color.y(),
|
||||||
|
clearPayload.color.z(),
|
||||||
|
ResolveColorClearAlpha(liveTexture.get(), clearPayload.color.w())
|
||||||
|
};
|
||||||
}
|
}
|
||||||
if ((clearPayload.mask & GL_DEPTH_BUFFER_BIT) != 0) {
|
if ((clearPayload.mask & GL_DEPTH_BUFFER_BIT) != 0) {
|
||||||
clearValues[pending.attachmentIndex].depthStencil.depth = clearPayload.depth;
|
clearValues[pending.attachmentIndex].depthStencil.depth = clearPayload.depth;
|
||||||
@@ -1530,17 +1251,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
renderPassBeginInfo.pClearValues = clearValues.data();
|
renderPassBeginInfo.pClearValues = clearValues.data();
|
||||||
|
|
||||||
vkCmdBeginRenderPass(commandBuffer, &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
|
vkCmdBeginRenderPass(commandBuffer, &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
|
||||||
// Pre-pass stream bookkeeping: this pass's attachment images are now
|
|
||||||
// referenced by the open frame recording.
|
|
||||||
if (s_textureManager != nullptr) {
|
|
||||||
for (const auto& tracked : renderPassEntry.trackedAttachmentLayouts) {
|
|
||||||
if (tracked.target == TrackedAttachmentTarget::Texture) {
|
|
||||||
if (const auto texture = tracked.texture.lock()) {
|
|
||||||
s_textureManager->StampTextureRecordingUse(texture.get());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (const auto& pending: renderPassEntry.pendingClearAttachments) {
|
for (const auto& pending: renderPassEntry.pendingClearAttachments) {
|
||||||
if (pending.hasInlinePayload) {
|
if (pending.hasInlinePayload) {
|
||||||
if (s_renderPassManager != nullptr) {
|
if (s_renderPassManager != nullptr) {
|
||||||
@@ -1593,15 +1303,11 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
case TrackedAttachmentTarget::SwapchainColor:
|
case TrackedAttachmentTarget::SwapchainColor:
|
||||||
MOBILEGL_ASSERT(s_swapchainObject != nullptr, "EndRenderPass: swapchain object is null");
|
MOBILEGL_ASSERT(s_swapchainObject != nullptr, "EndRenderPass: swapchain object is null");
|
||||||
s_swapchainObject->SetImageLayout(trackedAttachment.swapchainImageIndex, trackedAttachment.finalLayout);
|
s_swapchainObject->SetImageLayout(trackedAttachment.swapchainImageIndex, trackedAttachment.finalLayout);
|
||||||
// The pass stored into the attachment: its content is defined
|
|
||||||
// until the image is next presented.
|
|
||||||
s_swapchainObject->SetImageContentDefined(trackedAttachment.swapchainImageIndex, true);
|
|
||||||
break;
|
break;
|
||||||
case TrackedAttachmentTarget::SwapchainDepthStencil:
|
case TrackedAttachmentTarget::SwapchainDepthStencil:
|
||||||
MOBILEGL_ASSERT(s_swapchainObject != nullptr, "EndRenderPass: swapchain object is null");
|
MOBILEGL_ASSERT(s_swapchainObject != nullptr, "EndRenderPass: swapchain object is null");
|
||||||
s_swapchainObject->SetDepthStencilImageLayout(trackedAttachment.swapchainImageIndex,
|
s_swapchainObject->SetDepthStencilImageLayout(trackedAttachment.swapchainImageIndex,
|
||||||
trackedAttachment.finalLayout);
|
trackedAttachment.finalLayout);
|
||||||
s_swapchainObject->SetDepthStencilContentDefined(trackedAttachment.swapchainImageIndex, true);
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
MOBILEGL_ASSERT(false, "EndRenderPass: unsupported tracked attachment target=%d",
|
MOBILEGL_ASSERT(false, "EndRenderPass: unsupported tracked attachment target=%d",
|
||||||
|
|||||||
@@ -16,7 +16,6 @@
|
|||||||
#include "MG_State/GLState/FramebufferState/FramebufferObject.h"
|
#include "MG_State/GLState/FramebufferState/FramebufferObject.h"
|
||||||
|
|
||||||
#include <Includes.h>
|
#include <Includes.h>
|
||||||
#include <unordered_map>
|
|
||||||
#include <vk_mem_alloc.h>
|
#include <vk_mem_alloc.h>
|
||||||
|
|
||||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||||
@@ -43,11 +42,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
struct TrackedAttachmentLayoutInfo {
|
struct TrackedAttachmentLayoutInfo {
|
||||||
TrackedAttachmentTarget target = TrackedAttachmentTarget::Texture;
|
TrackedAttachmentTarget target = TrackedAttachmentTarget::Texture;
|
||||||
WeakPtr<MG_State::GLState::ITextureObject> texture;
|
WeakPtr<MG_State::GLState::ITextureObject> texture;
|
||||||
// Identity-compare shortcut for the per-draw "does the active pass use
|
|
||||||
// this sampled texture" probe: comparing this against a LIVE texture's
|
|
||||||
// address needs no weak_ptr::lock (two refcount atomics per probe).
|
|
||||||
// May dangle once the texture dies - compare only, never dereference.
|
|
||||||
MG_State::GLState::ITextureObject* textureRaw = nullptr;
|
|
||||||
WeakPtr<MG_State::GLState::RenderbufferObject> renderbuffer;
|
WeakPtr<MG_State::GLState::RenderbufferObject> renderbuffer;
|
||||||
Uint32 textureMipLevel = 0;
|
Uint32 textureMipLevel = 0;
|
||||||
Uint32 swapchainImageIndex = 0;
|
Uint32 swapchainImageIndex = 0;
|
||||||
@@ -163,53 +157,19 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
class VkRenderPassManager {
|
class VkRenderPassManager {
|
||||||
public:
|
public:
|
||||||
using HashType = Uint64;
|
using HashType = Uint64;
|
||||||
|
|
||||||
// Notified once per OnPresent sweep with every aged-out entry's VkRenderPass
|
|
||||||
// value: pipelines are hashed on the raw handle, and once destroyed the value
|
|
||||||
// may be recycled for an incompatible pass, so dependent caches must purge
|
|
||||||
// everything keyed on them before any new pass can be created (the sweep and
|
|
||||||
// the notification run back-to-back with no creation in between; observers
|
|
||||||
// compare the values, never dereference them). Batched so a mass-idle cohort
|
|
||||||
// (shader-pack switch, dimension exit) costs the observer one pipeline-cache
|
|
||||||
// scan, not one per dying pass. The wholesale paths
|
|
||||||
// (Shutdown/RecreateSwapchain) do not notify - their callers already drop
|
|
||||||
// every pipeline outright.
|
|
||||||
class IEvictionObserver {
|
|
||||||
public:
|
|
||||||
virtual ~IEvictionObserver() = default;
|
|
||||||
virtual void OnRenderPassesDestroyed(const Vector<VkRenderPass>& renderPasses) = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
VkRenderPassManager(VkDevice device,
|
VkRenderPassManager(VkDevice device,
|
||||||
VkPhysicalDevice physicalDevice, VmaAllocator allocator, const VulkanRendererConfig& config,
|
VkPhysicalDevice physicalDevice, VmaAllocator allocator, const VulkanRendererConfig& config,
|
||||||
VkClearManager& clearManager, VkTextureManager& textureManager, SwapchainObject& swapchainObject);
|
VkClearManager& clearManager, VkTextureManager& textureManager, SwapchainObject& swapchainObject);
|
||||||
~VkRenderPassManager();
|
~VkRenderPassManager();
|
||||||
|
|
||||||
// Observer may be null (no notifications). Not owned.
|
|
||||||
void SetEvictionObserver(IEvictionObserver* observer) { m_evictionObserver = observer; }
|
|
||||||
|
|
||||||
Bool Initialize();
|
Bool Initialize();
|
||||||
void Shutdown();
|
void Shutdown();
|
||||||
|
|
||||||
HashType ComputeHash(
|
HashType ComputeHash(
|
||||||
const MG_State::GLState::FramebufferObject& fbo,
|
const MG_State::GLState::FramebufferObject& fbo,
|
||||||
Uint32 swapchainImageIndex,
|
Uint32 swapchainImageIndex,
|
||||||
Bool includePendingClear = true,
|
Bool includePendingClear = true);
|
||||||
Bool includeDefaultFboDepthStencil = true);
|
RenderPassEntry& GetOrCreateRenderPass(const MG_State::GLState::FramebufferObject& fbo, Uint32 swapchainImageIndex);
|
||||||
// drawUsesDepthStencil: whether the operation about to run inside the pass
|
|
||||||
// reads or writes the depth/stencil buffer (depth test or stencil test
|
|
||||||
// enabled, or a depth/stencil clear). Only consulted for the DEFAULT
|
|
||||||
// framebuffer: EGL undefines its ancillary buffers at every swap, so a
|
|
||||||
// default-FBO pass whose draws provably never touch depth/stencil is
|
|
||||||
// created WITHOUT the depth attachment - on a tiler that skips the whole
|
|
||||||
// depth tile load AND store. The flavor only escalates: once a pass with
|
|
||||||
// depth is active, later depth-less draws keep using it, and a depth-using
|
|
||||||
// draw against a depth-less active pass resolves to a new (incompatible)
|
|
||||||
// entry, which the caller's compatibility check turns into a pass split;
|
|
||||||
// the new pass's depth loads DONT_CARE (content was undefined all along).
|
|
||||||
RenderPassEntry& GetOrCreateRenderPass(const MG_State::GLState::FramebufferObject& fbo,
|
|
||||||
Uint32 swapchainImageIndex,
|
|
||||||
Bool drawUsesDepthStencil = true);
|
|
||||||
void QueueRenderbufferClear(GLbitfield mask, const ClearFramebufferPayload& clearPayload,
|
void QueueRenderbufferClear(GLbitfield mask, const ClearFramebufferPayload& clearPayload,
|
||||||
const MG_State::GLState::FramebufferObject& drawFbo);
|
const MG_State::GLState::FramebufferObject& drawFbo);
|
||||||
void QueueRenderbufferClear(const ClearAttachmentPayload& clearPayload,
|
void QueueRenderbufferClear(const ClearAttachmentPayload& clearPayload,
|
||||||
@@ -232,20 +192,12 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
UnorderedMap<Uint64, RenderPassEntry> m_renderPasses;
|
UnorderedMap<Uint64, RenderPassEntry> m_renderPasses;
|
||||||
// Monotonic frame counter (bumped in OnPresent) for render-pass cache aging.
|
// Monotonic frame counter (bumped in OnPresent) for render-pass cache aging.
|
||||||
Uint64 m_frameCounter = 0;
|
Uint64 m_frameCounter = 0;
|
||||||
IEvictionObserver* m_evictionObserver = nullptr;
|
|
||||||
|
|
||||||
// Bumped whenever a renderbuffer VkImage is (re)created; together with the texture
|
// Bumped whenever a renderbuffer VkImage is (re)created; together with the texture
|
||||||
// manager's image epoch this invalidates the render-pass fast path on any attachment
|
// manager's image epoch this invalidates the render-pass fast path on any attachment
|
||||||
// image recreation.
|
// image recreation.
|
||||||
Uint64 m_renderbufferImageEpoch = 1;
|
Uint64 m_renderbufferImageEpoch = 1;
|
||||||
|
|
||||||
public:
|
|
||||||
// Bumped whenever a renderbuffer backing is (re)created; consecutive-draw
|
|
||||||
// snapshots include it so an attachment respecify forces a re-resolve.
|
|
||||||
Uint64 GetRenderbufferImageEpoch() const { return m_renderbufferImageEpoch; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
|
|
||||||
// Per-draw fast-path memo for GetOrCreateRenderPass (dirty-flag state tracking): when the
|
// Per-draw fast-path memo for GetOrCreateRenderPass (dirty-flag state tracking): when the
|
||||||
// framebuffer state is provably unchanged since the last resolution, the active render pass
|
// framebuffer state is provably unchanged since the last resolution, the active render pass
|
||||||
// is reused WITHOUT recomputing the expensive per-draw hash. Invalidated by FBO switch /
|
// is reused WITHOUT recomputing the expensive per-draw hash. Invalidated by FBO switch /
|
||||||
@@ -258,26 +210,13 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
Uint64 m_rpFastTexEpoch = 0;
|
Uint64 m_rpFastTexEpoch = 0;
|
||||||
Uint64 m_rpFastRbEpoch = 0;
|
Uint64 m_rpFastRbEpoch = 0;
|
||||||
Uint64 m_rpFastRenderPassHash = 0;
|
Uint64 m_rpFastRenderPassHash = 0;
|
||||||
// Whether the memoized entry carries a depth/stencil attachment; a
|
|
||||||
// default-FBO resolution whose effective depth request differs must
|
|
||||||
// miss the memo (the depth-less/depth-full flavors hash differently).
|
|
||||||
Bool m_rpFastHadDepthStencil = false;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
struct RenderbufferResource {
|
struct RenderbufferResource {
|
||||||
// deadSinceFrame sentinel: the owning weak reference has not been observed
|
|
||||||
// expired. Dead resources age past every in-flight frame before Destroy
|
|
||||||
// (see CollectRenderbufferGarbage); the GPU may still reference the image
|
|
||||||
// for frames-in-flight frames after the GL object dies.
|
|
||||||
static constexpr Uint64 kNeverObservedDead = UINT64_MAX;
|
|
||||||
|
|
||||||
WeakPtr<MG_State::GLState::RenderbufferObject> renderbuffer;
|
WeakPtr<MG_State::GLState::RenderbufferObject> renderbuffer;
|
||||||
VkImage image = VK_NULL_HANDLE;
|
VkImage image = VK_NULL_HANDLE;
|
||||||
VmaAllocation allocation = nullptr;
|
VmaAllocation allocation = nullptr;
|
||||||
VkImageView view = VK_NULL_HANDLE;
|
VkImageView view = VK_NULL_HANDLE;
|
||||||
// UNORM reinterpretation of an sRGB image, used as the attachment view while
|
|
||||||
// GL_FRAMEBUFFER_SRGB is disabled (raw writes). Null for non-sRGB formats.
|
|
||||||
VkImageView unormTwinView = VK_NULL_HANDLE;
|
|
||||||
VkImageLayout layout = VK_IMAGE_LAYOUT_UNDEFINED;
|
VkImageLayout layout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||||
VkFormat format = VK_FORMAT_UNDEFINED;
|
VkFormat format = VK_FORMAT_UNDEFINED;
|
||||||
VkImageAspectFlags aspect = VK_IMAGE_ASPECT_NONE;
|
VkImageAspectFlags aspect = VK_IMAGE_ASPECT_NONE;
|
||||||
@@ -285,8 +224,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
VkSampleCountFlagBits sampleCount = VK_SAMPLE_COUNT_1_BIT;
|
VkSampleCountFlagBits sampleCount = VK_SAMPLE_COUNT_1_BIT;
|
||||||
TextureInternalFormat internalFormat = TextureInternalFormat::Unknown;
|
TextureInternalFormat internalFormat = TextureInternalFormat::Unknown;
|
||||||
Int samples = 0;
|
Int samples = 0;
|
||||||
// m_frameCounter value at which the weak reference was first seen expired.
|
|
||||||
Uint64 deadSinceFrame = kNeverObservedDead;
|
|
||||||
|
|
||||||
void Destroy(VkDevice device, VmaAllocator allocator);
|
void Destroy(VkDevice device, VmaAllocator allocator);
|
||||||
};
|
};
|
||||||
@@ -304,52 +241,12 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
ClearAttachmentPayload payload{};
|
ClearAttachmentPayload payload{};
|
||||||
};
|
};
|
||||||
|
|
||||||
// A superseded renderbuffer backing (glRenderbufferStorage respecify) parked
|
UnorderedMap<MG_State::GLState::RenderbufferObject*, RenderbufferResource> m_renderbufferResources;
|
||||||
// until enough frame boundaries have passed that no in-flight command buffer
|
|
||||||
// can still reference it; destroyed in OnPresent (see RetireAgeFrames).
|
|
||||||
struct DeferredRenderbufferRelease {
|
|
||||||
VkImage image = VK_NULL_HANDLE;
|
|
||||||
VmaAllocation allocation = nullptr;
|
|
||||||
VkImageView view = VK_NULL_HANDLE;
|
|
||||||
VkImageView unormTwinView = VK_NULL_HANDLE;
|
|
||||||
Uint64 deferredAtFrame = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Node-based std::unordered_map, deliberately not FastSTL's open-addressing UnorderedMap:
|
|
||||||
// callers cache a RenderbufferResource* - or a bare &resource->layout - and then make further
|
|
||||||
// calls that touch this map. BlitFramebuffer is the one that bit: it resolves the source and
|
|
||||||
// destination colour bindings (ResolveColorBlitBinding caches &rbResource->layout), then
|
|
||||||
// materializes the source's pending clear, which looks that same resource up again. FastSTL's
|
|
||||||
// operator[] runs its load-factor check before find_key and reallocates the whole bucket array
|
|
||||||
// when occupancy crosses it, so even a plain lookup relocates every element; erase only
|
|
||||||
// tombstones and never decrements the occupancy, so the doubling keeps firing. After a
|
|
||||||
// relocation the cached pointer names freed storage still holding the pre-clear
|
|
||||||
// VK_IMAGE_LAYOUT_UNDEFINED, and BlitFramebuffer bails out at "source image layout is
|
|
||||||
// undefined", silently dropping the blit - renderbuffers_storage_multisample read back zero
|
|
||||||
// instead of the clear colour on exactly the iterations that grew the table.
|
|
||||||
//
|
|
||||||
// Reordering the materialize ahead of the resolves - the fix ReadPixels got - does not cover
|
|
||||||
// this: the destination resolve still runs after the source pointer is taken. The depth blit,
|
|
||||||
// GetOrCreateRenderPass's depthRenderbufferResource and ReadDepthStencilPixels cache the same
|
|
||||||
// kind of pointer, so the invariant belongs in the container rather than in a per-call-site
|
|
||||||
// ordering rule. m_textureResources is node-based for the same reason. This buys stability
|
|
||||||
// across rehash and insert only - erase still invalidates the erased element, which is safe
|
|
||||||
// here because a renderbuffer that is an FBO attachment is held alive by that attachment.
|
|
||||||
std::unordered_map<MG_State::GLState::RenderbufferObject*, RenderbufferResource> m_renderbufferResources;
|
|
||||||
UnorderedMap<MG_State::GLState::RenderbufferObject*, PendingRenderbufferClear> m_pendingRenderbufferClears;
|
UnorderedMap<MG_State::GLState::RenderbufferObject*, PendingRenderbufferClear> m_pendingRenderbufferClears;
|
||||||
Vector<DeferredRenderbufferRelease> m_deferredRenderbufferReleases;
|
|
||||||
// Supported sample counts per attachment format, so per-draw resource lookups
|
|
||||||
// do not repeat vkGetPhysicalDeviceImageFormatProperties.
|
|
||||||
UnorderedMap<VkFormat, VkSampleCountFlags> m_attachmentSampleCountsByFormat;
|
|
||||||
|
|
||||||
Bool HasPendingRenderbufferClear(
|
Bool HasPendingRenderbufferClear(
|
||||||
const MG_State::GLState::FramebufferAttachmentObject& attachment) const;
|
const MG_State::GLState::FramebufferAttachmentObject& attachment) const;
|
||||||
void CollectRenderbufferGarbage();
|
void CollectRenderbufferGarbage();
|
||||||
// Frame-boundary margin after which a resource last referenced by a retired
|
|
||||||
// GL object (or superseded backing) is provably past every in-flight frame.
|
|
||||||
Uint64 RetireAgeFrames() const;
|
|
||||||
void DeferRenderbufferBackingRelease(RenderbufferResource& resource);
|
|
||||||
void CollectDeferredRenderbufferReleases(Bool destroyAll);
|
|
||||||
|
|
||||||
static inline XXH64_state_t* m_hashState = XXH64_createState();
|
static inline XXH64_state_t* m_hashState = XXH64_createState();
|
||||||
static inline ActiveRenderPassInfo s_activeRenderPass{};
|
static inline ActiveRenderPassInfo s_activeRenderPass{};
|
||||||
|
|||||||
@@ -51,18 +51,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
Float ResolveEffectiveMinLod(const MG_State::GLState::SamplerObject& sampler, Float effectiveMaxLod) {
|
Float ResolveEffectiveMinLod(const MG_State::GLState::SamplerObject& sampler, Float effectiveMaxLod) {
|
||||||
return std::min(sampler.GetMinLod(), effectiveMaxLod);
|
return std::min(sampler.GetMinLod(), effectiveMaxLod);
|
||||||
}
|
}
|
||||||
|
|
||||||
// A single-level view can only ever deliver the base level, but the LOD clamp must not be
|
|
||||||
// collapsed to exactly 0: both GL and Vulkan pick magFilter over minFilter from the
|
|
||||||
// *clamped* lambda, so maxLod = 0 would make every fragment magnify and quietly retire the
|
|
||||||
// min filter. 0.25 is the value VkSamplerCreateInfo's own note prescribes for emulating
|
|
||||||
// GL's non-mipmapped minification - large enough for lambda to stay positive, small enough
|
|
||||||
// that a NEAREST mip mode still rounds down to level 0. Clamped rather than assigned, so a
|
|
||||||
// texture whose GL_TEXTURE_MAX_LOD really is 0 keeps magnifying as GL says it must.
|
|
||||||
Float ResolveSingleLevelMaxLod(const MG_State::GLState::SamplerObject& sampler, Bool singleLevelView) {
|
|
||||||
const Float maxLod = ResolveEffectiveMaxLod(sampler);
|
|
||||||
return singleLevelView ? std::min(maxLod, 0.25f) : maxLod;
|
|
||||||
}
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
Bool VkSamplerManager::Initialize(const InitInfo& initInfo) {
|
Bool VkSamplerManager::Initialize(const InitInfo& initInfo) {
|
||||||
@@ -101,43 +89,15 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
|
|
||||||
m_device = VK_NULL_HANDLE;
|
m_device = VK_NULL_HANDLE;
|
||||||
m_config = nullptr;
|
m_config = nullptr;
|
||||||
m_frameBoundaryCounter = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void VkSamplerManager::OnFrameBoundary() {
|
|
||||||
++m_frameBoundaryCounter;
|
|
||||||
|
|
||||||
// Sweep occasionally; destroy samplers whose last use is far past every
|
|
||||||
// in-flight frame. Destroy and erase must stay atomic, or Shutdown would
|
|
||||||
// double-free the handle; an evicted key that recurs simply re-creates
|
|
||||||
// its sampler on the next miss.
|
|
||||||
constexpr Uint64 kSweepInterval = 256;
|
|
||||||
constexpr Uint64 kRetireAgeBoundaries = 1024;
|
|
||||||
if ((m_frameBoundaryCounter % kSweepInterval) != 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto it = m_samplers.begin(); it != m_samplers.end();) {
|
|
||||||
auto& entry = it->second;
|
|
||||||
if (m_frameBoundaryCounter - entry.lastUsedFrameBoundary > kRetireAgeBoundaries) {
|
|
||||||
if (m_device != VK_NULL_HANDLE && entry.handle != VK_NULL_HANDLE) {
|
|
||||||
vkDestroySampler(m_device, entry.handle, nullptr);
|
|
||||||
}
|
|
||||||
it = m_samplers.erase(it);
|
|
||||||
} else {
|
|
||||||
++it;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Uint64 VkSamplerManager::BuildSamplerKey(const MG_State::GLState::SamplerObject& sampler,
|
Uint64 VkSamplerManager::BuildSamplerKey(const MG_State::GLState::SamplerObject& sampler,
|
||||||
const MG_State::GLState::ITextureObject& texture,
|
const MG_State::GLState::ITextureObject& texture,
|
||||||
Bool forceNearestFiltering, Bool singleLevelView) const {
|
Bool forceNearestFiltering) const {
|
||||||
MOBILEGL_ASSERT(m_config != nullptr, "VkSamplerManager::BuildSamplerKey: m_config is null");
|
MOBILEGL_ASSERT(m_config != nullptr, "VkSamplerManager::BuildSamplerKey: m_config is null");
|
||||||
XXHASH_VERIFY(XXH64_reset(m_hashState, m_config->CacheVersion));
|
XXHASH_VERIFY(XXH64_reset(m_hashState, m_config->CacheVersion));
|
||||||
|
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState, &forceNearestFiltering, sizeof(forceNearestFiltering)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &forceNearestFiltering, sizeof(forceNearestFiltering)));
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState, &singleLevelView, sizeof(singleLevelView)));
|
|
||||||
|
|
||||||
const auto minFilter = sampler.GetMinFilter();
|
const auto minFilter = sampler.GetMinFilter();
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState, &minFilter, sizeof(minFilter)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &minFilter, sizeof(minFilter)));
|
||||||
@@ -151,7 +111,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
XXHASH_VERIFY(XXH64_update(m_hashState, &wrapT, sizeof(wrapT)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &wrapT, sizeof(wrapT)));
|
||||||
const auto wrapR = sampler.GetWrapR();
|
const auto wrapR = sampler.GetWrapR();
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState, &wrapR, sizeof(wrapR)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &wrapR, sizeof(wrapR)));
|
||||||
const auto maxLod = ResolveSingleLevelMaxLod(sampler, singleLevelView);
|
const auto maxLod = ResolveEffectiveMaxLod(sampler);
|
||||||
const auto minLod = ResolveEffectiveMinLod(sampler, maxLod);
|
const auto minLod = ResolveEffectiveMinLod(sampler, maxLod);
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState, &minLod, sizeof(minLod)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &minLod, sizeof(minLod)));
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState, &maxLod, sizeof(maxLod)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &maxLod, sizeof(maxLod)));
|
||||||
@@ -164,7 +124,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
XXHASH_VERIFY(XXH64_update(m_hashState, &maxAnisotropy, sizeof(maxAnisotropy)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &maxAnisotropy, sizeof(maxAnisotropy)));
|
||||||
const auto compareMode = sampler.GetCompareMode();
|
const auto compareMode = sampler.GetCompareMode();
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState, &compareMode, sizeof(compareMode)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &compareMode, sizeof(compareMode)));
|
||||||
const auto compareFunc = sampler.GetSamplerCompareFunc();
|
const auto compareFunc = ResolveCompareFunc(sampler, texture);
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState, &compareFunc, sizeof(compareFunc)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &compareFunc, sizeof(compareFunc)));
|
||||||
const auto borderColor = ResolveVkBorderColor(sampler, texture);
|
const auto borderColor = ResolveVkBorderColor(sampler, texture);
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState, &borderColor, sizeof(borderColor)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &borderColor, sizeof(borderColor)));
|
||||||
@@ -173,20 +133,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
|
|
||||||
VkSampler VkSamplerManager::GetOrCreateSampler(const MG_State::GLState::SamplerObject& sampler,
|
VkSampler VkSamplerManager::GetOrCreateSampler(const MG_State::GLState::SamplerObject& sampler,
|
||||||
const MG_State::GLState::ITextureObject& texture,
|
const MG_State::GLState::ITextureObject& texture,
|
||||||
Bool forceNearestFiltering, Uint32 viewLevelCount) {
|
Bool forceNearestFiltering) {
|
||||||
// A view that exposes a single mip level has no second level to blend with, so GL's
|
const Uint64 key = BuildSamplerKey(sampler, texture, forceNearestFiltering);
|
||||||
// *_MIPMAP_* minification filters degenerate to plain filtering on the base level -
|
|
||||||
// sampling is unchanged by pinning the Vulkan sampler to NEAREST mip mode at LOD 0.
|
|
||||||
// It is not cosmetic: MobileGL backs such a view with a fully allocated mip chain whose
|
|
||||||
// tail is never written, and a LINEAR mip mode lets the texture unit issue the level+1
|
|
||||||
// fetch anyway. On Adreno that fetch lands in uninitialized UBWC pages (or past the
|
|
||||||
// allocation for a genuinely single-level image) and faults the GPU - the same failure
|
|
||||||
// the default-framebuffer blit shader had to work around with an explicit-LOD sample.
|
|
||||||
const Bool singleLevelView = viewLevelCount == 1;
|
|
||||||
const Uint64 key = BuildSamplerKey(sampler, texture, forceNearestFiltering, singleLevelView);
|
|
||||||
auto it = m_samplers.find(key);
|
auto it = m_samplers.find(key);
|
||||||
if (it != m_samplers.end()) {
|
if (it != m_samplers.end()) {
|
||||||
it->second.lastUsedFrameBoundary = m_frameBoundaryCounter;
|
|
||||||
return it->second.handle;
|
return it->second.handle;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -194,9 +144,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
|
samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
|
||||||
samplerInfo.magFilter = forceNearestFiltering ? VK_FILTER_NEAREST : ToVkFilter(sampler.GetMagFilter());
|
samplerInfo.magFilter = forceNearestFiltering ? VK_FILTER_NEAREST : ToVkFilter(sampler.GetMagFilter());
|
||||||
samplerInfo.minFilter = forceNearestFiltering ? VK_FILTER_NEAREST : ToVkFilter(sampler.GetMinFilter());
|
samplerInfo.minFilter = forceNearestFiltering ? VK_FILTER_NEAREST : ToVkFilter(sampler.GetMinFilter());
|
||||||
samplerInfo.mipmapMode = (forceNearestFiltering || singleLevelView)
|
samplerInfo.mipmapMode = forceNearestFiltering ? VK_SAMPLER_MIPMAP_MODE_NEAREST
|
||||||
? VK_SAMPLER_MIPMAP_MODE_NEAREST
|
: ToVkMipmapMode(sampler.GetMipmapMode());
|
||||||
: ToVkMipmapMode(sampler.GetMipmapMode());
|
|
||||||
samplerInfo.addressModeU = ToVkAddressMode(sampler.GetWrapS());
|
samplerInfo.addressModeU = ToVkAddressMode(sampler.GetWrapS());
|
||||||
samplerInfo.addressModeV = ToVkAddressMode(sampler.GetWrapT());
|
samplerInfo.addressModeV = ToVkAddressMode(sampler.GetWrapT());
|
||||||
samplerInfo.addressModeW = ToVkAddressMode(sampler.GetWrapR());
|
samplerInfo.addressModeW = ToVkAddressMode(sampler.GetWrapR());
|
||||||
@@ -207,9 +156,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
samplerInfo.anisotropyEnable = maxAnisotropy > 1.0f ? VK_TRUE : VK_FALSE;
|
samplerInfo.anisotropyEnable = maxAnisotropy > 1.0f ? VK_TRUE : VK_FALSE;
|
||||||
samplerInfo.maxAnisotropy = maxAnisotropy;
|
samplerInfo.maxAnisotropy = maxAnisotropy;
|
||||||
samplerInfo.compareEnable = sampler.GetCompareMode() == SamplerCompareMode::CompareToTexture ? VK_TRUE : VK_FALSE;
|
samplerInfo.compareEnable = sampler.GetCompareMode() == SamplerCompareMode::CompareToTexture ? VK_TRUE : VK_FALSE;
|
||||||
samplerInfo.compareOp = ToVkCompareOp(sampler.GetSamplerCompareFunc());
|
samplerInfo.compareOp = ToVkCompareOp(ResolveCompareFunc(sampler, texture));
|
||||||
// Must match BuildSamplerKey's resolution exactly.
|
samplerInfo.maxLod = ResolveEffectiveMaxLod(sampler);
|
||||||
samplerInfo.maxLod = ResolveSingleLevelMaxLod(sampler, singleLevelView);
|
|
||||||
samplerInfo.minLod = ResolveEffectiveMinLod(sampler, samplerInfo.maxLod);
|
samplerInfo.minLod = ResolveEffectiveMinLod(sampler, samplerInfo.maxLod);
|
||||||
samplerInfo.borderColor = ResolveVkBorderColor(sampler, texture);
|
samplerInfo.borderColor = ResolveVkBorderColor(sampler, texture);
|
||||||
samplerInfo.unnormalizedCoordinates = VK_FALSE;
|
samplerInfo.unnormalizedCoordinates = VK_FALSE;
|
||||||
@@ -221,7 +169,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
entry.handle = vkSampler;
|
entry.handle = vkSampler;
|
||||||
entry.externalIndex = sampler.GetExternalIndex();
|
entry.externalIndex = sampler.GetExternalIndex();
|
||||||
entry.version = sampler.GetVersion();
|
entry.version = sampler.GetVersion();
|
||||||
entry.lastUsedFrameBoundary = m_frameBoundaryCounter;
|
|
||||||
m_samplers[key] = entry;
|
m_samplers[key] = entry;
|
||||||
return vkSampler;
|
return vkSampler;
|
||||||
}
|
}
|
||||||
@@ -281,15 +228,24 @@ 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,
|
VkBorderColor VkSamplerManager::ResolveVkBorderColor(const MG_State::GLState::SamplerObject& sampler,
|
||||||
const MG_State::GLState::ITextureObject& texture) {
|
const MG_State::GLState::ITextureObject& texture) {
|
||||||
if (!UsesBorderColor(sampler)) {
|
if (!UsesBorderColor(sampler)) {
|
||||||
return VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK;
|
return VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Border colour is sampler state: a bound sampler object supplies its own, and a texture
|
const auto& borderColor = texture.GetBorderColor();
|
||||||
// with none reaches the very same value through the sampler object it owns.
|
|
||||||
const auto& borderColor = sampler.GetBorderColor();
|
|
||||||
const Bool isDepthTexture = IsDepthTextureFormat(texture.GetFormat());
|
const Bool isDepthTexture = IsDepthTextureFormat(texture.GetFormat());
|
||||||
|
|
||||||
if (isDepthTexture) {
|
if (isDepthTexture) {
|
||||||
|
|||||||
@@ -33,42 +33,26 @@ public:
|
|||||||
Bool Initialize(const InitInfo& initInfo);
|
Bool Initialize(const InitInfo& initInfo);
|
||||||
void Shutdown();
|
void Shutdown();
|
||||||
|
|
||||||
// viewLevelCount is the mip-level count of the image view this sampler will be paired
|
|
||||||
// with; 0 means "unknown, do not narrow". See GetOrCreateSampler for why it matters.
|
|
||||||
VkSampler GetOrCreateSampler(const MG_State::GLState::SamplerObject& sampler,
|
VkSampler GetOrCreateSampler(const MG_State::GLState::SamplerObject& sampler,
|
||||||
const MG_State::GLState::ITextureObject& texture,
|
const MG_State::GLState::ITextureObject& texture,
|
||||||
Bool forceNearestFiltering = false,
|
Bool forceNearestFiltering = false);
|
||||||
Uint32 viewLevelCount = 0);
|
|
||||||
// Frame boundary hook: ages the sampler cache and destroys samplers not used
|
|
||||||
// for many frames. The key hashes continuous float state (lodBias, LOD clamps,
|
|
||||||
// anisotropy), so an app animating those would otherwise mint an unbounded
|
|
||||||
// stream of never-destroyed VkSamplers and eventually exhaust the device's
|
|
||||||
// maxSamplerAllocationCount. A sampler idle for over a thousand frame
|
|
||||||
// boundaries cannot be referenced by any in-flight command buffer (frames in
|
|
||||||
// flight are single digits), and every descriptor set the GPU consumes is
|
|
||||||
// written that same frame with live handles (the per-binding resolve memo and
|
|
||||||
// descriptor-set reuse are both frame-reset), so destruction here needs no
|
|
||||||
// fence wait. Self-gated: one counter bump and compare except on sweep
|
|
||||||
// boundaries.
|
|
||||||
void OnFrameBoundary();
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct SamplerCacheEntry {
|
struct SamplerCacheEntry {
|
||||||
VkSampler handle = VK_NULL_HANDLE;
|
VkSampler handle = VK_NULL_HANDLE;
|
||||||
Uint externalIndex = 0;
|
Uint externalIndex = 0;
|
||||||
Uint16 version = 0;
|
Uint16 version = 0;
|
||||||
// Frame boundary of the last cache hit; entries idle past the
|
|
||||||
// OnFrameBoundary retirement age have their VkSampler destroyed.
|
|
||||||
Uint64 lastUsedFrameBoundary = 0;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Uint64 BuildSamplerKey(const MG_State::GLState::SamplerObject& sampler,
|
Uint64 BuildSamplerKey(const MG_State::GLState::SamplerObject& sampler,
|
||||||
const MG_State::GLState::ITextureObject& texture,
|
const MG_State::GLState::ITextureObject& texture,
|
||||||
Bool forceNearestFiltering, Bool singleLevelView) const;
|
Bool forceNearestFiltering) const;
|
||||||
static VkFilter ToVkFilter(SamplerFilterMode mode);
|
static VkFilter ToVkFilter(SamplerFilterMode mode);
|
||||||
static VkSamplerMipmapMode ToVkMipmapMode(SamplerMipmapMode mode);
|
static VkSamplerMipmapMode ToVkMipmapMode(SamplerMipmapMode mode);
|
||||||
static VkSamplerAddressMode ToVkAddressMode(SamplerWrapMode mode);
|
static VkSamplerAddressMode ToVkAddressMode(SamplerWrapMode mode);
|
||||||
static VkCompareOp ToVkCompareOp(SamplerCompareFunc func);
|
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,
|
static VkBorderColor ResolveVkBorderColor(const MG_State::GLState::SamplerObject& sampler,
|
||||||
const MG_State::GLState::ITextureObject& texture);
|
const MG_State::GLState::ITextureObject& texture);
|
||||||
// The anisotropy Vulkan will actually apply: 1.0 (i.e. disabled) unless the feature is on and
|
// The anisotropy Vulkan will actually apply: 1.0 (i.e. disabled) unless the feature is on and
|
||||||
@@ -83,8 +67,6 @@ private:
|
|||||||
Bool m_samplerAnisotropySupported = false;
|
Bool m_samplerAnisotropySupported = false;
|
||||||
Float m_maxSamplerAnisotropy = 1.0f;
|
Float m_maxSamplerAnisotropy = 1.0f;
|
||||||
UnorderedMap<Uint64, SamplerCacheEntry> m_samplers;
|
UnorderedMap<Uint64, SamplerCacheEntry> m_samplers;
|
||||||
// Monotonic frame-boundary counter (bumped in OnFrameBoundary) for cache aging.
|
|
||||||
Uint64 m_frameBoundaryCounter = 0;
|
|
||||||
static inline XXH64_state_t* m_hashState = XXH64_createState();
|
static inline XXH64_state_t* m_hashState = XXH64_createState();
|
||||||
};
|
};
|
||||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||||
|
|||||||
@@ -120,21 +120,31 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static Bool TryResolveSampleCountFlagBits(Int requestedSamples, VkSampleCountFlagBits& outSampleCount) {
|
static Bool TryResolveSampleCountFlagBits(Int requestedSamples, VkSampleCountFlagBits& outSampleCount) {
|
||||||
// GL promises "at least the requested samples", so a non-power-of-two
|
switch (requestedSamples) {
|
||||||
// request (legal in GL, e.g. 3) rounds up to the next Vulkan bit.
|
case 1:
|
||||||
if (requestedSamples <= 1) {
|
|
||||||
outSampleCount = VK_SAMPLE_COUNT_1_BIT;
|
outSampleCount = VK_SAMPLE_COUNT_1_BIT;
|
||||||
return true;
|
return true;
|
||||||
}
|
case 2:
|
||||||
if (requestedSamples > 64) {
|
outSampleCount = VK_SAMPLE_COUNT_2_BIT;
|
||||||
|
return true;
|
||||||
|
case 4:
|
||||||
|
outSampleCount = VK_SAMPLE_COUNT_4_BIT;
|
||||||
|
return true;
|
||||||
|
case 8:
|
||||||
|
outSampleCount = VK_SAMPLE_COUNT_8_BIT;
|
||||||
|
return true;
|
||||||
|
case 16:
|
||||||
|
outSampleCount = VK_SAMPLE_COUNT_16_BIT;
|
||||||
|
return true;
|
||||||
|
case 32:
|
||||||
|
outSampleCount = VK_SAMPLE_COUNT_32_BIT;
|
||||||
|
return true;
|
||||||
|
case 64:
|
||||||
|
outSampleCount = VK_SAMPLE_COUNT_64_BIT;
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
Uint32 bit = 1;
|
|
||||||
while (bit < static_cast<Uint32>(requestedSamples)) {
|
|
||||||
bit <<= 1;
|
|
||||||
}
|
|
||||||
outSampleCount = static_cast<VkSampleCountFlagBits>(bit);
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static Bool IsCubeMapFaceUploadTarget(TextureUploadTarget target) {
|
static Bool IsCubeMapFaceUploadTarget(TextureUploadTarget target) {
|
||||||
@@ -564,27 +574,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
outShape.depth = 1;
|
outShape.depth = 1;
|
||||||
outShape.arrayLayers = 6;
|
outShape.arrayLayers = 6;
|
||||||
return true;
|
return true;
|
||||||
case TextureUploadTarget::CubeMapArray:
|
|
||||||
case TextureUploadTarget::ProxyCubeMapArray:
|
|
||||||
// GL_TEXTURE_CUBE_MAP_ARRAY is an array texture whose layers happen to be cube faces:
|
|
||||||
// one 2D image with arrayLayers = 6 * cubeCount, CUBE_COMPATIBLE so the whole thing can
|
|
||||||
// be sampled as a samplerCubeArray. glTexStorage3D hands the 6*n through as the GL depth
|
|
||||||
// and the upload path's depthSelectsArrayLayer already lists VK_IMAGE_VIEW_TYPE_CUBE_ARRAY,
|
|
||||||
// so the copies address layers correctly.
|
|
||||||
//
|
|
||||||
// A depth that is not a whole number of cubes, or a non-square level, has no Vulkan shape
|
|
||||||
// - declined the way every other unrepresentable target is. This function's Bool return
|
|
||||||
// exists for exactly that; asserting here would abort the process on ordinary application
|
|
||||||
// input, GL_PROXY_TEXTURE_CUBE_MAP_ARRAY above all.
|
|
||||||
if (texelSize.z() <= 0 || (texelSize.z() % 6) != 0 || texelSize.x() != texelSize.y()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
outShape.imageType = VK_IMAGE_TYPE_2D;
|
|
||||||
outShape.viewType = VK_IMAGE_VIEW_TYPE_CUBE_ARRAY;
|
|
||||||
outShape.imageFlags = VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT;
|
|
||||||
outShape.depth = 1;
|
|
||||||
outShape.arrayLayers = static_cast<Uint32>(texelSize.z());
|
|
||||||
return true;
|
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -598,7 +587,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
m_allocator = initInfo.allocator;
|
m_allocator = initInfo.allocator;
|
||||||
m_commandPool = initInfo.commandPool;
|
m_commandPool = initInfo.commandPool;
|
||||||
m_graphicsQueue = initInfo.graphicsQueue;
|
m_graphicsQueue = initInfo.graphicsQueue;
|
||||||
m_imageFormatListSupported = initInfo.imageFormatListSupported;
|
|
||||||
m_currentFrameIndex = 0;
|
m_currentFrameIndex = 0;
|
||||||
m_deferredReleases.clear();
|
m_deferredReleases.clear();
|
||||||
m_deferredReleases.resize(initInfo.frameCount);
|
m_deferredReleases.resize(initInfo.frameCount);
|
||||||
@@ -618,14 +606,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void VkTextureManager::Shutdown() {
|
void VkTextureManager::Shutdown() {
|
||||||
if (m_device != VK_NULL_HANDLE) {
|
|
||||||
ReclaimCompletedUploads(/*waitAll=*/true);
|
|
||||||
}
|
|
||||||
DestroyDeferredReleases();
|
DestroyDeferredReleases();
|
||||||
++m_resourceEraseEpoch; // every memoized resource pointer dies with the map
|
|
||||||
m_textureResources.clear();
|
m_textureResources.clear();
|
||||||
m_aliveObjects.clear();
|
m_aliveObjects.clear();
|
||||||
m_storageImageTextures.clear();
|
|
||||||
|
|
||||||
m_device = VK_NULL_HANDLE;
|
m_device = VK_NULL_HANDLE;
|
||||||
m_physicalDevice = VK_NULL_HANDLE;
|
m_physicalDevice = VK_NULL_HANDLE;
|
||||||
@@ -644,27 +627,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
frameIndex, m_deferredViewReleases.size());
|
frameIndex, m_deferredViewReleases.size());
|
||||||
m_currentFrameIndex = frameIndex;
|
m_currentFrameIndex = frameIndex;
|
||||||
CollectDeferredReleases(frameIndex);
|
CollectDeferredReleases(frameIndex);
|
||||||
ReclaimCompletedUploads();
|
|
||||||
|
|
||||||
// Frame-boundary GC: every 64 frame boundaries (~1 s at 60 fps) bounds the reclaim
|
|
||||||
// latency for dead textures regardless of draw traffic — workloads that churn
|
|
||||||
// textures through clears/readbacks alone never reach the draw-gated
|
|
||||||
// CollectGarbage. Must run after CollectDeferredReleases above: the prune defers
|
|
||||||
// its releases into this frame's slot, which was just drained, so they are
|
|
||||||
// destroyed only after the slot's fence has been waited again one full frame-ring
|
|
||||||
// cycle from now (never while an in-flight frame may still reference them).
|
|
||||||
constexpr Uint32 kGcFrameInterval = 64;
|
|
||||||
++m_gcFrameCounter;
|
|
||||||
if (m_gcFrameCounter % kGcFrameInterval == 0) {
|
|
||||||
PruneDeadTextures();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void VkTextureManager::CollectAllDeferredReleases() {
|
|
||||||
const SizeT frameCount = std::min(m_deferredReleases.size(), m_deferredViewReleases.size());
|
|
||||||
for (SizeT frameIndex = 0; frameIndex < frameCount; ++frameIndex) {
|
|
||||||
CollectDeferredReleases(static_cast<Uint32>(frameIndex));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void VkTextureManager::EraseTrackedTexture(const TextureIdentity& identity) {
|
void VkTextureManager::EraseTrackedTexture(const TextureIdentity& identity) {
|
||||||
@@ -674,10 +636,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
m_textureResources.erase(resourceIt);
|
m_textureResources.erase(resourceIt);
|
||||||
}
|
}
|
||||||
m_aliveObjects.erase(identity);
|
m_aliveObjects.erase(identity);
|
||||||
m_storageImageTextures.erase(identity);
|
|
||||||
// Invalidate every cross-draw sampled-texture memo: the erased
|
|
||||||
// resource's address may be reused by a future emplace.
|
|
||||||
++m_resourceEraseEpoch;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void VkTextureManager::PruneStaleTextureAliases(MG_State::GLState::ITextureObject* texture) {
|
void VkTextureManager::PruneStaleTextureAliases(MG_State::GLState::ITextureObject* texture) {
|
||||||
@@ -733,63 +691,32 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cross-draw memo probe (see SyncedTextureMemoEntry): skips both map
|
auto aliveIt = m_aliveObjects.find(identity);
|
||||||
// lookups and the (re)registration path for repeat-bound textures.
|
if (aliveIt != m_aliveObjects.end() && aliveIt->second.expired()) {
|
||||||
TextureResource* resourcePtr = nullptr;
|
EraseTrackedTexture(aliveIt->first);
|
||||||
for (Uint32 i = 0; i < kSyncedTextureMemoSize; ++i) {
|
aliveIt = m_aliveObjects.end();
|
||||||
const SyncedTextureMemoEntry& memo = m_syncedTextureMemo[i];
|
}
|
||||||
if (memo.texture == &texture && memo.lifetimeId == identity.lifetimeId &&
|
|
||||||
memo.eraseEpoch == m_resourceEraseEpoch) {
|
// Only (re)register and prune when this (texture, lifetime) pair is new: stale
|
||||||
resourcePtr = memo.resource;
|
// aliases can only come into existence through an address reuse, which by
|
||||||
break;
|
// construction introduces a new identity. Doing this unconditionally made every
|
||||||
|
// sampled-texture sync scan the entire alive-texture map per draw.
|
||||||
|
if (aliveIt == m_aliveObjects.end()) {
|
||||||
|
const auto& liveTexture = MG_State::pGLContext->GetTextureObject(texture.GetExternalIndex());
|
||||||
|
if (liveTexture && liveTexture.get() == &texture) {
|
||||||
|
m_aliveObjects[identity] = WeakPtr<MG_State::GLState::ITextureObject>(liveTexture);
|
||||||
|
PruneStaleTextureAliases(&texture);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (resourcePtr == nullptr) {
|
auto it = m_textureResources.find(identity);
|
||||||
auto aliveIt = m_aliveObjects.find(identity);
|
if (it == m_textureResources.end()) {
|
||||||
if (aliveIt != m_aliveObjects.end() && aliveIt->second.expired()) {
|
TextureResource initial{};
|
||||||
EraseTrackedTexture(aliveIt->first);
|
auto [insertIt, _] = m_textureResources.emplace(identity, Move(initial));
|
||||||
aliveIt = m_aliveObjects.end();
|
it = insertIt;
|
||||||
}
|
|
||||||
|
|
||||||
// Only (re)register and prune when this (texture, lifetime) pair is new: stale
|
|
||||||
// aliases can only come into existence through an address reuse, which by
|
|
||||||
// construction introduces a new identity. Doing this unconditionally made every
|
|
||||||
// sampled-texture sync scan the entire alive-texture map per draw.
|
|
||||||
if (aliveIt == m_aliveObjects.end()) {
|
|
||||||
WeakPtr<MG_State::GLState::ITextureObject> aliveTexture;
|
|
||||||
const auto& liveTexture = MG_State::pGLContext->GetTextureObject(texture.GetExternalIndex());
|
|
||||||
if (liveTexture && liveTexture.get() == &texture) {
|
|
||||||
aliveTexture = liveTexture;
|
|
||||||
} else {
|
|
||||||
// The name lookup legally fails while the object is alive: the name was
|
|
||||||
// deleted with the texture still attached to an FBO (the attachment's
|
|
||||||
// SharedPtr keeps it alive), or the name was reused by a new texture, or
|
|
||||||
// this is a default texture object (name 0 lives outside the name map).
|
|
||||||
// Register through the object's own control block so the resource created
|
|
||||||
// below still participates in weak-expiry GC instead of becoming an
|
|
||||||
// orphan no reclamation path can reach until Shutdown.
|
|
||||||
aliveTexture = texture.weak_from_this();
|
|
||||||
}
|
|
||||||
if (!aliveTexture.expired()) {
|
|
||||||
m_aliveObjects[identity] = Move(aliveTexture);
|
|
||||||
PruneStaleTextureAliases(&texture);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
auto it = m_textureResources.find(identity);
|
|
||||||
if (it == m_textureResources.end()) {
|
|
||||||
TextureResource initial{};
|
|
||||||
auto [insertIt, _] = m_textureResources.emplace(identity, Move(initial));
|
|
||||||
it = insertIt;
|
|
||||||
}
|
|
||||||
resourcePtr = &(it->second);
|
|
||||||
m_syncedTextureMemo[m_syncedTextureMemoNext] =
|
|
||||||
SyncedTextureMemoEntry{&texture, identity.lifetimeId, m_resourceEraseEpoch, resourcePtr};
|
|
||||||
m_syncedTextureMemoNext = (m_syncedTextureMemoNext + 1) % kSyncedTextureMemoSize;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!SyncTexture(texture, *resourcePtr)) {
|
if (!SyncTexture(texture, it->second)) {
|
||||||
MGLOG_D("%s: Syncing texture %d failed", __func__, texture.GetExternalIndex());
|
MGLOG_D("%s: Syncing texture %d failed", __func__, texture.GetExternalIndex());
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
@@ -803,11 +730,11 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!recorded) {
|
if (!recorded) {
|
||||||
m_drawSyncedThisDraw.push_back({identity, resourcePtr});
|
m_drawSyncedThisDraw.push_back({identity, &(it->second)});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return resourcePtr;
|
return &(it->second);
|
||||||
}
|
}
|
||||||
|
|
||||||
VkImageView VkTextureManager::GetOrCreateViewAtMipLevel(MG_State::GLState::ITextureObject& texture, Uint32 mipLevel) {
|
VkImageView VkTextureManager::GetOrCreateViewAtMipLevel(MG_State::GLState::ITextureObject& texture, Uint32 mipLevel) {
|
||||||
@@ -843,35 +770,15 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
if (resource == nullptr || resource->image == VK_NULL_HANDLE || mipLevel >= resource->mipLevels) {
|
if (resource == nullptr || resource->image == VK_NULL_HANDLE || mipLevel >= resource->mipLevels) {
|
||||||
return VK_NULL_HANDLE;
|
return VK_NULL_HANDLE;
|
||||||
}
|
}
|
||||||
// A 3D image has arrayLayers == 1 and keeps its GL layers on the z axis, so a per-slice
|
if (layerCount == 0 || baseArrayLayer >= resource->arrayLayers ||
|
||||||
// attachment view is a 2D view whose "array layer" is the slice - legal only on a
|
baseArrayLayer + layerCount > resource->arrayLayers) {
|
||||||
// 2D-array-compatible image (VUID-VkImageViewCreateInfo-image-04970), which
|
|
||||||
// SyncTextureResource asks for and may have had refused per format.
|
|
||||||
if (resource->viewType == VK_IMAGE_VIEW_TYPE_3D && viewType == VK_IMAGE_VIEW_TYPE_2D) {
|
|
||||||
const Uint32 sliceCount = std::max(resource->depth >> mipLevel, 1u);
|
|
||||||
if ((resource->imageCreateFlags & VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT) == 0 ||
|
|
||||||
layerCount == 0 || baseArrayLayer >= sliceCount || baseArrayLayer + layerCount > sliceCount) {
|
|
||||||
MGLOG_D("%s: cannot name slice span [%u, %u) of 3D textureId=%d (mip %u has %u slices, "
|
|
||||||
"2D-array-compatible=%d)",
|
|
||||||
__func__, baseArrayLayer, baseArrayLayer + layerCount, texture.GetExternalIndex(),
|
|
||||||
mipLevel, sliceCount,
|
|
||||||
(int)((resource->imageCreateFlags & VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT) != 0));
|
|
||||||
return VK_NULL_HANDLE;
|
|
||||||
}
|
|
||||||
} else if (layerCount == 0 || baseArrayLayer >= resource->arrayLayers ||
|
|
||||||
baseArrayLayer + layerCount > resource->arrayLayers) {
|
|
||||||
MGLOG_D("%s: invalid layer span [%u, %u) for textureId=%d arrayLayers=%u",
|
MGLOG_D("%s: invalid layer span [%u, %u) for textureId=%d arrayLayers=%u",
|
||||||
__func__, baseArrayLayer, baseArrayLayer + layerCount, texture.GetExternalIndex(),
|
__func__, baseArrayLayer, baseArrayLayer + layerCount, texture.GetExternalIndex(),
|
||||||
resource->arrayLayers);
|
resource->arrayLayers);
|
||||||
return VK_NULL_HANDLE;
|
return VK_NULL_HANDLE;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Bool framebufferSrgbEnabled =
|
if (baseArrayLayer == 0 && layerCount == resource->arrayLayers && viewType == resource->viewType) {
|
||||||
MG_State::pGLContext->IsCapabilityEnabled(MobileGL::CapabilityInput::FramebufferSrgb);
|
|
||||||
const VkFormat attachmentFormat = ResolveSrgbAttachmentWriteFormat(resource->format, framebufferSrgbEnabled);
|
|
||||||
|
|
||||||
if (attachmentFormat == resource->format && baseArrayLayer == 0 && layerCount == resource->arrayLayers &&
|
|
||||||
viewType == resource->viewType) {
|
|
||||||
return GetOrCreateViewAtMipLevel(texture, mipLevel);
|
return GetOrCreateViewAtMipLevel(texture, mipLevel);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -880,7 +787,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
.baseArrayLayer = baseArrayLayer,
|
.baseArrayLayer = baseArrayLayer,
|
||||||
.layerCount = layerCount,
|
.layerCount = layerCount,
|
||||||
.viewType = viewType,
|
.viewType = viewType,
|
||||||
.viewFormat = attachmentFormat,
|
|
||||||
};
|
};
|
||||||
auto it = resource->attachmentViews.find(key);
|
auto it = resource->attachmentViews.find(key);
|
||||||
if (it == resource->attachmentViews.end()) {
|
if (it == resource->attachmentViews.end()) {
|
||||||
@@ -891,7 +797,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
return attachmentView;
|
return attachmentView;
|
||||||
}
|
}
|
||||||
|
|
||||||
attachmentView = CreateImageView(resource->image, attachmentFormat, resource->aspect, viewType,
|
attachmentView = CreateImageView(resource->image, resource->format, resource->aspect, viewType,
|
||||||
mipLevel, 1, baseArrayLayer, layerCount);
|
mipLevel, 1, baseArrayLayer, layerCount);
|
||||||
if (attachmentView == VK_NULL_HANDLE) {
|
if (attachmentView == VK_NULL_HANDLE) {
|
||||||
MGLOG_D("%s: CreateImageView failed for textureId=%d mipLevel=%u baseArrayLayer=%u layerCount=%u viewType=%d",
|
MGLOG_D("%s: CreateImageView failed for textureId=%d mipLevel=%u baseArrayLayer=%u layerCount=%u viewType=%d",
|
||||||
@@ -1107,16 +1013,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
void VkTextureManager::StampTextureRecordingUse(MG_State::GLState::ITextureObject* texture) {
|
|
||||||
if (texture == nullptr) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
auto it = m_textureResources.find(MakeTextureIdentity(texture));
|
|
||||||
if (it != m_textureResources.end()) {
|
|
||||||
it->second.lastRecordingGeneration = m_recordingGeneration;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void VkTextureManager::UpdateTrackedImageLayout(MG_State::GLState::ITextureObject* texture, VkImageLayout newLayout) {
|
void VkTextureManager::UpdateTrackedImageLayout(MG_State::GLState::ITextureObject* texture, VkImageLayout newLayout) {
|
||||||
MOBILEGL_ASSERT(texture != nullptr, "UpdateTrackedImageLayout: texture is null");
|
MOBILEGL_ASSERT(texture != nullptr, "UpdateTrackedImageLayout: texture is null");
|
||||||
auto it = m_textureResources.find(MakeTextureIdentity(texture));
|
auto it = m_textureResources.find(MakeTextureIdentity(texture));
|
||||||
@@ -1144,8 +1040,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
MOBILEGL_ASSERT(writtenMipLevel < resource.mipLevels,
|
MOBILEGL_ASSERT(writtenMipLevel < resource.mipLevels,
|
||||||
"UpdateTrackedImageLayoutAfterAttachmentWrite: textureId=%d mipLevel=%u out of range %u",
|
"UpdateTrackedImageLayoutAfterAttachmentWrite: textureId=%d mipLevel=%u out of range %u",
|
||||||
texture->GetExternalIndex(), writtenMipLevel, resource.mipLevels);
|
texture->GetExternalIndex(), writtenMipLevel, resource.mipLevels);
|
||||||
// Pre-pass stream bookkeeping: the render pass that just ended wrote this image.
|
|
||||||
StampResourceRecordingUse(resource);
|
|
||||||
|
|
||||||
if (resource.layout != newLayout && resource.mipLevels > 1) {
|
if (resource.layout != newLayout && resource.mipLevels > 1) {
|
||||||
VkPipelineStageFlags srcStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
|
VkPipelineStageFlags srcStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
|
||||||
@@ -1230,8 +1124,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
VK_ACCESS_SHADER_READ_BIT, resource->aspect, 0, resource->mipLevels,
|
VK_ACCESS_SHADER_READ_BIT, resource->aspect, 0, resource->mipLevels,
|
||||||
resource->arrayLayers);
|
resource->arrayLayers);
|
||||||
MOBILEGL_ASSERT(ok, "TransitionTextureForSampling: transition failed for textureId=%d", texture.GetExternalIndex());
|
MOBILEGL_ASSERT(ok, "TransitionTextureForSampling: transition failed for textureId=%d", texture.GetExternalIndex());
|
||||||
// Pre-pass stream bookkeeping: a command referencing the image was recorded.
|
|
||||||
StampResourceRecordingUse(*resource);
|
|
||||||
return ok;
|
return ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1261,44 +1153,11 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
resource->aspect, 0, resource->mipLevels, resource->arrayLayers);
|
resource->aspect, 0, resource->mipLevels, resource->arrayLayers);
|
||||||
MOBILEGL_ASSERT(ok, "TransitionTextureForStorageImage: transition failed for textureId=%d",
|
MOBILEGL_ASSERT(ok, "TransitionTextureForStorageImage: transition failed for textureId=%d",
|
||||||
texture.GetExternalIndex());
|
texture.GetExternalIndex());
|
||||||
// Pre-pass stream bookkeeping: a command referencing the image was recorded.
|
|
||||||
StampResourceRecordingUse(*resource);
|
|
||||||
return ok;
|
return ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
void VkTextureManager::MarkStorageImageTexture(MG_State::GLState::ITextureObject& texture) {
|
|
||||||
m_storageImageTextures.insert(MakeTextureIdentity(&texture));
|
|
||||||
}
|
|
||||||
|
|
||||||
Bool VkTextureManager::NeedsStorageUsageUpgrade(MG_State::GLState::ITextureObject& texture) const {
|
|
||||||
const TextureIdentity identity = MakeTextureIdentity(&texture);
|
|
||||||
if (m_storageImageTextures.find(identity) == m_storageImageTextures.end()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const auto it = m_textureResources.find(identity);
|
|
||||||
// No image yet: the first sync creates it with STORAGE straight away, so there is nothing
|
|
||||||
// to preserve and nothing to order against.
|
|
||||||
return it != m_textureResources.end() && it->second.image != VK_NULL_HANDLE &&
|
|
||||||
!it->second.storageUsageResolved;
|
|
||||||
}
|
|
||||||
|
|
||||||
Bool VkTextureManager::NeedsMipChainGrowth(MG_State::GLState::ITextureObject& texture) const {
|
|
||||||
const TextureIdentity identity = MakeTextureIdentity(&texture);
|
|
||||||
const auto it = m_textureResources.find(identity);
|
|
||||||
// No image yet: the first sync sizes the chain from the levels the texture already
|
|
||||||
// defines, so nothing is recreated and there is nothing to order against.
|
|
||||||
if (it == m_textureResources.end() || it->second.image == VK_NULL_HANDLE) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const TextureResource& resource = it->second;
|
|
||||||
const IntVec3 extent = {static_cast<Int>(resource.extent.width), static_cast<Int>(resource.extent.height),
|
|
||||||
static_cast<Int>(resource.depth)};
|
|
||||||
return resource.mipLevels < ComputeFullMipLevelCount(extent);
|
|
||||||
}
|
|
||||||
|
|
||||||
Bool VkTextureManager::NeedsStorageImagePreparation(MG_State::GLState::ITextureObject& texture) const {
|
Bool VkTextureManager::NeedsStorageImagePreparation(MG_State::GLState::ITextureObject& texture) const {
|
||||||
const TextureIdentity identity = MakeTextureIdentity(&texture);
|
const auto it = m_textureResources.find(MakeTextureIdentity(&texture));
|
||||||
const auto it = m_textureResources.find(identity);
|
|
||||||
if (it == m_textureResources.end()) {
|
if (it == m_textureResources.end()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -1306,12 +1165,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
if (resource.image == VK_NULL_HANDLE || resource.layout != VK_IMAGE_LAYOUT_GENERAL) {
|
if (resource.image == VK_NULL_HANDLE || resource.layout != VK_IMAGE_LAYOUT_GENERAL) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
// The image predates this texture's first image-unit binding, so it was created without
|
|
||||||
// STORAGE usage and has to be recreated - which is illegal inside a render pass.
|
|
||||||
if (!resource.storageUsageResolved &&
|
|
||||||
m_storageImageTextures.find(identity) != m_storageImageTextures.end()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// Mirror SyncTexture's cross-draw skip condition: any version drift means the sync
|
// Mirror SyncTexture's cross-draw skip condition: any version drift means the sync
|
||||||
// path may upload or rebuild, both of which need the render pass ended first.
|
// path may upload or rebuild, both of which need the render pass ended first.
|
||||||
const auto* mipTexture = MG_State::GLState::AsMipmapTexture(&texture);
|
const auto* mipTexture = MG_State::GLState::AsMipmapTexture(&texture);
|
||||||
@@ -1359,22 +1212,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SizeT VkTextureManager::CollectGarbage() {
|
SizeT VkTextureManager::CollectGarbage() {
|
||||||
// Draw-gated stagger (1 in 256 calls): keeps the per-draw cost at one counter
|
|
||||||
// bump. The guaranteed reclaim path is the frame-boundary prune in BeginFrame;
|
|
||||||
// this remains as a cheap assist so draw-heavy workloads reclaim sooner.
|
|
||||||
m_gcCounter++;
|
m_gcCounter++;
|
||||||
if (m_gcCounter != 0) {
|
if (m_gcCounter != 0) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
return PruneDeadTextures();
|
|
||||||
}
|
|
||||||
|
|
||||||
SizeT VkTextureManager::PruneDeadTextures() {
|
|
||||||
// Erasing entries would dangle the raw TextureResource pointers memoized for the
|
|
||||||
// current draw; every call path (BeginFrame, and CollectGarbage at the top of a
|
|
||||||
// freshly opened draw-sync scope) runs before any memo entry is recorded.
|
|
||||||
MOBILEGL_ASSERT(m_drawSyncedThisDraw.empty(),
|
|
||||||
"PruneDeadTextures: draw-sync memo holds raw resource pointers an erase would dangle");
|
|
||||||
|
|
||||||
Vector<MG_State::GLState::ITextureObject*> expiredTextures;
|
Vector<MG_State::GLState::ITextureObject*> expiredTextures;
|
||||||
expiredTextures.reserve(m_aliveObjects.size());
|
expiredTextures.reserve(m_aliveObjects.size());
|
||||||
@@ -1386,25 +1227,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
for (auto* texture : expiredTextures) {
|
for (auto* texture : expiredTextures) {
|
||||||
PruneStaleTextureAliases(texture);
|
PruneStaleTextureAliases(texture);
|
||||||
}
|
}
|
||||||
SizeT prunedCount = expiredTextures.size();
|
return expiredTextures.size();
|
||||||
|
|
||||||
// Orphan sweep: after the pass above, m_aliveObjects holds only live entries.
|
|
||||||
// Registration in SyncTextureAndGetDescriptor cannot fail for a SharedPtr-owned
|
|
||||||
// texture (weak_from_this fallback), so a resource whose identity has no alive
|
|
||||||
// entry has no trackable owner: its GL-side object is gone, or was never
|
|
||||||
// shared-owned, in which case recreation on a later sync is the safe fallback.
|
|
||||||
// Destruction goes through the per-frame deferred queues, never immediate.
|
|
||||||
Vector<TextureIdentity> orphanIdentities;
|
|
||||||
for (auto it = m_textureResources.begin(); it != m_textureResources.end(); ++it) {
|
|
||||||
if (m_aliveObjects.find(it->first) == m_aliveObjects.end()) {
|
|
||||||
orphanIdentities.emplace_back(it->first);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (const auto& identity : orphanIdentities) {
|
|
||||||
EraseTrackedTexture(identity);
|
|
||||||
}
|
|
||||||
prunedCount += orphanIdentities.size();
|
|
||||||
return prunedCount;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Bool VkTextureManager::SyncTexture(MG_State::GLState::ITextureObject &texture,
|
Bool VkTextureManager::SyncTexture(MG_State::GLState::ITextureObject &texture,
|
||||||
@@ -1418,13 +1241,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
const auto* syncingMipTexture = MG_State::GLState::AsMipmapTexture(&texture);
|
const auto* syncingMipTexture = MG_State::GLState::AsMipmapTexture(&texture);
|
||||||
const Uint32 syncingMipLevelCount =
|
const Uint32 syncingMipLevelCount =
|
||||||
syncingMipTexture != nullptr ? syncingMipTexture->GetMipmapLevelCount() : 0u;
|
syncingMipTexture != nullptr ? syncingMipTexture->GetMipmapLevelCount() : 0u;
|
||||||
// A pending storage-usage upgrade also has to bust the skip: nothing about the texture's
|
if (outResource.image != VK_NULL_HANDLE &&
|
||||||
// content or params changed, but the image itself must be recreated with STORAGE usage
|
|
||||||
// before it can back an image-unit descriptor.
|
|
||||||
const Bool storageUpgradePending =
|
|
||||||
!outResource.storageUsageResolved &&
|
|
||||||
m_storageImageTextures.find(MakeTextureIdentity(&texture)) != m_storageImageTextures.end();
|
|
||||||
if (outResource.image != VK_NULL_HANDLE && !storageUpgradePending &&
|
|
||||||
outResource.syncedContentVersion == syncingContentVersion &&
|
outResource.syncedContentVersion == syncingContentVersion &&
|
||||||
outResource.syncedTextureParamsVersion == texture.GetTextureParamsVersion() &&
|
outResource.syncedTextureParamsVersion == texture.GetTextureParamsVersion() &&
|
||||||
outResource.syncedMipLevelCount == syncingMipLevelCount) {
|
outResource.syncedMipLevelCount == syncingMipLevelCount) {
|
||||||
@@ -1494,23 +1311,11 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
const IntVec3 &texelSize, SizeT byteSize, Uint32 mipLevels,
|
const IntVec3 &texelSize, SizeT byteSize, Uint32 mipLevels,
|
||||||
TextureResource &resource) {
|
TextureResource &resource) {
|
||||||
const TextureFormatInfo formatInfo = ResolveTextureFormatInfo(texture.GetFormat());
|
const TextureFormatInfo formatInfo = ResolveTextureFormatInfo(texture.GetFormat());
|
||||||
VkFormat format = formatInfo.format;
|
const VkFormat format = formatInfo.format;
|
||||||
if (format == VK_FORMAT_UNDEFINED) {
|
if (format == VK_FORMAT_UNDEFINED) {
|
||||||
MGLOG_D("%s: format == VK_FORMAT_UNDEFINED", __func__);
|
MGLOG_D("%s: format == VK_FORMAT_UNDEFINED", __func__);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// X8_D24 lacks optimal-tiling support on several drivers (lavapipe included);
|
|
||||||
// D32_SFLOAT holds every 24-bit depth value exactly, and the upload path
|
|
||||||
// converts the shadow words to float (see the pure-depth branch below).
|
|
||||||
if (format == VK_FORMAT_X8_D24_UNORM_PACK32) {
|
|
||||||
VkFormatProperties formatProperties{};
|
|
||||||
vkGetPhysicalDeviceFormatProperties(m_physicalDevice, format, &formatProperties);
|
|
||||||
constexpr VkFormatFeatureFlags kDepthAttachmentAndSample =
|
|
||||||
VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT;
|
|
||||||
if ((formatProperties.optimalTilingFeatures & kDepthAttachmentAndSample) != kDepthAttachmentAndSample) {
|
|
||||||
format = VK_FORMAT_D32_SFLOAT;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (texelSize.x() <= 0 || texelSize.y() <= 0 /*|| byteSize == 0*/) {
|
if (texelSize.x() <= 0 || texelSize.y() <= 0 /*|| byteSize == 0*/) {
|
||||||
MGLOG_D("%s: texelSize or byteSize is zero", __func__);
|
MGLOG_D("%s: texelSize or byteSize is zero", __func__);
|
||||||
return false;
|
return false;
|
||||||
@@ -1520,36 +1325,19 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const Bool isMultisampleTexture = IsMultisampleTextureUploadTarget(uploadTarget);
|
const Bool isMultisampleTexture = IsMultisampleTextureUploadTarget(uploadTarget);
|
||||||
// A texture that has only ever defined level 0 gets a single-level backing
|
const Uint32 backingMipLevels =
|
||||||
// (ANGLE's model). Preallocating the full chain put every render target
|
isMultisampleTexture ? 1u : std::max(mipLevels, ComputeFullMipLevelCount(texelSize));
|
||||||
// onto Adreno's multi-mip image layout and grew each texture by a third
|
|
||||||
// for levels most textures never define. Once a second level is defined
|
|
||||||
// the backing is recreated ONE time with the full chain (the
|
|
||||||
// preserve-copy path below carries the pixels over), so sequentially-
|
|
||||||
// defined atlas mips do not recreate per level, and glGenerateMipmap -
|
|
||||||
// which defines every level before syncing - works unchanged.
|
|
||||||
TextureShapeInfo shapeInfo{};
|
TextureShapeInfo shapeInfo{};
|
||||||
const Bool supportedShape = TryResolveTextureShapeInfo(texture, uploadTarget, texelSize, shapeInfo);
|
const Bool supportedShape = TryResolveTextureShapeInfo(texture, uploadTarget, texelSize, shapeInfo);
|
||||||
// ComputeFullMipLevelCount takes max(x, y, z), and for every ARRAY shape z is the layer
|
MOBILEGL_ASSERT(supportedShape,
|
||||||
// count, not a mip-able axis: a 4x4 array with 192 layers asked for 6 levels on an image
|
"SyncTextureResource: unsupported uploadTarget=%s textureTarget=%s textureId=%d size=(%d,%d,%d) "
|
||||||
// whose legal maximum is 3 (VUID-VkImageCreateInfo-mipLevels-00958). Only the image's own
|
"mipLevels=%u vkViewType=%d",
|
||||||
// extent - width, height and shapeInfo.depth, which is 1 for every array - can bound it.
|
MG_Util::ConvertTextureUploadTargetToString(uploadTarget).c_str(),
|
||||||
// lavapipe has been letting this through unvalidated; a strict driver would not.
|
MG_Util::ConvertTextureTargetToString(texture.GetTarget()).c_str(),
|
||||||
const IntVec3 mipExtent{texelSize.x(), texelSize.y(), static_cast<Int>(shapeInfo.depth)};
|
texture.GetExternalIndex(), texelSize.x(), texelSize.y(), texelSize.z(), mipLevels,
|
||||||
const Uint32 fullMipLevels = ComputeFullMipLevelCount(mipExtent);
|
static_cast<Int>(MG_Util::ConvertTextureUploadTargetToVkEnum(uploadTarget)));
|
||||||
const Uint32 backingMipLevels =
|
|
||||||
isMultisampleTexture ? 1u : (mipLevels > 1 ? std::min(std::max(mipLevels, fullMipLevels), fullMipLevels) : 1u);
|
|
||||||
if (!supportedShape) {
|
if (!supportedShape) {
|
||||||
// A gap in this backend's coverage, not a broken invariant: the GL front end accepts
|
MGLOG_D("%s: not Texture2D, unsupported", __func__);
|
||||||
// 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;
|
return false;
|
||||||
}
|
}
|
||||||
VkSampleCountFlagBits resolvedSampleCount = VK_SAMPLE_COUNT_1_BIT;
|
VkSampleCountFlagBits resolvedSampleCount = VK_SAMPLE_COUNT_1_BIT;
|
||||||
@@ -1560,116 +1348,19 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
MG_Util::ConvertTextureUploadTargetToString(uploadTarget).c_str());
|
MG_Util::ConvertTextureUploadTargetToString(uploadTarget).c_str());
|
||||||
return false;
|
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);
|
const VkImageAspectFlags aspect = GetAspectMaskForFormat(format);
|
||||||
VkFormatProperties formatProperties{};
|
VkFormatProperties formatProperties{};
|
||||||
vkGetPhysicalDeviceFormatProperties(m_physicalDevice, format, &formatProperties);
|
vkGetPhysicalDeviceFormatProperties(m_physicalDevice, format, &formatProperties);
|
||||||
// Only textures that have actually been bound to a GL image unit get STORAGE usage (and
|
const Bool supportsStorageImage =
|
||||||
// the MUTABLE_FORMAT it drags in for format-reinterpreting image views). Requesting it
|
|
||||||
// for every storage-capable colour texture costs real bandwidth: Adreno cannot keep UBWC
|
|
||||||
// compression on an image that may be written through a storage descriptor, so the whole
|
|
||||||
// render target - MC's included - runs uncompressed. MarkStorageImageTexture upgrades a
|
|
||||||
// texture before its first image-unit draw, and the usage below feeds the compatibility
|
|
||||||
// check so the upgrade recreates the image.
|
|
||||||
const Bool markedAsStorageImage =
|
|
||||||
m_storageImageTextures.find(MakeTextureIdentity(
|
|
||||||
const_cast<MG_State::GLState::ITextureObject*>(&texture))) != m_storageImageTextures.end();
|
|
||||||
// Storage-image CAPABILITY (does the format allow it at all) is deliberately separate from
|
|
||||||
// whether this texture actually needs the usage. MUTABLE_FORMAT keys off capability, as
|
|
||||||
// before: format-reinterpreting views are not a storage-only concern - the SAMPLED path
|
|
||||||
// needs them too (GetOrCreateSampledImageView bails out without it, see ~line 892), so
|
|
||||||
// tying MUTABLE_FORMAT to the image-unit mark would break sampled format reinterpretation
|
|
||||||
// for every texture that never becomes a storage image.
|
|
||||||
const Bool storageImageCapable =
|
|
||||||
!isMultisampleTexture &&
|
!isMultisampleTexture &&
|
||||||
(aspect & VK_IMAGE_ASPECT_COLOR_BIT) != 0 &&
|
(aspect & VK_IMAGE_ASPECT_COLOR_BIT) != 0 &&
|
||||||
(formatProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT) != 0;
|
(formatProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT) != 0;
|
||||||
const Bool supportsStorageImage = storageImageCapable && markedAsStorageImage;
|
|
||||||
VkImageCreateFlags imageCreateFlags = shapeInfo.imageFlags;
|
VkImageCreateFlags imageCreateFlags = shapeInfo.imageFlags;
|
||||||
// One z slice of a 3D texture can only be attached to a framebuffer through a 2D view over
|
if (supportsStorageImage && IsMutableStorageImageFormat(format) &&
|
||||||
// it, which needs the image to be 2D-array-compatible (Vulkan 1.1 core, promoted from
|
|
||||||
// VK_KHR_maintenance1). Asked for optimistically and withdrawn per format below if the
|
|
||||||
// driver refuses - losing it only costs per-slice attachment, while failing creation would
|
|
||||||
// lose the texture entirely.
|
|
||||||
if (shapeInfo.imageType == VK_IMAGE_TYPE_3D && !isMultisampleTexture &&
|
|
||||||
m_2dArrayCompatibleUnsupported.find(format) == m_2dArrayCompatibleUnsupported.end()) {
|
|
||||||
imageCreateFlags |= VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT;
|
|
||||||
}
|
|
||||||
if (storageImageCapable && IsMutableStorageImageFormat(format) &&
|
|
||||||
m_mutableFormatUnsupported.find(format) == m_mutableFormatUnsupported.end()) {
|
m_mutableFormatUnsupported.find(format) == m_mutableFormatUnsupported.end()) {
|
||||||
imageCreateFlags |= VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT;
|
imageCreateFlags |= VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT;
|
||||||
}
|
}
|
||||||
// sRGB color images attach through their UNORM twin while GL_FRAMEBUFFER_SRGB is
|
|
||||||
// disabled (see ResolveSrgbAttachmentWriteFormat), which needs format-reinterpreting
|
|
||||||
// views - multisample sRGB render targets included.
|
|
||||||
if (ResolveSrgbAttachmentWriteFormat(format, false) != format &&
|
|
||||||
(aspect & VK_IMAGE_ASPECT_COLOR_BIT) != 0 &&
|
|
||||||
m_mutableFormatUnsupported.find(format) == m_mutableFormatUnsupported.end()) {
|
|
||||||
imageCreateFlags |= VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT;
|
|
||||||
}
|
|
||||||
|
|
||||||
VkImageUsageFlags desiredUsage =
|
|
||||||
VK_IMAGE_USAGE_SAMPLED_BIT |
|
|
||||||
(supportsStorageImage ? VK_IMAGE_USAGE_STORAGE_BIT : 0) |
|
|
||||||
((aspect & VK_IMAGE_ASPECT_COLOR_BIT) ? VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT : 0) |
|
|
||||||
(((aspect & VK_IMAGE_ASPECT_DEPTH_BIT) || (aspect & VK_IMAGE_ASPECT_STENCIL_BIT)) ?
|
|
||||||
VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT :
|
|
||||||
0);
|
|
||||||
if (!isMultisampleTexture) {
|
|
||||||
desiredUsage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Round a multisample request up to a count the device supports for this
|
|
||||||
// format (GL only promises "at least"), mirroring the renderbuffer path.
|
|
||||||
if (isMultisampleTexture && resolvedSampleCount != VK_SAMPLE_COUNT_1_BIT) {
|
|
||||||
auto supportedIt = m_multisampleCountsByFormat.find(format);
|
|
||||||
if (supportedIt == m_multisampleCountsByFormat.end()) {
|
|
||||||
VkImageFormatProperties imageFormatProperties{};
|
|
||||||
VkSampleCountFlags supported = VK_SAMPLE_COUNT_1_BIT;
|
|
||||||
if (vkGetPhysicalDeviceImageFormatProperties(m_physicalDevice, format, shapeInfo.imageType,
|
|
||||||
VK_IMAGE_TILING_OPTIMAL, desiredUsage, imageCreateFlags,
|
|
||||||
&imageFormatProperties) == VK_SUCCESS) {
|
|
||||||
supported = imageFormatProperties.sampleCounts;
|
|
||||||
}
|
|
||||||
supportedIt = m_multisampleCountsByFormat.emplace(format, supported).first;
|
|
||||||
}
|
|
||||||
const VkSampleCountFlags supported = supportedIt->second;
|
|
||||||
if ((supported & resolvedSampleCount) == 0) {
|
|
||||||
Uint32 rounded = 0;
|
|
||||||
for (Uint32 bit = static_cast<Uint32>(resolvedSampleCount) << 1; bit <= VK_SAMPLE_COUNT_64_BIT;
|
|
||||||
bit <<= 1) {
|
|
||||||
if ((supported & bit) != 0) {
|
|
||||||
rounded = bit;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (rounded == 0) {
|
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (rounded != 0) {
|
|
||||||
resolvedSampleCount = static_cast<VkSampleCountFlagBits>(rounded);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const Bool compatible = resource.image != VK_NULL_HANDLE && resource.format == format &&
|
const Bool compatible = resource.image != VK_NULL_HANDLE && resource.format == format &&
|
||||||
resource.extent.width == static_cast<Uint32>(texelSize.x()) &&
|
resource.extent.width == static_cast<Uint32>(texelSize.x()) &&
|
||||||
@@ -1679,7 +1370,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
resource.viewType == shapeInfo.viewType &&
|
resource.viewType == shapeInfo.viewType &&
|
||||||
resource.sampleCount == resolvedSampleCount &&
|
resource.sampleCount == resolvedSampleCount &&
|
||||||
resource.imageCreateFlags == imageCreateFlags &&
|
resource.imageCreateFlags == imageCreateFlags &&
|
||||||
resource.usageFlags == desiredUsage &&
|
|
||||||
resource.mipLevels == backingMipLevels;
|
resource.mipLevels == backingMipLevels;
|
||||||
if (compatible) {
|
if (compatible) {
|
||||||
if (resource.perMipViews.size() != backingMipLevels) {
|
if (resource.perMipViews.size() != backingMipLevels) {
|
||||||
@@ -1688,10 +1378,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
if (resource.perMipSampledViews.size() != backingMipLevels) {
|
if (resource.perMipSampledViews.size() != backingMipLevels) {
|
||||||
resource.perMipSampledViews.resize(backingMipLevels, VK_NULL_HANDLE);
|
resource.perMipSampledViews.resize(backingMipLevels, VK_NULL_HANDLE);
|
||||||
}
|
}
|
||||||
// Keeping the image is itself the answer to the mark: either it already carries
|
|
||||||
// STORAGE, or this format can never carry it. Either way there is nothing left to
|
|
||||||
// recreate, so stop reporting the texture as needing preparation.
|
|
||||||
resource.storageUsageResolved = markedAsStorageImage;
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1706,10 +1392,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
resource.sampleCount == resolvedSampleCount &&
|
resource.sampleCount == resolvedSampleCount &&
|
||||||
resource.imageCreateFlags == imageCreateFlags &&
|
resource.imageCreateFlags == imageCreateFlags &&
|
||||||
resolvedSampleCount == VK_SAMPLE_COUNT_1_BIT &&
|
resolvedSampleCount == VK_SAMPLE_COUNT_1_BIT &&
|
||||||
// '<=' rather than '<': a storage-usage upgrade recreates the image with an
|
resource.mipLevels < backingMipLevels &&
|
||||||
// unchanged mip count, and its contents (a render target's pixels live only on the
|
|
||||||
// GPU) still have to survive. The vkCmdCopyImage below copies min(mipLevels).
|
|
||||||
resource.mipLevels <= backingMipLevels &&
|
|
||||||
resource.layout != VK_IMAGE_LAYOUT_UNDEFINED;
|
resource.layout != VK_IMAGE_LAYOUT_UNDEFINED;
|
||||||
|
|
||||||
std::unique_ptr<TextureResource> preservedResource;
|
std::unique_ptr<TextureResource> preservedResource;
|
||||||
@@ -1731,39 +1414,17 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
imageInfo.format = format;
|
imageInfo.format = format;
|
||||||
imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
|
imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
|
||||||
imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||||
imageInfo.usage = desiredUsage;
|
imageInfo.usage = VK_IMAGE_USAGE_SAMPLED_BIT |
|
||||||
imageInfo.samples = resolvedSampleCount;
|
(supportsStorageImage ? VK_IMAGE_USAGE_STORAGE_BIT : 0) |
|
||||||
|
((aspect & VK_IMAGE_ASPECT_COLOR_BIT) ? VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT : 0) |
|
||||||
// Bound the mutability. A blindly-mutable image has to be laid out so that ANY format in
|
(((aspect & VK_IMAGE_ASPECT_DEPTH_BIT) || (aspect & VK_IMAGE_ASPECT_STENCIL_BIT)) ?
|
||||||
// its compatibility class can be viewed, which costs bandwidth compression on tilers;
|
VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT :
|
||||||
// naming the exact set instead lets the driver keep it. Only safe when that set really is
|
0);
|
||||||
// exhaustive, so it is restricted to textures that are not image-unit bound: sampled views
|
if (!isMultisampleTexture) {
|
||||||
// can only ever ask for ResolveSampledImageViewFormat's output, whereas glBindImageTexture
|
imageInfo.usage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
|
||||||
// may name any compatible format, which nothing here can enumerate ahead of time.
|
|
||||||
Vector<VkFormat> viewFormats;
|
|
||||||
VkImageFormatListCreateInfo formatListInfo{};
|
|
||||||
if (m_imageFormatListSupported && !supportsStorageImage &&
|
|
||||||
(imageInfo.flags & VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT) != 0) {
|
|
||||||
viewFormats.push_back(format);
|
|
||||||
for (const SamplerNumericDomain domain : {SamplerNumericDomain::Float,
|
|
||||||
SamplerNumericDomain::SignedInteger,
|
|
||||||
SamplerNumericDomain::UnsignedInteger}) {
|
|
||||||
const VkFormat viewFormat = ResolveSampledImageViewFormat(format, domain);
|
|
||||||
if (viewFormat == VK_FORMAT_UNDEFINED) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (std::find(viewFormats.begin(), viewFormats.end(), viewFormat) == viewFormats.end()) {
|
|
||||||
viewFormats.push_back(viewFormat);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
formatListInfo.sType = VK_STRUCTURE_TYPE_IMAGE_FORMAT_LIST_CREATE_INFO;
|
|
||||||
formatListInfo.viewFormatCount = static_cast<Uint32>(viewFormats.size());
|
|
||||||
formatListInfo.pViewFormats = viewFormats.data();
|
|
||||||
imageInfo.pNext = &formatListInfo;
|
|
||||||
}
|
}
|
||||||
|
imageInfo.samples = resolvedSampleCount;
|
||||||
if (isMultisampleTexture || (imageInfo.flags & (VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT |
|
if (isMultisampleTexture || (imageInfo.flags & VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT) != 0) {
|
||||||
VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT)) != 0) {
|
|
||||||
VkImageFormatProperties imageFormatProperties{};
|
VkImageFormatProperties imageFormatProperties{};
|
||||||
VkResult imageFormatResult = vkGetPhysicalDeviceImageFormatProperties(
|
VkResult imageFormatResult = vkGetPhysicalDeviceImageFormatProperties(
|
||||||
m_physicalDevice, format, imageInfo.imageType, imageInfo.tiling, imageInfo.usage,
|
m_physicalDevice, format, imageInfo.imageType, imageInfo.tiling, imageInfo.usage,
|
||||||
@@ -1786,22 +1447,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
m_physicalDevice, format, imageInfo.imageType, imageInfo.tiling, imageInfo.usage,
|
m_physicalDevice, format, imageInfo.imageType, imageInfo.tiling, imageInfo.usage,
|
||||||
imageInfo.flags, &imageFormatProperties);
|
imageInfo.flags, &imageFormatProperties);
|
||||||
}
|
}
|
||||||
if (imageFormatResult != VK_SUCCESS && !isMultisampleTexture &&
|
|
||||||
(imageInfo.flags & VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT) != 0) {
|
|
||||||
// Losing 2D-array compatibility only costs per-slice framebuffer attachment for this
|
|
||||||
// format; failing creation would lose the texture entirely. Remembered so later syncs
|
|
||||||
// neither reprobe nor flag-mismatch against this image and recreate it.
|
|
||||||
MGLOG_W("%s: VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT is unsupported for format=%d "
|
|
||||||
"textureId=%d; creating without it (per-slice framebuffer attachment will be "
|
|
||||||
"unavailable for it)",
|
|
||||||
__func__, static_cast<Int>(format), texture.GetExternalIndex());
|
|
||||||
m_2dArrayCompatibleUnsupported.insert(format);
|
|
||||||
imageInfo.flags &= ~VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT;
|
|
||||||
imageCreateFlags = imageInfo.flags;
|
|
||||||
imageFormatResult = vkGetPhysicalDeviceImageFormatProperties(
|
|
||||||
m_physicalDevice, format, imageInfo.imageType, imageInfo.tiling, imageInfo.usage,
|
|
||||||
imageInfo.flags, &imageFormatProperties);
|
|
||||||
}
|
|
||||||
if (imageFormatResult != VK_SUCCESS ||
|
if (imageFormatResult != VK_SUCCESS ||
|
||||||
(isMultisampleTexture && (imageFormatProperties.sampleCounts & resolvedSampleCount) == 0)) {
|
(isMultisampleTexture && (imageFormatProperties.sampleCounts & resolvedSampleCount) == 0)) {
|
||||||
MGLOG_D("%s: image flags=0x%x sampleCount=%d are unsupported for textureId=%d target=%s "
|
MGLOG_D("%s: image flags=0x%x sampleCount=%d are unsupported for textureId=%d target=%s "
|
||||||
@@ -1817,21 +1462,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
VmaAllocationCreateInfo allocationInfo{};
|
VmaAllocationCreateInfo allocationInfo{};
|
||||||
allocationInfo.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE;
|
allocationInfo.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE;
|
||||||
allocationInfo.requiredFlags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
|
allocationInfo.requiredFlags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
|
||||||
// Soft failure like the unsupported-sample-count path above: a driver can pass the
|
VK_VERIFY(vmaCreateImage(m_allocator, &imageInfo, &allocationInfo, &resource.image, &resource.allocation, nullptr),
|
||||||
// vkGetPhysicalDeviceImageFormatProperties pre-check yet still refuse the creation
|
"vmaCreateImage(texture)");
|
||||||
// (e.g. multisampled depth on lavapipe); the texture simply stays unbacked.
|
|
||||||
const VkResult createImageResult =
|
|
||||||
vmaCreateImage(m_allocator, &imageInfo, &allocationInfo, &resource.image, &resource.allocation, nullptr);
|
|
||||||
if (createImageResult != VK_SUCCESS) {
|
|
||||||
MGLOG_F("SyncTextureResource: vmaCreateImage failed (%d) textureId=%d extent=%ux%u depth=%u layers=%u "
|
|
||||||
"mips=%u samples=%d format=%d",
|
|
||||||
createImageResult, texture.GetExternalIndex(), imageInfo.extent.width, imageInfo.extent.height,
|
|
||||||
imageInfo.extent.depth, imageInfo.arrayLayers, imageInfo.mipLevels,
|
|
||||||
static_cast<Int>(imageInfo.samples), static_cast<Int>(imageInfo.format));
|
|
||||||
resource.image = VK_NULL_HANDLE;
|
|
||||||
resource.allocation = nullptr;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
++m_textureImageEpoch; // a new attachment image invalidates cached render passes
|
++m_textureImageEpoch; // a new attachment image invalidates cached render passes
|
||||||
|
|
||||||
resource.layout = VK_IMAGE_LAYOUT_UNDEFINED;
|
resource.layout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||||
@@ -1848,8 +1480,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
resource.viewType = shapeInfo.viewType;
|
resource.viewType = shapeInfo.viewType;
|
||||||
resource.sampleCount = resolvedSampleCount;
|
resource.sampleCount = resolvedSampleCount;
|
||||||
resource.imageCreateFlags = imageCreateFlags;
|
resource.imageCreateFlags = imageCreateFlags;
|
||||||
resource.usageFlags = imageInfo.usage;
|
|
||||||
resource.storageUsageResolved = markedAsStorageImage;
|
|
||||||
resource.syncedTextureParamsVersion = 0;
|
resource.syncedTextureParamsVersion = 0;
|
||||||
|
|
||||||
if (preservedResource) {
|
if (preservedResource) {
|
||||||
@@ -1899,28 +1529,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
m_deferredViewReleases[frameIndex].clear();
|
m_deferredViewReleases[frameIndex].clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
void VkTextureManager::ReclaimCompletedUploads(Bool waitAll) {
|
|
||||||
if (m_pendingUploadReclaims.empty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
SizeT completed = 0;
|
|
||||||
for (; completed < m_pendingUploadReclaims.size(); ++completed) {
|
|
||||||
PendingUploadReclaim& entry = m_pendingUploadReclaims[completed];
|
|
||||||
if (waitAll) {
|
|
||||||
VK_VERIFY(vkWaitForFences(m_device, 1, &entry.fence, VK_TRUE, UINT64_MAX),
|
|
||||||
"vkWaitForFences(texture upload reclaim)");
|
|
||||||
} else if (vkGetFenceStatus(m_device, entry.fence) != VK_SUCCESS) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
vkDestroyFence(m_device, entry.fence, nullptr);
|
|
||||||
vkFreeCommandBuffers(m_device, m_commandPool, 1, &entry.commandBuffer);
|
|
||||||
vmaDestroyBuffer(m_allocator, entry.stagingBuffer, entry.stagingAllocation);
|
|
||||||
}
|
|
||||||
m_pendingUploadReclaims.erase(m_pendingUploadReclaims.begin(),
|
|
||||||
m_pendingUploadReclaims.begin() + static_cast<std::ptrdiff_t>(completed));
|
|
||||||
}
|
|
||||||
|
|
||||||
void VkTextureManager::DestroyDeferredReleases() {
|
void VkTextureManager::DestroyDeferredReleases() {
|
||||||
for (auto& deferredReleases : m_deferredReleases) {
|
for (auto& deferredReleases : m_deferredReleases) {
|
||||||
deferredReleases.clear();
|
deferredReleases.clear();
|
||||||
@@ -2122,119 +1730,17 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Combined depth-stencil images need per-aspect copies (VkBufferImageCopy aspectMask
|
// Combined depth-stencil images need per-aspect de-interleaved copies (VkBufferImageCopy
|
||||||
// must have exactly one bit set), so de-interleave the shadow's GL wire format into
|
// aspectMask must have exactly one bit set). Until that is implemented, skip the upload
|
||||||
// a depth plane followed by a stencil plane per upload item.
|
// instead of recording an invalid command buffer that kills the process.
|
||||||
const VkImageAspectFlags uploadAspectMask = GetAspectMaskForFormat(outResource.format);
|
const VkImageAspectFlags uploadAspectMask = GetAspectMaskForFormat(outResource.format);
|
||||||
const Bool isCombinedDepthStencil =
|
if ((uploadAspectMask & VK_IMAGE_ASPECT_DEPTH_BIT) && (uploadAspectMask & VK_IMAGE_ASPECT_STENCIL_BIT)) {
|
||||||
(uploadAspectMask & VK_IMAGE_ASPECT_DEPTH_BIT) && (uploadAspectMask & VK_IMAGE_ASPECT_STENCIL_BIT);
|
MGLOG_E("UploadDirtyMipLevels: skipping unimplemented depth-stencil data upload for textureId=%d",
|
||||||
if (isCombinedDepthStencil) {
|
mipmapTexture.GetExternalIndex());
|
||||||
const Bool srcIsD24S8 = outResource.format == VK_FORMAT_D24_UNORM_S8_UINT;
|
for (const auto& item : uploadItems) {
|
||||||
const Bool srcIsD32FS8 = outResource.format == VK_FORMAT_D32_SFLOAT_S8_UINT;
|
mipmapTexture.MarkStorageDirty(item.target, item.level, false);
|
||||||
if (!srcIsD24S8 && !srcIsD32FS8) {
|
|
||||||
MGLOG_E("UploadDirtyMipLevels: unsupported combined depth-stencil format %d for textureId=%d",
|
|
||||||
static_cast<Int>(outResource.format), mipmapTexture.GetExternalIndex());
|
|
||||||
for (const auto& item : uploadItems) {
|
|
||||||
mipmapTexture.MarkStorageDirty(item.target, item.level, false);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
stagingSize = 0;
|
|
||||||
for (auto& item : uploadItems) {
|
|
||||||
const SizeT texelCount = static_cast<SizeT>(item.texelSize.x()) *
|
|
||||||
static_cast<SizeT>(item.texelSize.y()) *
|
|
||||||
static_cast<SizeT>(std::max(item.texelSize.z(), 1));
|
|
||||||
const SizeT shadowTexelSize = item.uploadByteSize / std::max<SizeT>(texelCount, 1);
|
|
||||||
MOBILEGL_ASSERT(shadowTexelSize == 4 || shadowTexelSize == 8,
|
|
||||||
"UploadDirtyMipLevels: unexpected depth-stencil shadow texel size %zu for textureId=%d",
|
|
||||||
shadowTexelSize, mipmapTexture.GetExternalIndex());
|
|
||||||
// Depth plane as the aspect's buffer-copy format (32-bit word for
|
|
||||||
// D24: low 24 bits; float for D32F), then one stencil byte per texel.
|
|
||||||
Vector<Uint8> deinterleaved(texelCount * 4 + texelCount);
|
|
||||||
Uint8* depthPlane = deinterleaved.data();
|
|
||||||
Uint8* stencilPlane = deinterleaved.data() + texelCount * 4;
|
|
||||||
const Uint8* shadow = static_cast<const Uint8*>(item.source);
|
|
||||||
for (SizeT t = 0; t < texelCount; ++t) {
|
|
||||||
if (shadowTexelSize == 8) {
|
|
||||||
// GL_FLOAT_32_UNSIGNED_INT_24_8_REV: float depth, then a word
|
|
||||||
// with stencil in its low 8 bits.
|
|
||||||
float depthValue;
|
|
||||||
Uint32 stencilWord;
|
|
||||||
std::memcpy(&depthValue, shadow + t * 8, sizeof(depthValue));
|
|
||||||
std::memcpy(&stencilWord, shadow + t * 8 + 4, sizeof(stencilWord));
|
|
||||||
if (srcIsD32FS8) {
|
|
||||||
std::memcpy(depthPlane + t * 4, &depthValue, sizeof(depthValue));
|
|
||||||
} else {
|
|
||||||
const float clamped = std::min(std::max(depthValue, 0.0f), 1.0f);
|
|
||||||
const Uint32 depthWord = static_cast<Uint32>(clamped * 16777215.0f + 0.5f);
|
|
||||||
std::memcpy(depthPlane + t * 4, &depthWord, sizeof(depthWord));
|
|
||||||
}
|
|
||||||
stencilPlane[t] = static_cast<Uint8>(stencilWord & 0xFFu);
|
|
||||||
} else {
|
|
||||||
// GL_UNSIGNED_INT_24_8: depth in the high 24 bits, stencil low 8.
|
|
||||||
Uint32 packed;
|
|
||||||
std::memcpy(&packed, shadow + t * 4, sizeof(packed));
|
|
||||||
if (srcIsD24S8) {
|
|
||||||
const Uint32 depthWord = packed >> 8;
|
|
||||||
std::memcpy(depthPlane + t * 4, &depthWord, sizeof(depthWord));
|
|
||||||
} else {
|
|
||||||
const float depthValue = static_cast<float>(packed >> 8) / 16777215.0f;
|
|
||||||
std::memcpy(depthPlane + t * 4, &depthValue, sizeof(depthValue));
|
|
||||||
}
|
|
||||||
stencilPlane[t] = static_cast<Uint8>(packed & 0xFFu);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
item.expandedData = Move(deinterleaved);
|
|
||||||
item.source = item.expandedData.data();
|
|
||||||
item.uploadByteSize = item.expandedData.size();
|
|
||||||
item.offset = stagingSize;
|
|
||||||
stagingSize += static_cast<VkDeviceSize>(item.uploadByteSize);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pure-depth images whose canonical shadow layout differs from the image texel
|
|
||||||
// layout (the shadow keeps a full-scale 16/32-bit unorm word or a float; the
|
|
||||||
// image may be X8_D24 or a D32_SFLOAT fallback) convert per texel here.
|
|
||||||
if (uploadAspectMask == VK_IMAGE_ASPECT_DEPTH_BIT) {
|
|
||||||
const TextureInternalFormat depthInternal = mipmapTexture.GetFormat();
|
|
||||||
const Bool shadowIsFloat = depthInternal == TextureInternalFormat::DepthComponent32F;
|
|
||||||
const Bool dstIsFloat = outResource.format == VK_FORMAT_D32_SFLOAT;
|
|
||||||
const Bool dstIsD24Word = outResource.format == VK_FORMAT_X8_D24_UNORM_PACK32;
|
|
||||||
stagingSize = 0;
|
|
||||||
for (auto& item : uploadItems) {
|
|
||||||
const SizeT texelCount = static_cast<SizeT>(item.texelSize.x()) *
|
|
||||||
static_cast<SizeT>(item.texelSize.y()) *
|
|
||||||
static_cast<SizeT>(std::max(item.texelSize.z(), 1));
|
|
||||||
const SizeT shadowTexelSize = item.uploadByteSize / std::max<SizeT>(texelCount, 1);
|
|
||||||
const Bool needsConversion =
|
|
||||||
(dstIsFloat && !shadowIsFloat) || (dstIsD24Word && shadowTexelSize == 4 && !shadowIsFloat);
|
|
||||||
if (needsConversion) {
|
|
||||||
Vector<Uint8> converted(texelCount * 4);
|
|
||||||
const Uint8* shadow = static_cast<const Uint8*>(item.source);
|
|
||||||
for (SizeT t = 0; t < texelCount; ++t) {
|
|
||||||
Uint32 wide = 0;
|
|
||||||
if (shadowTexelSize == 2) {
|
|
||||||
Uint16 raw = 0;
|
|
||||||
std::memcpy(&raw, shadow + t * 2, sizeof(raw));
|
|
||||||
wide = (static_cast<Uint32>(raw) << 16) | raw;
|
|
||||||
} else {
|
|
||||||
std::memcpy(&wide, shadow + t * 4, sizeof(wide));
|
|
||||||
}
|
|
||||||
if (dstIsFloat) {
|
|
||||||
const float value = static_cast<float>(static_cast<double>(wide) / 4294967295.0);
|
|
||||||
std::memcpy(converted.data() + t * 4, &value, sizeof(value));
|
|
||||||
} else { // X8_D24: depth in the low 24 bits of a 32-bit word
|
|
||||||
const Uint32 word = wide >> 8;
|
|
||||||
std::memcpy(converted.data() + t * 4, &word, sizeof(word));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
item.expandedData = Move(converted);
|
|
||||||
item.source = item.expandedData.data();
|
|
||||||
item.uploadByteSize = item.expandedData.size();
|
|
||||||
}
|
|
||||||
item.offset = stagingSize;
|
|
||||||
stagingSize += static_cast<VkDeviceSize>(item.uploadByteSize);
|
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
VkBuffer stagingBuffer = VK_NULL_HANDLE;
|
VkBuffer stagingBuffer = VK_NULL_HANDLE;
|
||||||
@@ -2287,14 +1793,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
aspectMask, 0, outResource.mipLevels, outResource.arrayLayers);
|
aspectMask, 0, outResource.mipLevels, outResource.arrayLayers);
|
||||||
MOBILEGL_ASSERT(ok, "TransitionImageLayout to VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL failed");
|
MOBILEGL_ASSERT(ok, "TransitionImageLayout to VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL failed");
|
||||||
|
|
||||||
// Array textures keep their GL "depth" in VkImage array layers, so the
|
|
||||||
// copy must address layerCount, not imageExtent.depth (which is invalid
|
|
||||||
// for 2D images and silently dropped every layer past the first).
|
|
||||||
const Bool depthSelectsArrayLayer = outResource.viewType == VK_IMAGE_VIEW_TYPE_1D_ARRAY ||
|
|
||||||
outResource.viewType == VK_IMAGE_VIEW_TYPE_2D_ARRAY ||
|
|
||||||
outResource.viewType == VK_IMAGE_VIEW_TYPE_CUBE_ARRAY;
|
|
||||||
for (const auto& item : uploadItems) {
|
for (const auto& item : uploadItems) {
|
||||||
const Uint32 depthOrLayers = item.texelSize.z() > 0 ? static_cast<Uint32>(item.texelSize.z()) : 1u;
|
|
||||||
VkBufferImageCopy copy{};
|
VkBufferImageCopy copy{};
|
||||||
copy.bufferOffset = item.offset;
|
copy.bufferOffset = item.offset;
|
||||||
copy.bufferRowLength = 0;
|
copy.bufferRowLength = 0;
|
||||||
@@ -2302,24 +1801,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
copy.imageSubresource.aspectMask = aspectMask;
|
copy.imageSubresource.aspectMask = aspectMask;
|
||||||
copy.imageSubresource.mipLevel = item.level;
|
copy.imageSubresource.mipLevel = item.level;
|
||||||
copy.imageSubresource.baseArrayLayer = item.baseArrayLayer;
|
copy.imageSubresource.baseArrayLayer = item.baseArrayLayer;
|
||||||
copy.imageSubresource.layerCount = depthSelectsArrayLayer ? depthOrLayers : 1;
|
copy.imageSubresource.layerCount = 1;
|
||||||
copy.imageOffset = {0, 0, 0};
|
copy.imageOffset = {0, 0, 0};
|
||||||
copy.imageExtent = {static_cast<Uint32>(item.texelSize.x()), static_cast<Uint32>(item.texelSize.y()),
|
copy.imageExtent = {static_cast<Uint32>(item.texelSize.x()), static_cast<Uint32>(item.texelSize.y()),
|
||||||
depthSelectsArrayLayer ? 1u : depthOrLayers};
|
item.texelSize.z() > 0 ? static_cast<Uint32>(item.texelSize.z()) : 1u};
|
||||||
if (isCombinedDepthStencil) {
|
|
||||||
const SizeT texelCount = static_cast<SizeT>(item.texelSize.x()) *
|
|
||||||
static_cast<SizeT>(item.texelSize.y()) *
|
|
||||||
static_cast<SizeT>(std::max(item.texelSize.z(), 1));
|
|
||||||
VkBufferImageCopy depthCopy = copy;
|
|
||||||
depthCopy.imageSubresource.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;
|
|
||||||
VkBufferImageCopy stencilCopy = copy;
|
|
||||||
stencilCopy.imageSubresource.aspectMask = VK_IMAGE_ASPECT_STENCIL_BIT;
|
|
||||||
stencilCopy.bufferOffset = item.offset + static_cast<VkDeviceSize>(texelCount) * 4;
|
|
||||||
const VkBufferImageCopy copies[2] = {depthCopy, stencilCopy};
|
|
||||||
vkCmdCopyBufferToImage(commandBuffer, stagingBuffer, outResource.image,
|
|
||||||
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 2, copies);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
vkCmdCopyBufferToImage(commandBuffer, stagingBuffer, outResource.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
vkCmdCopyBufferToImage(commandBuffer, stagingBuffer, outResource.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
||||||
1, ©);
|
1, ©);
|
||||||
}
|
}
|
||||||
@@ -2350,23 +1835,11 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
VK_VERIFY(vkCreateFence(m_device, &fenceInfo, nullptr, &uploadFence), "vkCreateFence(texture upload)");
|
VK_VERIFY(vkCreateFence(m_device, &fenceInfo, nullptr, &uploadFence), "vkCreateFence(texture upload)");
|
||||||
|
|
||||||
VK_VERIFY(vkQueueSubmit(m_graphicsQueue, 1, &submitInfo, uploadFence), "vkQueueSubmit(texture)");
|
VK_VERIFY(vkQueueSubmit(m_graphicsQueue, 1, &submitInfo, uploadFence), "vkQueueSubmit(texture)");
|
||||||
// Do NOT wait the fence here: this submit sits behind the previous
|
VK_VERIFY(vkWaitForFences(m_device, 1, &uploadFence, VK_TRUE, UINT64_MAX), "vkWaitForFences(texture upload)");
|
||||||
// frame's rendering on the queue, so a synchronous wait stalls the CPU
|
vkDestroyFence(m_device, uploadFence, nullptr);
|
||||||
// until the GPU drains - a per-frame vkQueueWaitIdle for any workload
|
vkFreeCommandBuffers(m_device, m_commandPool, 1, &commandBuffer);
|
||||||
// with animated textures. Ordering against the current frame's draws is
|
|
||||||
// already guaranteed (its command buffer is submitted later, at
|
vmaDestroyBuffer(m_allocator, stagingBuffer, stagingAllocation);
|
||||||
// present), so only the transient objects need to survive execution;
|
|
||||||
// park them until the fence signals.
|
|
||||||
m_pendingUploadReclaims.push_back({uploadFence, commandBuffer, stagingBuffer, stagingAllocation});
|
|
||||||
ReclaimCompletedUploads();
|
|
||||||
// Backstop for pathological upload storms: bound in-flight staging
|
|
||||||
// memory by blocking on the oldest upload only once the list is deep.
|
|
||||||
constexpr SizeT kMaxPendingTextureUploads = 16;
|
|
||||||
if (m_pendingUploadReclaims.size() > kMaxPendingTextureUploads) {
|
|
||||||
VK_VERIFY(vkWaitForFences(m_device, 1, &m_pendingUploadReclaims.front().fence, VK_TRUE, UINT64_MAX),
|
|
||||||
"vkWaitForFences(texture upload backstop)");
|
|
||||||
ReclaimCompletedUploads();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
MGLOG_D("%s: texture upload cmd failed", __func__);
|
MGLOG_D("%s: texture upload cmd failed", __func__);
|
||||||
|
|||||||
@@ -28,9 +28,6 @@ public:
|
|||||||
// manager keys its per-draw fast path on this so an attachment's image recreation
|
// manager keys its per-draw fast path on this so an attachment's image recreation
|
||||||
// invalidates the cached render pass (dirty-flag tracking; portable to Vulkan 1.1).
|
// invalidates the cached render pass (dirty-flag tracking; portable to Vulkan 1.1).
|
||||||
Uint64 GetTextureImageEpoch() const { return m_textureImageEpoch; }
|
Uint64 GetTextureImageEpoch() const { return m_textureImageEpoch; }
|
||||||
// Bumped whenever any tracked texture resource is erased; cached
|
|
||||||
// TextureResource pointers are valid only while this is unchanged.
|
|
||||||
Uint64 GetResourceEraseEpoch() const { return m_resourceEraseEpoch; }
|
|
||||||
|
|
||||||
struct TextureIdentity {
|
struct TextureIdentity {
|
||||||
MG_State::GLState::ITextureObject* texture = nullptr;
|
MG_State::GLState::ITextureObject* texture = nullptr;
|
||||||
@@ -56,9 +53,6 @@ public:
|
|||||||
VkCommandPool commandPool = VK_NULL_HANDLE;
|
VkCommandPool commandPool = VK_NULL_HANDLE;
|
||||||
VkQueue graphicsQueue = VK_NULL_HANDLE;
|
VkQueue graphicsQueue = VK_NULL_HANDLE;
|
||||||
Uint32 frameCount = 0;
|
Uint32 frameCount = 0;
|
||||||
// VK_KHR_image_format_list is enabled: MUTABLE_FORMAT images can name the exact set of
|
|
||||||
// formats they will be viewed as, which is what lets a tiler keep them compressed.
|
|
||||||
Bool imageFormatListSupported = false;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct TextureResource {
|
struct TextureResource {
|
||||||
@@ -67,16 +61,12 @@ public:
|
|||||||
Uint32 baseArrayLayer = 0;
|
Uint32 baseArrayLayer = 0;
|
||||||
Uint32 layerCount = 1;
|
Uint32 layerCount = 1;
|
||||||
VkImageViewType viewType = VK_IMAGE_VIEW_TYPE_2D;
|
VkImageViewType viewType = VK_IMAGE_VIEW_TYPE_2D;
|
||||||
// May differ from the image format: sRGB images attach through their UNORM
|
|
||||||
// twin while GL_FRAMEBUFFER_SRGB is disabled.
|
|
||||||
VkFormat viewFormat = VK_FORMAT_UNDEFINED;
|
|
||||||
|
|
||||||
Bool operator==(const AttachmentViewKey& other) const {
|
Bool operator==(const AttachmentViewKey& other) const {
|
||||||
return mipLevel == other.mipLevel &&
|
return mipLevel == other.mipLevel &&
|
||||||
baseArrayLayer == other.baseArrayLayer &&
|
baseArrayLayer == other.baseArrayLayer &&
|
||||||
layerCount == other.layerCount &&
|
layerCount == other.layerCount &&
|
||||||
viewType == other.viewType &&
|
viewType == other.viewType;
|
||||||
viewFormat == other.viewFormat;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -87,8 +77,6 @@ public:
|
|||||||
hash ^= std::hash<Uint32>{}(key.layerCount) + 0x9e3779b9u + (hash << 6) + (hash >> 2);
|
hash ^= std::hash<Uint32>{}(key.layerCount) + 0x9e3779b9u + (hash << 6) + (hash >> 2);
|
||||||
hash ^= std::hash<Uint32>{}(static_cast<Uint32>(key.viewType)) +
|
hash ^= std::hash<Uint32>{}(static_cast<Uint32>(key.viewType)) +
|
||||||
0x9e3779b9u + (hash << 6) + (hash >> 2);
|
0x9e3779b9u + (hash << 6) + (hash >> 2);
|
||||||
hash ^= std::hash<Uint32>{}(static_cast<Uint32>(key.viewFormat)) +
|
|
||||||
0x9e3779b9u + (hash << 6) + (hash >> 2);
|
|
||||||
return hash;
|
return hash;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -169,25 +157,7 @@ public:
|
|||||||
VkImageViewType viewType = VK_IMAGE_VIEW_TYPE_2D;
|
VkImageViewType viewType = VK_IMAGE_VIEW_TYPE_2D;
|
||||||
VkSampleCountFlagBits sampleCount = VK_SAMPLE_COUNT_1_BIT;
|
VkSampleCountFlagBits sampleCount = VK_SAMPLE_COUNT_1_BIT;
|
||||||
VkImageCreateFlags imageCreateFlags = 0;
|
VkImageCreateFlags imageCreateFlags = 0;
|
||||||
// Usage the live image was created with. STORAGE is only requested for textures that
|
|
||||||
// have actually been bound to a GL image unit, because on Adreno a storage-capable
|
|
||||||
// image loses UBWC bandwidth compression; a later image binding upgrades the usage
|
|
||||||
// and recreates the image, so the resolved usage has to be part of the compatibility
|
|
||||||
// check that decides whether the existing image can be kept.
|
|
||||||
VkImageUsageFlags usageFlags = 0;
|
|
||||||
// True once this image was (re)resolved while the texture was already marked as an
|
|
||||||
// image-unit texture. Distinguishes "not upgraded yet" from "cannot be upgraded"
|
|
||||||
// (a format whose optimalTilingFeatures lack STORAGE_IMAGE never gains the bit), so
|
|
||||||
// NeedsStorageImagePreparation cannot ask for a recreate that will never happen.
|
|
||||||
Bool storageUsageResolved = false;
|
|
||||||
Uint16 syncedTextureParamsVersion = 0;
|
Uint16 syncedTextureParamsVersion = 0;
|
||||||
// Recording generation (VkTextureManager::GetRecordingGeneration) of the last
|
|
||||||
// command referencing this image that was recorded into the CURRENT frame
|
|
||||||
// command buffer. An image untouched by the open recording may have its
|
|
||||||
// out-of-pass work (deferred clears, sampled-layout transitions) recorded
|
|
||||||
// into the frame's PRE command buffer - which executes strictly before the
|
|
||||||
// frame's commands - instead of splitting the active render pass.
|
|
||||||
Uint64 lastRecordingGeneration = 0;
|
|
||||||
// Snapshot of ITextureObject::GetContentVersion() at the last successful sync;
|
// Snapshot of ITextureObject::GetContentVersion() at the last successful sync;
|
||||||
// lets SyncTexture skip the whole re-check/re-upload when content is unchanged.
|
// lets SyncTexture skip the whole re-check/re-upload when content is unchanged.
|
||||||
Uint64 syncedContentVersion = 0;
|
Uint64 syncedContentVersion = 0;
|
||||||
@@ -220,10 +190,7 @@ public:
|
|||||||
std::swap(this->viewType, that.viewType);
|
std::swap(this->viewType, that.viewType);
|
||||||
std::swap(this->sampleCount, that.sampleCount);
|
std::swap(this->sampleCount, that.sampleCount);
|
||||||
std::swap(this->imageCreateFlags, that.imageCreateFlags);
|
std::swap(this->imageCreateFlags, that.imageCreateFlags);
|
||||||
std::swap(this->usageFlags, that.usageFlags);
|
|
||||||
std::swap(this->storageUsageResolved, that.storageUsageResolved);
|
|
||||||
std::swap(this->syncedTextureParamsVersion, that.syncedTextureParamsVersion);
|
std::swap(this->syncedTextureParamsVersion, that.syncedTextureParamsVersion);
|
||||||
std::swap(this->lastRecordingGeneration, that.lastRecordingGeneration);
|
|
||||||
std::swap(this->syncedContentVersion, that.syncedContentVersion);
|
std::swap(this->syncedContentVersion, that.syncedContentVersion);
|
||||||
std::swap(this->syncedMipLevelCount, that.syncedMipLevelCount);
|
std::swap(this->syncedMipLevelCount, that.syncedMipLevelCount);
|
||||||
}
|
}
|
||||||
@@ -284,8 +251,6 @@ public:
|
|||||||
viewType = VK_IMAGE_VIEW_TYPE_2D;
|
viewType = VK_IMAGE_VIEW_TYPE_2D;
|
||||||
sampleCount = VK_SAMPLE_COUNT_1_BIT;
|
sampleCount = VK_SAMPLE_COUNT_1_BIT;
|
||||||
imageCreateFlags = 0;
|
imageCreateFlags = 0;
|
||||||
usageFlags = 0;
|
|
||||||
storageUsageResolved = false;
|
|
||||||
syncedTextureParamsVersion = 0;
|
syncedTextureParamsVersion = 0;
|
||||||
syncedContentVersion = 0;
|
syncedContentVersion = 0;
|
||||||
syncedMipLevelCount = 0;
|
syncedMipLevelCount = 0;
|
||||||
@@ -302,10 +267,6 @@ public:
|
|||||||
Bool Initialize(const InitInfo& initInfo);
|
Bool Initialize(const InitInfo& initInfo);
|
||||||
void Shutdown();
|
void Shutdown();
|
||||||
void BeginFrame(Uint32 frameIndex);
|
void BeginFrame(Uint32 frameIndex);
|
||||||
// Drains every frame slot's deferred image/view releases. Only valid when
|
|
||||||
// the caller has proven every queue submission complete; used by the
|
|
||||||
// present-less frame-boundary drain.
|
|
||||||
void CollectAllDeferredReleases();
|
|
||||||
|
|
||||||
TextureResource* SyncTextureAndGetDescriptor(
|
TextureResource* SyncTextureAndGetDescriptor(
|
||||||
MG_State::GLState::ITextureObject& texture);
|
MG_State::GLState::ITextureObject& texture);
|
||||||
@@ -324,36 +285,6 @@ public:
|
|||||||
VkImageLayout newLayout);
|
VkImageLayout newLayout);
|
||||||
Bool TransitionTextureForSampling(VkCommandBuffer commandBuffer, MG_State::GLState::ITextureObject& texture);
|
Bool TransitionTextureForSampling(VkCommandBuffer commandBuffer, MG_State::GLState::ITextureObject& texture);
|
||||||
Bool TransitionTextureForStorageImage(VkCommandBuffer commandBuffer, MG_State::GLState::ITextureObject& texture);
|
Bool TransitionTextureForStorageImage(VkCommandBuffer commandBuffer, MG_State::GLState::ITextureObject& texture);
|
||||||
|
|
||||||
// Recording-generation bookkeeping for the pre-pass command stream. The
|
|
||||||
// generation advances every time the frame command buffer (re)begins
|
|
||||||
// recording; a resource whose stamp does not match was not referenced by
|
|
||||||
// any command in the open recording, so its out-of-pass work may safely
|
|
||||||
// execute ahead of the whole recording (in the pre command buffer).
|
|
||||||
void AdvanceRecordingGeneration() { ++m_recordingGeneration; }
|
|
||||||
void StampResourceRecordingUse(TextureResource& resource) const {
|
|
||||||
resource.lastRecordingGeneration = m_recordingGeneration;
|
|
||||||
}
|
|
||||||
// Map-lookup variant for callers that only hold the GL texture object.
|
|
||||||
void StampTextureRecordingUse(MG_State::GLState::ITextureObject* texture);
|
|
||||||
Bool WasTouchedThisRecording(const TextureResource& resource) const {
|
|
||||||
return resource.lastRecordingGeneration == m_recordingGeneration;
|
|
||||||
}
|
|
||||||
// Records that this texture is bound to a GL image unit, so its image must carry
|
|
||||||
// VK_IMAGE_USAGE_STORAGE_BIT. Must be called before NeedsStorageImagePreparation, and
|
|
||||||
// therefore before the render pass is committed: an image that has to be upgraded is
|
|
||||||
// recreated, which is illegal inside a render pass. Sticky for the texture's lifetime -
|
|
||||||
// GL lets an image binding come and go, and re-creating the image every time it does
|
|
||||||
// would cost far more than the compression it wins back.
|
|
||||||
void MarkStorageImageTexture(MG_State::GLState::ITextureObject& texture);
|
|
||||||
// True when this texture is marked but its live image predates the mark, i.e. the next sync
|
|
||||||
// will recreate it with STORAGE usage and copy the old contents forward. Callers use this to
|
|
||||||
// submit their pending recording first, so that copy cannot read pre-flush content.
|
|
||||||
Bool NeedsStorageUsageUpgrade(MG_State::GLState::ITextureObject& texture) const;
|
|
||||||
// The same ordering question for the other recreate-and-preserve trigger: true when this
|
|
||||||
// texture's live image carries a shorter mip chain than a full one, so defining the missing
|
|
||||||
// levels recreates it and copies the old contents forward.
|
|
||||||
Bool NeedsMipChainGrowth(MG_State::GLState::ITextureObject& texture) const;
|
|
||||||
// Non-mutating probe for the per-draw storage-image fast path: true when preparing this
|
// Non-mutating probe for the per-draw storage-image fast path: true when preparing this
|
||||||
// texture as a storage image may need work that is illegal inside a render pass (resource
|
// texture as a storage image may need work that is illegal inside a render pass (resource
|
||||||
// creation, dirty-content upload, or a layout transition to GENERAL). Unknown state reports
|
// creation, dirty-content upload, or a layout transition to GENERAL). Unknown state reports
|
||||||
@@ -400,9 +331,6 @@ public:
|
|||||||
private:
|
private:
|
||||||
// Bumped in SyncTextureResource right after vmaCreateImage(texture). See GetTextureImageEpoch().
|
// Bumped in SyncTextureResource right after vmaCreateImage(texture). See GetTextureImageEpoch().
|
||||||
Uint64 m_textureImageEpoch = 1;
|
Uint64 m_textureImageEpoch = 1;
|
||||||
// See AdvanceRecordingGeneration. Starts above every resource's default
|
|
||||||
// stamp of 0 so a fresh resource counts as untouched.
|
|
||||||
Uint64 m_recordingGeneration = 1;
|
|
||||||
|
|
||||||
Bool SyncTexture(MG_State::GLState::ITextureObject &texture,
|
Bool SyncTexture(MG_State::GLState::ITextureObject &texture,
|
||||||
TextureResource &outResource);
|
TextureResource &outResource);
|
||||||
@@ -433,28 +361,18 @@ private:
|
|||||||
void DeferViewRelease(VkImageView view);
|
void DeferViewRelease(VkImageView view);
|
||||||
void CollectDeferredReleases(Uint32 frameIndex);
|
void CollectDeferredReleases(Uint32 frameIndex);
|
||||||
void DestroyDeferredReleases();
|
void DestroyDeferredReleases();
|
||||||
// Frees the fence/command buffer/staging buffer of every in-flight texture
|
|
||||||
// upload whose fence has signaled (submission order = completion order on
|
|
||||||
// the single queue, so the scan stops at the first still-pending entry).
|
|
||||||
// waitAll blocks on every entry - Shutdown's drain.
|
|
||||||
void ReclaimCompletedUploads(Bool waitAll = false);
|
|
||||||
static TextureIdentity MakeTextureIdentity(MG_State::GLState::ITextureObject* texture);
|
static TextureIdentity MakeTextureIdentity(MG_State::GLState::ITextureObject* texture);
|
||||||
void EraseTrackedTexture(const TextureIdentity& identity);
|
void EraseTrackedTexture(const TextureIdentity& identity);
|
||||||
void PruneStaleTextureAliases(MG_State::GLState::ITextureObject* texture);
|
void PruneStaleTextureAliases(MG_State::GLState::ITextureObject* texture);
|
||||||
SizeT PruneDeadTextures();
|
|
||||||
|
|
||||||
VkDevice m_device = VK_NULL_HANDLE;
|
VkDevice m_device = VK_NULL_HANDLE;
|
||||||
VkPhysicalDevice m_physicalDevice = VK_NULL_HANDLE;
|
VkPhysicalDevice m_physicalDevice = VK_NULL_HANDLE;
|
||||||
VmaAllocator m_allocator = nullptr;
|
VmaAllocator m_allocator = nullptr;
|
||||||
VkCommandPool m_commandPool = VK_NULL_HANDLE;
|
VkCommandPool m_commandPool = VK_NULL_HANDLE;
|
||||||
VkQueue m_graphicsQueue = VK_NULL_HANDLE;
|
VkQueue m_graphicsQueue = VK_NULL_HANDLE;
|
||||||
Bool m_imageFormatListSupported = false;
|
|
||||||
Uint32 m_currentFrameIndex = 0;
|
Uint32 m_currentFrameIndex = 0;
|
||||||
|
|
||||||
Uint8 m_gcCounter = 0;
|
Uint8 m_gcCounter = 0;
|
||||||
// Frame-boundary GC gate: counts BeginFrame calls, not draws, so texture churn
|
|
||||||
// through non-draw paths (FBO clears, readbacks) still reaches the prune.
|
|
||||||
Uint32 m_gcFrameCounter = 0;
|
|
||||||
// Active only between BeginDrawSyncScope/EndDrawSyncScope; identities of
|
// Active only between BeginDrawSyncScope/EndDrawSyncScope; identities of
|
||||||
// textures already fully synced in the current draw (small N -> flat scan).
|
// textures already fully synced in the current draw (small N -> flat scan).
|
||||||
Bool m_drawSyncScopeActive = false;
|
Bool m_drawSyncScopeActive = false;
|
||||||
@@ -467,49 +385,12 @@ private:
|
|||||||
TextureResource* resource = nullptr;
|
TextureResource* resource = nullptr;
|
||||||
};
|
};
|
||||||
Vector<DrawSyncedTexture> m_drawSyncedThisDraw;
|
Vector<DrawSyncedTexture> m_drawSyncedThisDraw;
|
||||||
// Cross-draw sampled-texture memo: the same few textures (atlas, lightmap)
|
|
||||||
// are resolved on every draw, so cache their resource pointers and skip the
|
|
||||||
// alive/resource map lookups. Node-based std::unordered_map keeps the
|
|
||||||
// pointees stable across inserts; erases bump m_resourceEraseEpoch, which
|
|
||||||
// every memo entry must match. SyncTexture still runs on memo hits, so
|
|
||||||
// content/param freshness is unaffected. A dead-then-reused texture address
|
|
||||||
// cannot false-hit: the new object carries a new lifetime id.
|
|
||||||
struct SyncedTextureMemoEntry {
|
|
||||||
const MG_State::GLState::ITextureObject* texture = nullptr;
|
|
||||||
Uint64 lifetimeId = 0;
|
|
||||||
Uint64 eraseEpoch = 0;
|
|
||||||
TextureResource* resource = nullptr;
|
|
||||||
};
|
|
||||||
static constexpr Uint32 kSyncedTextureMemoSize = 8;
|
|
||||||
SyncedTextureMemoEntry m_syncedTextureMemo[kSyncedTextureMemoSize];
|
|
||||||
Uint32 m_syncedTextureMemoNext = 0;
|
|
||||||
Uint64 m_resourceEraseEpoch = 1;
|
|
||||||
// Formats whose mutable-image probe failed on this device; their images are created
|
// Formats whose mutable-image probe failed on this device; their images are created
|
||||||
// without MUTABLE_FORMAT_BIT so repeat syncs neither re-probe nor flag-mismatch.
|
// without MUTABLE_FORMAT_BIT so repeat syncs neither re-probe nor flag-mismatch.
|
||||||
std::unordered_set<VkFormat> m_mutableFormatUnsupported;
|
std::unordered_set<VkFormat> m_mutableFormatUnsupported;
|
||||||
// Formats whose 3D images refused VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT. Per format+usage,
|
|
||||||
// exactly like the mutable-format verdict above, so it is answered at image creation and
|
|
||||||
// remembered rather than probed once globally.
|
|
||||||
std::unordered_set<VkFormat> m_2dArrayCompatibleUnsupported;
|
|
||||||
std::unordered_map<TextureIdentity, WeakPtr<MG_State::GLState::ITextureObject>, TextureIdentityHash> m_aliveObjects;
|
std::unordered_map<TextureIdentity, WeakPtr<MG_State::GLState::ITextureObject>, TextureIdentityHash> m_aliveObjects;
|
||||||
std::unordered_map<TextureIdentity, TextureResource, TextureIdentityHash> m_textureResources;
|
std::unordered_map<TextureIdentity, TextureResource, TextureIdentityHash> m_textureResources;
|
||||||
// Textures that have been bound to a GL image unit (see MarkStorageImageTexture).
|
|
||||||
std::unordered_set<TextureIdentity, TextureIdentityHash> m_storageImageTextures;
|
|
||||||
// Supported multisample counts per format, so repeat texture syncs do not
|
|
||||||
// re-query vkGetPhysicalDeviceImageFormatProperties.
|
|
||||||
std::unordered_map<VkFormat, VkSampleCountFlags> m_multisampleCountsByFormat;
|
|
||||||
Vector<Vector<TextureResource>> m_deferredReleases;
|
Vector<Vector<TextureResource>> m_deferredReleases;
|
||||||
Vector<Vector<VkImageView>> m_deferredViewReleases;
|
Vector<Vector<VkImageView>> m_deferredViewReleases;
|
||||||
// Texture uploads are submitted out-of-band but NOT waited on (waiting
|
|
||||||
// behind the queue serialized the CPU against the previous frame's GPU
|
|
||||||
// work every time an animated atlas re-uploaded). Their transient objects
|
|
||||||
// are parked here and reclaimed once the upload fence signals.
|
|
||||||
struct PendingUploadReclaim {
|
|
||||||
VkFence fence = VK_NULL_HANDLE;
|
|
||||||
VkCommandBuffer commandBuffer = VK_NULL_HANDLE;
|
|
||||||
VkBuffer stagingBuffer = VK_NULL_HANDLE;
|
|
||||||
VmaAllocation stagingAllocation = nullptr;
|
|
||||||
};
|
|
||||||
Vector<PendingUploadReclaim> m_pendingUploadReclaims;
|
|
||||||
};
|
};
|
||||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -76,10 +76,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
GLenum indexType = GL_UNSIGNED_SHORT;
|
GLenum indexType = GL_UNSIGNED_SHORT;
|
||||||
SizeT indexByteOffset = 0;
|
SizeT indexByteOffset = 0;
|
||||||
SizeT indexByteSize = 0;
|
SizeT indexByteSize = 0;
|
||||||
// Interpret indexByteOffset as a raw client pointer even when an element
|
|
||||||
// array buffer is bound (backend-synthesized index lists, e.g. the
|
|
||||||
// GL_LINE_LOOP -> LINE_STRIP rewrite).
|
|
||||||
Bool forceClientMemory = false;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct DrawIndexedCmd {
|
struct DrawIndexedCmd {
|
||||||
@@ -118,10 +114,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
class VulkanRenderer : public IBufferCopyCommandProvider,
|
class VulkanRenderer : public IBufferCopyCommandProvider, public FrameContext::IRecordingObserver {
|
||||||
public FrameContext::IRecordingObserver,
|
|
||||||
public VkRenderPassManager::IEvictionObserver,
|
|
||||||
public ProgramFactory::IEvictionObserver {
|
|
||||||
public:
|
public:
|
||||||
VulkanRenderer(NativeWindowType window, const VulkanRendererConfig& cfg = {});
|
VulkanRenderer(NativeWindowType window, const VulkanRendererConfig& cfg = {});
|
||||||
~VulkanRenderer();
|
~VulkanRenderer();
|
||||||
@@ -138,31 +131,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
// recording, before any render pass.
|
// recording, before any render pass.
|
||||||
void OnFrameCommandRecordingBegan(VkCommandBuffer commandBuffer) override;
|
void OnFrameCommandRecordingBegan(VkCommandBuffer commandBuffer) override;
|
||||||
|
|
||||||
// VkRenderPassManager::IEvictionObserver: the render-pass aging sweep just
|
|
||||||
// destroyed these VkRenderPasses; evict every graphics pipeline hashed on a
|
|
||||||
// dying handle (they share its >1024-boundary idleness, so immediate
|
|
||||||
// destruction is safe) and drop the last-pipeline memo if any went.
|
|
||||||
void OnRenderPassesDestroyed(const Vector<VkRenderPass>& renderPasses) override;
|
|
||||||
|
|
||||||
// ProgramFactory::IEvictionObserver: an aged-out program entry was
|
|
||||||
// destroyed; evict its compute pipeline and graphics pipelines (same
|
|
||||||
// idleness guarantee - they are only bound through draws/dispatches that
|
|
||||||
// stamp the program entry) and purge the descriptor-set cache entries
|
|
||||||
// keyed by its now-recyclable VkDescriptorSetLayout handle.
|
|
||||||
void OnProgramEvicted(ProgramFactory::HashType programHash,
|
|
||||||
VkDescriptorSetLayout descriptorSetLayout) override;
|
|
||||||
|
|
||||||
Bool SetupDraw(FrameContext::FrameData& frame, GLenum mode, Flags<DrawSetupAspect> aspects,
|
Bool SetupDraw(FrameContext::FrameData& frame, GLenum mode, Flags<DrawSetupAspect> aspects,
|
||||||
const DrawCmdParam& drawParams,
|
const DrawCmdParam& drawParams,
|
||||||
const IndexBufferView* pIndexBufferView = nullptr);
|
const IndexBufferView* pIndexBufferView = nullptr);
|
||||||
// ANGLE-style consecutive-draw fast path: SetupDraw snapshots the fully
|
|
||||||
// resolved draw configuration; the next draw whose cheap version/identity
|
|
||||||
// checks all match skips the resolution half (LOD probe, sampled-set
|
|
||||||
// walk, render-pass and pipeline resolution) and jumps straight to the
|
|
||||||
// per-draw tail. Returns false (leaving no side effects that the full
|
|
||||||
// path cannot redo idempotently) whenever anything might have changed.
|
|
||||||
Bool TrySetupDrawFastPath(FrameContext::FrameData& frame, GLenum mode, Flags<DrawSetupAspect> aspects,
|
|
||||||
const DrawCmdParam& drawParams, const IndexBufferView* pIndexBufferView);
|
|
||||||
void ClearAttachmentsOnActiveRenderPass(VkCommandBuffer commandBuffer,
|
void ClearAttachmentsOnActiveRenderPass(VkCommandBuffer commandBuffer,
|
||||||
const RenderPassEntry& compatibleRenderPassEntry);
|
const RenderPassEntry& compatibleRenderPassEntry);
|
||||||
|
|
||||||
@@ -181,10 +152,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
void ClearBufferiv(GLenum buffer, GLint drawbuffer, const GLint* value);
|
void ClearBufferiv(GLenum buffer, GLint drawbuffer, const GLint* value);
|
||||||
void ClearNamedFramebufferfv(const SharedPtr<MG_State::GLState::FramebufferObject>& framebuffer,
|
void ClearNamedFramebufferfv(const SharedPtr<MG_State::GLState::FramebufferObject>& framebuffer,
|
||||||
GLenum buffer, GLint drawbuffer, const GLfloat* value);
|
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,
|
void ClearNamedFramebufferfi(const SharedPtr<MG_State::GLState::FramebufferObject>& framebuffer,
|
||||||
GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil);
|
GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil);
|
||||||
void BlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1,
|
void BlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1,
|
||||||
@@ -204,25 +171,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
GLsizei srcWidth, GLsizei srcHeight, GLsizei srcDepth);
|
GLsizei srcWidth, GLsizei srcHeight, GLsizei srcDepth);
|
||||||
void GenerateMipmap(GLenum target);
|
void GenerateMipmap(GLenum target);
|
||||||
void ReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void* pixels);
|
void ReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void* pixels);
|
||||||
// GL_DEPTH_COMPONENT / GL_DEPTH_STENCIL / GL_STENCIL_INDEX readback from the
|
|
||||||
// read framebuffer's depth/stencil attachment (per-aspect buffer copies with
|
|
||||||
// CPU repacking into the requested client layout).
|
|
||||||
void ReadDepthStencilPixels(MG_State::GLState::FramebufferObject& readFbo, GLint x, GLint y, GLsizei width,
|
|
||||||
GLsizei height, GLenum format, GLenum type, void* pixels);
|
|
||||||
// Copy-and-repack core shared by depth-stencil ReadPixels and GetTexImage;
|
|
||||||
// expects command recording to be active and any render pass already ended.
|
|
||||||
void ReadDepthStencilImageToClient(VkImage image, VkFormat vkFormat, VkImageLayout* trackedLayout,
|
|
||||||
VkImageAspectFlags imageAspect, Uint32 mipLevel, Uint32 baseArrayLayer,
|
|
||||||
GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type,
|
|
||||||
void* pixels);
|
|
||||||
// Same-extent depth blit between images of different depth formats: host
|
|
||||||
// round-trip with a per-texel re-encode (see BlitNamedFramebuffer).
|
|
||||||
Bool BlitDepthAcrossFormats(FrameContext::FrameData& frame, VkImage srcImage, VkFormat srcFormat,
|
|
||||||
VkImageLayout* srcTrackedLayout, Uint32 srcMipLevel, Uint32 srcBaseArrayLayer,
|
|
||||||
VkImage dstImage, VkFormat dstFormat, VkImageLayout* dstTrackedLayout,
|
|
||||||
Uint32 dstMipLevel, Uint32 dstBaseArrayLayer, GLint srcX, GLint srcY, GLint dstX,
|
|
||||||
GLint dstY, GLint width, GLint height, VkImageLayout srcRestoreLayout,
|
|
||||||
VkImageLayout dstRestoreLayout, Bool stencilAspect);
|
|
||||||
static SizeT GetReadbackTexelSize(VkFormat sourceFormat);
|
static SizeT GetReadbackTexelSize(VkFormat sourceFormat);
|
||||||
static Bool ConvertReadbackPixels(const Uint8* sourcePixels, VkFormat sourceFormat,
|
static Bool ConvertReadbackPixels(const Uint8* sourcePixels, VkFormat sourceFormat,
|
||||||
GLsizei width, GLsizei height, GLenum destinationFormat,
|
GLsizei width, GLsizei height, GLenum destinationFormat,
|
||||||
@@ -308,20 +256,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
const VkTimerQueryManager::TimestampRecord& end) const;
|
const VkTimerQueryManager::TimestampRecord& end) const;
|
||||||
Uint64 GetTimerQueryTimestampNs(const VkTimerQueryManager::TimestampRecord& record) const;
|
Uint64 GetTimerQueryTimestampNs(const VkTimerQueryManager::TimestampRecord& record) const;
|
||||||
|
|
||||||
// GL_SAMPLES_PASSED occlusion queries: every app draw between Start and Stop is
|
|
||||||
// wrapped in a Vulkan occlusion query slot; the result is the slot sum. Requires
|
|
||||||
// hostQueryReset for slot recycling - Start fails (frontend keeps the query
|
|
||||||
// unsupported) when the device lacks it.
|
|
||||||
Bool StartOcclusionQueryCapture();
|
|
||||||
void StopOcclusionQueryCapture(Vector<Uint32>& outSlots);
|
|
||||||
// Flushes pending commands, waits, sums the slots, and recycles them.
|
|
||||||
Bool ResolveOcclusionQueryResult(const Vector<Uint32>& slots, Uint64& outSamples);
|
|
||||||
|
|
||||||
void RequestSwapchainResize(Uint32 width, Uint32 height);
|
void RequestSwapchainResize(Uint32 width, Uint32 height);
|
||||||
// Re-query the surface and report whether the live swapchain no longer matches it
|
|
||||||
// (size or orientation). This - not a VK_SUBOPTIMAL_KHR result - is what decides a
|
|
||||||
// rebuild, so a surface the driver merely considers suboptimal cannot thrash.
|
|
||||||
Bool SwapchainIsOutOfDate();
|
|
||||||
// Returns false when the surface is zero-area (minimized/hidden window):
|
// Returns false when the surface is zero-area (minimized/hidden window):
|
||||||
// no new swapchain is installed and presentation must stay suspended.
|
// no new swapchain is installed and presentation must stay suspended.
|
||||||
Bool RecreateSwapchain();
|
Bool RecreateSwapchain();
|
||||||
@@ -410,40 +345,16 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
VkFence AcquirePooledSubmitFence();
|
VkFence AcquirePooledSubmitFence();
|
||||||
void DestroySubmitFencePool();
|
void DestroySubmitFencePool();
|
||||||
Bool HasPendingRecordedWork() const;
|
Bool HasPendingRecordedWork() const;
|
||||||
// Frame-boundary housekeeping for paths that never reach Present's
|
|
||||||
// tail (present-less readback loops, suspended presentation, blocking
|
|
||||||
// sync waits): runs the same per-frame drains Present performs, but
|
|
||||||
// only when every queue submission has been observed complete AND no
|
|
||||||
// recorded-but-unsubmitted commands exist - i.e. when CPU-GPU overlap
|
|
||||||
// is provably already zero. Never blocks (non-blocking fence poll
|
|
||||||
// only), so the presenting path's frames-in-flight pipelining is
|
|
||||||
// untouched. Returns true when the drain ran.
|
|
||||||
Bool TryDrainFrameTransients();
|
|
||||||
|
|
||||||
Vector<SubmitRecord> m_inFlightSubmits;
|
Vector<SubmitRecord> m_inFlightSubmits;
|
||||||
Vector<VkFence> m_freeSubmitFences;
|
Vector<VkFence> m_freeSubmitFences;
|
||||||
Uint64 m_submitCounter = 0;
|
Uint64 m_submitCounter = 0;
|
||||||
Uint64 m_completedSubmitCounter = 0;
|
Uint64 m_completedSubmitCounter = 0;
|
||||||
// Drains since the last Present, gating the drain's frame-boundary-equivalent
|
|
||||||
// work (arena rewind + cache aging): a presenting app's mid-frame
|
|
||||||
// readbacks/waits must neither churn the transient caches nor accelerate the
|
|
||||||
// aging clocks, while present-less loops still cross a boundary every few
|
|
||||||
// iterations. Reset in Present.
|
|
||||||
Uint32 m_drainsSinceLastPresent = 0;
|
|
||||||
|
|
||||||
NativeWindowType m_window = 0;
|
NativeWindowType m_window = 0;
|
||||||
void* m_platformDisplay = nullptr;
|
void* m_platformDisplay = nullptr;
|
||||||
void* m_platformLibrary = nullptr;
|
void* m_platformLibrary = nullptr;
|
||||||
void* m_platformCloseDisplay = nullptr;
|
void* m_platformCloseDisplay = nullptr;
|
||||||
// Some real ICDs (e.g. NVIDIA's proprietary Linux driver) don't implement
|
|
||||||
// VK_EXT_headless_surface at all. Detected once in CreateInstance() from the
|
|
||||||
// enumerated instance extensions; when false, CreateSurface() falls back to a
|
|
||||||
// hidden Xlib window instead of vkCreateHeadlessSurfaceEXT.
|
|
||||||
Bool m_headlessSurfaceSupported = true;
|
|
||||||
// Set when CreateSurface() had to create its own Xlib window for the fallback
|
|
||||||
// above (rather than being handed one by the caller), so Shutdown() knows it
|
|
||||||
// owns that window and must destroy it.
|
|
||||||
Bool m_ownsFallbackXlibWindow = false;
|
|
||||||
VulkanRendererConfig m_config;
|
VulkanRendererConfig m_config;
|
||||||
Bool m_swapchainResizeRequested = false;
|
Bool m_swapchainResizeRequested = false;
|
||||||
// Presentation is suspended while the window is zero-area (minimized): the
|
// Presentation is suspended while the window is zero-area (minimized): the
|
||||||
@@ -456,9 +367,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
Vector<VkExtensionProperties> m_extensions;
|
Vector<VkExtensionProperties> m_extensions;
|
||||||
VkInstance m_instance = VK_NULL_HANDLE;
|
VkInstance m_instance = VK_NULL_HANDLE;
|
||||||
VkDebugUtilsMessengerEXT m_debugMessenger = VK_NULL_HANDLE;
|
VkDebugUtilsMessengerEXT m_debugMessenger = VK_NULL_HANDLE;
|
||||||
// Fallback reporting channel for drivers that ship the validation layers but
|
|
||||||
// only expose the older VK_EXT_debug_report (Adreno 650 / Vulkan 1.1.128).
|
|
||||||
VkDebugReportCallbackEXT m_debugReportCallback = VK_NULL_HANDLE;
|
|
||||||
PhysicalDevice m_physicalDevice;
|
PhysicalDevice m_physicalDevice;
|
||||||
VkDevice m_device = VK_NULL_HANDLE;
|
VkDevice m_device = VK_NULL_HANDLE;
|
||||||
VmaAllocator m_allocator = nullptr;
|
VmaAllocator m_allocator = nullptr;
|
||||||
@@ -496,98 +404,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
Uint32 stride);
|
Uint32 stride);
|
||||||
static inline PFNDrawIndexedIndirectCountFunc s_vkCmdDrawIndexedIndirectCount = nullptr;
|
static inline PFNDrawIndexedIndirectCountFunc s_vkCmdDrawIndexedIndirectCount = nullptr;
|
||||||
|
|
||||||
// VK_EXT_transform_feedback (GL transform feedback capture)
|
|
||||||
Bool m_transformFeedbackFeatureEnabled = false;
|
|
||||||
// VK_EXT_provoking_vertex. Vulkan's built-in convention is "provoking vertex first"; GL's
|
|
||||||
// default is LAST_VERTEX_CONVENTION, and GL derives BOTH flat shading and the transform
|
|
||||||
// feedback vertex order from it. provokingVertexLast alone fixes flat shading and the
|
|
||||||
// input-assembler capture order and has no dependency on transform feedback; only
|
|
||||||
// transformFeedbackPreservesProvokingVertex does.
|
|
||||||
Bool m_provokingVertexLastEnabled = false;
|
|
||||||
// transformFeedbackPreservesProvokingVertex was actually enabled at device creation. Kept
|
|
||||||
// separate because it is the only thing that arms
|
|
||||||
// VUID-VkGraphicsPipelineCreateInfo-topology-04884, the rule that forbids a TRIANGLE_FAN
|
|
||||||
// pipeline from asking for LAST on a device that cannot preserve a fan's provoking vertex.
|
|
||||||
Bool m_provokingVertexXfbPreserveEnabled = false;
|
|
||||||
// provokingVertexModePerPipeline: when VK_FALSE every pipeline in one render pass instance
|
|
||||||
// must agree on the mode, so glProvokingVertex(GL_FIRST_VERTEX_CONVENTION) cannot be honoured
|
|
||||||
// per draw and every pipeline takes GL's default (LAST) instead.
|
|
||||||
Bool m_provokingVertexModePerPipeline = false;
|
|
||||||
// transformFeedbackPreservesTriangleFanProvokingVertex.
|
|
||||||
Bool m_provokingVertexFanPreserved = false;
|
|
||||||
// Per-pipeline provoking-vertex mode. capturesXfbFromGeometryStage must be a LINK-TIME
|
|
||||||
// property of the program, never the dynamic "is transform feedback active" flag: the
|
|
||||||
// 8-entry m_pipelineMemo and the SetupDrawSnapshot fast path key on programObj.hash and
|
|
||||||
// GetRenderStateParametersVersion(), neither of which moves when glBeginTransformFeedback is
|
|
||||||
// called, so a dynamic input here would hand back a stale VkPipeline.
|
|
||||||
VkProvokingVertexModeEXT SelectProvokingVertexMode(VkPrimitiveTopology topology,
|
|
||||||
Bool capturesXfbFromGeometryStage) const;
|
|
||||||
// VK_EXT_vertex_attribute_divisor: without it every non-zero glVertexAttribDivisor
|
|
||||||
// behaves as 1, because that is all Vulkan's instance input rate can express.
|
|
||||||
Bool m_vertexAttributeDivisorEnabled = false;
|
|
||||||
static inline PFN_vkCmdBindTransformFeedbackBuffersEXT s_vkCmdBindTransformFeedbackBuffersEXT = nullptr;
|
|
||||||
static inline PFN_vkCmdBeginTransformFeedbackEXT s_vkCmdBeginTransformFeedbackEXT = nullptr;
|
|
||||||
static inline PFN_vkCmdEndTransformFeedbackEXT s_vkCmdEndTransformFeedbackEXT = nullptr;
|
|
||||||
// Counter buffers (one 4-byte slot per capture binding) let consecutive
|
|
||||||
// draws within one glBeginTransformFeedback append GL-style. Transform feedback
|
|
||||||
// objects can each hold an open, paused span at the same time, so the counters are
|
|
||||||
// per object: one group of four slots each, handed out on first use.
|
|
||||||
static constexpr SizeT kXfbCounterObjectSlots = 16;
|
|
||||||
VkBufferObject m_xfbCounterBuffer;
|
|
||||||
UnorderedMap<Uint, Uint32> m_xfbCounterSlotByObject;
|
|
||||||
Uint32 m_xfbNextCounterSlot = 0;
|
|
||||||
// Set for a slot once a captured draw has been recorded into its span; selects
|
|
||||||
// counter-buffer resume on the next captured draw of the same span.
|
|
||||||
Array<Bool, kXfbCounterObjectSlots> m_xfbCountersValid{};
|
|
||||||
Array<Uint64, kXfbCounterObjectSlots> m_xfbLastSeenGeneration{};
|
|
||||||
// Counter slot group of the bound transform feedback object.
|
|
||||||
Uint32 CurrentXfbCounterSlot();
|
|
||||||
// Wraps a recorded draw with BeginTransformFeedbackEXT/EndTransformFeedbackEXT
|
|
||||||
// when GL transform feedback is active; binds capture buffers on demand.
|
|
||||||
Bool BeginXfbCaptureForDraw(FrameContext::FrameData& frame);
|
|
||||||
void EndXfbCaptureForDraw(FrameContext::FrameData& frame, Bool began);
|
|
||||||
// Makes the captured bytes visible to whatever reads them next. Deferred rather than
|
|
||||||
// recorded next to the capture, because the capturing draw runs inside a render pass
|
|
||||||
// that declares no self-dependency.
|
|
||||||
void MakeXfbWritesVisible();
|
|
||||||
Bool m_xfbWritesPendingVisibility = false;
|
|
||||||
// Wrap one app draw in an occlusion-query slot while a GL_SAMPLES_PASSED
|
|
||||||
// query is active. Returns whether a slot was begun (End must mirror it).
|
|
||||||
Bool BeginOcclusionForDraw(VkCommandBuffer commandBuffer);
|
|
||||||
void EndOcclusionForDraw(VkCommandBuffer commandBuffer, Bool began);
|
|
||||||
Bool m_occlusionQueryPreciseEnabled = false;
|
|
||||||
Bool m_hostQueryResetEnabled = false;
|
|
||||||
PFN_vkResetQueryPool s_vkResetQueryPool = nullptr;
|
|
||||||
VkQueryPool m_occlusionQueryPool = VK_NULL_HANDLE;
|
|
||||||
static constexpr Uint32 kOcclusionQuerySlots = 8192;
|
|
||||||
Uint32 m_occlusionSlotCursor = 0;
|
|
||||||
Bool m_occlusionCaptureActive = false;
|
|
||||||
Vector<Uint32> m_occlusionActiveSlots;
|
|
||||||
// Transform feedback primitive queries: one pool slot per captured draw yields
|
|
||||||
// the (written, needed) pair; GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN sums the
|
|
||||||
// first, GL_PRIMITIVES_GENERATED the second - exact with geometry shaders,
|
|
||||||
// unlike the CPU fallback accounting.
|
|
||||||
Bool m_xfbQueriesSupported = false;
|
|
||||||
PFN_vkCmdBeginQueryIndexedEXT s_vkCmdBeginQueryIndexedEXT = nullptr;
|
|
||||||
PFN_vkCmdEndQueryIndexedEXT s_vkCmdEndQueryIndexedEXT = nullptr;
|
|
||||||
VkQueryPool m_xfbQueryPool = VK_NULL_HANDLE;
|
|
||||||
static constexpr Uint32 kXfbQuerySlots = 8192;
|
|
||||||
Uint32 m_xfbQuerySlotCursor = 0;
|
|
||||||
Bool m_xfbQueryCaptureActive[2] = {false, false}; // [0]=written, [1]=generated
|
|
||||||
Vector<Uint32> m_xfbQueryActiveSlots[2];
|
|
||||||
Bool m_xfbQuerySlotOpen = false;
|
|
||||||
Uint32 m_xfbQueryOpenSlot = 0;
|
|
||||||
|
|
||||||
public:
|
|
||||||
// kind: 0 = PRIMITIVES_WRITTEN, 1 = PRIMITIVES_GENERATED.
|
|
||||||
Bool StartXfbQueryCapture(Uint32 kind);
|
|
||||||
void StopXfbQueryCapture(Uint32 kind, Vector<Uint32>& outSlots);
|
|
||||||
Bool ResolveXfbQueryResult(const Vector<Uint32>& slots, Bool wantGenerated, Uint64& outPrimitives);
|
|
||||||
|
|
||||||
private:
|
|
||||||
void BeginXfbQueryForDraw(VkCommandBuffer commandBuffer);
|
|
||||||
void EndXfbQueryForDraw(VkCommandBuffer commandBuffer);
|
|
||||||
|
|
||||||
VkCommandPool m_commandPool = VK_NULL_HANDLE;
|
VkCommandPool m_commandPool = VK_NULL_HANDLE;
|
||||||
|
|
||||||
VkBufferManager m_bufferManager;
|
VkBufferManager m_bufferManager;
|
||||||
@@ -600,30 +416,14 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
// gather + synthetic vertex-input rebuild + payload hash + lookup) when the full pipeline
|
// gather + synthetic vertex-input rebuild + payload hash + lookup) when the full pipeline
|
||||||
// state is unchanged from the previous draw. The key provably covers every pipeline field.
|
// state is unchanged from the previous draw. The key provably covers every pipeline field.
|
||||||
// Reset per-frame and on pipeline destruction so the cached handle can never dangle.
|
// Reset per-frame and on pipeline destruction so the cached handle can never dangle.
|
||||||
// Small N-way pipeline-resolution memo (round-robin replacement). A
|
Bool m_lastPipelineValid = false;
|
||||||
// single-entry memo thrashed on draw sequences that alternate a few
|
GLenum m_lastPipelineMode = 0;
|
||||||
// pipelines (GUI text/quad program ping-pong), paying the full
|
Uint64 m_lastPipelineProgramHash = 0;
|
||||||
// payload-hash lookup per draw; eight entries cover such working sets
|
Uint64 m_lastPipelineVertexInputHash = 0;
|
||||||
// while keeping the hit path a trivial linear scan.
|
Uint64 m_lastPipelineRenderPassHash = 0;
|
||||||
struct PipelineMemoEntry {
|
Uint m_lastPipelineRenderStateVersion = 0;
|
||||||
GLenum mode = 0;
|
ProgramFactory::CompileOptionFlags m_lastPipelineTransformFlags = {};
|
||||||
Uint64 programHash = 0;
|
VkPipeline m_lastPipelineResult = VK_NULL_HANDLE;
|
||||||
Uint64 vertexInputHash = 0;
|
|
||||||
Uint64 renderPassHash = 0;
|
|
||||||
Uint renderStateVersion = 0;
|
|
||||||
ProgramFactory::CompileOptionFlags transformFlags = {};
|
|
||||||
VkPipeline pipeline = VK_NULL_HANDLE;
|
|
||||||
};
|
|
||||||
static constexpr Uint32 kPipelineMemoSize = 8;
|
|
||||||
PipelineMemoEntry m_pipelineMemo[kPipelineMemoSize];
|
|
||||||
Uint32 m_pipelineMemoCount = 0;
|
|
||||||
Uint32 m_pipelineMemoNext = 0;
|
|
||||||
// Drops every memoized pipeline handle. Required at command-buffer
|
|
||||||
// boundaries and whenever any pipeline may have been destroyed.
|
|
||||||
void InvalidatePipelineMemo() {
|
|
||||||
m_pipelineMemoCount = 0;
|
|
||||||
m_pipelineMemoNext = 0;
|
|
||||||
}
|
|
||||||
UnorderedMap<ProgramFactory::HashType, VkPipeline> m_computePipelines;
|
UnorderedMap<ProgramFactory::HashType, VkPipeline> m_computePipelines;
|
||||||
UniquePtr<ProgramFactory> m_programFactory;
|
UniquePtr<ProgramFactory> m_programFactory;
|
||||||
UniquePtr<UniformManager> m_uniformManager;
|
UniquePtr<UniformManager> m_uniformManager;
|
||||||
@@ -652,61 +452,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
ProgramFactory::CompileOptionFlags m_lastSampledSetTransformFlags = {};
|
ProgramFactory::CompileOptionFlags m_lastSampledSetTransformFlags = {};
|
||||||
Uint64 m_lastSampledSetBindGeneration = 0;
|
Uint64 m_lastSampledSetBindGeneration = 0;
|
||||||
|
|
||||||
// Memo for the per-draw explicit-LOD-0 eligibility probe
|
|
||||||
// (ProgramSamplesOnlySingleLevelTextures): same key family as the
|
|
||||||
// sampled-set memo, plus the sampled textures' params-version sum so a
|
|
||||||
// level-range or filter change re-probes. On a hit the resolved
|
|
||||||
// transform flags are reused, which also collapses the two
|
|
||||||
// GetOrCreateProgram lookups into one.
|
|
||||||
Bool m_lastLodDecisionValid = false;
|
|
||||||
Uint64 m_lastLodProgramLifetimeId = 0;
|
|
||||||
Uint32 m_lastLodProgramVersion = 0;
|
|
||||||
Uint64 m_lastLodBindGeneration = 0;
|
|
||||||
Uint64 m_lastLodParamsSum = 0;
|
|
||||||
ProgramFactory::CompileOptionFlags m_lastLodBaseFlags = {};
|
|
||||||
ProgramFactory::CompileOptionFlags m_lastLodResultFlags = {};
|
|
||||||
|
|
||||||
// Snapshot behind TrySetupDrawFastPath. Values only: the program and
|
|
||||||
// render-pass caches are open-addressing maps whose entries move on
|
|
||||||
// insert, so no pointers into them are cached; the pipeline handle is
|
|
||||||
// protected by the command-buffer-boundary reset plus the mid-frame
|
|
||||||
// pipeline-destruction resets, and monotonic epochs guard everything
|
|
||||||
// that can be destroyed or recreated between draws.
|
|
||||||
struct SetupDrawSnapshot {
|
|
||||||
Bool valid = false;
|
|
||||||
Uint8 aspects = 0;
|
|
||||||
GLenum mode = 0;
|
|
||||||
Uint64 programLifetimeId = 0;
|
|
||||||
Uint32 programVersion = 0;
|
|
||||||
const void* vao = nullptr;
|
|
||||||
Uint32 vaoConfigVersion = 0;
|
|
||||||
const void* drawFbo = nullptr;
|
|
||||||
Uint16 fboVersion = 0;
|
|
||||||
Bool drawFboIsDefault = false;
|
|
||||||
Uint renderStateVersion = 0;
|
|
||||||
Uint64 bindGeneration = 0;
|
|
||||||
Uint32 baseTransformFlags = 0;
|
|
||||||
Uint32 resolvedTransformFlags = 0;
|
|
||||||
Uint64 renderPassHash = 0;
|
|
||||||
Uint32 imageIndex = 0;
|
|
||||||
Uint64 textureEraseEpoch = 0;
|
|
||||||
Uint64 textureImageEpoch = 0;
|
|
||||||
Uint64 renderbufferImageEpoch = 0;
|
|
||||||
Uint64 sampledContentSum = 0;
|
|
||||||
Uint64 sampledParamsSum = 0;
|
|
||||||
IntVec2 renderPassExtent = {0, 0};
|
|
||||||
VkPipeline pipeline = VK_NULL_HANDLE;
|
|
||||||
};
|
|
||||||
SetupDrawSnapshot m_setupDrawSnapshot;
|
|
||||||
|
|
||||||
// Per-draw scratch buffers (clear keeps capacity) — these paths run for every
|
// Per-draw scratch buffers (clear keeps capacity) — these paths run for every
|
||||||
// draw call and must not allocate.
|
// draw call and must not allocate.
|
||||||
Vector<MG_State::GLState::ITextureObject*> m_sampledTexturesScratch;
|
Vector<MG_State::GLState::ITextureObject*> m_sampledTexturesScratch;
|
||||||
// Parallel to m_sampledTexturesScratch, refilled by every SetupDraw's
|
|
||||||
// first sampled-texture loop: the resolved backend resources, so the
|
|
||||||
// post-transition loop can skip re-resolving textures whose layout is
|
|
||||||
// already sampleable.
|
|
||||||
Vector<VkTextureManager::TextureResource*> m_sampledResourcesScratch;
|
|
||||||
Vector<MG_State::GLState::ITextureObject*> m_storageImageTexturesScratch;
|
Vector<MG_State::GLState::ITextureObject*> m_storageImageTexturesScratch;
|
||||||
Vector<VkBuffer> m_vertexBuffersScratch;
|
Vector<VkBuffer> m_vertexBuffersScratch;
|
||||||
Vector<VkDeviceSize> m_vertexOffsetsScratch;
|
Vector<VkDeviceSize> m_vertexOffsetsScratch;
|
||||||
@@ -769,8 +517,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
void CreateInstance();
|
void CreateInstance();
|
||||||
VkResult SetupDebugMessenger();
|
VkResult SetupDebugMessenger();
|
||||||
VkResult DestroyDebugMessenger();
|
VkResult DestroyDebugMessenger();
|
||||||
VkResult SetupDebugReportCallback();
|
|
||||||
void DestroyDebugReportCallback();
|
|
||||||
VkDebugUtilsMessengerCreateInfoEXT PopulateDebugMessengerCreateInfo();
|
VkDebugUtilsMessengerCreateInfoEXT PopulateDebugMessengerCreateInfo();
|
||||||
void CreateSurface();
|
void CreateSurface();
|
||||||
void PickPhysicalDevice();
|
void PickPhysicalDevice();
|
||||||
@@ -789,10 +535,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
const RenderPassEntry& renderPassEntry);
|
const RenderPassEntry& renderPassEntry);
|
||||||
VkPipeline GetOrCreateComputePipeline(const ProgramFactory::VkProgramObject& programObj);
|
VkPipeline GetOrCreateComputePipeline(const ProgramFactory::VkProgramObject& programObj);
|
||||||
void DestroyComputePipelines();
|
void DestroyComputePipelines();
|
||||||
// Takes the frame rather than a command buffer: a first-time storage-usage upgrade has to
|
|
||||||
// flush the pending recording (see the body), which retires the current command buffer.
|
|
||||||
Bool PrepareStorageImageTextures(
|
Bool PrepareStorageImageTextures(
|
||||||
FrameContext::FrameData& frame,
|
VkCommandBuffer commandBuffer,
|
||||||
const MG_State::GLState::ProgramObject& program,
|
const MG_State::GLState::ProgramObject& program,
|
||||||
const ProgramFactory::VkProgramObject& programObj);
|
const ProgramFactory::VkProgramObject& programObj);
|
||||||
|
|
||||||
@@ -815,11 +559,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1,
|
GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1,
|
||||||
GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1,
|
GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1,
|
||||||
GLenum filter);
|
GLenum filter);
|
||||||
// Clears one z slice of a VK_IMAGE_TYPE_3D colour image. See the call site in
|
|
||||||
// MaterializePendingClearForTexture for why a transfer clear cannot do this.
|
|
||||||
Bool ClearDepthSliceWithRenderPass(VkCommandBuffer commandBuffer,
|
|
||||||
MG_State::GLState::ITextureObject& texture, Uint32 mipLevel,
|
|
||||||
Uint32 depthSlice, const VkClearValue& clearValue);
|
|
||||||
Bool MaterializePendingClearForTexture(VkCommandBuffer commandBuffer,
|
Bool MaterializePendingClearForTexture(VkCommandBuffer commandBuffer,
|
||||||
MG_State::GLState::ITextureObject& texture);
|
MG_State::GLState::ITextureObject& texture);
|
||||||
Bool MaterializePendingClearForRenderbuffer(
|
Bool MaterializePendingClearForRenderbuffer(
|
||||||
@@ -836,14 +575,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
VkImageLayout finalLayout);
|
VkImageLayout finalLayout);
|
||||||
Bool SubmitReadbackCommandsAndWait(FrameContext::FrameData& frame);
|
Bool SubmitReadbackCommandsAndWait(FrameContext::FrameData& frame);
|
||||||
|
|
||||||
public:
|
|
||||||
// Submits whatever is recorded and waits for it. The CPU is about to read memory
|
|
||||||
// a shader wrote (a mapped shader storage buffer), and coherent host-visible
|
|
||||||
// storage only guarantees visibility once the work that produced it has retired.
|
|
||||||
Bool FinishPendingGpuWork();
|
|
||||||
|
|
||||||
private:
|
|
||||||
|
|
||||||
void ShutdownSwapchain();
|
void ShutdownSwapchain();
|
||||||
|
|
||||||
// Static functions
|
// Static functions
|
||||||
@@ -867,10 +598,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
const PhysicalDevice& compareWithDevice,
|
const PhysicalDevice& compareWithDevice,
|
||||||
PhysicalDevice& outBetterDevice);
|
PhysicalDevice& outBetterDevice);
|
||||||
static constexpr const char* s_validationLayerNames[] = {"VK_LAYER_KHRONOS_validation"};
|
static constexpr const char* s_validationLayerNames[] = {"VK_LAYER_KHRONOS_validation"};
|
||||||
// VK_KHR_image_format_list: lets MUTABLE_FORMAT images declare their exact view-format
|
|
||||||
// set so the driver can keep bandwidth compression (see CreateLogicalDeviceAndQueues).
|
|
||||||
Bool m_imageFormatListExtensionEnabled = false;
|
|
||||||
|
|
||||||
static constexpr const char* s_deviceExtensionNames[] = {VK_KHR_SWAPCHAIN_EXTENSION_NAME};
|
static constexpr const char* s_deviceExtensionNames[] = {VK_KHR_SWAPCHAIN_EXTENSION_NAME};
|
||||||
static Bool CheckValidationLayerSupport();
|
static Bool CheckValidationLayerSupport();
|
||||||
|
|
||||||
|
|||||||
@@ -52,48 +52,19 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
}
|
}
|
||||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||||
|
|
||||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
|
||||||
// GL renders into sRGB color attachments RAW while GL_FRAMEBUFFER_SRGB is disabled
|
|
||||||
// (the core-profile default); Vulkan sRGB attachments always encode on write. The
|
|
||||||
// attachment view (and render pass format) therefore drops to the UNORM twin
|
|
||||||
// whenever the capability is off. Sampled views keep the sRGB format (decode on
|
|
||||||
// sample is unconditional in GL).
|
|
||||||
inline VkFormat ResolveSrgbAttachmentWriteFormat(VkFormat format, bool framebufferSrgbEnabled) {
|
|
||||||
if (framebufferSrgbEnabled) return format;
|
|
||||||
switch (format) {
|
|
||||||
case VK_FORMAT_R8G8B8A8_SRGB:
|
|
||||||
return VK_FORMAT_R8G8B8A8_UNORM;
|
|
||||||
case VK_FORMAT_B8G8R8A8_SRGB:
|
|
||||||
return VK_FORMAT_B8G8R8A8_UNORM;
|
|
||||||
default:
|
|
||||||
return format;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
|
||||||
|
|
||||||
// The context line (__VA_ARGS__ = its own format string + args) must be a SEPARATE log
|
|
||||||
// call: appending its format to the base format while its arguments precede the base
|
|
||||||
// arguments makes every conversion read the wrong slot (a %s pulling an int crashes).
|
|
||||||
#define VK_VERIFY(expr, ...) \
|
#define VK_VERIFY(expr, ...) \
|
||||||
do { \
|
do { \
|
||||||
VkResult _vk_verify_result = (expr); \
|
VkResult _vk_verify_result = (expr); \
|
||||||
if (_vk_verify_result != VK_SUCCESS) { \
|
if (_vk_verify_result != VK_SUCCESS) { \
|
||||||
__VA_OPT__(MGLOG_F(__VA_ARGS__);) \
|
MGLOG_F("Vulkan error %s (%d) at %s:%d" __VA_OPT__(" - ") __VA_ARGS__, \
|
||||||
MGLOG_F("Vulkan error %s (%d) at %s:%d", \
|
|
||||||
MobileGL::MG_Backend::DirectVulkan::VkResultToString(_vk_verify_result), \
|
MobileGL::MG_Backend::DirectVulkan::VkResultToString(_vk_verify_result), \
|
||||||
_vk_verify_result, __FILE__, __LINE__); \
|
_vk_verify_result, __FILE__, __LINE__); \
|
||||||
} \
|
} \
|
||||||
MOBILEGL_ASSERT(_vk_verify_result == VK_SUCCESS, "Vulkan error %s (%d) at %s:%d", \
|
MOBILEGL_ASSERT(_vk_verify_result == VK_SUCCESS, "Vulkan error %s (%d) at %s:%d" __VA_OPT__(" - ") __VA_ARGS__, MobileGL::MG_Backend::DirectVulkan::VkResultToString(_vk_verify_result), _vk_verify_result, __FILE__, __LINE__); \
|
||||||
MobileGL::MG_Backend::DirectVulkan::VkResultToString(_vk_verify_result), \
|
|
||||||
_vk_verify_result, __FILE__, __LINE__); \
|
|
||||||
} while (0)
|
} while (0)
|
||||||
|
|
||||||
#define XXHASH_VERIFY(expr, ...) \
|
#define XXHASH_VERIFY(expr, ...) \
|
||||||
do { \
|
do { \
|
||||||
XXH_errorcode _xxh_verify_result = (expr); \
|
XXH_errorcode _xxh_verify_result = (expr); \
|
||||||
if (_xxh_verify_result != XXH_OK) { \
|
MOBILEGL_ASSERT(_xxh_verify_result == XXH_OK, "XXHash error %d at %s:%d" __VA_OPT__(" - ") __VA_ARGS__, _xxh_verify_result, __FILE__, __LINE__); \
|
||||||
__VA_OPT__(MGLOG_F(__VA_ARGS__);) \
|
|
||||||
} \
|
|
||||||
MOBILEGL_ASSERT(_xxh_verify_result == XXH_OK, "XXHash error %d at %s:%d", _xxh_verify_result, __FILE__, \
|
|
||||||
__LINE__); \
|
|
||||||
} while (0)
|
} while (0)
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ namespace MobileGL::MG_Impl::CGLImpl {
|
|||||||
GLint Samples = 0;
|
GLint Samples = 0;
|
||||||
GLint Profile = kCGLOGLPVersion_3_2_Core;
|
GLint Profile = kCGLOGLPVersion_3_2_Core;
|
||||||
GLint RendererId = 0x4d474c;
|
GLint RendererId = 0x4d474c;
|
||||||
GLint DisplayMask = 0;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ContextObject {
|
struct ContextObject {
|
||||||
@@ -135,9 +134,6 @@ namespace MobileGL::MG_Impl::CGLImpl {
|
|||||||
case kCGLPFARendererID:
|
case kCGLPFARendererID:
|
||||||
pixelFormat.RendererId = value;
|
pixelFormat.RendererId = value;
|
||||||
break;
|
break;
|
||||||
case kCGLPFADisplayMask:
|
|
||||||
pixelFormat.DisplayMask = value;
|
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -347,9 +343,6 @@ namespace MobileGL::MG_Impl::CGLImpl {
|
|||||||
case kCGLPFARendererID:
|
case kCGLPFARendererID:
|
||||||
*value = pixelFormat->RendererId;
|
*value = pixelFormat->RendererId;
|
||||||
return kCGLNoError;
|
return kCGLNoError;
|
||||||
case kCGLPFADisplayMask:
|
|
||||||
*value = pixelFormat->DisplayMask;
|
|
||||||
return kCGLNoError;
|
|
||||||
case kCGLPFAOpenGLProfile:
|
case kCGLPFAOpenGLProfile:
|
||||||
*value = pixelFormat->Profile;
|
*value = pixelFormat->Profile;
|
||||||
return kCGLNoError;
|
return kCGLNoError;
|
||||||
@@ -488,32 +481,6 @@ namespace MobileGL::MG_Impl::CGLImpl {
|
|||||||
return it == currentContexts.end() ? nullptr : it->second;
|
return it == currentContexts.end() ? nullptr : it->second;
|
||||||
}
|
}
|
||||||
|
|
||||||
CGLError SetVirtualScreen(CGLContextObj ctx, GLint screen) {
|
|
||||||
const std::lock_guard<std::recursive_mutex> lock(RegistryMutex());
|
|
||||||
auto* object = TryGetContext(ctx);
|
|
||||||
if (!object) {
|
|
||||||
return kCGLBadContext;
|
|
||||||
}
|
|
||||||
if (screen != 0) {
|
|
||||||
return kCGLBadValue;
|
|
||||||
}
|
|
||||||
object->VirtualScreen = screen;
|
|
||||||
return kCGLNoError;
|
|
||||||
}
|
|
||||||
|
|
||||||
CGLError GetVirtualScreen(CGLContextObj ctx, GLint* screen) {
|
|
||||||
const std::lock_guard<std::recursive_mutex> lock(RegistryMutex());
|
|
||||||
auto* object = TryGetContext(ctx);
|
|
||||||
if (!object) {
|
|
||||||
return kCGLBadContext;
|
|
||||||
}
|
|
||||||
if (!screen) {
|
|
||||||
return kCGLBadAddress;
|
|
||||||
}
|
|
||||||
*screen = object->VirtualScreen;
|
|
||||||
return kCGLNoError;
|
|
||||||
}
|
|
||||||
|
|
||||||
CGLError SetParameter(CGLContextObj ctx, CGLContextParameter pname, const GLint* params) {
|
CGLError SetParameter(CGLContextObj ctx, CGLContextParameter pname, const GLint* params) {
|
||||||
const std::lock_guard<std::recursive_mutex> lock(RegistryMutex());
|
const std::lock_guard<std::recursive_mutex> lock(RegistryMutex());
|
||||||
auto* object = TryGetContext(ctx);
|
auto* object = TryGetContext(ctx);
|
||||||
|
|||||||
@@ -32,8 +32,6 @@ namespace MobileGL::MG_Impl::CGLImpl {
|
|||||||
|
|
||||||
CGLError SetCurrentContext(CGLContextObj ctx);
|
CGLError SetCurrentContext(CGLContextObj ctx);
|
||||||
CGLContextObj GetCurrentContext();
|
CGLContextObj GetCurrentContext();
|
||||||
CGLError SetVirtualScreen(CGLContextObj ctx, GLint screen);
|
|
||||||
CGLError GetVirtualScreen(CGLContextObj ctx, GLint* screen);
|
|
||||||
CGLError SetParameter(CGLContextObj ctx, CGLContextParameter pname, const GLint* params);
|
CGLError SetParameter(CGLContextObj ctx, CGLContextParameter pname, const GLint* params);
|
||||||
CGLError GetParameter(CGLContextObj ctx, CGLContextParameter pname, GLint* params);
|
CGLError GetParameter(CGLContextObj ctx, CGLContextParameter pname, GLint* params);
|
||||||
CGLError UpdateContext(CGLContextObj ctx);
|
CGLError UpdateContext(CGLContextObj ctx);
|
||||||
|
|||||||
@@ -71,14 +71,6 @@ MOBILEGL_CGL_API CGLContextObj CGLGetCurrentContext(void) {
|
|||||||
return MobileGL::MG_Impl::CGLImpl::GetCurrentContext();
|
return MobileGL::MG_Impl::CGLImpl::GetCurrentContext();
|
||||||
}
|
}
|
||||||
|
|
||||||
MOBILEGL_CGL_API CGLError CGLSetVirtualScreen(CGLContextObj ctx, GLint screen) {
|
|
||||||
return MobileGL::MG_Impl::CGLImpl::SetVirtualScreen(ctx, screen);
|
|
||||||
}
|
|
||||||
|
|
||||||
MOBILEGL_CGL_API CGLError CGLGetVirtualScreen(CGLContextObj ctx, GLint* screen) {
|
|
||||||
return MobileGL::MG_Impl::CGLImpl::GetVirtualScreen(ctx, screen);
|
|
||||||
}
|
|
||||||
|
|
||||||
MOBILEGL_CGL_API CGLError CGLSetParameter(CGLContextObj ctx, CGLContextParameter pname, const GLint* params) {
|
MOBILEGL_CGL_API CGLError CGLSetParameter(CGLContextObj ctx, CGLContextParameter pname, const GLint* params) {
|
||||||
return MobileGL::MG_Impl::CGLImpl::SetParameter(ctx, pname, params);
|
return MobileGL::MG_Impl::CGLImpl::SetParameter(ctx, pname, params);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,12 +10,8 @@
|
|||||||
|
|
||||||
#if defined(__APPLE__)
|
#if defined(__APPLE__)
|
||||||
|
|
||||||
#include "MG_Impl/CGLImpl/CGLImpl.h"
|
|
||||||
#include "MG_Impl/GetProcAddress.h"
|
#include "MG_Impl/GetProcAddress.h"
|
||||||
|
|
||||||
#include <CoreGraphics/CoreGraphics.h>
|
|
||||||
#include <CoreVideo/CVDisplayLink.h>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <dlfcn.h>
|
#include <dlfcn.h>
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
@@ -51,52 +47,10 @@ namespace {
|
|||||||
return dlsym(handle, symbol);
|
return dlsym(handle, symbol);
|
||||||
}
|
}
|
||||||
|
|
||||||
CGDirectDisplayID DisplayForMask(GLint displayMask) {
|
|
||||||
constexpr std::uint32_t MaxDisplays = sizeof(CGOpenGLDisplayMask) * 8;
|
|
||||||
CGDirectDisplayID displays[MaxDisplays] = {};
|
|
||||||
std::uint32_t displayCount = 0;
|
|
||||||
if (displayMask != 0 &&
|
|
||||||
CGGetActiveDisplayList(MaxDisplays, displays, &displayCount) == kCGErrorSuccess) {
|
|
||||||
const auto mask = static_cast<CGOpenGLDisplayMask>(displayMask);
|
|
||||||
for (std::uint32_t i = 0; i < displayCount; ++i) {
|
|
||||||
if ((CGDisplayIDToOpenGLDisplayMask(displays[i]) & mask) != 0) {
|
|
||||||
return displays[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return CGMainDisplayID();
|
|
||||||
}
|
|
||||||
|
|
||||||
#pragma clang diagnostic push
|
|
||||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
|
||||||
CVReturn MobileGLCVDisplayLinkSetCurrentCGDisplayFromOpenGLContext(
|
|
||||||
CVDisplayLinkRef displayLink,
|
|
||||||
CGLContextObj context,
|
|
||||||
CGLPixelFormatObj pixelFormat) {
|
|
||||||
GLint virtualScreen = 0;
|
|
||||||
if (MobileGL::MG_Impl::CGLImpl::GetVirtualScreen(context, &virtualScreen) == kCGLNoError) {
|
|
||||||
GLint displayMask = 0;
|
|
||||||
if (!displayLink ||
|
|
||||||
MobileGL::MG_Impl::CGLImpl::DescribePixelFormat(
|
|
||||||
pixelFormat, virtualScreen, kCGLPFADisplayMask, &displayMask) != kCGLNoError) {
|
|
||||||
return kCVReturnInvalidArgument;
|
|
||||||
}
|
|
||||||
return CVDisplayLinkSetCurrentCGDisplay(displayLink, DisplayForMask(displayMask));
|
|
||||||
}
|
|
||||||
|
|
||||||
using OriginalFunction = CVReturn (*)(CVDisplayLinkRef, CGLContextObj, CGLPixelFormatObj);
|
|
||||||
static const auto original = reinterpret_cast<OriginalFunction>(
|
|
||||||
dlsym(RTLD_NEXT, "CVDisplayLinkSetCurrentCGDisplayFromOpenGLContext"));
|
|
||||||
return original ? original(displayLink, context, pixelFormat) : kCVReturnError;
|
|
||||||
}
|
|
||||||
|
|
||||||
__attribute__((used)) static const DyldInterposeEntry kMobileGLDyldInterpose[]
|
__attribute__((used)) static const DyldInterposeEntry kMobileGLDyldInterpose[]
|
||||||
__attribute__((section("__DATA,__interpose"))) = {
|
__attribute__((section("__DATA,__interpose"))) = {
|
||||||
{reinterpret_cast<const void*>(MobileGLDlsym), reinterpret_cast<const void*>(dlsym)},
|
{reinterpret_cast<const void*>(MobileGLDlsym), reinterpret_cast<const void*>(dlsym)},
|
||||||
{reinterpret_cast<const void*>(MobileGLCVDisplayLinkSetCurrentCGDisplayFromOpenGLContext),
|
|
||||||
reinterpret_cast<const void*>(CVDisplayLinkSetCurrentCGDisplayFromOpenGLContext)},
|
|
||||||
};
|
};
|
||||||
#pragma clang diagnostic pop
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
# Public CGL entry points.
|
|
||||||
_CGL*
|
|
||||||
|
|
||||||
# Public EGL entry points.
|
|
||||||
_egl*
|
|
||||||
|
|
||||||
# Public OpenGL and GLX entry points. OpenGL function names always use an
|
|
||||||
# uppercase letter or digit after the "gl" prefix; excluding lowercase here
|
|
||||||
# deliberately prevents glslang_* from matching this pattern.
|
|
||||||
_gl[A-Z0-9]*
|
|
||||||
@@ -8,9 +8,6 @@
|
|||||||
|
|
||||||
#include "GL_Buffer.h"
|
#include "GL_Buffer.h"
|
||||||
#include "Validators.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 <Config.h>
|
||||||
#include <MG_State/GLState/Core.h>
|
#include <MG_State/GLState/Core.h>
|
||||||
#include <MG_State/GLState/ErrorState/Error.h>
|
#include <MG_State/GLState/ErrorState/Error.h>
|
||||||
@@ -41,7 +38,6 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
GetNamedBufferParameteriv,
|
GetNamedBufferParameteriv,
|
||||||
GetNamedBufferParameteri64v,
|
GetNamedBufferParameteri64v,
|
||||||
GetNamedBufferPointerv,
|
GetNamedBufferPointerv,
|
||||||
GetNamedBufferSubData,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const char* GetBufferOpName(BufferOp op) {
|
const char* GetBufferOpName(BufferOp op) {
|
||||||
@@ -80,8 +76,6 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
return "UnmapNamedBuffer";
|
return "UnmapNamedBuffer";
|
||||||
case BufferOp::FlushMappedNamedBufferRange:
|
case BufferOp::FlushMappedNamedBufferRange:
|
||||||
return "FlushMappedNamedBufferRange";
|
return "FlushMappedNamedBufferRange";
|
||||||
case BufferOp::GetNamedBufferSubData:
|
|
||||||
return "GetNamedBufferSubData";
|
|
||||||
case BufferOp::GetNamedBufferParameteriv:
|
case BufferOp::GetNamedBufferParameteriv:
|
||||||
return "GetNamedBufferParameteriv";
|
return "GetNamedBufferParameteriv";
|
||||||
case BufferOp::GetNamedBufferParameteri64v:
|
case BufferOp::GetNamedBufferParameteri64v:
|
||||||
@@ -95,64 +89,25 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
|
|
||||||
SharedPtr<MG_State::GLState::BufferObject> GetNamedBufferObject(GLuint buffer, BufferOp op);
|
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) {
|
SizeT GetClearPatternSize(GLenum internalformat, GLenum format, GLenum type, BufferOp op) {
|
||||||
if (!IsBufferTextureInternalFormat(internalformat)) {
|
if (format != GL_RED_INTEGER) {
|
||||||
MG_State::pGLContext->RecordError(
|
|
||||||
ErrorCode::InvalidEnum,
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
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(
|
MG_State::pGLContext->RecordError(
|
||||||
ErrorCode::InvalidEnum,
|
ErrorCode::InvalidEnum,
|
||||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", GetBufferOpName(op),
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", GetBufferOpName(op),
|
||||||
std::format("internalformat 0x{:X} has no known element size.",
|
"Only GL_RED_INTEGER buffer clears are currently supported."));
|
||||||
internalformat)));
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// The pattern is replicated verbatim, which is only the whole story while the client
|
if (internalformat == GL_R8UI && type == GL_UNSIGNED_BYTE) return sizeof(GLubyte);
|
||||||
// layout already matches the internal format - the case every entry point in practice
|
if (internalformat == GL_R32UI && type == GL_UNSIGNED_INT) return sizeof(GLuint);
|
||||||
// uses, and the only one the conversion machinery here can express. Say so rather than
|
|
||||||
// quietly writing a differently-sized pattern.
|
MG_State::pGLContext->RecordError(
|
||||||
const SizeT sourceSize = MG_Util::GetInputBytesPerPixel(inputFormat, pixelType);
|
ErrorCode::InvalidEnum,
|
||||||
if (sourceSize != elementSize) {
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", GetBufferOpName(op),
|
||||||
MGLOG_W("%s: clear pattern is %zu bytes but internalformat 0x%X stores %zu; "
|
std::format("Unsupported clear format tuple: internalformat=0x{:X}, "
|
||||||
"converting between them is not implemented",
|
"format=0x{:X}, type=0x{:X}",
|
||||||
GetBufferOpName(op), sourceSize, internalformat, elementSize);
|
internalformat, format, type)));
|
||||||
}
|
return 0;
|
||||||
return elementSize;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Bool ValidateBufferClearRange(const SharedPtr<MG_State::GLState::BufferObject>& bufferObject, GLintptr offset,
|
Bool ValidateBufferClearRange(const SharedPtr<MG_State::GLState::BufferObject>& bufferObject, GLintptr offset,
|
||||||
@@ -375,21 +330,12 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
} else if (access & BufferMappingAccessBit::Write) {
|
} else if (access & BufferMappingAccessBit::Write) {
|
||||||
*params = GL_WRITE_ONLY;
|
*params = GL_WRITE_ONLY;
|
||||||
} else {
|
} else {
|
||||||
*params = GL_READ_WRITE;
|
*params = 0;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Initial value, and what glUnmapBuffer restores (GL 4.6 core table 6.2).
|
*params = 0;
|
||||||
*params = GL_READ_WRITE;
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case GL_BUFFER_ACCESS_FLAGS:
|
|
||||||
// The MapBufferRange flags verbatim; glMapBuffer's access enum has already been
|
|
||||||
// normalised into the same bits. Zero while the buffer is not mapped.
|
|
||||||
*params = bufferObject->IsMapped()
|
|
||||||
? static_cast<GLint>(
|
|
||||||
MG_Util::ConvertBufferMappingAccessToGLEnum(bufferObject->GetMappingAccess()))
|
|
||||||
: 0;
|
|
||||||
break;
|
|
||||||
case GL_BUFFER_MAPPED:
|
case GL_BUFFER_MAPPED:
|
||||||
*params = bufferObject->IsMapped() ? GL_TRUE : GL_FALSE;
|
*params = bufferObject->IsMapped() ? GL_TRUE : GL_FALSE;
|
||||||
break;
|
break;
|
||||||
@@ -932,45 +878,6 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
bufferObject->SyncGpuWrites();
|
|
||||||
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));
|
bufferObject->DownloadSubData(data, static_cast<SizeT>(offset), static_cast<SizeT>(size));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1444,14 +1351,6 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
BufferTarget bufferTarget = MG_Util::ConvertGLEnumToBufferTarget(target);
|
BufferTarget bufferTarget = MG_Util::ConvertGLEnumToBufferTarget(target);
|
||||||
if (!BufferImpl::ValidateBufferBindingPointTarget(bufferTarget)) return;
|
if (!BufferImpl::ValidateBufferBindingPointTarget(bufferTarget)) return;
|
||||||
if (!BufferImpl::ValidateBufferBindingPointIndex(bufferTarget, pointIndex)) return;
|
if (!BufferImpl::ValidateBufferBindingPointIndex(bufferTarget, pointIndex)) return;
|
||||||
if (bufferTarget == BufferTarget::TransformFeedback && MG_State::pGLContext->IsTransformFeedbackActive()) {
|
|
||||||
MG_State::pGLContext->RecordError(
|
|
||||||
ErrorCode::InvalidOperation,
|
|
||||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
|
||||||
"Transform feedback buffer bindings cannot change while transform "
|
|
||||||
"feedback is active."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
MG_State::pGLContext->TouchBufferBindingPoint(bufferTarget, pointIndex);
|
MG_State::pGLContext->TouchBufferBindingPoint(bufferTarget, pointIndex);
|
||||||
|
|
||||||
auto& point = MG_State::pGLContext->GetBufferBindingPoint(bufferTarget, pointIndex);
|
auto& point = MG_State::pGLContext->GetBufferBindingPoint(bufferTarget, pointIndex);
|
||||||
@@ -1459,7 +1358,6 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
if (buffer == 0) {
|
if (buffer == 0) {
|
||||||
point.Bind(nullptr);
|
point.Bind(nullptr);
|
||||||
point.SetRange(Range1D(0, 0));
|
point.SetRange(Range1D(0, 0));
|
||||||
GetBufferBindingSlot(bufferTarget).Bind(nullptr);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1478,12 +1376,6 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
} else {
|
} else {
|
||||||
point.ClearRange();
|
point.ClearRange();
|
||||||
}
|
}
|
||||||
// The indexed bind also binds to the generic binding point of the same target
|
|
||||||
// (GL 4.6 core 6.1.1). Callers rely on it: the texture_gather tests set up their
|
|
||||||
// SSBO with BindBufferBase and then size it through glBufferData on the generic
|
|
||||||
// target alone, which would otherwise raise GL_INVALID_OPERATION and leave the
|
|
||||||
// buffer with no storage.
|
|
||||||
GetBufferBindingSlot(bufferTarget).Bind(bufferObject);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void BindBufferRange_State(GLenum target, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size) {
|
void BindBufferRange_State(GLenum target, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size) {
|
||||||
@@ -1492,14 +1384,6 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
BufferTarget bufferTarget = MG_Util::ConvertGLEnumToBufferTarget(target);
|
BufferTarget bufferTarget = MG_Util::ConvertGLEnumToBufferTarget(target);
|
||||||
if (!BufferImpl::ValidateBufferBindingPointTarget(bufferTarget)) return;
|
if (!BufferImpl::ValidateBufferBindingPointTarget(bufferTarget)) return;
|
||||||
if (!BufferImpl::ValidateBufferBindingPointIndex(bufferTarget, index)) return;
|
if (!BufferImpl::ValidateBufferBindingPointIndex(bufferTarget, index)) return;
|
||||||
if (bufferTarget == BufferTarget::TransformFeedback && MG_State::pGLContext->IsTransformFeedbackActive()) {
|
|
||||||
MG_State::pGLContext->RecordError(
|
|
||||||
ErrorCode::InvalidOperation,
|
|
||||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
|
||||||
"Transform feedback buffer bindings cannot change while transform "
|
|
||||||
"feedback is active."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
MG_State::pGLContext->TouchBufferBindingPoint(bufferTarget, index);
|
MG_State::pGLContext->TouchBufferBindingPoint(bufferTarget, index);
|
||||||
|
|
||||||
auto& point = MG_State::pGLContext->GetBufferBindingPoint(bufferTarget, index);
|
auto& point = MG_State::pGLContext->GetBufferBindingPoint(bufferTarget, index);
|
||||||
@@ -1507,7 +1391,6 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
if (buffer == 0) {
|
if (buffer == 0) {
|
||||||
point.Bind(nullptr);
|
point.Bind(nullptr);
|
||||||
point.SetRange(Range1D(0, 0));
|
point.SetRange(Range1D(0, 0));
|
||||||
GetBufferBindingSlot(bufferTarget).Bind(nullptr);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1525,8 +1408,6 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
} else {
|
} else {
|
||||||
point.ClearRange();
|
point.ClearRange();
|
||||||
}
|
}
|
||||||
// Also the generic binding point, exactly as BindBufferBase (GL 4.6 core 6.1.1).
|
|
||||||
GetBufferBindingSlot(bufferTarget).Bind(bufferObject);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* @INSERTION_POINT:FUNCTION_IMPLEMENTATION@ */
|
/* @INSERTION_POINT:FUNCTION_IMPLEMENTATION@ */
|
||||||
@@ -1636,10 +1517,6 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
BufferSubData_State(target, offset, size, data);
|
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) {
|
void GetBufferSubData(GLenum target, GLintptr offset, GLsizeiptr size, void* data) {
|
||||||
GetBufferSubData_State(target, offset, size, data);
|
GetBufferSubData_State(target, offset, size, data);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,7 +41,6 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
GLsizeiptr size);
|
GLsizeiptr size);
|
||||||
void BufferSubData(GLenum target, GLintptr offset, GLsizeiptr size, const void* data);
|
void BufferSubData(GLenum target, GLintptr offset, GLsizeiptr size, const void* data);
|
||||||
void GetBufferSubData(GLenum target, GLintptr offset, GLsizeiptr size, 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 BufferData(GLenum target, GLsizeiptr size, const void* data, GLenum usage);
|
||||||
void BindBuffer(GLenum target, GLuint buffer);
|
void BindBuffer(GLenum target, GLuint buffer);
|
||||||
void GenBuffers(GLsizei n, GLuint* buffers);
|
void GenBuffers(GLsizei n, GLuint* buffers);
|
||||||
|
|||||||
@@ -60,11 +60,6 @@ namespace MobileGL::MG_Impl::GLImpl::BufferImpl {
|
|||||||
MG_Backend::pActiveBackendObject->GetDynamicParameters().MaxShaderStorageBufferBindings;
|
MG_Backend::pActiveBackendObject->GetDynamicParameters().MaxShaderStorageBufferBindings;
|
||||||
pointCount = std::min(pointCount, static_cast<SizeT>(std::max(backendCount, 0)));
|
pointCount = std::min(pointCount, static_cast<SizeT>(std::max(backendCount, 0)));
|
||||||
}
|
}
|
||||||
if (target == BufferTarget::TransformFeedback) {
|
|
||||||
// GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS bounds the indexed capture
|
|
||||||
// binding points in GL 3.3 (no ARB_transform_feedback3).
|
|
||||||
pointCount = std::min<SizeT>(pointCount, 4);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (index < pointCount) {
|
if (index < pointCount) {
|
||||||
return true;
|
return true;
|
||||||
@@ -112,10 +107,14 @@ namespace MobileGL::MG_Impl::GLImpl::BufferImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Bool ValidateBufferMappingAccess(Flags<BufferMappingAccessBit> accessBits) {
|
Bool ValidateBufferMappingAccess(Flags<BufferMappingAccessBit> accessBits) {
|
||||||
// An empty mask is a legal value for a bitfield - it just fails the rule that a mapping
|
if (accessBits == BufferMappingAccessBit::Null) {
|
||||||
// must ask for read or write access, which is INVALID_OPERATION and belongs to the callers
|
MG_State::pGLContext->RecordError(ErrorCode::InvalidEnum,
|
||||||
// (both of them check it immediately after this). Rejecting it here as INVALID_ENUM
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl/BufferImpl",
|
||||||
// reported the wrong error and hid theirs.
|
"ValidateBufferMappingAccess",
|
||||||
|
"Access bits cannot be null."));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
const auto validBits = BufferMappingAccessBit::Read | BufferMappingAccessBit::Write |
|
const auto validBits = BufferMappingAccessBit::Read | BufferMappingAccessBit::Write |
|
||||||
BufferMappingAccessBit::InvalidateRange | BufferMappingAccessBit::InvalidateBuffer |
|
BufferMappingAccessBit::InvalidateRange | BufferMappingAccessBit::InvalidateBuffer |
|
||||||
BufferMappingAccessBit::FlushExplicit | BufferMappingAccessBit::Unsynchronized |
|
BufferMappingAccessBit::FlushExplicit | BufferMappingAccessBit::Unsynchronized |
|
||||||
|
|||||||
@@ -11,11 +11,10 @@
|
|||||||
#include <MG_State/GLState/Core.h>
|
#include <MG_State/GLState/Core.h>
|
||||||
#include <MG_State/EGLState/Core.h>
|
#include <MG_State/EGLState/Core.h>
|
||||||
#include <MG_Backend/BackendObjects.h>
|
#include <MG_Backend/BackendObjects.h>
|
||||||
#include "../Getter/GL_Getter.h"
|
|
||||||
|
|
||||||
namespace MobileGL::MG_Impl::GLImpl {
|
namespace MobileGL::MG_Impl::GLImpl {
|
||||||
static Bool ValidateCurrentProgramForExecution(const char* functionName) {
|
static Bool ValidateCurrentProgramForExecution(const char* functionName) {
|
||||||
const auto& currentProgram = MG_State::pGLContext->GetProgramForDraw();
|
const auto& currentProgram = MG_State::pGLContext->GetCurrentProgram();
|
||||||
if (!currentProgram) {
|
if (!currentProgram) {
|
||||||
MG_State::pGLContext->RecordError(
|
MG_State::pGLContext->RecordError(
|
||||||
ErrorCode::InvalidOperation,
|
ErrorCode::InvalidOperation,
|
||||||
@@ -37,7 +36,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
static Bool ValidateCurrentProgramForCompute(const char* functionName) {
|
static Bool ValidateCurrentProgramForCompute(const char* functionName) {
|
||||||
if (!ValidateCurrentProgramForExecution(functionName)) return false;
|
if (!ValidateCurrentProgramForExecution(functionName)) return false;
|
||||||
|
|
||||||
const auto& currentProgram = MG_State::pGLContext->GetProgramForDraw();
|
const auto& currentProgram = MG_State::pGLContext->GetCurrentProgram();
|
||||||
if (currentProgram->GetShaderIndexByStage(ShaderStage::Compute) < 0) {
|
if (currentProgram->GetShaderIndexByStage(ShaderStage::Compute) < 0) {
|
||||||
MG_State::pGLContext->RecordError(
|
MG_State::pGLContext->RecordError(
|
||||||
ErrorCode::InvalidOperation,
|
ErrorCode::InvalidOperation,
|
||||||
@@ -49,109 +48,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Primitives a draw of `count` vertices in `mode` assembles (0 for
|
|
||||||
// incomplete primitives). Used for the CPU-side transform feedback
|
|
||||||
// primitive accounting.
|
|
||||||
static Uint64 CountPrimitivesForDraw(GLenum mode, GLsizei count) {
|
|
||||||
if (count <= 0) return 0;
|
|
||||||
switch (mode) {
|
|
||||||
case GL_POINTS: return static_cast<Uint64>(count);
|
|
||||||
case GL_LINES: return static_cast<Uint64>(count / 2);
|
|
||||||
case GL_LINE_STRIP: return count >= 2 ? static_cast<Uint64>(count - 1) : 0;
|
|
||||||
case GL_LINE_LOOP: return count >= 2 ? static_cast<Uint64>(count) : 0;
|
|
||||||
case GL_TRIANGLES: return static_cast<Uint64>(count / 3);
|
|
||||||
case GL_TRIANGLE_STRIP:
|
|
||||||
case GL_TRIANGLE_FAN: return count >= 3 ? static_cast<Uint64>(count - 2) : 0;
|
|
||||||
default: return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Accumulate the transform feedback primitive counter for a captured draw.
|
|
||||||
// Draws without a geometry stage write exactly the primitives they assemble,
|
|
||||||
// clamped by the capture buffers' remaining capacity (a full buffer stops
|
|
||||||
// recording whole primitives, which is what PRIMITIVES_WRITTEN reports).
|
|
||||||
// Geometry amplification is not modelled here.
|
|
||||||
static void AccountTransformFeedbackPrimitives(GLenum mode, GLsizei count) {
|
|
||||||
if (!MG_State::pGLContext->IsTransformFeedbackActive()) return;
|
|
||||||
// A paused span captures nothing, so a draw made while paused contributes to
|
|
||||||
// PRIMITIVES_GENERATED but not to TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN.
|
|
||||||
if (MG_State::pGLContext->IsTransformFeedbackPaused()) {
|
|
||||||
MG_State::pGLContext->AddTransformFeedbackPausedPrimitives(CountPrimitivesForDraw(mode, count));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Uint64 primitives = CountPrimitivesForDraw(mode, count);
|
|
||||||
if (primitives == 0) return;
|
|
||||||
MG_State::pGLContext->AddTransformFeedbackInputPrimitives(primitives);
|
|
||||||
|
|
||||||
Uint64 verticesPerPrimitive = 1;
|
|
||||||
switch (mode) {
|
|
||||||
case GL_LINES:
|
|
||||||
case GL_LINE_STRIP:
|
|
||||||
case GL_LINE_LOOP:
|
|
||||||
verticesPerPrimitive = 2;
|
|
||||||
break;
|
|
||||||
case GL_TRIANGLES:
|
|
||||||
case GL_TRIANGLE_STRIP:
|
|
||||||
case GL_TRIANGLE_FAN:
|
|
||||||
verticesPerPrimitive = 3;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto& program = MG_State::pGLContext->GetTransformFeedbackProgram();
|
|
||||||
if (program != nullptr) {
|
|
||||||
// Capacity in captured vertices = the tightest bound buffer.
|
|
||||||
Uint64 capacityVertices = ~0ull;
|
|
||||||
for (SizeT i = 0; i < program->GetTransformFeedbackBufferCount(); ++i) {
|
|
||||||
const Uint32 stride = program->GetTransformFeedbackStride(static_cast<Uint32>(i));
|
|
||||||
if (stride == 0) continue;
|
|
||||||
const auto& point = MG_State::pGLContext->GetBufferBindingPoint(BufferTarget::TransformFeedback,
|
|
||||||
static_cast<Uint>(i));
|
|
||||||
const Range1D range = point.GetRange();
|
|
||||||
const Uint64 bytes = range.end > range.start ? static_cast<Uint64>(range.end - range.start) : 0;
|
|
||||||
capacityVertices = std::min<Uint64>(capacityVertices, bytes / stride);
|
|
||||||
}
|
|
||||||
if (capacityVertices != ~0ull) {
|
|
||||||
const Uint64 usedVertices = MG_State::pGLContext->GetTransformFeedbackCapturedVertices();
|
|
||||||
const Uint64 remainingVertices = capacityVertices > usedVertices ? capacityVertices - usedVertices : 0;
|
|
||||||
primitives = std::min<Uint64>(primitives, remainingVertices / verticesPerPrimitive);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
MG_State::pGLContext->AddTransformFeedbackPrimitives(primitives);
|
|
||||||
MG_State::pGLContext->AddTransformFeedbackCapturedVertices(primitives * verticesPerPrimitive);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Every primitive mode a draw command accepts (GL 4.6 core table 10.1, plus
|
|
||||||
// GL_PATCHES for the tessellation pipeline). Anything else is GL_INVALID_ENUM.
|
|
||||||
static Bool IsAcceptedPrimitiveMode(GLenum mode) {
|
|
||||||
switch (mode) {
|
|
||||||
case GL_POINTS:
|
|
||||||
case GL_LINES:
|
|
||||||
case GL_LINE_LOOP:
|
|
||||||
case GL_LINE_STRIP:
|
|
||||||
case GL_LINES_ADJACENCY:
|
|
||||||
case GL_LINE_STRIP_ADJACENCY:
|
|
||||||
case GL_TRIANGLES:
|
|
||||||
case GL_TRIANGLE_STRIP:
|
|
||||||
case GL_TRIANGLE_FAN:
|
|
||||||
case GL_TRIANGLES_ADJACENCY:
|
|
||||||
case GL_TRIANGLE_STRIP_ADJACENCY:
|
|
||||||
case GL_PATCHES:
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static Bool ValidatePrimitiveModeForBackend(const char* functionName, GLenum mode) {
|
static Bool ValidatePrimitiveModeForBackend(const char* functionName, GLenum mode) {
|
||||||
if (!IsAcceptedPrimitiveMode(mode)) {
|
|
||||||
MG_State::pGLContext->RecordError(
|
|
||||||
ErrorCode::InvalidEnum,
|
|
||||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", functionName, "mode is not an accepted primitive type."));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto& activeBackendObject = MG_Backend::pActiveBackendObject;
|
const auto& activeBackendObject = MG_Backend::pActiveBackendObject;
|
||||||
if (!activeBackendObject) {
|
if (!activeBackendObject) {
|
||||||
MG_State::pGLContext->RecordError(
|
MG_State::pGLContext->RecordError(
|
||||||
@@ -160,6 +57,15 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (activeBackendObject->GetBackendType() == BackendType::DirectVulkan && mode == GL_LINE_LOOP) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidOperation,
|
||||||
|
MakeUnique<GenericErrorInfo>(
|
||||||
|
"MG_Impl/GLImpl", functionName,
|
||||||
|
"Primitive mode GL_LINE_LOOP is not supported by the DirectVulkan backend."));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
const auto& vao = MG_State::pGLContext->GetBoundVertexArray();
|
const auto& vao = MG_State::pGLContext->GetBoundVertexArray();
|
||||||
if (vao && vao->GetExternalIndex() == 0 && !MG_State::IsRelaxedSemanticsActive()) {
|
if (vao && vao->GetExternalIndex() == 0 && !MG_State::IsRelaxedSemanticsActive()) {
|
||||||
MG_State::pGLContext->RecordError(
|
MG_State::pGLContext->RecordError(
|
||||||
@@ -169,133 +75,9 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// A geometry stage only accepts the primitive types that decompose into its declared
|
|
||||||
// input primitive (GL 4.6 core 11.3.1); anything else is INVALID_OPERATION. GL_PATCHES
|
|
||||||
// is the tessellation pipeline's input and reaches the geometry stage already
|
|
||||||
// converted, so it is not constrained here.
|
|
||||||
const auto& currentProgram = MG_State::pGLContext->GetProgramForDraw();
|
|
||||||
const GLenum gsInput = currentProgram ? currentProgram->GetGeometryInputType() : GL_NONE;
|
|
||||||
if (gsInput != GL_NONE && mode != GL_PATCHES) {
|
|
||||||
Bool compatible = false;
|
|
||||||
switch (gsInput) {
|
|
||||||
case GL_POINTS:
|
|
||||||
compatible = mode == GL_POINTS;
|
|
||||||
break;
|
|
||||||
case GL_LINES:
|
|
||||||
compatible = mode == GL_LINES || mode == GL_LINE_STRIP || mode == GL_LINE_LOOP;
|
|
||||||
break;
|
|
||||||
case GL_LINES_ADJACENCY:
|
|
||||||
compatible = mode == GL_LINES_ADJACENCY || mode == GL_LINE_STRIP_ADJACENCY;
|
|
||||||
break;
|
|
||||||
case GL_TRIANGLES:
|
|
||||||
compatible = mode == GL_TRIANGLES || mode == GL_TRIANGLE_STRIP || mode == GL_TRIANGLE_FAN;
|
|
||||||
break;
|
|
||||||
case GL_TRIANGLES_ADJACENCY:
|
|
||||||
compatible = mode == GL_TRIANGLES_ADJACENCY || mode == GL_TRIANGLE_STRIP_ADJACENCY;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (!compatible) {
|
|
||||||
MG_State::pGLContext->RecordError(
|
|
||||||
ErrorCode::InvalidOperation,
|
|
||||||
MakeUnique<GenericErrorInfo>(
|
|
||||||
"MG_Impl/GLImpl", functionName,
|
|
||||||
"Primitive mode is incompatible with the geometry shader's input primitive type."));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// While transform feedback is active the draw's primitive type must match
|
|
||||||
// the feedback primitive mode (GL 3.3 core 13.2.2). With a geometry shader
|
|
||||||
// the constraint moves to the shader's output primitive type instead, so
|
|
||||||
// the draw mode itself is unconstrained here. A paused span is exempt: it
|
|
||||||
// captures nothing, so there is nothing for the mode to be incompatible with
|
|
||||||
// (GL 4.6 core 13.2.3).
|
|
||||||
if (MG_State::pGLContext->IsTransformFeedbackActive() &&
|
|
||||||
!MG_State::pGLContext->IsTransformFeedbackPaused() &&
|
|
||||||
!(MG_State::pGLContext->GetTransformFeedbackProgram() &&
|
|
||||||
MG_State::pGLContext->GetTransformFeedbackProgram()->GetShaderIndexByStage(ShaderStage::Geometry) >= 0)) {
|
|
||||||
const GLenum feedbackMode = MG_State::pGLContext->GetTransformFeedbackPrimitiveMode();
|
|
||||||
Bool compatible = false;
|
|
||||||
switch (feedbackMode) {
|
|
||||||
case GL_POINTS:
|
|
||||||
compatible = mode == GL_POINTS;
|
|
||||||
break;
|
|
||||||
case GL_LINES:
|
|
||||||
compatible = mode == GL_LINES || mode == GL_LINE_STRIP || mode == GL_LINE_LOOP;
|
|
||||||
break;
|
|
||||||
case GL_TRIANGLES:
|
|
||||||
compatible = mode == GL_TRIANGLES || mode == GL_TRIANGLE_STRIP || mode == GL_TRIANGLE_FAN;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (!compatible) {
|
|
||||||
MG_State::pGLContext->RecordError(
|
|
||||||
ErrorCode::InvalidOperation,
|
|
||||||
MakeUnique<GenericErrorInfo>(
|
|
||||||
"MG_Impl/GLImpl", functionName,
|
|
||||||
"Primitive mode is incompatible with the active transform feedback primitive mode."));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Byte size of the command structures the indirect draws read (GL 4.6 core 10.3.10).
|
|
||||||
constexpr SizeT kDrawArraysIndirectCommandBytes = 4 * sizeof(Uint32);
|
|
||||||
constexpr SizeT kDrawElementsIndirectCommandBytes = 5 * sizeof(Uint32);
|
|
||||||
|
|
||||||
// Shared preconditions of every *Indirect draw: `indirect` is a byte offset into the
|
|
||||||
// buffer bound to GL_DRAW_INDIRECT_BUFFER, must be 4-byte aligned, and the whole
|
|
||||||
// command has to lie inside that buffer.
|
|
||||||
static Bool ValidateIndirectDrawSource(const char* functionName, const void* indirect, SizeT commandBytes) {
|
|
||||||
const auto offset = reinterpret_cast<uintptr_t>(indirect);
|
|
||||||
if (offset % 4 != 0) {
|
|
||||||
MG_State::pGLContext->RecordError(
|
|
||||||
ErrorCode::InvalidValue,
|
|
||||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", functionName,
|
|
||||||
"indirect offset must be a multiple of 4."));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto& buffer = MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::DrawIndirect).GetBoundObject();
|
|
||||||
if (!buffer) {
|
|
||||||
MG_State::pGLContext->RecordError(
|
|
||||||
ErrorCode::InvalidOperation,
|
|
||||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", functionName,
|
|
||||||
"No buffer is bound to GL_DRAW_INDIRECT_BUFFER."));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (offset + commandBytes > buffer->GetSize()) {
|
|
||||||
MG_State::pGLContext->RecordError(
|
|
||||||
ErrorCode::InvalidOperation,
|
|
||||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", functionName,
|
|
||||||
"The indirect command extends past the end of the bound "
|
|
||||||
"GL_DRAW_INDIRECT_BUFFER."));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Index type accepted by the DrawElements family (GL 4.6 core 10.3.9).
|
|
||||||
static Bool ValidateDrawElementsIndexType(const char* functionName, GLenum type) {
|
|
||||||
switch (type) {
|
|
||||||
case GL_UNSIGNED_BYTE:
|
|
||||||
case GL_UNSIGNED_SHORT:
|
|
||||||
case GL_UNSIGNED_INT:
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
MG_State::pGLContext->RecordError(
|
|
||||||
ErrorCode::InvalidEnum,
|
|
||||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", functionName, "type is not an accepted index type."));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Clear_Backend(GLbitfield mask) {
|
void Clear_Backend(GLbitfield mask) {
|
||||||
#ifdef TRACY_ENABLE
|
#ifdef TRACY_ENABLE
|
||||||
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
||||||
@@ -490,28 +272,6 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
dispatchComputeIndirect(indirect);
|
dispatchComputeIndirect(indirect);
|
||||||
}
|
}
|
||||||
|
|
||||||
void PatchParameteri(GLenum pname, GLint value) {
|
|
||||||
if (pname != GL_PATCH_VERTICES) {
|
|
||||||
MG_State::pGLContext->RecordError(
|
|
||||||
ErrorCode::InvalidEnum,
|
|
||||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "pname must be GL_PATCH_VERTICES."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
GLint maxPatchVertices = 32;
|
|
||||||
GetIntegerv(GL_MAX_PATCH_VERTICES, &maxPatchVertices);
|
|
||||||
if (value <= 0 || value > maxPatchVertices) {
|
|
||||||
MG_State::pGLContext->RecordError(
|
|
||||||
ErrorCode::InvalidValue,
|
|
||||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
|
||||||
"value must be in [1, GL_MAX_PATCH_VERTICES]."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
MG_State::pGLContext->SetPatchVertices(static_cast<Uint>(value));
|
|
||||||
if (const auto patchParameteri = MG_Backend::gBackendFunctionsTable.GL.PatchParameteri) {
|
|
||||||
patchParameteri(pname, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void MemoryBarrier(GLbitfield barriers) {
|
void MemoryBarrier(GLbitfield barriers) {
|
||||||
auto memoryBarrier = MG_Backend::gBackendFunctionsTable.GL.MemoryBarrier;
|
auto memoryBarrier = MG_Backend::gBackendFunctionsTable.GL.MemoryBarrier;
|
||||||
if (!memoryBarrier) {
|
if (!memoryBarrier) {
|
||||||
@@ -617,8 +377,6 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
void DrawElementsIndirect(GLenum mode, GLenum type, const void* indirect) {
|
void DrawElementsIndirect(GLenum mode, GLenum type, const void* indirect) {
|
||||||
if (!ValidateCurrentProgramForExecution(__func__)) return;
|
if (!ValidateCurrentProgramForExecution(__func__)) return;
|
||||||
if (!ValidatePrimitiveModeForBackend(__func__, mode)) return;
|
if (!ValidatePrimitiveModeForBackend(__func__, mode)) return;
|
||||||
if (!ValidateDrawElementsIndexType(__func__, type)) return;
|
|
||||||
if (!ValidateIndirectDrawSource(__func__, indirect, kDrawElementsIndirectCommandBytes)) return;
|
|
||||||
DrawElementsIndirect_Backend(mode, type, indirect);
|
DrawElementsIndirect_Backend(mode, type, indirect);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -638,21 +396,18 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
void DrawArraysIndirect(GLenum mode, const void* indirect) {
|
void DrawArraysIndirect(GLenum mode, const void* indirect) {
|
||||||
if (!ValidateCurrentProgramForExecution(__func__)) return;
|
if (!ValidateCurrentProgramForExecution(__func__)) return;
|
||||||
if (!ValidatePrimitiveModeForBackend(__func__, mode)) return;
|
if (!ValidatePrimitiveModeForBackend(__func__, mode)) return;
|
||||||
if (!ValidateIndirectDrawSource(__func__, indirect, kDrawArraysIndirectCommandBytes)) return;
|
|
||||||
DrawArraysIndirect_Backend(mode, indirect);
|
DrawArraysIndirect_Backend(mode, indirect);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DrawElementsBaseVertex(GLenum mode, GLsizei count, GLenum type, const void* indices, GLint basevertex) {
|
void DrawElementsBaseVertex(GLenum mode, GLsizei count, GLenum type, const void* indices, GLint basevertex) {
|
||||||
if (!ValidateCurrentProgramForExecution(__func__)) return;
|
if (!ValidateCurrentProgramForExecution(__func__)) return;
|
||||||
if (!ValidatePrimitiveModeForBackend(__func__, mode)) return;
|
if (!ValidatePrimitiveModeForBackend(__func__, mode)) return;
|
||||||
AccountTransformFeedbackPrimitives(mode, count);
|
|
||||||
DrawElementsBaseVertex_Backend(mode, count, type, indices, basevertex);
|
DrawElementsBaseVertex_Backend(mode, count, type, indices, basevertex);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DrawArrays(GLenum mode, GLint first, GLsizei count) {
|
void DrawArrays(GLenum mode, GLint first, GLsizei count) {
|
||||||
if (!ValidateCurrentProgramForExecution(__func__)) return;
|
if (!ValidateCurrentProgramForExecution(__func__)) return;
|
||||||
if (!ValidatePrimitiveModeForBackend(__func__, mode)) return;
|
if (!ValidatePrimitiveModeForBackend(__func__, mode)) return;
|
||||||
AccountTransformFeedbackPrimitives(mode, count);
|
|
||||||
DrawArrays_Backend(mode, first, count);
|
DrawArrays_Backend(mode, first, count);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -689,487 +444,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
void DrawElements(GLenum mode, GLsizei count, GLenum type, const void* indices) {
|
void DrawElements(GLenum mode, GLsizei count, GLenum type, const void* indices) {
|
||||||
if (!ValidateCurrentProgramForExecution(__func__)) return;
|
if (!ValidateCurrentProgramForExecution(__func__)) return;
|
||||||
if (!ValidatePrimitiveModeForBackend(__func__, mode)) return;
|
if (!ValidatePrimitiveModeForBackend(__func__, mode)) return;
|
||||||
AccountTransformFeedbackPrimitives(mode, count);
|
|
||||||
DrawElements_Backend(mode, count, type, indices);
|
DrawElements_Backend(mode, count, type, indices);
|
||||||
}
|
}
|
||||||
|
|
||||||
void BeginTransformFeedback(GLenum primitiveMode) {
|
|
||||||
if (primitiveMode != GL_POINTS && primitiveMode != GL_LINES && primitiveMode != GL_TRIANGLES) {
|
|
||||||
MG_State::pGLContext->RecordError(
|
|
||||||
ErrorCode::InvalidEnum,
|
|
||||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
|
||||||
"primitiveMode must be GL_POINTS, GL_LINES or GL_TRIANGLES."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (MG_State::pGLContext->IsTransformFeedbackActive()) {
|
|
||||||
MG_State::pGLContext->RecordError(
|
|
||||||
ErrorCode::InvalidOperation,
|
|
||||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "Transform feedback is already active."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const auto& program = MG_State::pGLContext->GetProgramForDraw();
|
|
||||||
if (!program || !program->GetLinkStatus() || program->GetTransformFeedbackVaryingCount() == 0) {
|
|
||||||
MG_State::pGLContext->RecordError(
|
|
||||||
ErrorCode::InvalidOperation,
|
|
||||||
MakeUnique<GenericErrorInfo>(
|
|
||||||
"MG_Impl/GLImpl", __func__,
|
|
||||||
"No program with transform feedback varyings is active."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Every capture buffer slot the program's mode uses must have a buffer bound. A slot
|
|
||||||
// of stride 0 - two consecutive gl_NextBuffer entries - captures nothing and so needs
|
|
||||||
// no binding.
|
|
||||||
const SizeT usedBufferCount = program->GetTransformFeedbackBufferCount();
|
|
||||||
for (SizeT i = 0; i < usedBufferCount; ++i) {
|
|
||||||
if (program->GetTransformFeedbackStride(static_cast<Uint32>(i)) == 0) continue;
|
|
||||||
const auto& point = MG_State::pGLContext->GetBufferBindingPoint(BufferTarget::TransformFeedback,
|
|
||||||
static_cast<Uint>(i));
|
|
||||||
if (point.GetBoundObject() == nullptr) {
|
|
||||||
MG_State::pGLContext->RecordError(
|
|
||||||
ErrorCode::InvalidOperation,
|
|
||||||
MakeUnique<GenericErrorInfo>(
|
|
||||||
"MG_Impl/GLImpl", __func__,
|
|
||||||
"Transform feedback buffer binding point " + std::to_string(i) + " has no buffer bound."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
MG_State::pGLContext->BeginTransformFeedback(primitiveMode, program);
|
|
||||||
if (const auto beginXfb = MG_Backend::gBackendFunctionsTable.GL.BeginTransformFeedback) {
|
|
||||||
beginXfb(primitiveMode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Vulkan transform feedback captures triangle strips in plain (i, i+1, i+2)
|
|
||||||
// vertex order, but GL decomposes odd strip triangles as (i+1, i, i+2)
|
|
||||||
// (GL 4.6 table 10.1). With the geometry stage's statically-known strip
|
|
||||||
// lengths the captured records are reordered in place: swap the first two
|
|
||||||
// vertex records of every odd triangle within each emitted strip.
|
|
||||||
static void FixupGsStripCaptureOrder(const SharedPtr<MG_State::GLState::ProgramObject>& program,
|
|
||||||
Uint64 inputPrimitives) {
|
|
||||||
// Only Vulkan-order captures need this. A backend that runs the capture on its
|
|
||||||
// own GL/ES driver (it owns the span, hence the EndTransformFeedback entry) has
|
|
||||||
// already produced GL's vertex order, and reordering it again would corrupt it.
|
|
||||||
if (MG_Backend::gBackendFunctionsTable.GL.EndTransformFeedback != nullptr) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (program == nullptr || !program->HasGsTriangleStripCaptureFixup() || inputPrimitives == 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const auto& stripTriangles = program->GetGsStripTriangles();
|
|
||||||
|
|
||||||
// Global triangle indices whose leading vertex pair must swap.
|
|
||||||
Vector<Uint64> swapTriangles;
|
|
||||||
Uint64 triangleBase = 0;
|
|
||||||
for (Uint64 input = 0; input < inputPrimitives; ++input) {
|
|
||||||
for (const Uint32 stripLength : stripTriangles) {
|
|
||||||
for (Uint32 t = 1; t < stripLength; t += 2) {
|
|
||||||
swapTriangles.push_back(triangleBase + t);
|
|
||||||
}
|
|
||||||
triangleBase += stripLength;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (swapTriangles.empty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (SizeT bufferIndex = 0; bufferIndex < program->GetTransformFeedbackBufferCount(); ++bufferIndex) {
|
|
||||||
const Uint32 stride = program->GetTransformFeedbackStride(static_cast<Uint32>(bufferIndex));
|
|
||||||
if (stride == 0) continue;
|
|
||||||
const auto& bindingPoint =
|
|
||||||
MG_State::pGLContext->GetBufferBindingPoint(BufferTarget::TransformFeedback,
|
|
||||||
static_cast<Uint>(bufferIndex));
|
|
||||||
const auto& buffer = bindingPoint.GetBoundObject();
|
|
||||||
if (buffer == nullptr) continue;
|
|
||||||
const Range1D range = bindingPoint.GetRange();
|
|
||||||
const Uint8* mapped = buffer->MappedData();
|
|
||||||
if (mapped == nullptr) continue;
|
|
||||||
// The geometry stage amplifies, so the CPU vertex counter does not bound
|
|
||||||
// the capture; the binding range's whole-triangle capacity does.
|
|
||||||
const Uint64 rangeBytes = range.end > range.start ? static_cast<Uint64>(range.end - range.start) : 0;
|
|
||||||
const Uint64 capturedTriangles = std::min<Uint64>(triangleBase, (rangeBytes / stride) / 3);
|
|
||||||
|
|
||||||
// Observed Vulkan capture order for odd strip triangles is (i, i+2, i+1)
|
|
||||||
// (winding preserved by swapping the trailing pair); GL wants
|
|
||||||
// (i+1, i, i+2), which is one rotation away: (a,b,c) -> (c,a,b).
|
|
||||||
Vector<Uint8> scratch(stride);
|
|
||||||
for (const Uint64 triangle : swapTriangles) {
|
|
||||||
if (triangle >= capturedTriangles) break;
|
|
||||||
const SizeT v0Offset = static_cast<SizeT>(range.start) + static_cast<SizeT>(triangle * 3) * stride;
|
|
||||||
const SizeT v1Offset = v0Offset + stride;
|
|
||||||
const SizeT v2Offset = v1Offset + stride;
|
|
||||||
Memcpy(scratch.data(), mapped + v2Offset, stride);
|
|
||||||
buffer->WritebackFromBackend({const_cast<Uint8*>(mapped) + v1Offset, stride}, v2Offset);
|
|
||||||
buffer->WritebackFromBackend({const_cast<Uint8*>(mapped) + v0Offset, stride}, v1Offset);
|
|
||||||
buffer->WritebackFromBackend({scratch.data(), stride}, v0Offset);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void EndTransformFeedback(void) {
|
|
||||||
if (!MG_State::pGLContext->IsTransformFeedbackActive()) {
|
|
||||||
MG_State::pGLContext->RecordError(
|
|
||||||
ErrorCode::InvalidOperation,
|
|
||||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "Transform feedback is not active."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const auto capturedProgram = MG_State::pGLContext->GetTransformFeedbackProgram();
|
|
||||||
const Uint64 inputPrimitives = MG_State::pGLContext->GetTransformFeedbackInputPrimitives();
|
|
||||||
// Closed while the capture state is still active: a backend that captures
|
|
||||||
// through its own driver reads the capture program and buffer bindings here.
|
|
||||||
if (const auto endXfb = MG_Backend::gBackendFunctionsTable.GL.EndTransformFeedback) {
|
|
||||||
endXfb();
|
|
||||||
}
|
|
||||||
MG_State::pGLContext->EndTransformFeedback();
|
|
||||||
// Captured results must be visible to MapBuffer/GetBufferSubData after
|
|
||||||
// End; the capture targets are host-coherent GPU memory, so completing
|
|
||||||
// the GPU work is all that is required.
|
|
||||||
auto& backendGL = MG_Backend::gBackendFunctionsTable.GL;
|
|
||||||
if (backendGL.FenceSync && backendGL.ClientWaitSync) {
|
|
||||||
if (auto sync = backendGL.FenceSync()) {
|
|
||||||
backendGL.ClientWaitSync(sync, GL_SYNC_FLUSH_COMMANDS_BIT, ~0ull);
|
|
||||||
if (backendGL.DeleteSync) {
|
|
||||||
backendGL.DeleteSync(sync);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
FixupGsStripCaptureOrder(capturedProgram, inputPrimitives);
|
|
||||||
}
|
|
||||||
|
|
||||||
void PauseTransformFeedback(void) {
|
|
||||||
if (!MG_State::pGLContext->IsTransformFeedbackActive() ||
|
|
||||||
MG_State::pGLContext->IsTransformFeedbackPaused()) {
|
|
||||||
MG_State::pGLContext->RecordError(
|
|
||||||
ErrorCode::InvalidOperation,
|
|
||||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
|
||||||
"Transform feedback is not active, or is already paused."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
MG_State::pGLContext->SetTransformFeedbackPaused(true);
|
|
||||||
if (const auto pauseXfb = MG_Backend::gBackendFunctionsTable.GL.PauseTransformFeedback) {
|
|
||||||
pauseXfb();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ResumeTransformFeedback(void) {
|
|
||||||
if (!MG_State::pGLContext->IsTransformFeedbackActive() ||
|
|
||||||
!MG_State::pGLContext->IsTransformFeedbackPaused()) {
|
|
||||||
MG_State::pGLContext->RecordError(
|
|
||||||
ErrorCode::InvalidOperation,
|
|
||||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "Transform feedback is not paused."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
MG_State::pGLContext->SetTransformFeedbackPaused(false);
|
|
||||||
if (const auto resumeXfb = MG_Backend::gBackendFunctionsTable.GL.ResumeTransformFeedback) {
|
|
||||||
resumeXfb();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void GenTransformFeedbacks(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);
|
|
||||||
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(
|
|
||||||
ErrorCode::InvalidValue,
|
|
||||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "n must be non-negative."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (ids == nullptr) return;
|
|
||||||
for (GLsizei i = 0; i < n; ++i) {
|
|
||||||
const GLuint id = ids[i];
|
|
||||||
// Unknown names and 0 are silently ignored; an object whose capture span is
|
|
||||||
// still open is not (GL 4.6 core 13.2.1).
|
|
||||||
if (id == 0 || !MG_State::pGLContext->ValidateTransformFeedbackName(id)) continue;
|
|
||||||
if (id == MG_State::pGLContext->GetBoundTransformFeedbackName() &&
|
|
||||||
MG_State::pGLContext->IsTransformFeedbackActive()) {
|
|
||||||
MG_State::pGLContext->RecordError(
|
|
||||||
ErrorCode::InvalidOperation,
|
|
||||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
|
||||||
"Cannot delete a transform feedback object whose capture is active."));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (const auto deleteXfb = MG_Backend::gBackendFunctionsTable.GL.DeleteTransformFeedback) {
|
|
||||||
deleteXfb(id);
|
|
||||||
}
|
|
||||||
MG_State::pGLContext->MarkTransformFeedbackObjectForDeletion(id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void BindTransformFeedback(GLenum target, GLuint id) {
|
|
||||||
if (target != GL_TRANSFORM_FEEDBACK) {
|
|
||||||
MG_State::pGLContext->RecordError(
|
|
||||||
ErrorCode::InvalidEnum,
|
|
||||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "target must be GL_TRANSFORM_FEEDBACK."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// A running capture pins its object; only a paused one may be swapped out.
|
|
||||||
if (MG_State::pGLContext->IsTransformFeedbackActive() &&
|
|
||||||
!MG_State::pGLContext->IsTransformFeedbackPaused()) {
|
|
||||||
MG_State::pGLContext->RecordError(
|
|
||||||
ErrorCode::InvalidOperation,
|
|
||||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
|
||||||
"Transform feedback is active and not paused."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!MG_State::pGLContext->ValidateTransformFeedbackName(id)) {
|
|
||||||
MG_State::pGLContext->RecordError(
|
|
||||||
ErrorCode::InvalidOperation,
|
|
||||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
|
||||||
std::to_string(id) + " is not a transform feedback object name."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
MG_State::pGLContext->BindTransformFeedbackObject(id);
|
|
||||||
if (const auto bindXfb = MG_Backend::gBackendFunctionsTable.GL.BindTransformFeedback) {
|
|
||||||
bindXfb(id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
GLboolean IsTransformFeedback(GLuint id) {
|
|
||||||
// Name 0 is the default object, and a name glGenTransformFeedbacks handed out only
|
|
||||||
// becomes the name of an object once it has been bound.
|
|
||||||
return MG_State::pGLContext->IsTransformFeedbackObject(id) ? GL_TRUE : GL_FALSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
// glDrawTransformFeedback[Stream][Instanced]: replays the vertices the named object
|
|
||||||
// captured in its last completed span, as if by glDrawArraysInstanced with that count
|
|
||||||
// (GL 4.6 core 10.3.7).
|
|
||||||
static void DrawTransformFeedbackImpl(const char* functionName, GLenum mode, GLuint id, GLuint stream,
|
|
||||||
GLsizei instancecount) {
|
|
||||||
if (!ValidateCurrentProgramForExecution(functionName)) return;
|
|
||||||
if (!ValidatePrimitiveModeForBackend(functionName, mode)) return;
|
|
||||||
if (instancecount < 0) {
|
|
||||||
MG_State::pGLContext->RecordError(
|
|
||||||
ErrorCode::InvalidValue,
|
|
||||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", functionName, "instancecount must be non-negative."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!MG_State::pGLContext->ValidateTransformFeedbackName(id)) {
|
|
||||||
MG_State::pGLContext->RecordError(
|
|
||||||
ErrorCode::InvalidValue,
|
|
||||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", functionName,
|
|
||||||
std::to_string(id) + " is not a transform feedback object name."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// GL_MAX_VERTEX_STREAMS is 1, so stream 0 is the only one that exists.
|
|
||||||
if (stream != 0) {
|
|
||||||
MG_State::pGLContext->RecordError(
|
|
||||||
ErrorCode::InvalidValue,
|
|
||||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", functionName,
|
|
||||||
"stream must be less than GL_MAX_VERTEX_STREAMS."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Drawing from an object whose capture is currently open is legal and deliberate:
|
|
||||||
// it is how a transform feedback result is fed straight back into the next span
|
|
||||||
// (ARB_transform_feedback2 lists no such restriction).
|
|
||||||
if (!MG_State::pGLContext->HasTransformFeedbackCompletedSpan(id)) {
|
|
||||||
MG_State::pGLContext->RecordError(
|
|
||||||
ErrorCode::InvalidOperation,
|
|
||||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", functionName,
|
|
||||||
"glEndTransformFeedback has never been called for this object."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Uint64 vertices = MG_State::pGLContext->GetTransformFeedbackRecordedVertices(id);
|
|
||||||
if (vertices == 0) return;
|
|
||||||
const auto count = static_cast<GLsizei>(vertices);
|
|
||||||
AccountTransformFeedbackPrimitives(mode, count);
|
|
||||||
if (instancecount == 1) {
|
|
||||||
DrawArrays_Backend(mode, 0, count);
|
|
||||||
} else {
|
|
||||||
DrawArraysInstanced_Backend(mode, 0, count, instancecount);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void DrawTransformFeedback(GLenum mode, GLuint id) {
|
|
||||||
DrawTransformFeedbackImpl(__func__, mode, id, 0, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
void DrawTransformFeedbackInstanced(GLenum mode, GLuint id, GLsizei instancecount) {
|
|
||||||
DrawTransformFeedbackImpl(__func__, mode, id, 0, instancecount);
|
|
||||||
}
|
|
||||||
|
|
||||||
void DrawTransformFeedbackStream(GLenum mode, GLuint id, GLuint stream) {
|
|
||||||
DrawTransformFeedbackImpl(__func__, mode, id, stream, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
void DrawTransformFeedbackStreamInstanced(GLenum mode, GLuint id, GLuint stream, GLsizei instancecount) {
|
|
||||||
DrawTransformFeedbackImpl(__func__, mode, id, stream, instancecount);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace MobileGL::MG_Impl::GLImpl
|
} // namespace MobileGL::MG_Impl::GLImpl
|
||||||
|
|||||||
@@ -11,27 +11,8 @@
|
|||||||
|
|
||||||
namespace MobileGL::MG_Impl::GLImpl {
|
namespace MobileGL::MG_Impl::GLImpl {
|
||||||
/* @INSERTION_POINT:FUNCTION_DECLARATION@ */
|
/* @INSERTION_POINT:FUNCTION_DECLARATION@ */
|
||||||
void BeginTransformFeedback(GLenum primitiveMode);
|
|
||||||
void EndTransformFeedback(void);
|
|
||||||
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);
|
|
||||||
void DrawTransformFeedbackInstanced(GLenum mode, GLuint id, GLsizei instancecount);
|
|
||||||
void DrawTransformFeedbackStream(GLenum mode, GLuint id, GLuint stream);
|
|
||||||
void DrawTransformFeedbackStreamInstanced(GLenum mode, GLuint id, GLuint stream, GLsizei instancecount);
|
|
||||||
void DispatchCompute(GLuint numGroupsX, GLuint numGroupsY, GLuint numGroupsZ);
|
void DispatchCompute(GLuint numGroupsX, GLuint numGroupsY, GLuint numGroupsZ);
|
||||||
void DispatchComputeIndirect(GLintptr indirect);
|
void DispatchComputeIndirect(GLintptr indirect);
|
||||||
void PatchParameteri(GLenum pname, GLint value);
|
|
||||||
void MemoryBarrier(GLbitfield barriers);
|
void MemoryBarrier(GLbitfield barriers);
|
||||||
void MemoryBarrierByRegion(GLbitfield barriers);
|
void MemoryBarrierByRegion(GLbitfield barriers);
|
||||||
void MultiDrawElementsIndirect(GLenum mode, GLenum type, const void* indirect, GLsizei drawcount, GLsizei stride);
|
void MultiDrawElementsIndirect(GLenum mode, GLenum type, const void* indirect, GLsizei drawcount, GLsizei stride);
|
||||||
|
|||||||
@@ -15,7 +15,6 @@
|
|||||||
#include "../Texture/GL_Texture.h"
|
#include "../Texture/GL_Texture.h"
|
||||||
#include "../Drawing/GL_Drawing.h"
|
#include "../Drawing/GL_Drawing.h"
|
||||||
#include "../Program/GL_Program.h"
|
#include "../Program/GL_Program.h"
|
||||||
#include "../Program/GL_ProgramPipeline.h"
|
|
||||||
#include "../RenderState/GL_RenderState.h"
|
#include "../RenderState/GL_RenderState.h"
|
||||||
#include "../Framebuffer/GL_Framebuffer.h"
|
#include "../Framebuffer/GL_Framebuffer.h"
|
||||||
#include "../VertexArray/GL_VertexArray.h"
|
#include "../VertexArray/GL_VertexArray.h"
|
||||||
@@ -237,12 +236,12 @@ DECLARE_GL_FUNCTION_HEAD(void, DeleteVertexArrays, GLsizei n, const GLuint* arra
|
|||||||
DECLARE_GL_FUNCTION_HEAD(void, GenVertexArrays, GLsizei n, GLuint* arrays) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GenVertexArrays, n, arrays)
|
DECLARE_GL_FUNCTION_HEAD(void, GenVertexArrays, GLsizei n, GLuint* arrays) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GenVertexArrays, n, arrays)
|
||||||
DECLARE_GL_FUNCTION_HEAD(GLboolean, IsVertexArray, GLuint array) DECLARE_GL_FUNCTION_END(GLboolean, IsVertexArray, array)
|
DECLARE_GL_FUNCTION_HEAD(GLboolean, IsVertexArray, GLuint array) DECLARE_GL_FUNCTION_END(GLboolean, IsVertexArray, array)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, GetIntegeri_v, GLenum target, GLuint index, GLint* data) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetIntegeri_v, target, index, data)
|
DECLARE_GL_FUNCTION_HEAD(void, GetIntegeri_v, GLenum target, GLuint index, GLint* data) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetIntegeri_v, target, index, data)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, BeginTransformFeedback, GLenum primitiveMode) DECLARE_GL_FUNCTION_END_NO_RETURN(void, BeginTransformFeedback, primitiveMode)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, BeginTransformFeedback, GLenum primitiveMode) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, BeginTransformFeedback, primitiveMode)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, EndTransformFeedback) DECLARE_GL_FUNCTION_END_NO_RETURN(void, EndTransformFeedback)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, EndTransformFeedback) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, EndTransformFeedback)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, BindBufferRange, GLenum target, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size) DECLARE_GL_FUNCTION_END_NO_RETURN(void, BindBufferRange, target, index, buffer, offset, size)
|
DECLARE_GL_FUNCTION_HEAD(void, BindBufferRange, GLenum target, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size) DECLARE_GL_FUNCTION_END_NO_RETURN(void, BindBufferRange, target, index, buffer, offset, size)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, BindBufferBase, GLenum target, GLuint index, GLuint buffer) DECLARE_GL_FUNCTION_END_NO_RETURN(void, BindBufferBase, target, index, buffer)
|
DECLARE_GL_FUNCTION_HEAD(void, BindBufferBase, GLenum target, GLuint index, GLuint buffer) DECLARE_GL_FUNCTION_END_NO_RETURN(void, BindBufferBase, target, index, buffer)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, TransformFeedbackVaryings, GLuint program, GLsizei count, const GLchar* const* varyings, GLenum bufferMode) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TransformFeedbackVaryings, program, count, varyings, bufferMode)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, TransformFeedbackVaryings, GLuint program, GLsizei count, const GLchar* const* varyings, GLenum bufferMode) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, TransformFeedbackVaryings, program, count, varyings, bufferMode)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, GetTransformFeedbackVarying, GLuint program, GLuint index, GLsizei bufSize, GLsizei* length, GLsizei* size, GLenum* type, GLchar* name) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetTransformFeedbackVarying, program, index, bufSize, length, size, type, name)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetTransformFeedbackVarying, GLuint program, GLuint index, GLsizei bufSize, GLsizei* length, GLsizei* size, GLenum* type, GLchar* name) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetTransformFeedbackVarying, program, index, bufSize, length, size, type, name)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, VertexAttribIPointer, GLuint index, GLint size, GLenum type, GLsizei stride, const void* pointer) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttribIPointer, index, size, type, stride, pointer)
|
DECLARE_GL_FUNCTION_HEAD(void, VertexAttribIPointer, GLuint index, GLint size, GLenum type, GLsizei stride, const void* pointer) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttribIPointer, index, size, type, stride, pointer)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, GetVertexAttribIiv, GLuint index, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetVertexAttribIiv, index, pname, params)
|
DECLARE_GL_FUNCTION_HEAD(void, GetVertexAttribIiv, GLuint index, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetVertexAttribIiv, index, pname, params)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, GetVertexAttribIuiv, GLuint index, GLenum pname, GLuint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetVertexAttribIuiv, index, pname, params)
|
DECLARE_GL_FUNCTION_HEAD(void, GetVertexAttribIuiv, GLuint index, GLenum pname, GLuint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetVertexAttribIuiv, index, pname, params)
|
||||||
@@ -293,17 +292,23 @@ DECLARE_GL_FUNCTION_HEAD(void, SamplerParameterfv, GLuint sampler, GLenum pname,
|
|||||||
DECLARE_GL_FUNCTION_HEAD(void, GetSamplerParameteriv, GLuint sampler, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetSamplerParameteriv, sampler, pname, params)
|
DECLARE_GL_FUNCTION_HEAD(void, GetSamplerParameteriv, GLuint sampler, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetSamplerParameteriv, sampler, pname, params)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, GetSamplerParameterfv, GLuint sampler, GLenum pname, GLfloat* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetSamplerParameterfv, sampler, pname, params)
|
DECLARE_GL_FUNCTION_HEAD(void, GetSamplerParameterfv, GLuint sampler, GLenum pname, GLfloat* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetSamplerParameterfv, sampler, pname, params)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, VertexAttribDivisor, GLuint index, GLuint divisor) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttribDivisor, index, divisor)
|
DECLARE_GL_FUNCTION_HEAD(void, VertexAttribDivisor, GLuint index, GLuint divisor) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttribDivisor, index, divisor)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, BindTransformFeedback, GLenum target, GLuint id) DECLARE_GL_FUNCTION_END_NO_RETURN(void, BindTransformFeedback, target, id)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, BindTransformFeedback, GLenum target, GLuint id) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, BindTransformFeedback, target, id)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, DeleteTransformFeedbacks, GLsizei n, const GLuint* ids) DECLARE_GL_FUNCTION_END_NO_RETURN(void, DeleteTransformFeedbacks, n, ids)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, DeleteTransformFeedbacks, GLsizei n, const GLuint* ids) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, DeleteTransformFeedbacks, n, ids)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, GenTransformFeedbacks, GLsizei n, GLuint* ids) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GenTransformFeedbacks, n, ids)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, GenTransformFeedbacks, GLsizei n, GLuint* ids) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GenTransformFeedbacks, n, ids)
|
||||||
DECLARE_GL_FUNCTION_HEAD(GLboolean, IsTransformFeedback, GLuint id) DECLARE_GL_FUNCTION_END(GLboolean, IsTransformFeedback, id)
|
// Transform feedback objects are not implemented, so no name is ever a live object. The shared
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, PauseTransformFeedback) DECLARE_GL_FUNCTION_END_NO_RETURN(void, PauseTransformFeedback)
|
// stub returns (type)1, telling a probing caller that every id it invents already exists; GL_FALSE
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, ResumeTransformFeedback) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ResumeTransformFeedback)
|
// is both truthful and what the spec requires for a name that was never generated.
|
||||||
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)
|
MOBILEGL_GL_API GLboolean glIsTransformFeedback(GLuint id) {
|
||||||
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)
|
MGLOG_W("Stub function: %s(...)", __FUNCTION__);
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, ProgramParameteri, GLuint program, GLenum pname, GLint value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ProgramParameteri, program, pname, value)
|
return GL_FALSE;
|
||||||
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_STUB_HEAD(void, PauseTransformFeedback) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, PauseTransformFeedback)
|
||||||
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, ResumeTransformFeedback) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ResumeTransformFeedback)
|
||||||
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetProgramBinary, GLuint program, GLsizei bufSize, GLsizei* length, GLenum* binaryFormat, void* binary) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetProgramBinary, program, bufSize, length, binaryFormat, binary)
|
||||||
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, ProgramBinary, GLuint program, GLenum binaryFormat, const void* binary, GLsizei length) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ProgramBinary, program, binaryFormat, binary, length)
|
||||||
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, ProgramParameteri, GLuint program, GLenum pname, GLint value) DECLARE_GL_FUNCTION_STUB_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, 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, 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, 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)
|
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)
|
||||||
@@ -311,21 +316,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, 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, 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_HEAD(void, DrawElementsIndirect, GLenum mode, GLenum type, const void* indirect) DECLARE_GL_FUNCTION_END_NO_RETURN(void, DrawElementsIndirect, mode, type, indirect)
|
||||||
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_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_HEAD(void, GetFramebufferParameteriv, GLenum target, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetFramebufferParameteriv, target, pname, params)
|
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, 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(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(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, 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(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_HEAD(GLint, GetProgramResourceLocation, GLuint program, GLenum programInterface, const GLchar* name) DECLARE_GL_FUNCTION_END(GLint, GetProgramResourceLocation, program, programInterface, name)
|
||||||
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_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_HEAD(void, ActiveShaderProgram, GLuint pipeline, GLuint program) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ActiveShaderProgram, pipeline, 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(GLuint, CreateShaderProgramv, GLenum type, GLsizei count, const GLchar* const* strings) DECLARE_GL_FUNCTION_END(GLuint, CreateShaderProgramv, type, count, strings)
|
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_HEAD(void, BindProgramPipeline, GLuint pipeline) DECLARE_GL_FUNCTION_END_NO_RETURN(void, BindProgramPipeline, pipeline)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, BindProgramPipeline, GLuint pipeline) DECLARE_GL_FUNCTION_STUB_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_STUB_HEAD(void, DeleteProgramPipelines, GLsizei n, const GLuint* pipelines) DECLARE_GL_FUNCTION_STUB_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_STUB_HEAD(void, GenProgramPipelines, GLsizei n, GLuint* pipelines) DECLARE_GL_FUNCTION_STUB_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_STUB_HEAD(GLboolean, IsProgramPipeline, GLuint pipeline) DECLARE_GL_FUNCTION_STUB_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_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, ProgramUniform1i, GLuint program, GLint location, GLint v0) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ProgramUniform1i, program, location, v0)
|
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, 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)
|
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)
|
||||||
@@ -359,8 +364,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, 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, 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_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_HEAD(void, ValidateProgramPipeline, GLuint pipeline) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ValidateProgramPipeline, pipeline)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, ValidateProgramPipeline, GLuint pipeline) DECLARE_GL_FUNCTION_STUB_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_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, 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, 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, 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)
|
DECLARE_GL_FUNCTION_HEAD(void, MemoryBarrier, GLbitfield barriers) DECLARE_GL_FUNCTION_END_NO_RETURN(void, MemoryBarrier, barriers)
|
||||||
@@ -420,12 +425,12 @@ DECLARE_GL_FUNCTION_HEAD(void, DrawElementsInstancedBaseVertex, GLenum mode, GLs
|
|||||||
DECLARE_GL_FUNCTION_HEAD(void, FramebufferTexture, GLenum target, GLenum attachment, GLuint texture, GLint level) DECLARE_GL_FUNCTION_END_NO_RETURN(void, FramebufferTexture, target, attachment, texture, level)
|
DECLARE_GL_FUNCTION_HEAD(void, FramebufferTexture, GLenum target, GLenum attachment, GLuint texture, GLint level) DECLARE_GL_FUNCTION_END_NO_RETURN(void, FramebufferTexture, target, attachment, texture, level)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, PrimitiveBoundingBox, GLfloat minX, GLfloat minY, GLfloat minZ, GLfloat minW, GLfloat maxX, GLfloat maxY, GLfloat maxZ, GLfloat maxW) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, PrimitiveBoundingBox, minX, minY, minZ, minW, maxX, maxY, maxZ, maxW)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, PrimitiveBoundingBox, GLfloat minX, GLfloat minY, GLfloat minZ, GLfloat minW, GLfloat maxX, GLfloat maxY, GLfloat maxZ, GLfloat maxW) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, PrimitiveBoundingBox, minX, minY, minZ, minW, maxX, maxY, maxZ, maxW)
|
||||||
DECLARE_GL_FUNCTION_HEAD(GLenum, GetGraphicsResetStatus) DECLARE_GL_FUNCTION_END(GLenum, GetGraphicsResetStatus)
|
DECLARE_GL_FUNCTION_HEAD(GLenum, GetGraphicsResetStatus) DECLARE_GL_FUNCTION_END(GLenum, GetGraphicsResetStatus)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, ReadnPixels, GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLsizei bufSize, void* data) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ReadnPixels, x, y, width, height, format, type, bufSize, data)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, ReadnPixels, GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLsizei bufSize, void* data) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ReadnPixels, x, y, width, height, format, type, bufSize, data)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetnUniformfv, GLuint program, GLint location, GLsizei bufSize, GLfloat* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetnUniformfv, program, location, bufSize, params)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetnUniformfv, GLuint program, GLint location, GLsizei bufSize, GLfloat* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetnUniformfv, program, location, bufSize, params)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetnUniformiv, GLuint program, GLint location, GLsizei bufSize, GLint* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetnUniformiv, program, location, bufSize, params)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetnUniformiv, GLuint program, GLint location, GLsizei bufSize, GLint* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetnUniformiv, program, location, bufSize, params)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetnUniformuiv, GLuint program, GLint location, GLsizei bufSize, GLuint* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetnUniformuiv, program, location, bufSize, params)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetnUniformuiv, GLuint program, GLint location, GLsizei bufSize, GLuint* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetnUniformuiv, program, location, bufSize, params)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, MinSampleShading, GLfloat value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, MinSampleShading, value)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, MinSampleShading, GLfloat value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, MinSampleShading, value)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, PatchParameteri, GLenum pname, GLint value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, PatchParameteri, pname, value)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, PatchParameteri, GLenum pname, GLint value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, PatchParameteri, pname, value)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, TexParameterIiv, GLenum target, GLenum pname, const GLint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TexParameterIiv, target, pname, params)
|
DECLARE_GL_FUNCTION_HEAD(void, TexParameterIiv, GLenum target, GLenum pname, const GLint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TexParameterIiv, target, pname, params)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, TexParameterIuiv, GLenum target, GLenum pname, const GLuint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TexParameterIuiv, target, pname, params)
|
DECLARE_GL_FUNCTION_HEAD(void, TexParameterIuiv, GLenum target, GLenum pname, const GLuint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TexParameterIuiv, target, pname, params)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, GetTexParameterIiv, GLenum target, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetTexParameterIiv, target, pname, params)
|
DECLARE_GL_FUNCTION_HEAD(void, GetTexParameterIiv, GLenum target, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetTexParameterIiv, target, pname, params)
|
||||||
@@ -435,7 +440,7 @@ DECLARE_GL_FUNCTION_HEAD(void, SamplerParameterIuiv, GLuint sampler, GLenum pnam
|
|||||||
DECLARE_GL_FUNCTION_HEAD(void, GetSamplerParameterIiv, GLuint sampler, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetSamplerParameterIiv, sampler, pname, params)
|
DECLARE_GL_FUNCTION_HEAD(void, GetSamplerParameterIiv, GLuint sampler, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetSamplerParameterIiv, sampler, pname, params)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, GetSamplerParameterIuiv, GLuint sampler, GLenum pname, GLuint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetSamplerParameterIuiv, sampler, pname, params)
|
DECLARE_GL_FUNCTION_HEAD(void, GetSamplerParameterIuiv, GLuint sampler, GLenum pname, GLuint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetSamplerParameterIuiv, sampler, pname, params)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, TexBuffer, GLenum target, GLenum internalformat, GLuint buffer) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TexBuffer, target, internalformat, buffer)
|
DECLARE_GL_FUNCTION_HEAD(void, TexBuffer, GLenum target, GLenum internalformat, GLuint buffer) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TexBuffer, target, internalformat, buffer)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, TexBufferRange, GLenum target, GLenum internalformat, GLuint buffer, GLintptr offset, GLsizeiptr size) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TexBufferRange, target, internalformat, buffer, offset, size)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, TexBufferRange, GLenum target, GLenum internalformat, GLuint buffer, GLintptr offset, GLsizeiptr size) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, TexBufferRange, target, internalformat, buffer, offset, size)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, TexStorage3DMultisample, GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLboolean fixedsamplelocations) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TexStorage3DMultisample, target, samples, internalformat, width, height, depth, fixedsamplelocations)
|
DECLARE_GL_FUNCTION_HEAD(void, TexStorage3DMultisample, GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLboolean fixedsamplelocations) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TexStorage3DMultisample, target, samples, internalformat, width, height, depth, fixedsamplelocations)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void*, MapBufferRange, GLenum target, GLintptr offset, GLsizeiptr length, GLbitfield access) DECLARE_GL_FUNCTION_END(void*, MapBufferRange, target, offset, length, access)
|
DECLARE_GL_FUNCTION_HEAD(void*, MapBufferRange, GLenum target, GLintptr offset, GLsizeiptr length, GLbitfield access) DECLARE_GL_FUNCTION_END(void*, MapBufferRange, target, offset, length, access)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, ClearIndex, GLfloat c) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ClearIndex, c)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, ClearIndex, GLfloat c) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ClearIndex, c)
|
||||||
@@ -910,24 +915,24 @@ DECLARE_GL_FUNCTION_STUB_HEAD(void, ColorP4ui, GLenum type, GLuint color) DECLAR
|
|||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, ColorP4uiv, GLenum type, const GLuint* color) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ColorP4uiv, type, color)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, ColorP4uiv, GLenum type, const GLuint* color) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ColorP4uiv, type, color)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, SecondaryColorP3ui, GLenum type, GLuint color) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, SecondaryColorP3ui, type, color)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, SecondaryColorP3ui, GLenum type, GLuint color) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, SecondaryColorP3ui, type, color)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, SecondaryColorP3uiv, GLenum type, const GLuint* color) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, SecondaryColorP3uiv, type, color)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, SecondaryColorP3uiv, GLenum type, const GLuint* color) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, SecondaryColorP3uiv, type, color)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, Uniform1d, GLint location, GLdouble x) DECLARE_GL_FUNCTION_END_NO_RETURN(void, Uniform1d, location, x)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, Uniform1d, GLint location, GLdouble x) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, Uniform1d, location, x)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, Uniform2d, GLint location, GLdouble x, GLdouble y) DECLARE_GL_FUNCTION_END_NO_RETURN(void, Uniform2d, location, x, y)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, Uniform2d, GLint location, GLdouble x, GLdouble y) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, Uniform2d, location, x, y)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, Uniform3d, GLint location, GLdouble x, GLdouble y, GLdouble z) DECLARE_GL_FUNCTION_END_NO_RETURN(void, Uniform3d, location, x, y, z)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, Uniform3d, GLint location, GLdouble x, GLdouble y, GLdouble z) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, Uniform3d, location, x, y, z)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, Uniform4d, GLint location, GLdouble x, GLdouble y, GLdouble z, GLdouble w) DECLARE_GL_FUNCTION_END_NO_RETURN(void, Uniform4d, location, x, y, z, w)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, Uniform4d, GLint location, GLdouble x, GLdouble y, GLdouble z, GLdouble w) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, Uniform4d, location, x, y, z, w)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, Uniform1dv, GLint location, GLsizei count, const GLdouble* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, Uniform1dv, location, count, value)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, Uniform1dv, GLint location, GLsizei count, const GLdouble* value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, Uniform1dv, location, count, value)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, Uniform2dv, GLint location, GLsizei count, const GLdouble* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, Uniform2dv, location, count, value)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, Uniform2dv, GLint location, GLsizei count, const GLdouble* value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, Uniform2dv, location, count, value)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, Uniform3dv, GLint location, GLsizei count, const GLdouble* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, Uniform3dv, location, count, value)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, Uniform3dv, GLint location, GLsizei count, const GLdouble* value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, Uniform3dv, location, count, value)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, Uniform4dv, GLint location, GLsizei count, const GLdouble* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, Uniform4dv, location, count, value)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, Uniform4dv, GLint location, GLsizei count, const GLdouble* value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, Uniform4dv, location, count, value)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, UniformMatrix2dv, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, UniformMatrix2dv, location, count, transpose, value)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, UniformMatrix2dv, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, UniformMatrix2dv, location, count, transpose, value)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, UniformMatrix3dv, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, UniformMatrix3dv, location, count, transpose, value)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, UniformMatrix3dv, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, UniformMatrix3dv, location, count, transpose, value)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, UniformMatrix4dv, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, UniformMatrix4dv, location, count, transpose, value)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, UniformMatrix4dv, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, UniformMatrix4dv, location, count, transpose, value)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, UniformMatrix2x3dv, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, UniformMatrix2x3dv, location, count, transpose, value)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, UniformMatrix2x3dv, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, UniformMatrix2x3dv, location, count, transpose, value)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, UniformMatrix2x4dv, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, UniformMatrix2x4dv, location, count, transpose, value)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, UniformMatrix2x4dv, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, UniformMatrix2x4dv, location, count, transpose, value)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, UniformMatrix3x2dv, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, UniformMatrix3x2dv, location, count, transpose, value)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, UniformMatrix3x2dv, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, UniformMatrix3x2dv, location, count, transpose, value)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, UniformMatrix3x4dv, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, UniformMatrix3x4dv, location, count, transpose, value)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, UniformMatrix3x4dv, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, UniformMatrix3x4dv, location, count, transpose, value)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, UniformMatrix4x2dv, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, UniformMatrix4x2dv, location, count, transpose, value)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, UniformMatrix4x2dv, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, UniformMatrix4x2dv, location, count, transpose, value)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, UniformMatrix4x3dv, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, UniformMatrix4x3dv, location, count, transpose, value)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, UniformMatrix4x3dv, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, UniformMatrix4x3dv, location, count, transpose, value)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, GetUniformdv, GLuint program, GLint location, GLdouble* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetUniformdv, program, location, params)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetUniformdv, GLuint program, GLint location, GLdouble* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetUniformdv, program, location, params)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(GLint, GetSubroutineUniformLocation, GLuint program, GLenum shadertype, const GLchar* name) DECLARE_GL_FUNCTION_STUB_END(GLint, GetSubroutineUniformLocation, program, shadertype, name)
|
DECLARE_GL_FUNCTION_STUB_HEAD(GLint, GetSubroutineUniformLocation, GLuint program, GLenum shadertype, const GLchar* name) DECLARE_GL_FUNCTION_STUB_END(GLint, GetSubroutineUniformLocation, program, shadertype, name)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(GLuint, GetSubroutineIndex, GLuint program, GLenum shadertype, const GLchar* name) DECLARE_GL_FUNCTION_STUB_END(GLuint, GetSubroutineIndex, program, shadertype, name)
|
DECLARE_GL_FUNCTION_STUB_HEAD(GLuint, GetSubroutineIndex, GLuint program, GLenum shadertype, const GLchar* name) DECLARE_GL_FUNCTION_STUB_END(GLuint, GetSubroutineIndex, program, shadertype, name)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetActiveSubroutineUniformiv, GLuint program, GLenum shadertype, GLuint index, GLenum pname, GLint* values) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetActiveSubroutineUniformiv, program, shadertype, index, pname, values)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetActiveSubroutineUniformiv, GLuint program, GLenum shadertype, GLuint index, GLenum pname, GLint* values) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetActiveSubroutineUniformiv, program, shadertype, index, pname, values)
|
||||||
@@ -937,28 +942,28 @@ DECLARE_GL_FUNCTION_STUB_HEAD(void, UniformSubroutinesuiv, GLenum shadertype, GL
|
|||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetUniformSubroutineuiv, GLenum shadertype, GLint location, GLuint* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetUniformSubroutineuiv, shadertype, location, params)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetUniformSubroutineuiv, GLenum shadertype, GLint location, GLuint* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetUniformSubroutineuiv, shadertype, location, params)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetProgramStageiv, GLuint program, GLenum shadertype, GLenum pname, GLint* values) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetProgramStageiv, program, shadertype, pname, values)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetProgramStageiv, GLuint program, GLenum shadertype, GLenum pname, GLint* values) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetProgramStageiv, program, shadertype, pname, values)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, PatchParameterfv, GLenum pname, const GLfloat* values) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, PatchParameterfv, pname, values)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, PatchParameterfv, GLenum pname, const GLfloat* values) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, PatchParameterfv, pname, values)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, DrawTransformFeedback, GLenum mode, GLuint id) DECLARE_GL_FUNCTION_END_NO_RETURN(void, DrawTransformFeedback, mode, id)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, DrawTransformFeedback, GLenum mode, GLuint id) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, DrawTransformFeedback, mode, id)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, DrawTransformFeedbackStream, GLenum mode, GLuint id, GLuint stream) DECLARE_GL_FUNCTION_END_NO_RETURN(void, DrawTransformFeedbackStream, mode, id, stream)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, DrawTransformFeedbackStream, GLenum mode, GLuint id, GLuint stream) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, DrawTransformFeedbackStream, mode, id, stream)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, BeginQueryIndexed, GLenum target, GLuint index, GLuint id) DECLARE_GL_FUNCTION_END_NO_RETURN(void, BeginQueryIndexed, target, index, id)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, BeginQueryIndexed, GLenum target, GLuint index, GLuint id) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, BeginQueryIndexed, target, index, id)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, EndQueryIndexed, GLenum target, GLuint index) DECLARE_GL_FUNCTION_END_NO_RETURN(void, EndQueryIndexed, target, index)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, EndQueryIndexed, GLenum target, GLuint index) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, EndQueryIndexed, target, index)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, GetQueryIndexediv, GLenum target, GLuint index, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetQueryIndexediv, target, index, pname, params)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetQueryIndexediv, GLenum target, GLuint index, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetQueryIndexediv, target, index, pname, params)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, ProgramUniform1d, GLuint program, GLint location, GLdouble v0) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ProgramUniform1d, program, location, v0)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, ProgramUniform1d, GLuint program, GLint location, GLdouble v0) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ProgramUniform1d, program, location, v0)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, ProgramUniform1dv, GLuint program, GLint location, GLsizei count, const GLdouble* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ProgramUniform1dv, program, location, count, value)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, ProgramUniform1dv, GLuint program, GLint location, GLsizei count, const GLdouble* value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ProgramUniform1dv, program, location, count, value)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, ProgramUniform2d, GLuint program, GLint location, GLdouble v0, GLdouble v1) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ProgramUniform2d, program, location, v0, v1)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, ProgramUniform2d, GLuint program, GLint location, GLdouble v0, GLdouble v1) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ProgramUniform2d, program, location, v0, v1)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, ProgramUniform2dv, GLuint program, GLint location, GLsizei count, const GLdouble* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ProgramUniform2dv, program, location, count, value)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, ProgramUniform2dv, GLuint program, GLint location, GLsizei count, const GLdouble* value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ProgramUniform2dv, program, location, count, value)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, ProgramUniform3d, GLuint program, GLint location, GLdouble v0, GLdouble v1, GLdouble v2) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ProgramUniform3d, program, location, v0, v1, v2)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, ProgramUniform3d, GLuint program, GLint location, GLdouble v0, GLdouble v1, GLdouble v2) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ProgramUniform3d, program, location, v0, v1, v2)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, ProgramUniform3dv, GLuint program, GLint location, GLsizei count, const GLdouble* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ProgramUniform3dv, program, location, count, value)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, ProgramUniform3dv, GLuint program, GLint location, GLsizei count, const GLdouble* value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ProgramUniform3dv, program, location, count, value)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, ProgramUniform4d, GLuint program, GLint location, GLdouble v0, GLdouble v1, GLdouble v2, GLdouble v3) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ProgramUniform4d, program, location, v0, v1, v2, v3)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, ProgramUniform4d, GLuint program, GLint location, GLdouble v0, GLdouble v1, GLdouble v2, GLdouble v3) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ProgramUniform4d, program, location, v0, v1, v2, v3)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, ProgramUniform4dv, GLuint program, GLint location, GLsizei count, const GLdouble* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ProgramUniform4dv, program, location, count, value)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, ProgramUniform4dv, GLuint program, GLint location, GLsizei count, const GLdouble* value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ProgramUniform4dv, program, location, count, value)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, ProgramUniformMatrix2dv, GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ProgramUniformMatrix2dv, program, location, count, transpose, value)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, ProgramUniformMatrix2dv, GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ProgramUniformMatrix2dv, program, location, count, transpose, value)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, ProgramUniformMatrix3dv, GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ProgramUniformMatrix3dv, program, location, count, transpose, value)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, ProgramUniformMatrix3dv, GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ProgramUniformMatrix3dv, program, location, count, transpose, value)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, ProgramUniformMatrix4dv, GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ProgramUniformMatrix4dv, program, location, count, transpose, value)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, ProgramUniformMatrix4dv, GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ProgramUniformMatrix4dv, program, location, count, transpose, value)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, ProgramUniformMatrix2x3dv, GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ProgramUniformMatrix2x3dv, program, location, count, transpose, value)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, ProgramUniformMatrix2x3dv, GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ProgramUniformMatrix2x3dv, program, location, count, transpose, value)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, ProgramUniformMatrix3x2dv, GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ProgramUniformMatrix3x2dv, program, location, count, transpose, value)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, ProgramUniformMatrix3x2dv, GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ProgramUniformMatrix3x2dv, program, location, count, transpose, value)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, ProgramUniformMatrix2x4dv, GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ProgramUniformMatrix2x4dv, program, location, count, transpose, value)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, ProgramUniformMatrix2x4dv, GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ProgramUniformMatrix2x4dv, program, location, count, transpose, value)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, ProgramUniformMatrix4x2dv, GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ProgramUniformMatrix4x2dv, program, location, count, transpose, value)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, ProgramUniformMatrix4x2dv, GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ProgramUniformMatrix4x2dv, program, location, count, transpose, value)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, ProgramUniformMatrix3x4dv, GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ProgramUniformMatrix3x4dv, program, location, count, transpose, value)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, ProgramUniformMatrix3x4dv, GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ProgramUniformMatrix3x4dv, program, location, count, transpose, value)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, ProgramUniformMatrix4x3dv, GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ProgramUniformMatrix4x3dv, program, location, count, transpose, value)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, ProgramUniformMatrix4x3dv, GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ProgramUniformMatrix4x3dv, program, location, count, transpose, value)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttribL1d, GLuint index, GLdouble x) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttribL1d, index, x)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttribL1d, GLuint index, GLdouble x) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttribL1d, index, x)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttribL2d, GLuint index, GLdouble x, GLdouble y) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttribL2d, index, x, y)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttribL2d, GLuint index, GLdouble x, GLdouble y) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttribL2d, index, x, y)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttribL3d, GLuint index, GLdouble x, GLdouble y, GLdouble z) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttribL3d, index, x, y, z)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttribL3d, GLuint index, GLdouble x, GLdouble y, GLdouble z) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttribL3d, index, x, y, z)
|
||||||
@@ -983,8 +988,8 @@ DECLARE_GL_FUNCTION_HEAD(void, DrawArraysInstancedBaseInstance, GLenum mode, GLi
|
|||||||
DECLARE_GL_FUNCTION_HEAD(void, DrawElementsInstancedBaseInstance, GLenum mode, GLsizei count, GLenum type, const void* indices, GLsizei instancecount, GLuint baseinstance) DECLARE_GL_FUNCTION_END_NO_RETURN(void, DrawElementsInstancedBaseInstance, mode, count, type, indices, instancecount, baseinstance)
|
DECLARE_GL_FUNCTION_HEAD(void, DrawElementsInstancedBaseInstance, GLenum mode, GLsizei count, GLenum type, const void* indices, GLsizei instancecount, GLuint baseinstance) DECLARE_GL_FUNCTION_END_NO_RETURN(void, DrawElementsInstancedBaseInstance, mode, count, type, indices, instancecount, baseinstance)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, DrawElementsInstancedBaseVertexBaseInstance, GLenum mode, GLsizei count, GLenum type, const void* indices, GLsizei instancecount, GLint basevertex, GLuint baseinstance) DECLARE_GL_FUNCTION_END_NO_RETURN(void, DrawElementsInstancedBaseVertexBaseInstance, mode, count, type, indices, instancecount, basevertex, baseinstance)
|
DECLARE_GL_FUNCTION_HEAD(void, DrawElementsInstancedBaseVertexBaseInstance, GLenum mode, GLsizei count, GLenum type, const void* indices, GLsizei instancecount, GLint basevertex, GLuint baseinstance) DECLARE_GL_FUNCTION_END_NO_RETURN(void, DrawElementsInstancedBaseVertexBaseInstance, mode, count, type, indices, instancecount, basevertex, baseinstance)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetActiveAtomicCounterBufferiv, GLuint program, GLuint bufferIndex, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetActiveAtomicCounterBufferiv, program, bufferIndex, pname, params)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetActiveAtomicCounterBufferiv, GLuint program, GLuint bufferIndex, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetActiveAtomicCounterBufferiv, program, bufferIndex, pname, params)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, DrawTransformFeedbackInstanced, GLenum mode, GLuint id, GLsizei instancecount) DECLARE_GL_FUNCTION_END_NO_RETURN(void, DrawTransformFeedbackInstanced, mode, id, instancecount)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, DrawTransformFeedbackInstanced, GLenum mode, GLuint id, GLsizei instancecount) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, DrawTransformFeedbackInstanced, mode, id, instancecount)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, DrawTransformFeedbackStreamInstanced, GLenum mode, GLuint id, GLuint stream, GLsizei instancecount) DECLARE_GL_FUNCTION_END_NO_RETURN(void, DrawTransformFeedbackStreamInstanced, mode, id, stream, instancecount)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, DrawTransformFeedbackStreamInstanced, GLenum mode, GLuint id, GLuint stream, GLsizei instancecount) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, DrawTransformFeedbackStreamInstanced, mode, id, stream, instancecount)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, ClearBufferData, GLenum target, GLenum internalformat, GLenum format, GLenum type, const void* data) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ClearBufferData, target, internalformat, format, type, data)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, ClearBufferData, GLenum target, GLenum internalformat, GLenum format, GLenum type, const void* data) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ClearBufferData, target, internalformat, format, type, data)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, ClearBufferSubData, GLenum target, GLenum internalformat, GLintptr offset, GLsizeiptr size, GLenum format, GLenum type, const void* data) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ClearBufferSubData, target, internalformat, offset, size, format, type, data)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, ClearBufferSubData, GLenum target, GLenum internalformat, GLintptr offset, GLsizeiptr size, GLenum format, GLenum type, const void* data) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ClearBufferSubData, target, internalformat, offset, size, format, type, data)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetInternalformati64v, GLenum target, GLenum internalformat, GLenum pname, GLsizei count, GLint64* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetInternalformati64v, target, internalformat, pname, count, params)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetInternalformati64v, GLenum target, GLenum internalformat, GLenum pname, GLsizei count, GLint64* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetInternalformati64v, target, internalformat, pname, count, params)
|
||||||
@@ -997,7 +1002,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(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_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, 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_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_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, 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, 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, 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)
|
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)
|
||||||
@@ -1008,12 +1013,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_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_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, ClipControl, GLenum origin, GLenum depth) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ClipControl, origin, depth)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, CreateTransformFeedbacks, GLsizei n, GLuint* ids) DECLARE_GL_FUNCTION_END_NO_RETURN(void, CreateTransformFeedbacks, n, ids)
|
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_HEAD(void, TransformFeedbackBufferBase, GLuint xfb, GLuint index, GLuint buffer) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TransformFeedbackBufferBase, xfb, index, buffer)
|
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_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_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_HEAD(void, GetTransformFeedbackiv, GLuint xfb, GLenum pname, GLint* param) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetTransformFeedbackiv, xfb, pname, param)
|
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_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_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_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_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, CreateBuffers, GLsizei n, GLuint* buffers) DECLARE_GL_FUNCTION_END_NO_RETURN(void, CreateBuffers, n, buffers)
|
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, 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)
|
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)
|
||||||
@@ -1026,32 +1031,32 @@ 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, 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, 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_HEAD(void, GetNamedBufferPointerv, GLuint buffer, GLenum pname, void** params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetNamedBufferPointerv, buffer, pname, params)
|
||||||
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_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, CreateFramebuffers, GLsizei n, GLuint* framebuffers) DECLARE_GL_FUNCTION_END_NO_RETURN(void, CreateFramebuffers, n, framebuffers)
|
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_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_HEAD(void, NamedFramebufferParameteri, GLuint framebuffer, GLenum pname, GLint param) DECLARE_GL_FUNCTION_END_NO_RETURN(void, NamedFramebufferParameteri, framebuffer, pname, param)
|
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, 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, 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, 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, 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, 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_HEAD(void, NamedFramebufferReadBuffer, GLuint framebuffer, GLenum src) DECLARE_GL_FUNCTION_END_NO_RETURN(void, NamedFramebufferReadBuffer, framebuffer, src)
|
||||||
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_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_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_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_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_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_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_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, 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, 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, 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(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_HEAD(GLenum, CheckNamedFramebufferStatus, GLuint framebuffer, GLenum target) DECLARE_GL_FUNCTION_END(GLenum, CheckNamedFramebufferStatus, framebuffer, target)
|
||||||
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_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, 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, 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, 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)
|
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)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, NamedRenderbufferStorageMultisample, GLuint renderbuffer, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height) DECLARE_GL_FUNCTION_END_NO_RETURN(void, NamedRenderbufferStorageMultisample, renderbuffer, samples, internalformat, width, height)
|
DECLARE_GL_FUNCTION_HEAD(void, NamedRenderbufferStorageMultisample, GLuint renderbuffer, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height) DECLARE_GL_FUNCTION_END_NO_RETURN(void, NamedRenderbufferStorageMultisample, renderbuffer, samples, internalformat, width, height)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, GetNamedRenderbufferParameteriv, GLuint renderbuffer, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetNamedRenderbufferParameteriv, renderbuffer, pname, params)
|
DECLARE_GL_FUNCTION_HEAD(void, GetNamedRenderbufferParameteriv, GLuint renderbuffer, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetNamedRenderbufferParameteriv, renderbuffer, pname, params)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, CreateTextures, GLenum target, GLsizei n, GLuint* textures) DECLARE_GL_FUNCTION_END_NO_RETURN(void, CreateTextures, target, n, textures)
|
DECLARE_GL_FUNCTION_HEAD(void, CreateTextures, GLenum target, GLsizei n, GLuint* textures) DECLARE_GL_FUNCTION_END_NO_RETURN(void, CreateTextures, target, n, textures)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, TextureBuffer, GLuint texture, GLenum internalformat, GLuint buffer) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TextureBuffer, texture, internalformat, buffer)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, TextureBuffer, GLuint texture, GLenum internalformat, GLuint buffer) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, TextureBuffer, texture, internalformat, buffer)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, TextureBufferRange, GLuint texture, GLenum internalformat, GLuint buffer, GLintptr offset, GLsizeiptr size) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TextureBufferRange, texture, internalformat, buffer, offset, size)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, TextureBufferRange, GLuint texture, GLenum internalformat, GLuint buffer, GLintptr offset, GLsizeiptr size) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, TextureBufferRange, texture, internalformat, buffer, offset, size)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, TextureStorage1D, GLuint texture, GLsizei levels, GLenum internalformat, GLsizei width) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TextureStorage1D, texture, levels, internalformat, width)
|
DECLARE_GL_FUNCTION_HEAD(void, TextureStorage1D, GLuint texture, GLsizei levels, GLenum internalformat, GLsizei width) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TextureStorage1D, texture, levels, internalformat, width)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, TextureStorage2D, GLuint texture, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TextureStorage2D, texture, levels, internalformat, width, height)
|
DECLARE_GL_FUNCTION_HEAD(void, TextureStorage2D, GLuint texture, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TextureStorage2D, texture, levels, internalformat, width, height)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, TextureStorage3D, GLuint texture, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TextureStorage3D, texture, levels, internalformat, width, height, depth)
|
DECLARE_GL_FUNCTION_HEAD(void, TextureStorage3D, GLuint texture, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TextureStorage3D, texture, levels, internalformat, width, height, depth)
|
||||||
@@ -1063,9 +1068,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, 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, 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, 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_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_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, 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_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_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_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, TextureParameterf, GLuint texture, GLenum pname, GLfloat param) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TextureParameterf, texture, pname, param)
|
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, 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)
|
DECLARE_GL_FUNCTION_HEAD(void, TextureParameteri, GLuint texture, GLenum pname, GLint param) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TextureParameteri, texture, pname, param)
|
||||||
@@ -1075,7 +1080,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, 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, 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_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_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_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, 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, 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, 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)
|
DECLARE_GL_FUNCTION_HEAD(void, GetTextureParameterfv, GLuint texture, GLenum pname, GLfloat* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetTextureParameterfv, texture, pname, params)
|
||||||
@@ -1091,18 +1096,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, 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, 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_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_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_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, VertexArrayBindingDivisor, GLuint vaobj, GLuint bindingindex, GLuint divisor) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexArrayBindingDivisor, vaobj, bindingindex, divisor)
|
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_STUB_HEAD(void, GetVertexArrayiv, GLuint vaobj, GLenum pname, GLint* param) DECLARE_GL_FUNCTION_STUB_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_STUB_HEAD(void, GetVertexArrayIndexediv, GLuint vaobj, GLuint index, GLenum pname, GLint* param) DECLARE_GL_FUNCTION_STUB_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_STUB_HEAD(void, GetVertexArrayIndexed64iv, GLuint vaobj, GLuint index, GLenum pname, GLint64* param) DECLARE_GL_FUNCTION_STUB_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_HEAD(void, CreateSamplers, GLsizei n, GLuint* samplers) DECLARE_GL_FUNCTION_END_NO_RETURN(void, CreateSamplers, n, samplers)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, CreateProgramPipelines, GLsizei n, GLuint* pipelines) DECLARE_GL_FUNCTION_END_NO_RETURN(void, CreateProgramPipelines, n, pipelines)
|
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, CreateQueries, GLenum target, GLsizei n, GLuint* ids) DECLARE_GL_FUNCTION_END_NO_RETURN(void, CreateQueries, target, n, ids)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, CreateQueries, GLenum target, GLsizei n, GLuint* ids) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, CreateQueries, target, n, ids)
|
||||||
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_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_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_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_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_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_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_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, 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_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, 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)
|
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
@@ -14,8 +14,6 @@
|
|||||||
namespace MobileGL::MG_Impl::GLImpl {
|
namespace MobileGL::MG_Impl::GLImpl {
|
||||||
/* @INSERTION_POINT:FUNCTION_DECLARATION@ */
|
/* @INSERTION_POINT:FUNCTION_DECLARATION@ */
|
||||||
void ReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void* pixels);
|
void ReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void* pixels);
|
||||||
void ReadnPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLsizei bufSize,
|
|
||||||
void* data);
|
|
||||||
void ClearBufferfi(GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil);
|
void ClearBufferfi(GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil);
|
||||||
void ClearBufferfv(GLenum buffer, GLint drawbuffer, const GLfloat* value);
|
void ClearBufferfv(GLenum buffer, GLint drawbuffer, const GLfloat* value);
|
||||||
void ClearBufferuiv(GLenum buffer, GLint drawbuffer, const GLuint* value);
|
void ClearBufferuiv(GLenum buffer, GLint drawbuffer, const GLuint* value);
|
||||||
@@ -58,19 +56,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
void NamedFramebufferReadBuffer(GLuint framebuffer, GLenum src);
|
void NamedFramebufferReadBuffer(GLuint framebuffer, GLenum src);
|
||||||
void ClearNamedFramebufferfv(GLuint framebuffer, GLenum buffer, GLint drawbuffer, const GLfloat* value);
|
void ClearNamedFramebufferfv(GLuint framebuffer, GLenum buffer, GLint drawbuffer, const GLfloat* value);
|
||||||
void ClearNamedFramebufferfi(GLuint framebuffer, GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil);
|
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);
|
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 GetNamedFramebufferAttachmentParameteriv(GLuint framebuffer, GLenum attachment, GLenum pname, GLint* params);
|
||||||
void BlitNamedFramebuffer(GLuint readFramebuffer, GLuint drawFramebuffer, GLint srcX0, GLint srcY0, GLint srcX1,
|
void BlitNamedFramebuffer(GLuint readFramebuffer, GLuint drawFramebuffer, GLint srcX0, GLint srcY0, GLint srcX1,
|
||||||
GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask,
|
GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask,
|
||||||
|
|||||||
@@ -7,7 +7,6 @@
|
|||||||
// End of Source File Header
|
// End of Source File Header
|
||||||
|
|
||||||
#include "Validators.h"
|
#include "Validators.h"
|
||||||
#include <MG_Backend/BackendObjects.h>
|
|
||||||
#include <MG_State/GLState/Core.h>
|
#include <MG_State/GLState/Core.h>
|
||||||
#include <MG_State/GLState/ErrorState/Error.h>
|
#include <MG_State/GLState/ErrorState/Error.h>
|
||||||
#include <MG_Util/Converters/GLToStr/GLEnumConverter.h>
|
#include <MG_Util/Converters/GLToStr/GLEnumConverter.h>
|
||||||
@@ -61,26 +60,6 @@ namespace MobileGL::MG_Impl::GLImpl::FramebufferImpl {
|
|||||||
return true;
|
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) {
|
Bool ValidateRenderbufferTarget(RenderbufferTarget target) {
|
||||||
if (target == RenderbufferTarget::Unknown) {
|
if (target == RenderbufferTarget::Unknown) {
|
||||||
using namespace MG_Util;
|
using namespace MG_Util;
|
||||||
@@ -118,100 +97,4 @@ namespace MobileGL::MG_Impl::GLImpl::FramebufferImpl {
|
|||||||
std::format("Renderbuffer name {} is not valid.", index)));
|
std::format("Renderbuffer name {} is not valid.", index)));
|
||||||
return false;
|
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
|
} // namespace MobileGL::MG_Impl::GLImpl::FramebufferImpl
|
||||||
|
|||||||
@@ -14,21 +14,6 @@ namespace MobileGL::MG_Impl::GLImpl::FramebufferImpl {
|
|||||||
Bool ValidateFramebufferTarget(FramebufferTarget target);
|
Bool ValidateFramebufferTarget(FramebufferTarget target);
|
||||||
Bool ValidateFramebufferName(Uint index, Bool allowZero = true);
|
Bool ValidateFramebufferName(Uint index, Bool allowZero = true);
|
||||||
Bool ValidateFramebufferAttachmentType(FramebufferAttachmentType attachment);
|
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 ValidateRenderbufferTarget(RenderbufferTarget target);
|
||||||
Bool ValidateRenderbufferName(Uint index, Bool allowZero = true);
|
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
|
} // namespace MobileGL::MG_Impl::GLImpl::FramebufferImpl
|
||||||
|
|||||||
@@ -213,13 +213,8 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
|
|
||||||
GLint maxSamples = 0;
|
GLint maxSamples = 0;
|
||||||
for (const auto& attachment : drawFbo->GetAllAttachmentObjects()) {
|
for (const auto& attachment : drawFbo->GetAllAttachmentObjects()) {
|
||||||
if (attachment.IsRenderbuffer() && attachment.GetRenderbuffer()) {
|
if (!attachment.IsRenderbuffer() || !attachment.GetRenderbuffer()) continue;
|
||||||
maxSamples = std::max(maxSamples, static_cast<GLint>(attachment.GetRenderbuffer()->GetSamples()));
|
maxSamples = std::max(maxSamples, static_cast<GLint>(attachment.GetRenderbuffer()->GetSamples()));
|
||||||
} else if (attachment.IsTexture() && attachment.GetTexture()) {
|
|
||||||
// Multisample texture attachments count too (GL_SAMPLE_BUFFERS must
|
|
||||||
// report 1 for any multisampled draw framebuffer).
|
|
||||||
maxSamples = std::max(maxSamples, static_cast<GLint>(attachment.GetTexture()->GetSamples()));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return maxSamples;
|
return maxSamples;
|
||||||
}
|
}
|
||||||
@@ -470,14 +465,6 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
case GL_STENCIL_TEST:
|
case GL_STENCIL_TEST:
|
||||||
*params = MG_State::pGLContext->IsCapabilityEnabled(CapabilityInput::StencilTest) ? GL_TRUE : GL_FALSE;
|
*params = MG_State::pGLContext->IsCapabilityEnabled(CapabilityInput::StencilTest) ? GL_TRUE : GL_FALSE;
|
||||||
return;
|
return;
|
||||||
case GL_MIN_FRAGMENT_INTERPOLATION_OFFSET:
|
|
||||||
case GL_MAX_FRAGMENT_INTERPOLATION_OFFSET:
|
|
||||||
case GL_FRAGMENT_INTERPOLATION_OFFSET_BITS: {
|
|
||||||
GLfloat value = 0.0f;
|
|
||||||
GetFloatv(pname, &value);
|
|
||||||
*params = value != 0.0f ? GL_TRUE : GL_FALSE;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -533,19 +520,6 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
params[1] = dynamicParameters.ViewportBoundsRangeMax;
|
params[1] = dynamicParameters.ViewportBoundsRangeMax;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
case GL_MIN_FRAGMENT_INTERPOLATION_OFFSET:
|
|
||||||
case GL_MAX_FRAGMENT_INTERPOLATION_OFFSET:
|
|
||||||
case GL_FRAGMENT_INTERPOLATION_OFFSET_BITS: {
|
|
||||||
const auto& dynamicParameters = MG_Backend::pActiveBackendObject->GetDynamicParameters();
|
|
||||||
if (pname == GL_MIN_FRAGMENT_INTERPOLATION_OFFSET) {
|
|
||||||
params[0] = dynamicParameters.MinFragmentInterpolationOffset;
|
|
||||||
} else if (pname == GL_MAX_FRAGMENT_INTERPOLATION_OFFSET) {
|
|
||||||
params[0] = dynamicParameters.MaxFragmentInterpolationOffset;
|
|
||||||
} else {
|
|
||||||
params[0] = static_cast<GLfloat>(dynamicParameters.FragmentInterpolationOffsetBits);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
case GL_DEPTH_CLEAR_VALUE:
|
case GL_DEPTH_CLEAR_VALUE:
|
||||||
params[0] = MG_State::pGLContext->GetClearDepth();
|
params[0] = MG_State::pGLContext->GetClearDepth();
|
||||||
return;
|
return;
|
||||||
@@ -671,40 +645,6 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch (target) {
|
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_NAME:
|
||||||
case GL_IMAGE_BINDING_LEVEL:
|
case GL_IMAGE_BINDING_LEVEL:
|
||||||
case GL_IMAGE_BINDING_LAYERED:
|
case GL_IMAGE_BINDING_LAYERED:
|
||||||
@@ -1064,11 +1004,6 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
*params = obj ? static_cast<GLint>(obj->GetExternalIndex()) : 0;
|
*params = obj ? static_cast<GLint>(obj->GetExternalIndex()) : 0;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
case GL_DRAW_INDIRECT_BUFFER_BINDING: {
|
|
||||||
auto& obj = MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::DrawIndirect).GetBoundObject();
|
|
||||||
*params = obj ? static_cast<GLint>(obj->GetExternalIndex()) : 0;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
case GL_MAX_DEBUG_GROUP_STACK_DEPTH:
|
case GL_MAX_DEBUG_GROUP_STACK_DEPTH:
|
||||||
*params = 0; // debug-group entrypoints are stubbed
|
*params = 0; // debug-group entrypoints are stubbed
|
||||||
return;
|
return;
|
||||||
@@ -1432,7 +1367,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
*params = 0; // program-binary entrypoints are stubbed
|
*params = 0; // program-binary entrypoints are stubbed
|
||||||
return;
|
return;
|
||||||
case GL_PROGRAM_PIPELINE_BINDING:
|
case GL_PROGRAM_PIPELINE_BINDING:
|
||||||
*params = static_cast<GLint>(MG_State::pGLContext->GetBoundProgramPipelineName());
|
*params = 0; // program-pipeline entrypoints are stubbed
|
||||||
return;
|
return;
|
||||||
case GL_PROGRAM_POINT_SIZE:
|
case GL_PROGRAM_POINT_SIZE:
|
||||||
*params = MG_State::pGLContext->IsCapabilityEnabled(CapabilityInput::ProgramPointSize) ? GL_TRUE : GL_FALSE;
|
*params = MG_State::pGLContext->IsCapabilityEnabled(CapabilityInput::ProgramPointSize) ? GL_TRUE : GL_FALSE;
|
||||||
@@ -1703,7 +1638,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
*params = static_cast<GLint>(MG_State::pGLContext->GetHint(pname));
|
*params = static_cast<GLint>(MG_State::pGLContext->GetHint(pname));
|
||||||
return;
|
return;
|
||||||
case GL_TEXTURE_BUFFER_OFFSET_ALIGNMENT:
|
case GL_TEXTURE_BUFFER_OFFSET_ALIGNMENT:
|
||||||
*params = MG_Backend::pActiveBackendObject->GetDynamicParameters().TextureBufferOffsetAlignment;
|
*params = 0; // texture-buffer range entrypoints are stubbed
|
||||||
return;
|
return;
|
||||||
case GL_TIMESTAMP: {
|
case GL_TIMESTAMP: {
|
||||||
Int64 timestamp = 0;
|
Int64 timestamp = 0;
|
||||||
@@ -1772,22 +1707,20 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
*params = vao ? static_cast<GLint>(vao->GetExternalIndex()) : 0;
|
*params = vao ? static_cast<GLint>(vao->GetExternalIndex()) : 0;
|
||||||
return;
|
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:
|
case GL_VERTEX_BINDING_DIVISOR:
|
||||||
|
*params = 0; // vertex-binding entrypoints are stubbed
|
||||||
|
return;
|
||||||
case GL_VERTEX_BINDING_OFFSET:
|
case GL_VERTEX_BINDING_OFFSET:
|
||||||
|
*params = 0; // vertex-binding entrypoints are stubbed
|
||||||
|
return;
|
||||||
case GL_VERTEX_BINDING_STRIDE:
|
case GL_VERTEX_BINDING_STRIDE:
|
||||||
RecordIndexedOnlyGetterError(__func__, pname);
|
*params = 0; // vertex-binding entrypoints are stubbed
|
||||||
return;
|
return;
|
||||||
case GL_MAX_VERTEX_ATTRIB_RELATIVE_OFFSET:
|
case GL_MAX_VERTEX_ATTRIB_RELATIVE_OFFSET:
|
||||||
*params = static_cast<GLint>(VertexArrayImpl::GetMaxVertexAttribRelativeOffset());
|
*params = 0; // vertex-binding entrypoints are stubbed
|
||||||
return;
|
return;
|
||||||
case GL_MAX_VERTEX_ATTRIB_BINDINGS:
|
case GL_MAX_VERTEX_ATTRIB_BINDINGS:
|
||||||
*params = static_cast<GLint>(VertexArrayImpl::GetMaxVertexAttribBindings());
|
*params = 0; // vertex-binding entrypoints are stubbed
|
||||||
return;
|
|
||||||
case GL_MAX_VERTEX_ATTRIB_STRIDE:
|
|
||||||
*params = static_cast<GLint>(VertexArrayImpl::GetMaxVertexAttribStride());
|
|
||||||
return;
|
return;
|
||||||
case GL_VIEWPORT: {
|
case GL_VIEWPORT: {
|
||||||
const auto& vp = MG_State::pGLContext->GetViewport();
|
const auto& vp = MG_State::pGLContext->GetViewport();
|
||||||
@@ -1953,21 +1886,6 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
case GL_MAX_SAMPLE_MASK_WORDS:
|
case GL_MAX_SAMPLE_MASK_WORDS:
|
||||||
*params = dynamicParameters.MaxSampleMaskWords;
|
*params = dynamicParameters.MaxSampleMaskWords;
|
||||||
break;
|
break;
|
||||||
case GL_PATCH_VERTICES:
|
|
||||||
*params = static_cast<GLint>(MG_State::pGLContext->GetPatchVertices());
|
|
||||||
break;
|
|
||||||
case GL_MAX_PATCH_VERTICES:
|
|
||||||
*params = dynamicParameters.MaxPatchVertices;
|
|
||||||
break;
|
|
||||||
case GL_MAX_TESS_GEN_LEVEL:
|
|
||||||
*params = dynamicParameters.MaxTessGenLevel;
|
|
||||||
break;
|
|
||||||
case GL_MIN_PROGRAM_TEXTURE_GATHER_OFFSET:
|
|
||||||
*params = dynamicParameters.MinProgramTextureGatherOffset;
|
|
||||||
break;
|
|
||||||
case GL_MAX_PROGRAM_TEXTURE_GATHER_OFFSET:
|
|
||||||
*params = dynamicParameters.MaxProgramTextureGatherOffset;
|
|
||||||
break;
|
|
||||||
case GL_MAX_SHADER_STORAGE_BUFFER_BINDINGS:
|
case GL_MAX_SHADER_STORAGE_BUFFER_BINDINGS:
|
||||||
*params = static_cast<GLint>(GetIndexedBufferQueryPointCount(BufferTarget::ShaderStorage));
|
*params = static_cast<GLint>(GetIndexedBufferQueryPointCount(BufferTarget::ShaderStorage));
|
||||||
break;
|
break;
|
||||||
@@ -1983,25 +1901,6 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
case GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_COMPONENTS:
|
case GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_COMPONENTS:
|
||||||
*params = kFrontendMaxTransformFeedbackSeparateComponents;
|
*params = kFrontendMaxTransformFeedbackSeparateComponents;
|
||||||
break;
|
break;
|
||||||
// ARB_transform_feedback3 limits. The GL CTS queries these before checking
|
|
||||||
// whether the extension is advertised and requires no GL error; desktop
|
|
||||||
// drivers all accept them, so answer with the separate-attrib capacity and
|
|
||||||
// the single vertex stream the backends provide.
|
|
||||||
case GL_MAX_TRANSFORM_FEEDBACK_BUFFERS:
|
|
||||||
*params = kFrontendMaxTransformFeedbackSeparateAttribs;
|
|
||||||
break;
|
|
||||||
case GL_MAX_VERTEX_STREAMS:
|
|
||||||
*params = 1;
|
|
||||||
break;
|
|
||||||
case GL_TRANSFORM_FEEDBACK_ACTIVE:
|
|
||||||
*params = MG_State::pGLContext->IsTransformFeedbackActive() ? 1 : 0;
|
|
||||||
break;
|
|
||||||
case GL_TRANSFORM_FEEDBACK_PAUSED:
|
|
||||||
*params = MG_State::pGLContext->IsTransformFeedbackPaused() ? 1 : 0;
|
|
||||||
break;
|
|
||||||
case GL_TRANSFORM_FEEDBACK_BINDING:
|
|
||||||
*params = static_cast<GLint>(MG_State::pGLContext->GetBoundTransformFeedbackName());
|
|
||||||
break;
|
|
||||||
case GL_MAX_TEXTURE_IMAGE_UNITS:
|
case GL_MAX_TEXTURE_IMAGE_UNITS:
|
||||||
*params = dynamicParameters.MaxTextureImageUnits;
|
*params = dynamicParameters.MaxTextureImageUnits;
|
||||||
break;
|
break;
|
||||||
@@ -2058,15 +1957,6 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
case GL_SUBPIXEL_BITS:
|
case GL_SUBPIXEL_BITS:
|
||||||
*params = std::max(dynamicParameters.ViewportSubpixelBits, kFrontendSubpixelBits);
|
*params = std::max(dynamicParameters.ViewportSubpixelBits, kFrontendSubpixelBits);
|
||||||
break;
|
break;
|
||||||
case GL_MIN_FRAGMENT_INTERPOLATION_OFFSET:
|
|
||||||
*params = static_cast<GLint>(std::lround(dynamicParameters.MinFragmentInterpolationOffset));
|
|
||||||
break;
|
|
||||||
case GL_MAX_FRAGMENT_INTERPOLATION_OFFSET:
|
|
||||||
*params = static_cast<GLint>(std::lround(dynamicParameters.MaxFragmentInterpolationOffset));
|
|
||||||
break;
|
|
||||||
case GL_FRAGMENT_INTERPOLATION_OFFSET_BITS:
|
|
||||||
*params = dynamicParameters.FragmentInterpolationOffsetBits;
|
|
||||||
break;
|
|
||||||
case GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT:
|
case GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT:
|
||||||
*params = static_cast<Int>(dynamicParameters.UniformBufferOffsetAlignment);
|
*params = static_cast<Int>(dynamicParameters.UniformBufferOffsetAlignment);
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -8,8 +8,6 @@
|
|||||||
|
|
||||||
#include "GL_Program.h"
|
#include "GL_Program.h"
|
||||||
#include "Config.h"
|
#include "Config.h"
|
||||||
#include <cmath>
|
|
||||||
#include <limits>
|
|
||||||
#include <MG_Impl/GLImpl/VertexArray/Validators.h>
|
#include <MG_Impl/GLImpl/VertexArray/Validators.h>
|
||||||
#include <MG_State/GLState/Core.h>
|
#include <MG_State/GLState/Core.h>
|
||||||
#include <MG_Util/Converters/GLToStr/GLEnumConverter.h>
|
#include <MG_Util/Converters/GLToStr/GLEnumConverter.h>
|
||||||
@@ -51,18 +49,10 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
|
|
||||||
static bool CheckProgramNameValidity(GLuint program) {
|
static bool CheckProgramNameValidity(GLuint program) {
|
||||||
if (!MG_State::pGLContext->ValidateProgramName(program)) {
|
if (!MG_State::pGLContext->ValidateProgramName(program)) {
|
||||||
// Programs and shaders share one name space: a name that exists but
|
|
||||||
// belongs to a shader is INVALID_OPERATION, a name GL never handed
|
|
||||||
// out is INVALID_VALUE (GL 3.3 core 2.11.x).
|
|
||||||
const ErrorCode error = MG_State::pGLContext->ValidateShaderName(program)
|
|
||||||
? ErrorCode::InvalidOperation
|
|
||||||
: ErrorCode::InvalidValue;
|
|
||||||
MG_State::pGLContext->RecordError(
|
MG_State::pGLContext->RecordError(
|
||||||
error,
|
ErrorCode::InvalidValue,
|
||||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
||||||
std::to_string(program) +
|
std::to_string(program) + " is not a valid name."));
|
||||||
(error == ErrorCode::InvalidOperation ? " is not a program object."
|
|
||||||
: " is not a valid name.")));
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@@ -205,50 +195,6 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// The GL_UNIFORM interface and glGetActiveUniform(s)iv are the same query in two
|
|
||||||
// spellings, so they answer from the same place - the frontend reflection. The backend
|
|
||||||
// program is not that place: it does not exist at all for a program whose types its
|
|
||||||
// shading language cannot express (a double-precision uniform has no ESSL form), and
|
|
||||||
// the interface queries would then describe a program with no uniforms.
|
|
||||||
//
|
|
||||||
// Writes the GL_UNIFORM value of `prop` for active uniform `index`; false for a prop
|
|
||||||
// the reflection does not model, which the caller forwards to the backend instead.
|
|
||||||
Bool GetUniformResourceProp(const SharedPtr<MG_State::GLState::ProgramObject>& programObject, Uint index,
|
|
||||||
GLenum prop, GLint* out) {
|
|
||||||
switch (prop) {
|
|
||||||
case GL_TYPE:
|
|
||||||
*out = static_cast<GLint>(programObject->GetActiveUniformType(index));
|
|
||||||
return true;
|
|
||||||
case GL_ARRAY_SIZE:
|
|
||||||
*out = programObject->GetActiveUniformArraySize(index);
|
|
||||||
return true;
|
|
||||||
case GL_NAME_LENGTH:
|
|
||||||
*out = static_cast<GLint>(programObject->GetActiveUniformName(index).length() + 1);
|
|
||||||
return true;
|
|
||||||
case GL_BLOCK_INDEX:
|
|
||||||
*out = programObject->GetActiveUniformBlockIndex(index);
|
|
||||||
return true;
|
|
||||||
case GL_OFFSET:
|
|
||||||
*out = programObject->GetActiveUniformOffset(index);
|
|
||||||
return true;
|
|
||||||
case GL_ARRAY_STRIDE:
|
|
||||||
*out = programObject->GetActiveUniformArrayStride(index);
|
|
||||||
return true;
|
|
||||||
case GL_MATRIX_STRIDE:
|
|
||||||
*out = programObject->GetActiveUniformMatrixStride(index);
|
|
||||||
return true;
|
|
||||||
case GL_IS_ROW_MAJOR:
|
|
||||||
*out = programObject->GetActiveUniformIsRowMajor(index);
|
|
||||||
return true;
|
|
||||||
case GL_LOCATION:
|
|
||||||
// A block member has no location; GetUniformLocation already reports -1 for one.
|
|
||||||
*out = programObject->GetUniformLocation(programObject->GetActiveUniformName(index));
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void CopyStr(GLsizei bufSize, GLsizei* length, GLchar* dst, const char* src, GLsizei srcLength) {
|
void CopyStr(GLsizei bufSize, GLsizei* length, GLchar* dst, const char* src, GLsizei srcLength) {
|
||||||
if (bufSize <= 0) {
|
if (bufSize <= 0) {
|
||||||
if (length) *length = 0;
|
if (length) *length = 0;
|
||||||
@@ -371,16 +317,11 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void DeleteProgram_State(GLuint program) {
|
void DeleteProgram_State(GLuint program) {
|
||||||
// "If program is zero, it is silently ignored" (GL 4.6 core 7.3) - unlike every
|
|
||||||
// other program entry point, where 0 is a name GL never handed out.
|
|
||||||
if (program == 0) return;
|
|
||||||
if (!CheckProgramNameValidity(program)) return;
|
if (!CheckProgramNameValidity(program)) return;
|
||||||
MG_State::pGLContext->MarkProgramForDeletion(program);
|
MG_State::pGLContext->MarkProgramForDeletion(program);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DeleteShader_State(GLuint shader) {
|
void DeleteShader_State(GLuint shader) {
|
||||||
// Same silent-zero rule as glDeleteProgram (GL 4.6 core 7.1).
|
|
||||||
if (shader == 0) return;
|
|
||||||
if (!CheckShaderNameValidity(shader)) return;
|
if (!CheckShaderNameValidity(shader)) return;
|
||||||
MG_State::pGLContext->MarkShaderForDeletion(shader);
|
MG_State::pGLContext->MarkShaderForDeletion(shader);
|
||||||
}
|
}
|
||||||
@@ -664,18 +605,6 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
*params = programObject->GetActiveUniformBlocksMaxNameLength() + 1;
|
*params = programObject->GetActiveUniformBlocksMaxNameLength() + 1;
|
||||||
MGLOG_D("%s: %s = %d", __func__, MG_Util::ConvertGLEnumToString(pname).c_str(), *params);
|
MGLOG_D("%s: %s = %d", __func__, MG_Util::ConvertGLEnumToString(pname).c_str(), *params);
|
||||||
break;
|
break;
|
||||||
case GL_TRANSFORM_FEEDBACK_VARYINGS:
|
|
||||||
*params = static_cast<GLint>(programObject->GetTransformFeedbackVaryingCount());
|
|
||||||
MGLOG_D("%s: %s = %d", __func__, MG_Util::ConvertGLEnumToString(pname).c_str(), *params);
|
|
||||||
break;
|
|
||||||
case GL_TRANSFORM_FEEDBACK_BUFFER_MODE:
|
|
||||||
*params = static_cast<GLint>(programObject->GetTransformFeedbackBufferMode());
|
|
||||||
MGLOG_D("%s: %s = %d", __func__, MG_Util::ConvertGLEnumToString(pname).c_str(), *params);
|
|
||||||
break;
|
|
||||||
case GL_TRANSFORM_FEEDBACK_VARYING_MAX_LENGTH:
|
|
||||||
*params = programObject->GetTransformFeedbackVaryingMaxLength();
|
|
||||||
MGLOG_D("%s: %s = %d", __func__, MG_Util::ConvertGLEnumToString(pname).c_str(), *params);
|
|
||||||
break;
|
|
||||||
case GL_COMPUTE_WORK_GROUP_SIZE: { // GL >= 4.3
|
case GL_COMPUTE_WORK_GROUP_SIZE: { // GL >= 4.3
|
||||||
if (!programObject->GetLinkStatus() || programObject->GetShaderIndexByStage(ShaderStage::Compute) < 0) {
|
if (!programObject->GetLinkStatus() || programObject->GetShaderIndexByStage(ShaderStage::Compute) < 0) {
|
||||||
MG_State::pGLContext->RecordError(
|
MG_State::pGLContext->RecordError(
|
||||||
@@ -694,17 +623,10 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case GL_PROGRAM_BINARY_LENGTH:
|
case GL_PROGRAM_BINARY_LENGTH:
|
||||||
// No program binary format is exposed, so a program never has a retrievable
|
|
||||||
// binary and its length is zero (ARB_get_program_binary).
|
|
||||||
*params = 0;
|
|
||||||
break;
|
|
||||||
case GL_PROGRAM_BINARY_RETRIEVABLE_HINT:
|
|
||||||
*params = programObject->GetBinaryRetrievableHint() ? GL_TRUE : GL_FALSE;
|
|
||||||
break;
|
|
||||||
case GL_PROGRAM_SEPARABLE:
|
|
||||||
*params = programObject->GetSeparable() ? GL_TRUE : GL_FALSE;
|
|
||||||
break;
|
|
||||||
|
|
||||||
|
case GL_TRANSFORM_FEEDBACK_BUFFER_MODE:
|
||||||
|
case GL_TRANSFORM_FEEDBACK_VARYINGS:
|
||||||
|
case GL_TRANSFORM_FEEDBACK_VARYING_MAX_LENGTH:
|
||||||
case GL_GEOMETRY_VERTICES_OUT:
|
case GL_GEOMETRY_VERTICES_OUT:
|
||||||
case GL_GEOMETRY_INPUT_TYPE:
|
case GL_GEOMETRY_INPUT_TYPE:
|
||||||
case GL_GEOMETRY_OUTPUT_TYPE:
|
case GL_GEOMETRY_OUTPUT_TYPE:
|
||||||
@@ -879,8 +801,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if constexpr (std::is_same_v<T, GLfloat>) {
|
if constexpr (std::is_same_v<T, GLfloat>) {
|
||||||
if (ttype->getBasicType() != glslang::EbtDouble && ttype->isMatrix() &&
|
if (ttype->isMatrix() && ttype->getMatrixCols() == 3) {
|
||||||
ttype->getMatrixCols() == 3) {
|
|
||||||
auto* pBase = pUBO + offset;
|
auto* pBase = pUBO + offset;
|
||||||
for (int i = 0; i < ttype->getMatrixRows(); i++) {
|
for (int i = 0; i < ttype->getMatrixRows(); i++) {
|
||||||
Memcpy(reinterpret_cast<char*>(params) + ttype->getMatrixCols() * sizeof(GLfloat) * i,
|
Memcpy(reinterpret_cast<char*>(params) + ttype->getMatrixCols() * sizeof(GLfloat) * i,
|
||||||
@@ -890,46 +811,9 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// A double-precision uniform is the one case where the stored component type can
|
|
||||||
// differ from the queried one for a non-opaque uniform, and the difference is not
|
|
||||||
// just a reinterpretation: it is twice as wide, so a raw copy would overrun the
|
|
||||||
// caller's buffer as well as return nonsense. Read component by component and let
|
|
||||||
// GL's conversion rules (7.6: round to nearest for the integer queries) apply.
|
|
||||||
if (ttype->getBasicType() == glslang::EbtDouble) {
|
|
||||||
const Int columns = ttype->isMatrix() ? ttype->getMatrixCols() : 1;
|
|
||||||
const Int rows = ttype->isMatrix() ? ttype->getMatrixRows()
|
|
||||||
: (ttype->isVector() ? ttype->getVectorSize() : 1);
|
|
||||||
// The slot the linker handed out is exactly `columns` columns wide, so it also
|
|
||||||
// states the column stride - which for a double matrix is not a float's 16 bytes.
|
|
||||||
const SizeT columnStride = columns > 0 ? size / static_cast<SizeT>(columns) : size;
|
|
||||||
for (Int column = 0; column < columns; ++column) {
|
|
||||||
for (Int row = 0; row < rows; ++row) {
|
|
||||||
GLdouble component = 0.0;
|
|
||||||
Memcpy(&component, pUBO + offset + column * columnStride + row * sizeof(GLdouble),
|
|
||||||
sizeof(component));
|
|
||||||
if constexpr (std::is_integral_v<T>) {
|
|
||||||
// Rounded to the nearest integer and clamped into the queried type's
|
|
||||||
// range, so a negative double read through glGetUniformuiv is 0
|
|
||||||
// rather than its two's complement.
|
|
||||||
const GLdouble rounded = std::nearbyint(component);
|
|
||||||
const GLdouble lowest = static_cast<GLdouble>(std::numeric_limits<T>::lowest());
|
|
||||||
const GLdouble highest = static_cast<GLdouble>(std::numeric_limits<T>::max());
|
|
||||||
params[column * rows + row] = static_cast<T>(std::clamp(rounded, lowest, highest));
|
|
||||||
} else {
|
|
||||||
params[column * rows + row] = static_cast<T>(component);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Memcpy(params, pUBO + offset, size);
|
Memcpy(params, pUBO + offset, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GetUniformdv_State(GLuint program, GLint location, GLdouble* params) {
|
|
||||||
GetUniformScalar_State(program, location, params);
|
|
||||||
}
|
|
||||||
|
|
||||||
void GetUniformfv_State(GLuint program, GLint location, GLfloat* params) {
|
void GetUniformfv_State(GLuint program, GLint location, GLfloat* params) {
|
||||||
GetUniformScalar_State(program, location, params);
|
GetUniformScalar_State(program, location, params);
|
||||||
}
|
}
|
||||||
@@ -943,13 +827,19 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
GLboolean IsProgram_State(GLuint program) {
|
GLboolean IsProgram_State(GLuint program) {
|
||||||
// Deletion-flagged names stay valid while the object is still GL-visible (program in
|
/* FIXME: Handle situations that:
|
||||||
// use, shader attached), so name validity is exactly the Is* answer.
|
* A program object marked for deletion with glDeleteProgram but still in use as part of current
|
||||||
|
* rendering state is still considered a program object and glIsProgram will return GL_TRUE.
|
||||||
|
*/
|
||||||
if (program == 0) return GL_FALSE;
|
if (program == 0) return GL_FALSE;
|
||||||
return MG_State::pGLContext->ValidateProgramName(program) ? GL_TRUE : GL_FALSE;
|
return MG_State::pGLContext->ValidateProgramName(program) ? GL_TRUE : GL_FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
GLboolean IsShader_State(GLuint shader) {
|
GLboolean IsShader_State(GLuint shader) {
|
||||||
|
/* FIXME: Handle situations that:
|
||||||
|
* A shader object marked for deletion with glDeleteShader but still attached to a program object is still
|
||||||
|
* considered a shader object and glIsShader will return GL_TRUE.
|
||||||
|
*/
|
||||||
if (shader == 0) return GL_FALSE;
|
if (shader == 0) return GL_FALSE;
|
||||||
return MG_State::pGLContext->ValidateShaderName(shader) ? GL_TRUE : GL_FALSE;
|
return MG_State::pGLContext->ValidateShaderName(shader) ? GL_TRUE : GL_FALSE;
|
||||||
}
|
}
|
||||||
@@ -959,18 +849,6 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
if (!programObject) return;
|
if (!programObject) return;
|
||||||
MGLOG_D("%s: linking program %d", __func__, program);
|
MGLOG_D("%s: linking program %d", __func__, program);
|
||||||
|
|
||||||
// Relinking the program an active transform feedback captures from would
|
|
||||||
// invalidate its varyings mid-capture (GL 3.3 core 2.11.3).
|
|
||||||
if (MG_State::pGLContext->IsTransformFeedbackActive() &&
|
|
||||||
MG_State::pGLContext->GetTransformFeedbackProgram().get() == programObject.get()) {
|
|
||||||
MG_State::pGLContext->RecordError(
|
|
||||||
ErrorCode::InvalidOperation,
|
|
||||||
MakeUnique<GenericErrorInfo>(
|
|
||||||
"MG_Impl/GLImpl", __func__,
|
|
||||||
"The program used by active transform feedback cannot be relinked."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
static Bool allowVSOnlyPrograms;
|
static Bool allowVSOnlyPrograms;
|
||||||
static Bool initialized = false;
|
static Bool initialized = false;
|
||||||
if (!initialized) {
|
if (!initialized) {
|
||||||
@@ -1014,18 +892,6 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
void UseProgram_State(GLuint program) {
|
void UseProgram_State(GLuint program) {
|
||||||
MGLOG_D("UseProgram_State: program=%u", program);
|
MGLOG_D("UseProgram_State: program=%u", program);
|
||||||
|
|
||||||
// The program in use may not change while transform feedback is active - unless
|
|
||||||
// the capture is paused, which is exactly what ARB_transform_feedback2 added the
|
|
||||||
// pause for (GL 4.6 core 7.3).
|
|
||||||
if (MG_State::pGLContext->IsTransformFeedbackActive() &&
|
|
||||||
!MG_State::pGLContext->IsTransformFeedbackPaused()) {
|
|
||||||
MG_State::pGLContext->RecordError(
|
|
||||||
ErrorCode::InvalidOperation,
|
|
||||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
|
||||||
"The current program cannot change while transform feedback is active."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (program == 0) {
|
if (program == 0) {
|
||||||
MG_State::pGLContext->UseProgram(0);
|
MG_State::pGLContext->UseProgram(0);
|
||||||
return;
|
return;
|
||||||
@@ -1097,7 +963,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
void Uniformv_State(GLint location, GLsizei count, T* value) {
|
void Uniformv_State(GLint location, GLsizei count, T* value) {
|
||||||
if (location == -1) return;
|
if (location == -1) return;
|
||||||
|
|
||||||
auto& programObject = MG_State::pGLContext->GetProgramForUniform();
|
auto& programObject = MG_State::pGLContext->GetCurrentProgram();
|
||||||
if (programObject == nullptr) {
|
if (programObject == nullptr) {
|
||||||
MG_State::pGLContext->RecordError(
|
MG_State::pGLContext->RecordError(
|
||||||
ErrorCode::InvalidOperation,
|
ErrorCode::InvalidOperation,
|
||||||
@@ -1148,38 +1014,6 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// glUniform*d / glUniformMatrix*dv. The vector forms need nothing beyond the shared
|
|
||||||
// upload template - it is already typed on the component - but a matrix does: the
|
|
||||||
// column stride the linker used for a double matrix is not the 16 bytes a float one
|
|
||||||
// gets. It is not guessed here; the slot the uniform was given is exactly `columns`
|
|
||||||
// columns wide, so dividing states the stride the rest of the pipeline agreed on.
|
|
||||||
template <typename Program>
|
|
||||||
void UniformMatrixdv_Object(Program& programObject, GLint location, GLsizei count, GLboolean transpose,
|
|
||||||
const GLdouble* value, Int columns, Int rows) {
|
|
||||||
const SizeT slotSize = programObject.GetUniformSizesInBytes(location);
|
|
||||||
const SizeT columnStride = columns > 0 ? slotSize / static_cast<SizeT>(columns) : slotSize;
|
|
||||||
const SizeT componentCount = static_cast<SizeT>(columns) * static_cast<SizeT>(rows);
|
|
||||||
Vector<GLdouble> column(static_cast<SizeT>(rows));
|
|
||||||
for (GLint matrix = 0; matrix < count; ++matrix) {
|
|
||||||
if (matrix > 0 && !programObject.UniformLocationsAliasSameUniform(location, location + matrix)) break;
|
|
||||||
if (!programObject.IsValidUniformLocation(location + matrix)) {
|
|
||||||
RecordInvalidUniformLocationError(__func__, location + matrix, "the current program object");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const GLdouble* source = value + matrix * componentCount;
|
|
||||||
for (Int c = 0; c < columns; ++c) {
|
|
||||||
for (Int r = 0; r < rows; ++r) {
|
|
||||||
column[r] = transpose == GL_TRUE ? source[r * columns + c] : source[c * rows + r];
|
|
||||||
}
|
|
||||||
Uniform_State<1>(programObject, location + matrix, column.data(), c * columnStride);
|
|
||||||
for (Int r = 1; r < rows; ++r) {
|
|
||||||
Uniform_State<1>(programObject, location + matrix, column.data() + r,
|
|
||||||
c * columnStride + r * sizeof(GLdouble));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper function to transpose a 2x2 matrix
|
// Helper function to transpose a 2x2 matrix
|
||||||
void TransposeMatrix2x2(const GLfloat* input, GLfloat* output) {
|
void TransposeMatrix2x2(const GLfloat* input, GLfloat* output) {
|
||||||
// Input matrix is in column-major order (OpenGL default)
|
// Input matrix is in column-major order (OpenGL default)
|
||||||
@@ -1289,7 +1123,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
// If transpose is GL_TRUE, we need to transpose the matrix data
|
// If transpose is GL_TRUE, we need to transpose the matrix data
|
||||||
if (location == -1) return;
|
if (location == -1) return;
|
||||||
|
|
||||||
auto& programObject = MG_State::pGLContext->GetProgramForUniform();
|
auto& programObject = MG_State::pGLContext->GetCurrentProgram();
|
||||||
if (programObject == nullptr) {
|
if (programObject == nullptr) {
|
||||||
MG_State::pGLContext->RecordError(
|
MG_State::pGLContext->RecordError(
|
||||||
ErrorCode::InvalidOperation,
|
ErrorCode::InvalidOperation,
|
||||||
@@ -1324,7 +1158,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
// If transpose is GL_TRUE, we need to transpose the matrix data
|
// If transpose is GL_TRUE, we need to transpose the matrix data
|
||||||
if (location == -1) return;
|
if (location == -1) return;
|
||||||
|
|
||||||
auto& programObject = MG_State::pGLContext->GetProgramForUniform();
|
auto& programObject = MG_State::pGLContext->GetCurrentProgram();
|
||||||
if (programObject == nullptr) {
|
if (programObject == nullptr) {
|
||||||
MG_State::pGLContext->RecordError(
|
MG_State::pGLContext->RecordError(
|
||||||
ErrorCode::InvalidOperation,
|
ErrorCode::InvalidOperation,
|
||||||
@@ -1364,7 +1198,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
// If transpose is GL_TRUE, we need to transpose the matrix data
|
// If transpose is GL_TRUE, we need to transpose the matrix data
|
||||||
if (location == -1) return;
|
if (location == -1) return;
|
||||||
|
|
||||||
auto& programObject = MG_State::pGLContext->GetProgramForUniform();
|
auto& programObject = MG_State::pGLContext->GetCurrentProgram();
|
||||||
if (programObject == nullptr) {
|
if (programObject == nullptr) {
|
||||||
MG_State::pGLContext->RecordError(
|
MG_State::pGLContext->RecordError(
|
||||||
ErrorCode::InvalidOperation,
|
ErrorCode::InvalidOperation,
|
||||||
@@ -1397,7 +1231,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
void UniformMatrixNonSquarefv_State(const char* caller, GLint location, GLsizei count) {
|
void UniformMatrixNonSquarefv_State(const char* caller, GLint location, GLsizei count) {
|
||||||
if (location == -1) return;
|
if (location == -1) return;
|
||||||
|
|
||||||
auto& programObject = MG_State::pGLContext->GetProgramForUniform();
|
auto& programObject = MG_State::pGLContext->GetCurrentProgram();
|
||||||
if (programObject == nullptr) {
|
if (programObject == nullptr) {
|
||||||
MG_State::pGLContext->RecordError(
|
MG_State::pGLContext->RecordError(
|
||||||
ErrorCode::InvalidOperation,
|
ErrorCode::InvalidOperation,
|
||||||
@@ -1988,312 +1822,6 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
GLuint v[] = {v0, v1, v2, v3};
|
GLuint v[] = {v0, v1, v2, v3};
|
||||||
Uniform4uiv(location, 1, v);
|
Uniform4uiv(location, 1, v);
|
||||||
}
|
}
|
||||||
void Uniform1d(GLint location, GLdouble v0) {
|
|
||||||
const GLdouble v[] = {v0};
|
|
||||||
Uniformv_State<1>(location, 1, v);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Uniform1dv(GLint location, GLsizei count, const GLdouble* value) {
|
|
||||||
Uniformv_State<1>(location, count, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ProgramUniform1d(GLuint program, GLint location, GLdouble v0) {
|
|
||||||
const GLdouble v[] = {v0};
|
|
||||||
ProgramUniformv_State<1>(program, location, 1, v);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ProgramUniform1dv(GLuint program, GLint location, GLsizei count, const GLdouble* value) {
|
|
||||||
ProgramUniformv_State<1>(program, location, count, value);
|
|
||||||
}
|
|
||||||
void Uniform2d(GLint location, GLdouble v0, GLdouble v1) {
|
|
||||||
const GLdouble v[] = {v0, v1};
|
|
||||||
Uniformv_State<2>(location, 1, v);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Uniform2dv(GLint location, GLsizei count, const GLdouble* value) {
|
|
||||||
Uniformv_State<2>(location, count, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ProgramUniform2d(GLuint program, GLint location, GLdouble v0, GLdouble v1) {
|
|
||||||
const GLdouble v[] = {v0, v1};
|
|
||||||
ProgramUniformv_State<2>(program, location, 1, v);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ProgramUniform2dv(GLuint program, GLint location, GLsizei count, const GLdouble* value) {
|
|
||||||
ProgramUniformv_State<2>(program, location, count, value);
|
|
||||||
}
|
|
||||||
void Uniform3d(GLint location, GLdouble v0, GLdouble v1, GLdouble v2) {
|
|
||||||
const GLdouble v[] = {v0, v1, v2};
|
|
||||||
Uniformv_State<3>(location, 1, v);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Uniform3dv(GLint location, GLsizei count, const GLdouble* value) {
|
|
||||||
Uniformv_State<3>(location, count, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ProgramUniform3d(GLuint program, GLint location, GLdouble v0, GLdouble v1, GLdouble v2) {
|
|
||||||
const GLdouble v[] = {v0, v1, v2};
|
|
||||||
ProgramUniformv_State<3>(program, location, 1, v);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ProgramUniform3dv(GLuint program, GLint location, GLsizei count, const GLdouble* value) {
|
|
||||||
ProgramUniformv_State<3>(program, location, count, value);
|
|
||||||
}
|
|
||||||
void Uniform4d(GLint location, GLdouble v0, GLdouble v1, GLdouble v2, GLdouble v3) {
|
|
||||||
const GLdouble v[] = {v0, v1, v2, v3};
|
|
||||||
Uniformv_State<4>(location, 1, v);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Uniform4dv(GLint location, GLsizei count, const GLdouble* value) {
|
|
||||||
Uniformv_State<4>(location, count, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ProgramUniform4d(GLuint program, GLint location, GLdouble v0, GLdouble v1, GLdouble v2, GLdouble v3) {
|
|
||||||
const GLdouble v[] = {v0, v1, v2, v3};
|
|
||||||
ProgramUniformv_State<4>(program, location, 1, v);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ProgramUniform4dv(GLuint program, GLint location, GLsizei count, const GLdouble* value) {
|
|
||||||
ProgramUniformv_State<4>(program, location, count, value);
|
|
||||||
}
|
|
||||||
void UniformMatrix2dv(GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) {
|
|
||||||
if (location == -1) return;
|
|
||||||
auto& programObject = MG_State::pGLContext->GetProgramForUniform();
|
|
||||||
if (programObject == nullptr) {
|
|
||||||
MG_State::pGLContext->RecordError(
|
|
||||||
ErrorCode::InvalidOperation,
|
|
||||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "There is no current program object."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
UniformMatrixdv_Object(*programObject, location, count, transpose, value, 2, 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ProgramUniformMatrix2dv(GLuint program, GLint location, GLsizei count, GLboolean transpose,
|
|
||||||
const GLdouble* value) {
|
|
||||||
if (location == -1) return;
|
|
||||||
auto& programObject = TryToGetProgramObject(program);
|
|
||||||
if (!programObject) return;
|
|
||||||
if (!programObject->GetLinkStatus()) {
|
|
||||||
MG_State::pGLContext->RecordError(
|
|
||||||
ErrorCode::InvalidOperation,
|
|
||||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
|
||||||
"program " + std::to_string(program) + " is not linked."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
UniformMatrixdv_Object(*programObject, location, count, transpose, value, 2, 2);
|
|
||||||
}
|
|
||||||
void UniformMatrix3dv(GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) {
|
|
||||||
if (location == -1) return;
|
|
||||||
auto& programObject = MG_State::pGLContext->GetProgramForUniform();
|
|
||||||
if (programObject == nullptr) {
|
|
||||||
MG_State::pGLContext->RecordError(
|
|
||||||
ErrorCode::InvalidOperation,
|
|
||||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "There is no current program object."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
UniformMatrixdv_Object(*programObject, location, count, transpose, value, 3, 3);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ProgramUniformMatrix3dv(GLuint program, GLint location, GLsizei count, GLboolean transpose,
|
|
||||||
const GLdouble* value) {
|
|
||||||
if (location == -1) return;
|
|
||||||
auto& programObject = TryToGetProgramObject(program);
|
|
||||||
if (!programObject) return;
|
|
||||||
if (!programObject->GetLinkStatus()) {
|
|
||||||
MG_State::pGLContext->RecordError(
|
|
||||||
ErrorCode::InvalidOperation,
|
|
||||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
|
||||||
"program " + std::to_string(program) + " is not linked."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
UniformMatrixdv_Object(*programObject, location, count, transpose, value, 3, 3);
|
|
||||||
}
|
|
||||||
void UniformMatrix4dv(GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) {
|
|
||||||
if (location == -1) return;
|
|
||||||
auto& programObject = MG_State::pGLContext->GetProgramForUniform();
|
|
||||||
if (programObject == nullptr) {
|
|
||||||
MG_State::pGLContext->RecordError(
|
|
||||||
ErrorCode::InvalidOperation,
|
|
||||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "There is no current program object."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
UniformMatrixdv_Object(*programObject, location, count, transpose, value, 4, 4);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ProgramUniformMatrix4dv(GLuint program, GLint location, GLsizei count, GLboolean transpose,
|
|
||||||
const GLdouble* value) {
|
|
||||||
if (location == -1) return;
|
|
||||||
auto& programObject = TryToGetProgramObject(program);
|
|
||||||
if (!programObject) return;
|
|
||||||
if (!programObject->GetLinkStatus()) {
|
|
||||||
MG_State::pGLContext->RecordError(
|
|
||||||
ErrorCode::InvalidOperation,
|
|
||||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
|
||||||
"program " + std::to_string(program) + " is not linked."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
UniformMatrixdv_Object(*programObject, location, count, transpose, value, 4, 4);
|
|
||||||
}
|
|
||||||
void UniformMatrix2x3dv(GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) {
|
|
||||||
if (location == -1) return;
|
|
||||||
auto& programObject = MG_State::pGLContext->GetProgramForUniform();
|
|
||||||
if (programObject == nullptr) {
|
|
||||||
MG_State::pGLContext->RecordError(
|
|
||||||
ErrorCode::InvalidOperation,
|
|
||||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "There is no current program object."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
UniformMatrixdv_Object(*programObject, location, count, transpose, value, 2, 3);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ProgramUniformMatrix2x3dv(GLuint program, GLint location, GLsizei count, GLboolean transpose,
|
|
||||||
const GLdouble* value) {
|
|
||||||
if (location == -1) return;
|
|
||||||
auto& programObject = TryToGetProgramObject(program);
|
|
||||||
if (!programObject) return;
|
|
||||||
if (!programObject->GetLinkStatus()) {
|
|
||||||
MG_State::pGLContext->RecordError(
|
|
||||||
ErrorCode::InvalidOperation,
|
|
||||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
|
||||||
"program " + std::to_string(program) + " is not linked."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
UniformMatrixdv_Object(*programObject, location, count, transpose, value, 2, 3);
|
|
||||||
}
|
|
||||||
void UniformMatrix2x4dv(GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) {
|
|
||||||
if (location == -1) return;
|
|
||||||
auto& programObject = MG_State::pGLContext->GetProgramForUniform();
|
|
||||||
if (programObject == nullptr) {
|
|
||||||
MG_State::pGLContext->RecordError(
|
|
||||||
ErrorCode::InvalidOperation,
|
|
||||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "There is no current program object."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
UniformMatrixdv_Object(*programObject, location, count, transpose, value, 2, 4);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ProgramUniformMatrix2x4dv(GLuint program, GLint location, GLsizei count, GLboolean transpose,
|
|
||||||
const GLdouble* value) {
|
|
||||||
if (location == -1) return;
|
|
||||||
auto& programObject = TryToGetProgramObject(program);
|
|
||||||
if (!programObject) return;
|
|
||||||
if (!programObject->GetLinkStatus()) {
|
|
||||||
MG_State::pGLContext->RecordError(
|
|
||||||
ErrorCode::InvalidOperation,
|
|
||||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
|
||||||
"program " + std::to_string(program) + " is not linked."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
UniformMatrixdv_Object(*programObject, location, count, transpose, value, 2, 4);
|
|
||||||
}
|
|
||||||
void UniformMatrix3x2dv(GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) {
|
|
||||||
if (location == -1) return;
|
|
||||||
auto& programObject = MG_State::pGLContext->GetProgramForUniform();
|
|
||||||
if (programObject == nullptr) {
|
|
||||||
MG_State::pGLContext->RecordError(
|
|
||||||
ErrorCode::InvalidOperation,
|
|
||||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "There is no current program object."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
UniformMatrixdv_Object(*programObject, location, count, transpose, value, 3, 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ProgramUniformMatrix3x2dv(GLuint program, GLint location, GLsizei count, GLboolean transpose,
|
|
||||||
const GLdouble* value) {
|
|
||||||
if (location == -1) return;
|
|
||||||
auto& programObject = TryToGetProgramObject(program);
|
|
||||||
if (!programObject) return;
|
|
||||||
if (!programObject->GetLinkStatus()) {
|
|
||||||
MG_State::pGLContext->RecordError(
|
|
||||||
ErrorCode::InvalidOperation,
|
|
||||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
|
||||||
"program " + std::to_string(program) + " is not linked."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
UniformMatrixdv_Object(*programObject, location, count, transpose, value, 3, 2);
|
|
||||||
}
|
|
||||||
void UniformMatrix3x4dv(GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) {
|
|
||||||
if (location == -1) return;
|
|
||||||
auto& programObject = MG_State::pGLContext->GetProgramForUniform();
|
|
||||||
if (programObject == nullptr) {
|
|
||||||
MG_State::pGLContext->RecordError(
|
|
||||||
ErrorCode::InvalidOperation,
|
|
||||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "There is no current program object."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
UniformMatrixdv_Object(*programObject, location, count, transpose, value, 3, 4);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ProgramUniformMatrix3x4dv(GLuint program, GLint location, GLsizei count, GLboolean transpose,
|
|
||||||
const GLdouble* value) {
|
|
||||||
if (location == -1) return;
|
|
||||||
auto& programObject = TryToGetProgramObject(program);
|
|
||||||
if (!programObject) return;
|
|
||||||
if (!programObject->GetLinkStatus()) {
|
|
||||||
MG_State::pGLContext->RecordError(
|
|
||||||
ErrorCode::InvalidOperation,
|
|
||||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
|
||||||
"program " + std::to_string(program) + " is not linked."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
UniformMatrixdv_Object(*programObject, location, count, transpose, value, 3, 4);
|
|
||||||
}
|
|
||||||
void UniformMatrix4x2dv(GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) {
|
|
||||||
if (location == -1) return;
|
|
||||||
auto& programObject = MG_State::pGLContext->GetProgramForUniform();
|
|
||||||
if (programObject == nullptr) {
|
|
||||||
MG_State::pGLContext->RecordError(
|
|
||||||
ErrorCode::InvalidOperation,
|
|
||||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "There is no current program object."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
UniformMatrixdv_Object(*programObject, location, count, transpose, value, 4, 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ProgramUniformMatrix4x2dv(GLuint program, GLint location, GLsizei count, GLboolean transpose,
|
|
||||||
const GLdouble* value) {
|
|
||||||
if (location == -1) return;
|
|
||||||
auto& programObject = TryToGetProgramObject(program);
|
|
||||||
if (!programObject) return;
|
|
||||||
if (!programObject->GetLinkStatus()) {
|
|
||||||
MG_State::pGLContext->RecordError(
|
|
||||||
ErrorCode::InvalidOperation,
|
|
||||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
|
||||||
"program " + std::to_string(program) + " is not linked."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
UniformMatrixdv_Object(*programObject, location, count, transpose, value, 4, 2);
|
|
||||||
}
|
|
||||||
void UniformMatrix4x3dv(GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) {
|
|
||||||
if (location == -1) return;
|
|
||||||
auto& programObject = MG_State::pGLContext->GetProgramForUniform();
|
|
||||||
if (programObject == nullptr) {
|
|
||||||
MG_State::pGLContext->RecordError(
|
|
||||||
ErrorCode::InvalidOperation,
|
|
||||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "There is no current program object."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
UniformMatrixdv_Object(*programObject, location, count, transpose, value, 4, 3);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ProgramUniformMatrix4x3dv(GLuint program, GLint location, GLsizei count, GLboolean transpose,
|
|
||||||
const GLdouble* value) {
|
|
||||||
if (location == -1) return;
|
|
||||||
auto& programObject = TryToGetProgramObject(program);
|
|
||||||
if (!programObject) return;
|
|
||||||
if (!programObject->GetLinkStatus()) {
|
|
||||||
MG_State::pGLContext->RecordError(
|
|
||||||
ErrorCode::InvalidOperation,
|
|
||||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
|
||||||
"program " + std::to_string(program) + " is not linked."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
UniformMatrixdv_Object(*programObject, location, count, transpose, value, 4, 3);
|
|
||||||
}
|
|
||||||
void GetUniformdv(GLuint program, GLint location, GLdouble* params) {
|
|
||||||
GetUniformdv_State(program, location, params);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Uniform1fv(GLint location, GLsizei count, const GLfloat* value) {
|
void Uniform1fv(GLint location, GLsizei count, const GLfloat* value) {
|
||||||
Uniform1fv_State(location, count, value);
|
Uniform1fv_State(location, count, value);
|
||||||
}
|
}
|
||||||
@@ -2573,17 +2101,6 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
"Backend does not support program interface queries."));
|
"Backend does not support program interface queries."));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (programInterface == GL_UNIFORM) {
|
|
||||||
if (pname == GL_ACTIVE_RESOURCES) {
|
|
||||||
*params = static_cast<GLint>(programObject->GetUniformCount());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (pname == GL_MAX_NAME_LENGTH) {
|
|
||||||
// Stored as the bare length; GL_MAX_NAME_LENGTH counts the terminator.
|
|
||||||
*params = programObject->GetUniformMaxLength() + 1;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
getProgramInterfaceiv(program, programInterface, pname, params);
|
getProgramInterfaceiv(program, programInterface, pname, params);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2592,10 +2109,6 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
if (!programObject) return GL_INVALID_INDEX;
|
if (!programObject) return GL_INVALID_INDEX;
|
||||||
if (!ValidateNamedProgramResourceInterface(programInterface, __func__)) return GL_INVALID_INDEX;
|
if (!ValidateNamedProgramResourceInterface(programInterface, __func__)) return GL_INVALID_INDEX;
|
||||||
if (!name) return GL_INVALID_INDEX;
|
if (!name) return GL_INVALID_INDEX;
|
||||||
if (programInterface == GL_UNIFORM) {
|
|
||||||
const Int uniformIndex = programObject->GetActiveUniformIndex(name);
|
|
||||||
return uniformIndex < 0 ? GL_INVALID_INDEX : static_cast<GLuint>(uniformIndex);
|
|
||||||
}
|
|
||||||
auto getProgramResourceIndex = MG_Backend::gBackendFunctionsTable.GL.GetProgramResourceIndex;
|
auto getProgramResourceIndex = MG_Backend::gBackendFunctionsTable.GL.GetProgramResourceIndex;
|
||||||
if (!getProgramResourceIndex) {
|
if (!getProgramResourceIndex) {
|
||||||
MG_State::pGLContext->RecordError(
|
MG_State::pGLContext->RecordError(
|
||||||
@@ -2632,13 +2145,6 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "bufSize must be non-negative."));
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "bufSize must be non-negative."));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (programInterface == GL_UNIFORM) {
|
|
||||||
// Same index space GetProgramResourceIndex answers in, and the range check above
|
|
||||||
// already used it.
|
|
||||||
const String& uniformName = programObject->GetActiveUniformName(index);
|
|
||||||
CopyStr(bufSize, length, name, uniformName.c_str(), static_cast<GLsizei>(uniformName.length()));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
auto getProgramResourceName = MG_Backend::gBackendFunctionsTable.GL.GetProgramResourceName;
|
auto getProgramResourceName = MG_Backend::gBackendFunctionsTable.GL.GetProgramResourceName;
|
||||||
if (!getProgramResourceName) {
|
if (!getProgramResourceName) {
|
||||||
MG_State::pGLContext->RecordError(
|
MG_State::pGLContext->RecordError(
|
||||||
@@ -2660,36 +2166,6 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
"propCount and bufSize must be non-negative."));
|
"propCount and bufSize must be non-negative."));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (programInterface == GL_UNIFORM) {
|
|
||||||
if (index >= programObject->GetUniformCount()) {
|
|
||||||
MG_State::pGLContext->RecordError(
|
|
||||||
ErrorCode::InvalidValue,
|
|
||||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "index is out of range."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (props == nullptr || params == nullptr) return;
|
|
||||||
GLsizei written = 0;
|
|
||||||
for (GLsizei i = 0; i < propCount && written < bufSize; ++i) {
|
|
||||||
GLint value = 0;
|
|
||||||
if (!GetUniformResourceProp(programObject, index, props[i], &value)) {
|
|
||||||
// GL_ATOMIC_COUNTER_BUFFER_INDEX and the GL_REFERENCED_BY_* stage props are
|
|
||||||
// not modelled here; ask the backend, which indexes resources by name.
|
|
||||||
auto backendGetIndex = MG_Backend::gBackendFunctionsTable.GL.GetProgramResourceIndex;
|
|
||||||
auto backendGetiv = MG_Backend::gBackendFunctionsTable.GL.GetProgramResourceiv;
|
|
||||||
if (backendGetIndex && backendGetiv) {
|
|
||||||
const GLuint backendIndex = backendGetIndex(program, GL_UNIFORM,
|
|
||||||
programObject->GetActiveUniformName(index).c_str());
|
|
||||||
if (backendIndex != GL_INVALID_INDEX) {
|
|
||||||
GLsizei one = 0;
|
|
||||||
backendGetiv(program, GL_UNIFORM, backendIndex, 1, &props[i], 1, &one, &value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
params[written++] = value;
|
|
||||||
}
|
|
||||||
if (length) *length = written;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
auto getProgramResourceiv = MG_Backend::gBackendFunctionsTable.GL.GetProgramResourceiv;
|
auto getProgramResourceiv = MG_Backend::gBackendFunctionsTable.GL.GetProgramResourceiv;
|
||||||
if (!getProgramResourceiv) {
|
if (!getProgramResourceiv) {
|
||||||
MG_State::pGLContext->RecordError(
|
MG_State::pGLContext->RecordError(
|
||||||
@@ -2741,189 +2217,4 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
void ValidateProgram(GLuint program) {
|
void ValidateProgram(GLuint program) {
|
||||||
ValidateProgram_State(program);
|
ValidateProgram_State(program);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ARB_get_program_binary with no supported binary format (GL_NUM_PROGRAM_BINARY_FORMATS
|
|
||||||
// is 0, which the extension explicitly allows). The three entry points below are what an
|
|
||||||
// application - and dEQP's function loader - reach through the extension; without it
|
|
||||||
// glProgramParameteri is not exposed in a 4.0 context at all.
|
|
||||||
void ProgramParameteri(GLuint program, GLenum pname, GLint value) {
|
|
||||||
auto& programObject = TryToGetProgramObject(program);
|
|
||||||
if (!programObject) return;
|
|
||||||
if (pname != GL_PROGRAM_BINARY_RETRIEVABLE_HINT && pname != GL_PROGRAM_SEPARABLE) {
|
|
||||||
MG_State::pGLContext->RecordError(
|
|
||||||
ErrorCode::InvalidEnum,
|
|
||||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "pname is not an accepted value."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (value != GL_TRUE && value != GL_FALSE) {
|
|
||||||
MG_State::pGLContext->RecordError(
|
|
||||||
ErrorCode::InvalidValue,
|
|
||||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "value must be GL_TRUE or GL_FALSE."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (pname == GL_PROGRAM_SEPARABLE) {
|
|
||||||
programObject->SetSeparable(value == GL_TRUE);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
programObject->SetBinaryRetrievableHint(value == GL_TRUE);
|
|
||||||
}
|
|
||||||
|
|
||||||
// GL 4.6 core 7.3: glCreateShaderProgramv is defined as the exact sequence below, so it
|
|
||||||
// is written as that sequence rather than as a private shortcut - every error it can
|
|
||||||
// raise is one of theirs, raised at the point they would raise it.
|
|
||||||
GLuint CreateShaderProgramv(GLenum type, GLsizei count, const GLchar* const* strings) {
|
|
||||||
const GLuint shader = CreateShader_State(type);
|
|
||||||
if (shader == 0) return 0;
|
|
||||||
|
|
||||||
ShaderSource_State(shader, count, strings, nullptr);
|
|
||||||
CompileShader_State(shader);
|
|
||||||
|
|
||||||
const GLuint program = CreateProgram_State();
|
|
||||||
if (program != 0) {
|
|
||||||
const auto& shaderObject = MG_State::pGLContext->GetShaderObject(shader);
|
|
||||||
const auto& programObject = MG_State::pGLContext->GetProgramObject(program);
|
|
||||||
// The program is separable whether or not the shader compiled: a failed
|
|
||||||
// compile leaves an unlinked but otherwise well-formed separable program.
|
|
||||||
if (programObject) programObject->SetSeparable(true);
|
|
||||||
if (shaderObject && programObject && shaderObject->GetCompileStatus()) {
|
|
||||||
AttachShader_State(program, shader);
|
|
||||||
// Not LinkProgram_State: that injects a default fragment shader into a
|
|
||||||
// program that has none, which is exactly wrong for a separable
|
|
||||||
// vertex-stage program - the pipeline supplies the real one.
|
|
||||||
programObject->Link(false);
|
|
||||||
// glDetachShader defers the removal to the next link, so the program keeps
|
|
||||||
// the shader object it was built from while no longer reporting it attached.
|
|
||||||
DetachShader_State(program, shader);
|
|
||||||
}
|
|
||||||
if (shaderObject && programObject && !shaderObject->GetInfoLog().empty()) {
|
|
||||||
programObject->AppendInfoLog(shaderObject->GetInfoLog());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DeleteShader_State(shader);
|
|
||||||
return program;
|
|
||||||
}
|
|
||||||
|
|
||||||
void GetProgramBinary(GLuint program, GLsizei bufSize, GLsizei* length, GLenum* binaryFormat, void* binary) {
|
|
||||||
(void)binaryFormat;
|
|
||||||
(void)binary;
|
|
||||||
auto& programObject = TryToGetProgramObject(program);
|
|
||||||
if (!programObject) return;
|
|
||||||
if (bufSize < 0) {
|
|
||||||
MG_State::pGLContext->RecordError(
|
|
||||||
ErrorCode::InvalidValue,
|
|
||||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "bufSize must be non-negative."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (length) *length = 0;
|
|
||||||
// GL_PROGRAM_BINARY_LENGTH is always zero here, which the spec makes an error to ask for.
|
|
||||||
MG_State::pGLContext->RecordError(
|
|
||||||
ErrorCode::InvalidOperation,
|
|
||||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "The program has no retrievable binary."));
|
|
||||||
}
|
|
||||||
|
|
||||||
void ProgramBinary(GLuint program, GLenum binaryFormat, const void* binary, GLsizei length) {
|
|
||||||
(void)binaryFormat;
|
|
||||||
(void)binary;
|
|
||||||
auto& programObject = TryToGetProgramObject(program);
|
|
||||||
if (!programObject) return;
|
|
||||||
if (length < 0) {
|
|
||||||
MG_State::pGLContext->RecordError(
|
|
||||||
ErrorCode::InvalidValue,
|
|
||||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "length must be non-negative."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// No format is supported, so every binary is rejected - and the program's link status
|
|
||||||
// has to read FALSE afterwards.
|
|
||||||
programObject->MarkLinkFailedByProgramBinary();
|
|
||||||
MG_State::pGLContext->RecordError(
|
|
||||||
ErrorCode::InvalidEnum,
|
|
||||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "binaryFormat is not a supported format."));
|
|
||||||
}
|
|
||||||
|
|
||||||
void TransformFeedbackVaryings(GLuint program, GLsizei count, const GLchar* const* varyings, GLenum bufferMode) {
|
|
||||||
auto& programObject = TryToGetProgramObject(program);
|
|
||||||
if (!programObject) return;
|
|
||||||
if (bufferMode != GL_INTERLEAVED_ATTRIBS && bufferMode != GL_SEPARATE_ATTRIBS) {
|
|
||||||
MG_State::pGLContext->RecordError(
|
|
||||||
ErrorCode::InvalidEnum,
|
|
||||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "bufferMode is not a valid capture mode."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (count < 0) {
|
|
||||||
MG_State::pGLContext->RecordError(
|
|
||||||
ErrorCode::InvalidValue,
|
|
||||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "count must be non-negative."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// GL 3.3 core: SEPARATE_ATTRIBS count may not exceed the separate-attrib limit.
|
|
||||||
if (bufferMode == GL_SEPARATE_ATTRIBS && count > 4) {
|
|
||||||
MG_State::pGLContext->RecordError(
|
|
||||||
ErrorCode::InvalidValue,
|
|
||||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
|
||||||
"count exceeds GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Vector<String> names;
|
|
||||||
names.reserve(static_cast<SizeT>(count));
|
|
||||||
for (GLsizei i = 0; i < count; ++i) {
|
|
||||||
names.emplace_back(varyings != nullptr && varyings[i] != nullptr ? varyings[i] : "");
|
|
||||||
}
|
|
||||||
// ARB_transform_feedback3's special names only mean anything in an interleaved
|
|
||||||
// capture, and gl_NextBuffer cannot advance past the last capture buffer.
|
|
||||||
constexpr Uint maxTransformFeedbackBuffers = 4;
|
|
||||||
Uint nextBufferCount = 0;
|
|
||||||
for (const String& name : names) {
|
|
||||||
const Bool isNextBuffer = name == "gl_NextBuffer";
|
|
||||||
const Bool isSkipComponents = name.size() == 18 && name.compare(0, 17, "gl_SkipComponents") == 0 &&
|
|
||||||
name[17] >= '1' && name[17] <= '4';
|
|
||||||
if (!isNextBuffer && !isSkipComponents) continue;
|
|
||||||
if (bufferMode != GL_INTERLEAVED_ATTRIBS) {
|
|
||||||
MG_State::pGLContext->RecordError(
|
|
||||||
ErrorCode::InvalidOperation,
|
|
||||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
|
||||||
"'" + name + "' requires GL_INTERLEAVED_ATTRIBS."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (isNextBuffer && ++nextBufferCount >= maxTransformFeedbackBuffers) {
|
|
||||||
MG_State::pGLContext->RecordError(
|
|
||||||
ErrorCode::InvalidOperation,
|
|
||||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
|
||||||
"More gl_NextBuffer entries than "
|
|
||||||
"GL_MAX_TRANSFORM_FEEDBACK_BUFFERS allows."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
programObject->SetTransformFeedbackVaryings(Move(names), bufferMode);
|
|
||||||
}
|
|
||||||
|
|
||||||
void GetTransformFeedbackVarying(GLuint program, GLuint index, GLsizei bufSize, GLsizei* length, GLsizei* size,
|
|
||||||
GLenum* type, GLchar* name) {
|
|
||||||
auto& programObject = TryToGetProgramObject(program);
|
|
||||||
if (!programObject) return;
|
|
||||||
if (!programObject->GetLinkStatus()) {
|
|
||||||
MG_State::pGLContext->RecordError(
|
|
||||||
ErrorCode::InvalidOperation,
|
|
||||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
|
||||||
std::to_string(program) + " has not been successfully linked."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const auto* varying = programObject->GetTransformFeedbackVarying(index);
|
|
||||||
if (varying == nullptr) {
|
|
||||||
MG_State::pGLContext->RecordError(
|
|
||||||
ErrorCode::InvalidValue,
|
|
||||||
MakeUnique<GenericErrorInfo>(
|
|
||||||
"MG_Impl/GLImpl", __func__,
|
|
||||||
"index is not an active transform feedback varying of the program."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (size != nullptr) *size = varying->size;
|
|
||||||
if (type != nullptr) *type = varying->type;
|
|
||||||
GLsizei written = 0;
|
|
||||||
if (name != nullptr && bufSize > 0) {
|
|
||||||
written = std::min<GLsizei>(bufSize - 1, static_cast<GLsizei>(varying->name.size()));
|
|
||||||
Memcpy(name, varying->name.data(), static_cast<SizeT>(written));
|
|
||||||
name[written] = '\0';
|
|
||||||
}
|
|
||||||
if (length != nullptr) *length = written;
|
|
||||||
}
|
|
||||||
} // namespace MobileGL::MG_Impl::GLImpl
|
} // namespace MobileGL::MG_Impl::GLImpl
|
||||||
|
|||||||
@@ -137,47 +137,5 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
GLint GetProgramResourceLocation(GLuint program, GLenum programInterface, const GLchar* name);
|
GLint GetProgramResourceLocation(GLuint program, GLenum programInterface, const GLchar* name);
|
||||||
GLint GetProgramResourceLocationIndex(GLuint program, GLenum programInterface, const GLchar* name);
|
GLint GetProgramResourceLocationIndex(GLuint program, GLenum programInterface, const GLchar* name);
|
||||||
void ShaderStorageBlockBinding(GLuint program, GLuint storageBlockIndex, GLuint storageBlockBinding);
|
void ShaderStorageBlockBinding(GLuint program, GLuint storageBlockIndex, GLuint storageBlockBinding);
|
||||||
void Uniform1d(GLint location, GLdouble v0);
|
|
||||||
void Uniform1dv(GLint location, GLsizei count, const GLdouble* value);
|
|
||||||
void ProgramUniform1d(GLuint program, GLint location, GLdouble v0);
|
|
||||||
void ProgramUniform1dv(GLuint program, GLint location, GLsizei count, const GLdouble* value);
|
|
||||||
void Uniform2d(GLint location, GLdouble v0, GLdouble v1);
|
|
||||||
void Uniform2dv(GLint location, GLsizei count, const GLdouble* value);
|
|
||||||
void ProgramUniform2d(GLuint program, GLint location, GLdouble v0, GLdouble v1);
|
|
||||||
void ProgramUniform2dv(GLuint program, GLint location, GLsizei count, const GLdouble* value);
|
|
||||||
void Uniform3d(GLint location, GLdouble v0, GLdouble v1, GLdouble v2);
|
|
||||||
void Uniform3dv(GLint location, GLsizei count, const GLdouble* value);
|
|
||||||
void ProgramUniform3d(GLuint program, GLint location, GLdouble v0, GLdouble v1, GLdouble v2);
|
|
||||||
void ProgramUniform3dv(GLuint program, GLint location, GLsizei count, const GLdouble* value);
|
|
||||||
void Uniform4d(GLint location, GLdouble v0, GLdouble v1, GLdouble v2, GLdouble v3);
|
|
||||||
void Uniform4dv(GLint location, GLsizei count, const GLdouble* value);
|
|
||||||
void ProgramUniform4d(GLuint program, GLint location, GLdouble v0, GLdouble v1, GLdouble v2, GLdouble v3);
|
|
||||||
void ProgramUniform4dv(GLuint program, GLint location, GLsizei count, const GLdouble* value);
|
|
||||||
void UniformMatrix2dv(GLint location, GLsizei count, GLboolean transpose, const GLdouble* value);
|
|
||||||
void ProgramUniformMatrix2dv(GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value);
|
|
||||||
void UniformMatrix3dv(GLint location, GLsizei count, GLboolean transpose, const GLdouble* value);
|
|
||||||
void ProgramUniformMatrix3dv(GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value);
|
|
||||||
void UniformMatrix4dv(GLint location, GLsizei count, GLboolean transpose, const GLdouble* value);
|
|
||||||
void ProgramUniformMatrix4dv(GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value);
|
|
||||||
void UniformMatrix2x3dv(GLint location, GLsizei count, GLboolean transpose, const GLdouble* value);
|
|
||||||
void ProgramUniformMatrix2x3dv(GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value);
|
|
||||||
void UniformMatrix2x4dv(GLint location, GLsizei count, GLboolean transpose, const GLdouble* value);
|
|
||||||
void ProgramUniformMatrix2x4dv(GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value);
|
|
||||||
void UniformMatrix3x2dv(GLint location, GLsizei count, GLboolean transpose, const GLdouble* value);
|
|
||||||
void ProgramUniformMatrix3x2dv(GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value);
|
|
||||||
void UniformMatrix3x4dv(GLint location, GLsizei count, GLboolean transpose, const GLdouble* value);
|
|
||||||
void ProgramUniformMatrix3x4dv(GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value);
|
|
||||||
void UniformMatrix4x2dv(GLint location, GLsizei count, GLboolean transpose, const GLdouble* value);
|
|
||||||
void ProgramUniformMatrix4x2dv(GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value);
|
|
||||||
void UniformMatrix4x3dv(GLint location, GLsizei count, GLboolean transpose, const GLdouble* value);
|
|
||||||
void ProgramUniformMatrix4x3dv(GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value);
|
|
||||||
void GetUniformdv(GLuint program, GLint location, GLdouble* params);
|
|
||||||
void ValidateProgram(GLuint program);
|
void ValidateProgram(GLuint program);
|
||||||
void ProgramParameteri(GLuint program, GLenum pname, GLint value);
|
|
||||||
GLuint CreateShaderProgramv(GLenum type, GLsizei count, const GLchar* const* strings);
|
|
||||||
void GetProgramBinary(GLuint program, GLsizei bufSize, GLsizei* length, GLenum* binaryFormat, void* binary);
|
|
||||||
void ProgramBinary(GLuint program, GLenum binaryFormat, const void* binary, GLsizei length);
|
|
||||||
void TransformFeedbackVaryings(GLuint program, GLsizei count, const GLchar* const* varyings, GLenum bufferMode);
|
|
||||||
void GetTransformFeedbackVarying(GLuint program, GLuint index, GLsizei bufSize, GLsizei* length, GLsizei* size,
|
|
||||||
GLenum* type, GLchar* name);
|
|
||||||
} // namespace MobileGL::MG_Impl::GLImpl
|
} // namespace MobileGL::MG_Impl::GLImpl
|
||||||
|
|||||||
@@ -1,231 +0,0 @@
|
|||||||
// 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
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
// 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
|
|
||||||
@@ -7,7 +7,6 @@
|
|||||||
// End of Source File Header
|
// End of Source File Header
|
||||||
|
|
||||||
#include "GL_Query.h"
|
#include "GL_Query.h"
|
||||||
#include "../Getter/GL_Getter.h"
|
|
||||||
#include <Config.h>
|
#include <Config.h>
|
||||||
#include <MG_Backend/BackendObjects.h>
|
#include <MG_Backend/BackendObjects.h>
|
||||||
#include <MG_State/GLState/Core.h>
|
#include <MG_State/GLState/Core.h>
|
||||||
@@ -23,16 +22,11 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
struct QueryObject {
|
struct QueryObject {
|
||||||
GLuint id = 0;
|
GLuint id = 0;
|
||||||
GLenum target = 0; // 0 = gen'd but never used with BeginQuery/QueryCounter
|
GLenum target = 0; // 0 = gen'd but never used with BeginQuery/QueryCounter
|
||||||
// glCreateQueries makes the object outright; glGenQueries only reserves the name,
|
|
||||||
// and the object appears when the name is first used (GL 4.6 core 4.2.1).
|
|
||||||
Bool created = false;
|
|
||||||
MG_Backend::BackendQueryHandle backendHandle = nullptr;
|
MG_Backend::BackendQueryHandle backendHandle = nullptr;
|
||||||
Bool active = false;
|
Bool active = false;
|
||||||
Bool ended = false;
|
Bool ended = false;
|
||||||
Bool resultCached = false;
|
Bool resultCached = false;
|
||||||
Uint64 cachedResult = 0;
|
Uint64 cachedResult = 0;
|
||||||
// Transform feedback primitive counter at BeginQuery time.
|
|
||||||
Uint64 counterSnapshot = 0;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Query calls may arrive from any thread (launchers migrate the context
|
// Query calls may arrive from any thread (launchers migrate the context
|
||||||
@@ -47,11 +41,6 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
GLuint g_nextQueryId = 1;
|
GLuint g_nextQueryId = 1;
|
||||||
// Id of the query currently active on GL_TIME_ELAPSED (0 = none).
|
// Id of the query currently active on GL_TIME_ELAPSED (0 = none).
|
||||||
GLuint g_activeTimeElapsedQueryId = 0;
|
GLuint g_activeTimeElapsedQueryId = 0;
|
||||||
// Ids of the queries active on the transform feedback targets (0 = none).
|
|
||||||
GLuint g_activePrimitivesWrittenQueryId = 0;
|
|
||||||
GLuint g_activePrimitivesGeneratedQueryId = 0;
|
|
||||||
// Id of the query active on GL_SAMPLES_PASSED (0 = none).
|
|
||||||
GLuint g_activeSamplesPassedQueryId = 0;
|
|
||||||
|
|
||||||
Bool TimerQueryDisabled() {
|
Bool TimerQueryDisabled() {
|
||||||
return MG_Config::Features.DisableTimerQuery;
|
return MG_Config::Features.DisableTimerQuery;
|
||||||
@@ -62,34 +51,6 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", function, message));
|
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.
|
// Callers must hold g_queryObjectsMutex.
|
||||||
QueryObject* FindQueryObjectLocked(GLuint id) {
|
QueryObject* FindQueryObjectLocked(GLuint id) {
|
||||||
const auto it = g_liveQueryObjects.find(id);
|
const auto it = g_liveQueryObjects.find(id);
|
||||||
@@ -123,13 +84,8 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Shared GetQueryObject* implementation. Returns false when an error
|
// Shared GetQueryObject* implementation. Returns false when an error
|
||||||
// was recorded and no value should be written back. `outValueProduced`, when given,
|
// was recorded and no value should be written back.
|
||||||
// additionally distinguishes "succeeded with a value" from "succeeded but the result is not
|
Bool GetQueryObjectValue(GLuint id, GLenum pname, const char* function, Uint64& outValue) {
|
||||||
// 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);
|
const std::lock_guard<std::mutex> lock(g_queryObjectsMutex);
|
||||||
auto* queryObject = FindQueryObjectLocked(id);
|
auto* queryObject = FindQueryObjectLocked(id);
|
||||||
if (!queryObject) {
|
if (!queryObject) {
|
||||||
@@ -142,41 +98,6 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch (pname) {
|
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: {
|
case GL_QUERY_RESULT_AVAILABLE: {
|
||||||
if (queryObject->resultCached || !queryObject->backendHandle) {
|
if (queryObject->resultCached || !queryObject->backendHandle) {
|
||||||
outValue = 1;
|
outValue = 1;
|
||||||
@@ -205,11 +126,6 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
outValue = 0;
|
outValue = 0;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
// ANY_SAMPLES_PASSED* report a boolean.
|
|
||||||
if (queryObject->target == GL_ANY_SAMPLES_PASSED ||
|
|
||||||
queryObject->target == GL_ANY_SAMPLES_PASSED_CONSERVATIVE) {
|
|
||||||
result = result != 0 ? 1 : 0;
|
|
||||||
}
|
|
||||||
// Final value produced (or no GetQueryResult64 hook: the
|
// Final value produced (or no GetQueryResult64 hook: the
|
||||||
// query degrades to a zero result); the backend handle is
|
// query degrades to a zero result); the backend handle is
|
||||||
// consumed and the value cached for later reads.
|
// consumed and the value cached for later reads.
|
||||||
@@ -228,21 +144,6 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
return false;
|
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
|
} // namespace
|
||||||
|
|
||||||
void GenQueries(GLsizei n, GLuint* ids) {
|
void GenQueries(GLsizei n, GLuint* ids) {
|
||||||
@@ -263,41 +164,6 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// glCreateQueries differs from glGenQueries in creating the objects outright, with their
|
|
||||||
// target already fixed and the rest of their state at the defaults (GL 4.6 core 4.2.1).
|
|
||||||
void CreateQueries(GLenum target, GLsizei n, GLuint* ids) {
|
|
||||||
switch (target) {
|
|
||||||
case GL_SAMPLES_PASSED:
|
|
||||||
case GL_ANY_SAMPLES_PASSED:
|
|
||||||
case GL_ANY_SAMPLES_PASSED_CONSERVATIVE:
|
|
||||||
case GL_TIME_ELAPSED:
|
|
||||||
case GL_TIMESTAMP:
|
|
||||||
case GL_PRIMITIVES_GENERATED:
|
|
||||||
case GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN:
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
RecordQueryError(ErrorCode::InvalidEnum, __FUNCTION__, "Query target is not accepted.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (n < 0) {
|
|
||||||
RecordQueryError(ErrorCode::InvalidValue, __FUNCTION__, "n cannot be negative.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!ids) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const std::lock_guard<std::mutex> lock(g_queryObjectsMutex);
|
|
||||||
for (GLsizei i = 0; i < n; ++i) {
|
|
||||||
const GLuint id = g_nextQueryId++;
|
|
||||||
auto* queryObject = new QueryObject;
|
|
||||||
queryObject->id = id;
|
|
||||||
queryObject->target = target;
|
|
||||||
queryObject->created = true;
|
|
||||||
g_liveQueryObjects[id] = queryObject;
|
|
||||||
ids[i] = id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void DeleteQueries(GLsizei n, const GLuint* ids) {
|
void DeleteQueries(GLsizei n, const GLuint* ids) {
|
||||||
if (n < 0) {
|
if (n < 0) {
|
||||||
RecordQueryError(ErrorCode::InvalidValue, __FUNCTION__, "n cannot be negative.");
|
RecordQueryError(ErrorCode::InvalidValue, __FUNCTION__, "n cannot be negative.");
|
||||||
@@ -314,24 +180,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
}
|
}
|
||||||
QueryObject* queryObject = it->second;
|
QueryObject* queryObject = it->second;
|
||||||
if (queryObject->active) {
|
if (queryObject->active) {
|
||||||
// Implicitly end before deletion, releasing the matching active slot.
|
EndTimeElapsedQueryLocked(queryObject); // implicitly end before deletion
|
||||||
if (queryObject->target == GL_SAMPLES_PASSED || queryObject->target == GL_ANY_SAMPLES_PASSED ||
|
|
||||||
queryObject->target == GL_ANY_SAMPLES_PASSED_CONSERVATIVE) {
|
|
||||||
if (const auto endOcclusionQuery = MG_Backend::gBackendFunctionsTable.GL.EndOcclusionQuery;
|
|
||||||
endOcclusionQuery && queryObject->backendHandle) {
|
|
||||||
endOcclusionQuery(queryObject->backendHandle);
|
|
||||||
}
|
|
||||||
queryObject->active = false;
|
|
||||||
g_activeSamplesPassedQueryId = 0;
|
|
||||||
} else if (queryObject->target == GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN ||
|
|
||||||
queryObject->target == GL_PRIMITIVES_GENERATED) {
|
|
||||||
queryObject->active = false;
|
|
||||||
(queryObject->target == GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN
|
|
||||||
? g_activePrimitivesWrittenQueryId
|
|
||||||
: g_activePrimitivesGeneratedQueryId) = 0;
|
|
||||||
} else {
|
|
||||||
EndTimeElapsedQueryLocked(queryObject);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (queryObject->backendHandle) {
|
if (queryObject->backendHandle) {
|
||||||
if (const auto deleteBackendQuery = MG_Backend::gBackendFunctionsTable.GL.DeleteBackendQuery) {
|
if (const auto deleteBackendQuery = MG_Backend::gBackendFunctionsTable.GL.DeleteBackendQuery) {
|
||||||
@@ -349,23 +198,16 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
return GL_FALSE;
|
return GL_FALSE;
|
||||||
}
|
}
|
||||||
const std::lock_guard<std::mutex> lock(g_queryObjectsMutex);
|
const std::lock_guard<std::mutex> lock(g_queryObjectsMutex);
|
||||||
// A name from glGenQueries is not yet a query object: it becomes one when it is first
|
// Gen'd ids count as query objects here: the registry creates live
|
||||||
// used with BeginQuery/QueryCounter (which is what a non-zero target records), or
|
// objects at GenQueries time.
|
||||||
// immediately if it came from glCreateQueries.
|
return FindQueryObjectLocked(id) != nullptr ? GL_TRUE : GL_FALSE;
|
||||||
const auto* queryObject = FindQueryObjectLocked(id);
|
|
||||||
return (queryObject != nullptr && (queryObject->created || queryObject->target != 0)) ? GL_TRUE : GL_FALSE;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void BeginQuery(GLenum target, GLuint id) {
|
void BeginQuery(GLenum target, GLuint id) {
|
||||||
const Bool isTransformFeedbackQuery =
|
if (target != GL_TIME_ELAPSED) {
|
||||||
target == GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN || target == GL_PRIMITIVES_GENERATED;
|
// Only GL_TIME_ELAPSED timer queries are implemented (occlusion and
|
||||||
const Bool isOcclusionQuery =
|
// primitive queries remain stubs); GL_TIMESTAMP is not a valid
|
||||||
(target == GL_SAMPLES_PASSED || target == GL_ANY_SAMPLES_PASSED ||
|
// BeginQuery target either.
|
||||||
target == GL_ANY_SAMPLES_PASSED_CONSERVATIVE) &&
|
|
||||||
MG_Backend::gBackendFunctionsTable.GL.BeginOcclusionQuery != nullptr;
|
|
||||||
if (target != GL_TIME_ELAPSED && !isTransformFeedbackQuery && !isOcclusionQuery) {
|
|
||||||
// GL_TIMESTAMP is not a valid BeginQuery target; the occlusion targets
|
|
||||||
// need backend support.
|
|
||||||
RecordQueryError(ErrorCode::InvalidEnum, __FUNCTION__, "Query target is not supported.");
|
RecordQueryError(ErrorCode::InvalidEnum, __FUNCTION__, "Query target is not supported.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -379,13 +221,9 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
RecordQueryError(ErrorCode::InvalidOperation, __FUNCTION__, "Query object does not exist.");
|
RecordQueryError(ErrorCode::InvalidOperation, __FUNCTION__, "Query object does not exist.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
GLuint& activeQueryId = isTransformFeedbackQuery
|
if (g_activeTimeElapsedQueryId != 0) {
|
||||||
? (target == GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN ? g_activePrimitivesWrittenQueryId
|
|
||||||
: g_activePrimitivesGeneratedQueryId)
|
|
||||||
: (isOcclusionQuery ? g_activeSamplesPassedQueryId : g_activeTimeElapsedQueryId);
|
|
||||||
if (activeQueryId != 0) {
|
|
||||||
RecordQueryError(ErrorCode::InvalidOperation, __FUNCTION__,
|
RecordQueryError(ErrorCode::InvalidOperation, __FUNCTION__,
|
||||||
"A query is already active on this target.");
|
"A query is already active on GL_TIME_ELAPSED.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (queryObject->active) {
|
if (queryObject->active) {
|
||||||
@@ -401,72 +239,25 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
ResetQueryObjectLocked(queryObject); // discard any previous result
|
ResetQueryObjectLocked(queryObject); // discard any previous result
|
||||||
queryObject->target = target;
|
queryObject->target = target;
|
||||||
queryObject->active = true;
|
queryObject->active = true;
|
||||||
if (isTransformFeedbackQuery) {
|
const auto beginTimeElapsedQuery = MG_Backend::gBackendFunctionsTable.GL.BeginTimeElapsedQuery;
|
||||||
// Prefer real GPU transform-feedback queries (exact with geometry shaders);
|
queryObject->backendHandle =
|
||||||
// the CPU accounting delta stays as the fallback when the backend lacks them.
|
(!TimerQueryDisabled() && beginTimeElapsedQuery) ? beginTimeElapsedQuery() : nullptr;
|
||||||
const auto beginXfbPrimitivesQuery = MG_Backend::gBackendFunctionsTable.GL.BeginXfbPrimitivesQuery;
|
g_activeTimeElapsedQueryId = id;
|
||||||
queryObject->backendHandle =
|
|
||||||
beginXfbPrimitivesQuery ? beginXfbPrimitivesQuery(target == GL_PRIMITIVES_GENERATED) : nullptr;
|
|
||||||
queryObject->counterSnapshot = MG_State::pGLContext->GetTransformFeedbackPrimitiveCounter();
|
|
||||||
} else if (isOcclusionQuery) {
|
|
||||||
queryObject->backendHandle = MG_Backend::gBackendFunctionsTable.GL.BeginOcclusionQuery();
|
|
||||||
} else {
|
|
||||||
const auto beginTimeElapsedQuery = MG_Backend::gBackendFunctionsTable.GL.BeginTimeElapsedQuery;
|
|
||||||
queryObject->backendHandle =
|
|
||||||
(!TimerQueryDisabled() && beginTimeElapsedQuery) ? beginTimeElapsedQuery() : nullptr;
|
|
||||||
}
|
|
||||||
activeQueryId = id;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void EndQuery(GLenum target) {
|
void EndQuery(GLenum target) {
|
||||||
const Bool isTransformFeedbackQuery =
|
if (target != GL_TIME_ELAPSED) {
|
||||||
target == GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN || target == GL_PRIMITIVES_GENERATED;
|
|
||||||
const Bool isOcclusionQuery =
|
|
||||||
(target == GL_SAMPLES_PASSED || target == GL_ANY_SAMPLES_PASSED ||
|
|
||||||
target == GL_ANY_SAMPLES_PASSED_CONSERVATIVE) &&
|
|
||||||
MG_Backend::gBackendFunctionsTable.GL.BeginOcclusionQuery != nullptr;
|
|
||||||
if (target != GL_TIME_ELAPSED && !isTransformFeedbackQuery && !isOcclusionQuery) {
|
|
||||||
RecordQueryError(ErrorCode::InvalidEnum, __FUNCTION__, "Query target is not supported.");
|
RecordQueryError(ErrorCode::InvalidEnum, __FUNCTION__, "Query target is not supported.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const std::lock_guard<std::mutex> lock(g_queryObjectsMutex);
|
const std::lock_guard<std::mutex> lock(g_queryObjectsMutex);
|
||||||
GLuint& activeQueryId = isTransformFeedbackQuery
|
if (g_activeTimeElapsedQueryId == 0) {
|
||||||
? (target == GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN ? g_activePrimitivesWrittenQueryId
|
RecordQueryError(ErrorCode::InvalidOperation, __FUNCTION__, "No query is active on GL_TIME_ELAPSED.");
|
||||||
: g_activePrimitivesGeneratedQueryId)
|
|
||||||
: (isOcclusionQuery ? g_activeSamplesPassedQueryId : g_activeTimeElapsedQueryId);
|
|
||||||
if (activeQueryId == 0) {
|
|
||||||
RecordQueryError(ErrorCode::InvalidOperation, __FUNCTION__, "No query is active on this target.");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
auto* queryObject = FindQueryObjectLocked(activeQueryId);
|
auto* queryObject = FindQueryObjectLocked(g_activeTimeElapsedQueryId);
|
||||||
if (!queryObject) {
|
if (!queryObject) {
|
||||||
activeQueryId = 0; // should not happen; keep state consistent
|
g_activeTimeElapsedQueryId = 0; // should not happen; keep state consistent
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (isTransformFeedbackQuery) {
|
|
||||||
if (queryObject->backendHandle) {
|
|
||||||
if (const auto endXfbPrimitivesQuery = MG_Backend::gBackendFunctionsTable.GL.EndXfbPrimitivesQuery) {
|
|
||||||
endXfbPrimitivesQuery(queryObject->backendHandle);
|
|
||||||
}
|
|
||||||
// Result comes from the GPU query at read time.
|
|
||||||
} else {
|
|
||||||
queryObject->cachedResult =
|
|
||||||
MG_State::pGLContext->GetTransformFeedbackPrimitiveCounter() - queryObject->counterSnapshot;
|
|
||||||
queryObject->resultCached = true;
|
|
||||||
}
|
|
||||||
queryObject->active = false;
|
|
||||||
queryObject->ended = true;
|
|
||||||
activeQueryId = 0;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (isOcclusionQuery) {
|
|
||||||
if (const auto endOcclusionQuery = MG_Backend::gBackendFunctionsTable.GL.EndOcclusionQuery;
|
|
||||||
endOcclusionQuery && queryObject->backendHandle) {
|
|
||||||
endOcclusionQuery(queryObject->backendHandle);
|
|
||||||
}
|
|
||||||
queryObject->active = false;
|
|
||||||
queryObject->ended = true;
|
|
||||||
activeQueryId = 0;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
EndTimeElapsedQueryLocked(queryObject);
|
EndTimeElapsedQueryLocked(queryObject);
|
||||||
@@ -512,25 +303,9 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
switch (pname) {
|
switch (pname) {
|
||||||
case GL_CURRENT_QUERY: {
|
case GL_CURRENT_QUERY: {
|
||||||
const std::lock_guard<std::mutex> lock(g_queryObjectsMutex);
|
const std::lock_guard<std::mutex> lock(g_queryObjectsMutex);
|
||||||
switch (target) {
|
// Only GL_TIME_ELAPSED queries can be active; GL_TIMESTAMP queries
|
||||||
case GL_TIME_ELAPSED:
|
// never are, and other targets remain unimplemented.
|
||||||
*params = static_cast<GLint>(g_activeTimeElapsedQueryId);
|
*params = target == GL_TIME_ELAPSED ? static_cast<GLint>(g_activeTimeElapsedQueryId) : 0;
|
||||||
break;
|
|
||||||
case GL_SAMPLES_PASSED:
|
|
||||||
case GL_ANY_SAMPLES_PASSED:
|
|
||||||
case GL_ANY_SAMPLES_PASSED_CONSERVATIVE:
|
|
||||||
*params = static_cast<GLint>(g_activeSamplesPassedQueryId);
|
|
||||||
break;
|
|
||||||
case GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN:
|
|
||||||
*params = static_cast<GLint>(g_activePrimitivesWrittenQueryId);
|
|
||||||
break;
|
|
||||||
case GL_PRIMITIVES_GENERATED:
|
|
||||||
*params = static_cast<GLint>(g_activePrimitivesGeneratedQueryId);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
*params = 0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
case GL_QUERY_COUNTER_BITS: {
|
case GL_QUERY_COUNTER_BITS: {
|
||||||
@@ -538,13 +313,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
// time: IsTimerQuerySupported is the dynamic truth (extension /
|
// time: IsTimerQuerySupported is the dynamic truth (extension /
|
||||||
// entry points / timestamp valid bits at call time, not at table
|
// entry points / timestamp valid bits at call time, not at table
|
||||||
// init), and the MOBILEGL_DISABLE_TIMERQUERY kill switch always
|
// init), and the MOBILEGL_DISABLE_TIMERQUERY kill switch always
|
||||||
// wins.
|
// wins. Non-timer targets remain unimplemented and report 0.
|
||||||
if (target == GL_SAMPLES_PASSED || target == GL_ANY_SAMPLES_PASSED ||
|
|
||||||
target == GL_ANY_SAMPLES_PASSED_CONSERVATIVE) {
|
|
||||||
const Bool occlusionSupported = MG_Backend::gBackendFunctionsTable.GL.BeginOcclusionQuery != nullptr;
|
|
||||||
*params = occlusionSupported ? (target == GL_SAMPLES_PASSED ? 32 : 1) : 0;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const Bool timerTarget = target == GL_TIME_ELAPSED || target == GL_TIMESTAMP;
|
const Bool timerTarget = target == GL_TIME_ELAPSED || target == GL_TIMESTAMP;
|
||||||
const auto isTimerQuerySupported = MG_Backend::gBackendFunctionsTable.GL.IsTimerQuerySupported;
|
const auto isTimerQuerySupported = MG_Backend::gBackendFunctionsTable.GL.IsTimerQuerySupported;
|
||||||
const Bool supported =
|
const Bool supported =
|
||||||
@@ -558,26 +327,9 @@ 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) {
|
void GetQueryObjectiv(GLuint id, GLenum pname, GLint* params) {
|
||||||
Uint64 value = 0;
|
Uint64 value = 0;
|
||||||
Bool valueProduced = false;
|
if (!GetQueryObjectValue(id, pname, __FUNCTION__, value) || !params) {
|
||||||
if (!GetQueryObjectValue(id, pname, __FUNCTION__, value, &valueProduced) || !valueProduced || !params) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
constexpr Uint64 kMaxInt = static_cast<Uint64>(INT_MAX);
|
constexpr Uint64 kMaxInt = static_cast<Uint64>(INT_MAX);
|
||||||
@@ -586,8 +338,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
|
|
||||||
void GetQueryObjectuiv(GLuint id, GLenum pname, GLuint* params) {
|
void GetQueryObjectuiv(GLuint id, GLenum pname, GLuint* params) {
|
||||||
Uint64 value = 0;
|
Uint64 value = 0;
|
||||||
Bool valueProduced = false;
|
if (!GetQueryObjectValue(id, pname, __FUNCTION__, value) || !params) {
|
||||||
if (!GetQueryObjectValue(id, pname, __FUNCTION__, value, &valueProduced) || !valueProduced || !params) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
*params = static_cast<GLuint>(value & 0xFFFFFFFFull);
|
*params = static_cast<GLuint>(value & 0xFFFFFFFFull);
|
||||||
@@ -595,8 +346,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
|
|
||||||
void GetQueryObjecti64v(GLuint id, GLenum pname, GLint64* params) {
|
void GetQueryObjecti64v(GLuint id, GLenum pname, GLint64* params) {
|
||||||
Uint64 value = 0;
|
Uint64 value = 0;
|
||||||
Bool valueProduced = false;
|
if (!GetQueryObjectValue(id, pname, __FUNCTION__, value) || !params) {
|
||||||
if (!GetQueryObjectValue(id, pname, __FUNCTION__, value, &valueProduced) || !valueProduced || !params) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
*params = static_cast<GLint64>(value);
|
*params = static_cast<GLint64>(value);
|
||||||
@@ -604,48 +354,9 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
|
|
||||||
void GetQueryObjectui64v(GLuint id, GLenum pname, GLuint64* params) {
|
void GetQueryObjectui64v(GLuint id, GLenum pname, GLuint64* params) {
|
||||||
Uint64 value = 0;
|
Uint64 value = 0;
|
||||||
Bool valueProduced = false;
|
if (!GetQueryObjectValue(id, pname, __FUNCTION__, value) || !params) {
|
||||||
if (!GetQueryObjectValue(id, pname, __FUNCTION__, value, &valueProduced) || !valueProduced || !params) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
*params = static_cast<GLuint64>(value);
|
*params = static_cast<GLuint64>(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace {
|
|
||||||
// The indexed query entry points differ from the plain ones only in the vertex
|
|
||||||
// stream they address (GL 4.6 core 4.2.1): index must be below GL_MAX_VERTEX_STREAMS
|
|
||||||
// for the two transform feedback targets and zero for every other target. With a
|
|
||||||
// single vertex stream both bounds are 1, so a valid call is always index 0 and
|
|
||||||
// forwards to the unindexed implementation.
|
|
||||||
Bool ValidateQueryStreamIndex(const char* function, GLenum target, GLuint index) {
|
|
||||||
const Bool perStreamTarget =
|
|
||||||
target == GL_PRIMITIVES_GENERATED || target == GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN;
|
|
||||||
GLint maxVertexStreams = 1;
|
|
||||||
if (perStreamTarget) {
|
|
||||||
GetIntegerv(GL_MAX_VERTEX_STREAMS, &maxVertexStreams);
|
|
||||||
}
|
|
||||||
if (index < static_cast<GLuint>(std::max(maxVertexStreams, 1))) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
RecordQueryError(ErrorCode::InvalidValue, function,
|
|
||||||
perStreamTarget ? "index is not less than GL_MAX_VERTEX_STREAMS."
|
|
||||||
: "index must be zero for this query target.");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
void BeginQueryIndexed(GLenum target, GLuint index, GLuint id) {
|
|
||||||
if (!ValidateQueryStreamIndex(__FUNCTION__, target, index)) return;
|
|
||||||
BeginQuery(target, id);
|
|
||||||
}
|
|
||||||
|
|
||||||
void EndQueryIndexed(GLenum target, GLuint index) {
|
|
||||||
if (!ValidateQueryStreamIndex(__FUNCTION__, target, index)) return;
|
|
||||||
EndQuery(target);
|
|
||||||
}
|
|
||||||
|
|
||||||
void GetQueryIndexediv(GLenum target, GLuint index, GLenum pname, GLint* params) {
|
|
||||||
if (!ValidateQueryStreamIndex(__FUNCTION__, target, index)) return;
|
|
||||||
GetQueryiv(target, pname, params);
|
|
||||||
}
|
|
||||||
} // namespace MobileGL::MG_Impl::GLImpl
|
} // namespace MobileGL::MG_Impl::GLImpl
|
||||||
|
|||||||
@@ -11,22 +11,14 @@
|
|||||||
|
|
||||||
namespace MobileGL::MG_Impl::GLImpl {
|
namespace MobileGL::MG_Impl::GLImpl {
|
||||||
void GenQueries(GLsizei n, GLuint* ids);
|
void GenQueries(GLsizei n, GLuint* ids);
|
||||||
void CreateQueries(GLenum target, GLsizei n, GLuint* ids);
|
|
||||||
void DeleteQueries(GLsizei n, const GLuint* ids);
|
void DeleteQueries(GLsizei n, const GLuint* ids);
|
||||||
GLboolean IsQuery(GLuint id);
|
GLboolean IsQuery(GLuint id);
|
||||||
void BeginQuery(GLenum target, GLuint id);
|
void BeginQuery(GLenum target, GLuint id);
|
||||||
void EndQuery(GLenum target);
|
void EndQuery(GLenum target);
|
||||||
void GetQueryiv(GLenum target, GLenum pname, GLint* params);
|
void GetQueryiv(GLenum target, GLenum pname, GLint* params);
|
||||||
void BeginQueryIndexed(GLenum target, GLuint index, GLuint id);
|
|
||||||
void EndQueryIndexed(GLenum target, GLuint index);
|
|
||||||
void GetQueryIndexediv(GLenum target, GLuint index, GLenum pname, GLint* params);
|
|
||||||
void GetQueryObjectiv(GLuint id, GLenum pname, GLint* params);
|
void GetQueryObjectiv(GLuint id, GLenum pname, GLint* params);
|
||||||
void GetQueryObjectuiv(GLuint id, GLenum pname, GLuint* params);
|
void GetQueryObjectuiv(GLuint id, GLenum pname, GLuint* params);
|
||||||
void GetQueryObjecti64v(GLuint id, GLenum pname, GLint64* params);
|
void GetQueryObjecti64v(GLuint id, GLenum pname, GLint64* params);
|
||||||
void GetQueryObjectui64v(GLuint id, GLenum pname, GLuint64* 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);
|
void QueryCounter(GLuint id, GLenum target);
|
||||||
} // namespace MobileGL::MG_Impl::GLImpl
|
} // namespace MobileGL::MG_Impl::GLImpl
|
||||||
|
|||||||
@@ -28,11 +28,6 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
case GL_TEXTURE_MAX_LOD:
|
case GL_TEXTURE_MAX_LOD:
|
||||||
case GL_TEXTURE_LOD_BIAS:
|
case GL_TEXTURE_LOD_BIAS:
|
||||||
return true;
|
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:
|
case GL_TEXTURE_MAX_ANISOTROPY_EXT:
|
||||||
if (ReadSamplerScalar(param, isFloat, isUnsignedInteger) >= 1.0f) return true;
|
if (ReadSamplerScalar(param, isFloat, isUnsignedInteger) >= 1.0f) return true;
|
||||||
MG_State::pGLContext->RecordError(
|
MG_State::pGLContext->RecordError(
|
||||||
@@ -104,20 +99,6 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
case GL_TEXTURE_COMPARE_FUNC:
|
case GL_TEXTURE_COMPARE_FUNC:
|
||||||
samplerObj->SetSamplerCompareFunc(MG_Util::ConvertGLEnumToSamplerCompareFunc(*(const GLint*)param));
|
samplerObj->SetSamplerCompareFunc(MG_Util::ConvertGLEnumToSamplerCompareFunc(*(const GLint*)param));
|
||||||
break;
|
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:
|
default:
|
||||||
MG_State::pGLContext->RecordError(ErrorCode::InvalidEnum,
|
MG_State::pGLContext->RecordError(ErrorCode::InvalidEnum,
|
||||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", "SetSamplerParam_State",
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", "SetSamplerParam_State",
|
||||||
@@ -181,31 +162,6 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
case GL_TEXTURE_COMPARE_FUNC:
|
case GL_TEXTURE_COMPARE_FUNC:
|
||||||
*(GLuint*)params = MG_Util::ConvertSamplerCompareFuncToGLEnum(samplerObj->GetSamplerCompareFunc());
|
*(GLuint*)params = MG_Util::ConvertSamplerCompareFuncToGLEnum(samplerObj->GetSamplerCompareFunc());
|
||||||
break;
|
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:
|
default:
|
||||||
MG_State::pGLContext->RecordError(ErrorCode::InvalidEnum,
|
MG_State::pGLContext->RecordError(ErrorCode::InvalidEnum,
|
||||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", "GetSamplerParam_State",
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", "GetSamplerParam_State",
|
||||||
@@ -229,11 +185,6 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
static thread_local Vector<GLuint> names;
|
static thread_local Vector<GLuint> names;
|
||||||
MG_State::pGLContext->GenSamplerNames(count, names);
|
MG_State::pGLContext->GenSamplerNames(count, names);
|
||||||
Memcpy(samplers, names.data(), count * sizeof(GLuint));
|
Memcpy(samplers, names.data(), count * sizeof(GLuint));
|
||||||
// Unlike textures/buffers, glGenSamplers CREATES the sampler objects: each name
|
|
||||||
// is immediately a sampler (glIsSampler == GL_TRUE before any bind).
|
|
||||||
for (GLsizei i = 0; i < count; ++i) {
|
|
||||||
MG_State::pGLContext->CreateSamplerObject(names[i]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void DeleteSamplers_State(GLsizei count, const GLuint* samplers) {
|
void DeleteSamplers_State(GLsizei count, const GLuint* samplers) {
|
||||||
|
|||||||
@@ -133,33 +133,4 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
values[0] = value;
|
values[0] = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void DestroyAllSyncObjects() {
|
|
||||||
// Detach the registry under the lock, release outside it. Entries the app
|
|
||||||
// already deleted were erased by DeleteSync, so nothing here double-frees;
|
|
||||||
// a DeleteSync racing this sweep finds an empty registry and returns. A
|
|
||||||
// thread still blocked inside ClientWaitSync/GetSynciv during teardown
|
|
||||||
// holds a raw SyncObject* these deletes invalidate - the same undefined
|
|
||||||
// race an app-driven DeleteSync already has.
|
|
||||||
UnorderedMap<GLsync, SyncObject*> orphans;
|
|
||||||
{
|
|
||||||
const std::lock_guard<std::mutex> lock(g_syncObjectsMutex);
|
|
||||||
orphans.swap(g_liveSyncObjects);
|
|
||||||
}
|
|
||||||
if (orphans.empty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Both backends' DeleteSync only free the heap wrapper once their GL
|
|
||||||
// context/renderer is gone (generation/current-thread guards), so this is
|
|
||||||
// safe after the backend has released its EGL resources - but not after
|
|
||||||
// the function table itself is cleared.
|
|
||||||
const auto backendDeleteSync = MG_Backend::gBackendFunctionsTable.GL.DeleteSync;
|
|
||||||
for (const auto& [_, syncObject] : orphans) {
|
|
||||||
if (backendDeleteSync && syncObject->backendHandle) {
|
|
||||||
backendDeleteSync(syncObject->backendHandle);
|
|
||||||
}
|
|
||||||
delete syncObject;
|
|
||||||
}
|
|
||||||
MGLOG_D("DestroyAllSyncObjects: reclaimed %zu sync object(s) the app left undeleted", orphans.size());
|
|
||||||
}
|
|
||||||
} // namespace MobileGL::MG_Impl::GLImpl
|
} // namespace MobileGL::MG_Impl::GLImpl
|
||||||
|
|||||||
@@ -16,12 +16,4 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
void WaitSync(GLsync sync, GLbitfield flags, GLuint64 timeout);
|
void WaitSync(GLsync sync, GLbitfield flags, GLuint64 timeout);
|
||||||
void DeleteSync(GLsync sync);
|
void DeleteSync(GLsync sync);
|
||||||
void GetSynciv(GLsync sync, GLenum pname, GLsizei bufSize, GLsizei* length, GLint* values);
|
void GetSynciv(GLsync sync, GLenum pname, GLsizei bufSize, GLsizei* length, GLint* values);
|
||||||
// Destroys every still-registered sync object exactly as DeleteSync would.
|
|
||||||
// GL requires syncs to die with their context; called only from full library
|
|
||||||
// teardown (DestroyImpl), where no context survives on any thread, so the
|
|
||||||
// process-global registry can be drained wholesale. Must run while the
|
|
||||||
// backend function table is still populated: each backend handle has to be
|
|
||||||
// released by the backend that created it, never by a later re-initialized
|
|
||||||
// one.
|
|
||||||
void DestroyAllSyncObjects();
|
|
||||||
} // namespace MobileGL::MG_Impl::GLImpl
|
} // namespace MobileGL::MG_Impl::GLImpl
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -11,10 +11,6 @@
|
|||||||
|
|
||||||
namespace MobileGL::MG_Impl::GLImpl {
|
namespace MobileGL::MG_Impl::GLImpl {
|
||||||
/* @INSERTION_POINT:FUNCTION_DECLARATION@ */
|
/* @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 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,
|
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);
|
GLsizei height, GLsizei depth, GLenum format, GLenum type, const void* data);
|
||||||
@@ -46,10 +42,6 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
void GenerateTextureMipmap(GLuint texture);
|
void GenerateTextureMipmap(GLuint texture);
|
||||||
void BindTextureUnit(GLuint unit, GLuint texture);
|
void BindTextureUnit(GLuint unit, GLuint texture);
|
||||||
void GetTextureImage(GLuint texture, GLint level, GLenum format, GLenum type, GLsizei bufSize, void* pixels);
|
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);
|
|
||||||
void GetTextureSubImage(GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width,
|
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);
|
GLsizei height, GLsizei depth, GLenum format, GLenum type, GLsizei bufSize, void* pixels);
|
||||||
void GetTextureParameterfv(GLuint texture, GLenum pname, GLfloat* params);
|
void GetTextureParameterfv(GLuint texture, GLenum pname, GLfloat* params);
|
||||||
@@ -106,9 +98,6 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
GLsizei width, GLsizei height);
|
GLsizei width, GLsizei height);
|
||||||
void CopyTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width,
|
void CopyTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width,
|
||||||
GLsizei height);
|
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,
|
void CopyTextureSubImage2D(GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y,
|
||||||
GLsizei width, GLsizei height);
|
GLsizei width, GLsizei height);
|
||||||
void CopyTexSubImage1D(GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width);
|
void CopyTexSubImage1D(GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width);
|
||||||
|
|||||||
@@ -226,9 +226,6 @@ namespace MobileGL::MG_Impl::GLImpl::TextureImpl {
|
|||||||
case TextureInternalFormat::Depth24Stencil8:
|
case TextureInternalFormat::Depth24Stencil8:
|
||||||
case TextureInternalFormat::Depth32FStencil8:
|
case TextureInternalFormat::Depth32FStencil8:
|
||||||
case TextureInternalFormat::DepthStencil:
|
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;
|
return true;
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -8,7 +8,6 @@
|
|||||||
|
|
||||||
#include "GL_VertexArray.h"
|
#include "GL_VertexArray.h"
|
||||||
#include "Validators.h"
|
#include "Validators.h"
|
||||||
#include <MG_Backend/BackendObjects.h>
|
|
||||||
#include <MG_Impl/GLImpl/Buffer/Validators.h>
|
#include <MG_Impl/GLImpl/Buffer/Validators.h>
|
||||||
#include <MG_State/GLState/Core.h>
|
#include <MG_State/GLState/Core.h>
|
||||||
#include <MG_State/GLState/ErrorState/Error.h>
|
#include <MG_State/GLState/ErrorState/Error.h>
|
||||||
@@ -106,45 +105,12 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
return pname == GL_CURRENT_VERTEX_ATTRIB;
|
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) {
|
static bool ValidateVertexBindingIndex(GLuint bindingindex, const char* funcName) {
|
||||||
if (bindingindex >= VertexArrayImpl::GetMaxVertexAttribBindings()) {
|
// 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()) {
|
||||||
MG_State::pGLContext->RecordError(
|
MG_State::pGLContext->RecordError(
|
||||||
ErrorCode::InvalidValue,
|
ErrorCode::InvalidValue,
|
||||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", funcName,
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", funcName,
|
||||||
@@ -174,9 +140,6 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
case GL_CURRENT_VERTEX_ATTRIB:
|
case GL_CURRENT_VERTEX_ATTRIB:
|
||||||
case GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING:
|
case GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING:
|
||||||
case GL_VERTEX_ATTRIB_ARRAY_INTEGER:
|
case GL_VERTEX_ATTRIB_ARRAY_INTEGER:
|
||||||
// Core since GL 4.1 (ARB_vertex_attrib_64bit). It was rejected while no attribute could
|
|
||||||
// ever be long; now that IsLong is real state the pname has to be accepted.
|
|
||||||
case GL_VERTEX_ATTRIB_ARRAY_LONG:
|
|
||||||
case GL_VERTEX_ATTRIB_ARRAY_DIVISOR:
|
case GL_VERTEX_ATTRIB_ARRAY_DIVISOR:
|
||||||
case GL_VERTEX_ATTRIB_ARRAY_POINTER:
|
case GL_VERTEX_ATTRIB_ARRAY_POINTER:
|
||||||
return true;
|
return true;
|
||||||
@@ -192,17 +155,6 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
|
|
||||||
SharedPtr<MG_State::GLState::VertexArrayObject> GetNamedVertexArrayObject_State(GLuint vaobj,
|
SharedPtr<MG_State::GLState::VertexArrayObject> GetNamedVertexArrayObject_State(GLuint vaobj,
|
||||||
const char* caller) {
|
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::ValidateVertexArrayName(vaobj)) return nullptr;
|
||||||
if (!VertexArrayImpl::ValidateVertexArrayObject(vaobj)) return nullptr;
|
if (!VertexArrayImpl::ValidateVertexArrayObject(vaobj)) return nullptr;
|
||||||
return MG_State::pGLContext->GetVertexArrayObject(vaobj);
|
return MG_State::pGLContext->GetVertexArrayObject(vaobj);
|
||||||
@@ -258,7 +210,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
|
|
||||||
DataType dataType = MG_Util::ConvertGLEnumToDataType(type);
|
DataType dataType = MG_Util::ConvertGLEnumToDataType(type);
|
||||||
// Integer path: never normalized, never BGRA/packed (the validator rejects those).
|
// Integer path: never normalized, never BGRA/packed (the validator rejects those).
|
||||||
if (!VertexArrayImpl::ValidateVertexAttribFormat(index, size, type, dataType, false, stride, true)) return;
|
if (!VertexArrayImpl::ValidateVertexAttribFormat(index, size, dataType, false, stride, true)) return;
|
||||||
|
|
||||||
auto& vao = MG_State::pGLContext->GetBoundVertexArray();
|
auto& vao = MG_State::pGLContext->GetBoundVertexArray();
|
||||||
if (!vao) {
|
if (!vao) {
|
||||||
@@ -275,7 +227,6 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
|
|
||||||
vao->SetAttributeFormat(index, size, dataType, false, stride, offset, true, false);
|
vao->SetAttributeFormat(index, size, dataType, false, stride, offset, true, false);
|
||||||
vao->BindAttributeBuffer(index, vbo);
|
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,
|
void VertexAttribPointer_State(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride,
|
||||||
@@ -283,7 +234,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
if (!VertexArrayImpl::ValidateVertexAttributeIndex(index)) return;
|
if (!VertexArrayImpl::ValidateVertexAttributeIndex(index)) return;
|
||||||
|
|
||||||
DataType dataType = MG_Util::ConvertGLEnumToDataType(type);
|
DataType dataType = MG_Util::ConvertGLEnumToDataType(type);
|
||||||
if (!VertexArrayImpl::ValidateVertexAttribFormat(index, size, type, dataType, normalized == GL_TRUE, stride, false))
|
if (!VertexArrayImpl::ValidateVertexAttribFormat(index, size, dataType, normalized == GL_TRUE, stride, false))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
auto& vao = MG_State::pGLContext->GetBoundVertexArray();
|
auto& vao = MG_State::pGLContext->GetBoundVertexArray();
|
||||||
@@ -305,7 +256,6 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
const int effectiveSize = isBgra ? 4 : size;
|
const int effectiveSize = isBgra ? 4 : size;
|
||||||
vao->SetAttributeFormat(index, effectiveSize, dataType, normalized, stride, offset, false, isBgra);
|
vao->SetAttributeFormat(index, effectiveSize, dataType, normalized, stride, offset, false, isBgra);
|
||||||
vao->BindAttributeBuffer(index, vbo);
|
vao->BindAttributeBuffer(index, vbo);
|
||||||
vao->MirrorPointerIntoBinding(index, vbo, offset, EffectiveVertexStride(stride, effectiveSize, type));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void BindVertexArray_State(GLuint array) {
|
void BindVertexArray_State(GLuint array) {
|
||||||
@@ -409,13 +359,6 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller, "offset and stride must be non-negative."));
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller, "offset and stride must be non-negative."));
|
||||||
return;
|
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);
|
auto bufferObject = GetVertexArrayBufferObject_State(buffer, caller);
|
||||||
if (buffer != 0 && !bufferObject) return;
|
if (buffer != 0 && !bufferObject) return;
|
||||||
|
|
||||||
@@ -433,7 +376,6 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
const GLintptr* offsets, const GLsizei* strides) {
|
const GLintptr* offsets, const GLsizei* strides) {
|
||||||
auto vao = GetNamedVertexArrayObject_State(vaobj, "VertexArrayVertexBuffers_State");
|
auto vao = GetNamedVertexArrayObject_State(vaobj, "VertexArrayVertexBuffers_State");
|
||||||
if (!vao) return;
|
if (!vao) return;
|
||||||
if (!ValidateVertexBindingRange(first, count, "VertexArrayVertexBuffers_State")) return;
|
|
||||||
for (GLsizei i = 0; i < count; ++i) {
|
for (GLsizei i = 0; i < count; ++i) {
|
||||||
if (!buffers) {
|
if (!buffers) {
|
||||||
VertexBufferBinding_State(vao, first + i, 0, 0, 16, "VertexArrayVertexBuffers_State");
|
VertexBufferBinding_State(vao, first + i, 0, 0, 16, "VertexArrayVertexBuffers_State");
|
||||||
@@ -447,55 +389,12 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
static void VertexAttribFormatSeparate_State(const SharedPtr<MG_State::GLState::VertexArrayObject>& vao,
|
static void VertexAttribFormatSeparate_State(const SharedPtr<MG_State::GLState::VertexArrayObject>& vao,
|
||||||
GLuint attribindex, GLint size, GLenum type, GLboolean normalized,
|
GLuint attribindex, GLint size, GLenum type, GLboolean normalized,
|
||||||
GLuint relativeoffset, Bool isInteger, const char* caller) {
|
GLuint relativeoffset, Bool isInteger, const char* caller) {
|
||||||
static_cast<void>(caller);
|
|
||||||
if (!VertexArrayImpl::ValidateVertexAttributeIndex(attribindex)) return;
|
if (!VertexArrayImpl::ValidateVertexAttributeIndex(attribindex)) return;
|
||||||
|
|
||||||
DataType dataType = MG_Util::ConvertGLEnumToDataType(type);
|
DataType dataType = MG_Util::ConvertGLEnumToDataType(type);
|
||||||
// The separate-format entry points take the same size/type rules as the pointer ones,
|
if (!VertexArrayImpl::ValidateVertexAttribPointerParams(attribindex, size, dataType, 0)) return;
|
||||||
// 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;
|
|
||||||
|
|
||||||
const Bool isBgra = (size == static_cast<GLint>(GL_BGRA));
|
vao->SetAttributeFormatSeparate(attribindex, size, dataType, normalized, isInteger, relativeoffset);
|
||||||
vao->SetAttributeFormatSeparate(attribindex, isBgra ? 4 : size, dataType, normalized, isInteger,
|
|
||||||
relativeoffset, isBgra);
|
|
||||||
}
|
|
||||||
|
|
||||||
// The long (64-bit) attribute format: the values reach the shader as doubles, unconverted
|
|
||||||
// (GL 4.6 core 10.3.2). ValidateVertexAttribLFormat has already pinned type to GL_DOUBLE, so the
|
|
||||||
// recorded DataType is always Float64 - what IsLong adds is that this is the *unconverted* form,
|
|
||||||
// as opposed to VertexAttribFormat(GL_DOUBLE), which asks for a float conversion.
|
|
||||||
//
|
|
||||||
// Whether the backend can feed it is detected, not assumed: DirectVulkan needs shaderFloat64,
|
|
||||||
// and DirectGLES can never have it at all. A backend without it declines here, loudly - GL error
|
|
||||||
// plus a log line naming the reason - rather than accepting state no draw could honour and
|
|
||||||
// rendering garbage. The matching startup POST row is in MG_Util/SelfTest/DriverPost.cpp.
|
|
||||||
static void VertexAttribLFormatSeparate_State(const SharedPtr<MG_State::GLState::VertexArrayObject>& vao,
|
|
||||||
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;
|
|
||||||
|
|
||||||
if (!MG_Backend::pActiveBackendObject ||
|
|
||||||
!MG_Backend::pActiveBackendObject->GetDynamicParameters().SupportsFloat64VertexAttributes) {
|
|
||||||
MGLOG_I("VertexAttribLFormat: attribute %u asked for a 64-bit (GL_DOUBLE) format, but this "
|
|
||||||
"backend has no double-precision vertex attribute support - see the "
|
|
||||||
"\"64-bit vertex attributes\" / \"shaderFloat64\" POST row for what that costs",
|
|
||||||
attribindex);
|
|
||||||
MG_State::pGLContext->RecordError(
|
|
||||||
ErrorCode::InvalidOperation,
|
|
||||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", "VertexAttribLFormat",
|
|
||||||
"64-bit vertex attributes are not supported by this backend."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
vao->SetAttributeFormatSeparate(attribindex, size, MG_Util::ConvertGLEnumToDataType(type),
|
|
||||||
/*normalized: */ false, /*isInteger: */ false, relativeoffset,
|
|
||||||
/*isBgra: */ false, /*isLong: */ true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void VertexArrayAttribFormat_State(GLuint vaobj, GLuint attribindex, GLint size, GLenum type,
|
void VertexArrayAttribFormat_State(GLuint vaobj, GLuint attribindex, GLint size, GLenum type,
|
||||||
@@ -938,9 +837,6 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
case GL_VERTEX_ATTRIB_ARRAY_INTEGER:
|
case GL_VERTEX_ATTRIB_ARRAY_INTEGER:
|
||||||
params[0] = attr->IsInteger ? 1.0f : 0.0f;
|
params[0] = attr->IsInteger ? 1.0f : 0.0f;
|
||||||
return;
|
return;
|
||||||
case GL_VERTEX_ATTRIB_ARRAY_LONG:
|
|
||||||
params[0] = attr->IsLong ? 1.0f : 0.0f;
|
|
||||||
return;
|
|
||||||
case GL_VERTEX_ATTRIB_ARRAY_DIVISOR:
|
case GL_VERTEX_ATTRIB_ARRAY_DIVISOR:
|
||||||
params[0] = static_cast<GLfloat>(attr->Divisor);
|
params[0] = static_cast<GLfloat>(attr->Divisor);
|
||||||
return;
|
return;
|
||||||
@@ -1001,9 +897,6 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
case GL_VERTEX_ATTRIB_ARRAY_INTEGER:
|
case GL_VERTEX_ATTRIB_ARRAY_INTEGER:
|
||||||
params[0] = attr->IsInteger ? 1.0 : 0.0;
|
params[0] = attr->IsInteger ? 1.0 : 0.0;
|
||||||
return;
|
return;
|
||||||
case GL_VERTEX_ATTRIB_ARRAY_LONG:
|
|
||||||
params[0] = attr->IsLong ? 1.0 : 0.0;
|
|
||||||
return;
|
|
||||||
case GL_VERTEX_ATTRIB_ARRAY_DIVISOR:
|
case GL_VERTEX_ATTRIB_ARRAY_DIVISOR:
|
||||||
params[0] = static_cast<GLdouble>(attr->Divisor);
|
params[0] = static_cast<GLdouble>(attr->Divisor);
|
||||||
return;
|
return;
|
||||||
@@ -1060,9 +953,6 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
case GL_VERTEX_ATTRIB_ARRAY_INTEGER:
|
case GL_VERTEX_ATTRIB_ARRAY_INTEGER:
|
||||||
params[0] = attr->IsInteger ? GL_TRUE : GL_FALSE;
|
params[0] = attr->IsInteger ? GL_TRUE : GL_FALSE;
|
||||||
return;
|
return;
|
||||||
case GL_VERTEX_ATTRIB_ARRAY_LONG:
|
|
||||||
params[0] = attr->IsLong ? GL_TRUE : GL_FALSE;
|
|
||||||
return;
|
|
||||||
case GL_VERTEX_ATTRIB_ARRAY_DIVISOR:
|
case GL_VERTEX_ATTRIB_ARRAY_DIVISOR:
|
||||||
params[0] = static_cast<GLint>(attr->Divisor);
|
params[0] = static_cast<GLint>(attr->Divisor);
|
||||||
return;
|
return;
|
||||||
@@ -1154,82 +1044,6 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
VertexArrayVertexBuffer_State(vaobj, bindingindex, buffer, offset, stride);
|
VertexArrayVertexBuffer_State(vaobj, bindingindex, buffer, offset, stride);
|
||||||
}
|
}
|
||||||
|
|
||||||
// glGetVertexArrayiv reports exactly one thing (GL 4.6 core table 23.4): which buffer the
|
|
||||||
// named vertex array takes its indices from. Everything else about a vertex array is
|
|
||||||
// per-attribute and belongs to the indexed queries below.
|
|
||||||
void GetVertexArrayiv(GLuint vaobj, GLenum pname, GLint* param) {
|
|
||||||
auto vao = GetNamedVertexArrayObject_State(vaobj, __func__);
|
|
||||||
if (!vao || !param) return;
|
|
||||||
if (pname != GL_ELEMENT_ARRAY_BUFFER_BINDING) {
|
|
||||||
MG_State::pGLContext->RecordError(
|
|
||||||
ErrorCode::InvalidEnum,
|
|
||||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
|
||||||
"pname must be GL_ELEMENT_ARRAY_BUFFER_BINDING."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const auto& indexBuffer = vao->GetIndexBufferBindingSlot().GetBoundObject();
|
|
||||||
*param = indexBuffer ? static_cast<GLint>(indexBuffer->GetExternalIndex()) : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void GetVertexArrayIndexediv(GLuint vaobj, GLuint index, GLenum pname, GLint* param) {
|
|
||||||
auto vao = GetNamedVertexArrayObject_State(vaobj, __func__);
|
|
||||||
if (!vao || !param) return;
|
|
||||||
if (!VertexArrayImpl::ValidateVertexAttributeIndex(index)) return;
|
|
||||||
const auto& attr = vao->GetAttribute(index);
|
|
||||||
switch (pname) {
|
|
||||||
case GL_VERTEX_ATTRIB_ARRAY_ENABLED:
|
|
||||||
*param = attr.Enabled ? GL_TRUE : GL_FALSE;
|
|
||||||
return;
|
|
||||||
case GL_VERTEX_ATTRIB_ARRAY_SIZE:
|
|
||||||
*param = static_cast<GLint>(attr.Size);
|
|
||||||
return;
|
|
||||||
case GL_VERTEX_ATTRIB_ARRAY_STRIDE:
|
|
||||||
*param = static_cast<GLint>(attr.Stride);
|
|
||||||
return;
|
|
||||||
case GL_VERTEX_ATTRIB_ARRAY_TYPE:
|
|
||||||
*param = static_cast<GLint>(MG_Util::ConvertDataTypeToGLEnum(attr.Type));
|
|
||||||
return;
|
|
||||||
case GL_VERTEX_ATTRIB_ARRAY_NORMALIZED:
|
|
||||||
*param = attr.Normalized ? GL_TRUE : GL_FALSE;
|
|
||||||
return;
|
|
||||||
case GL_VERTEX_ATTRIB_ARRAY_INTEGER:
|
|
||||||
*param = attr.IsInteger ? GL_TRUE : GL_FALSE;
|
|
||||||
return;
|
|
||||||
case GL_VERTEX_ATTRIB_ARRAY_LONG:
|
|
||||||
*param = attr.IsLong ? GL_TRUE : GL_FALSE;
|
|
||||||
return;
|
|
||||||
case GL_VERTEX_ATTRIB_ARRAY_DIVISOR:
|
|
||||||
*param = static_cast<GLint>(attr.Divisor);
|
|
||||||
return;
|
|
||||||
case GL_VERTEX_ATTRIB_RELATIVE_OFFSET:
|
|
||||||
*param = static_cast<GLint>(vao->GetAttributeRelativeOffset(index));
|
|
||||||
return;
|
|
||||||
default:
|
|
||||||
MG_State::pGLContext->RecordError(
|
|
||||||
ErrorCode::InvalidEnum,
|
|
||||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
|
||||||
"pname is not an accepted indexed vertex array query."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only GL_VERTEX_BINDING_OFFSET needs 64 bits. Its `index` names a vertex buffer binding
|
|
||||||
// point directly (GL 4.6 core 10.3.1), not an attribute - unlike every pname the 32-bit
|
|
||||||
// indexed query above accepts, which is why this one does not go through an attribute's
|
|
||||||
// binding index.
|
|
||||||
void GetVertexArrayIndexed64iv(GLuint vaobj, GLuint index, GLenum pname, GLint64* param) {
|
|
||||||
auto vao = GetNamedVertexArrayObject_State(vaobj, __func__);
|
|
||||||
if (!vao || !param) return;
|
|
||||||
if (!VertexArrayImpl::ValidateVertexAttributeIndex(index)) return;
|
|
||||||
if (pname != GL_VERTEX_BINDING_OFFSET) {
|
|
||||||
MG_State::pGLContext->RecordError(
|
|
||||||
ErrorCode::InvalidEnum,
|
|
||||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "pname must be GL_VERTEX_BINDING_OFFSET."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
*param = static_cast<GLint64>(vao->GetBindingPoint(index).Offset);
|
|
||||||
}
|
|
||||||
|
|
||||||
void VertexArrayAttribFormat(GLuint vaobj, GLuint attribindex, GLint size, GLenum type, GLboolean normalized,
|
void VertexArrayAttribFormat(GLuint vaobj, GLuint attribindex, GLint size, GLenum type, GLboolean normalized,
|
||||||
GLuint relativeoffset) {
|
GLuint relativeoffset) {
|
||||||
VertexArrayAttribFormat_State(vaobj, attribindex, size, type, normalized, relativeoffset);
|
VertexArrayAttribFormat_State(vaobj, attribindex, size, type, normalized, relativeoffset);
|
||||||
@@ -1262,7 +1076,6 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
const GLsizei* strides) {
|
const GLsizei* strides) {
|
||||||
auto vao = GetBoundVertexArrayOrError("BindVertexBuffers");
|
auto vao = GetBoundVertexArrayOrError("BindVertexBuffers");
|
||||||
if (!vao) return;
|
if (!vao) return;
|
||||||
if (!ValidateVertexBindingRange(first, count, "BindVertexBuffers")) return;
|
|
||||||
for (GLsizei i = 0; i < count; ++i) {
|
for (GLsizei i = 0; i < count; ++i) {
|
||||||
if (!buffers) {
|
if (!buffers) {
|
||||||
VertexBufferBinding_State(vao, first + i, 0, 0, 16, "BindVertexBuffers");
|
VertexBufferBinding_State(vao, first + i, 0, 0, 16, "BindVertexBuffers");
|
||||||
@@ -1287,18 +1100,6 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
"VertexAttribIFormat");
|
"VertexAttribIFormat");
|
||||||
}
|
}
|
||||||
|
|
||||||
void VertexAttribLFormat(GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset) {
|
|
||||||
auto vao = GetBoundVertexArrayOrError("VertexAttribLFormat");
|
|
||||||
if (!vao) return;
|
|
||||||
VertexAttribLFormatSeparate_State(vao, 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(vao, attribindex, size, type, relativeoffset);
|
|
||||||
}
|
|
||||||
|
|
||||||
void VertexAttribBinding(GLuint attribindex, GLuint bindingindex) {
|
void VertexAttribBinding(GLuint attribindex, GLuint bindingindex) {
|
||||||
auto vao = GetBoundVertexArrayOrError("VertexAttribBinding");
|
auto vao = GetBoundVertexArrayOrError("VertexAttribBinding");
|
||||||
if (!vao) return;
|
if (!vao) return;
|
||||||
|
|||||||
@@ -92,13 +92,9 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
void EnableVertexArrayAttrib(GLuint vaobj, GLuint index);
|
void EnableVertexArrayAttrib(GLuint vaobj, GLuint index);
|
||||||
void VertexArrayElementBuffer(GLuint vaobj, GLuint buffer);
|
void VertexArrayElementBuffer(GLuint vaobj, GLuint buffer);
|
||||||
void VertexArrayVertexBuffer(GLuint vaobj, GLuint bindingindex, GLuint buffer, GLintptr offset, GLsizei stride);
|
void VertexArrayVertexBuffer(GLuint vaobj, GLuint bindingindex, GLuint buffer, GLintptr offset, GLsizei stride);
|
||||||
void GetVertexArrayiv(GLuint vaobj, GLenum pname, GLint* param);
|
|
||||||
void GetVertexArrayIndexediv(GLuint vaobj, GLuint index, GLenum pname, GLint* param);
|
|
||||||
void GetVertexArrayIndexed64iv(GLuint vaobj, GLuint index, GLenum pname, GLint64* param);
|
|
||||||
void VertexArrayAttribFormat(GLuint vaobj, GLuint attribindex, GLint size, GLenum type, GLboolean normalized,
|
void VertexArrayAttribFormat(GLuint vaobj, GLuint attribindex, GLint size, GLenum type, GLboolean normalized,
|
||||||
GLuint relativeoffset);
|
GLuint relativeoffset);
|
||||||
void VertexArrayAttribIFormat(GLuint vaobj, GLuint attribindex, GLint size, GLenum type, 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 VertexArrayAttribBinding(GLuint vaobj, GLuint attribindex, GLuint bindingindex);
|
||||||
void VertexArrayBindingDivisor(GLuint vaobj, GLuint bindingindex, GLuint divisor);
|
void VertexArrayBindingDivisor(GLuint vaobj, GLuint bindingindex, GLuint divisor);
|
||||||
void VertexArrayVertexBuffers(GLuint vaobj, GLuint first, GLsizei count, const GLuint* buffers,
|
void VertexArrayVertexBuffers(GLuint vaobj, GLuint first, GLsizei count, const GLuint* buffers,
|
||||||
@@ -108,7 +104,6 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
const GLsizei* strides);
|
const GLsizei* strides);
|
||||||
void VertexAttribFormat(GLuint attribindex, GLint size, GLenum type, GLboolean normalized, GLuint relativeoffset);
|
void VertexAttribFormat(GLuint attribindex, GLint size, GLenum type, GLboolean normalized, GLuint relativeoffset);
|
||||||
void VertexAttribIFormat(GLuint attribindex, GLint size, GLenum type, 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 VertexAttribBinding(GLuint attribindex, GLuint bindingindex);
|
||||||
void VertexBindingDivisor(GLuint bindingindex, GLuint divisor);
|
void VertexBindingDivisor(GLuint bindingindex, GLuint divisor);
|
||||||
void VertexAttribDivisor(GLuint index, GLuint divisor);
|
void VertexAttribDivisor(GLuint index, GLuint divisor);
|
||||||
|
|||||||
@@ -23,18 +23,6 @@ namespace MobileGL::MG_Impl::GLImpl::VertexArrayImpl {
|
|||||||
return std::min(static_cast<Uint>(backendLimit), capacity);
|
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 ValidateVertexArrayName(Uint index) {
|
||||||
Bool isValid = MG_State::pGLContext->ValidateVertexArrayName(index);
|
Bool isValid = MG_State::pGLContext->ValidateVertexArrayName(index);
|
||||||
if (!isValid) {
|
if (!isValid) {
|
||||||
@@ -102,31 +90,9 @@ namespace MobileGL::MG_Impl::GLImpl::VertexArrayImpl {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
Bool ValidateVertexAttribFormat(Uint index, GLint sizeRaw, GLenum glType, DataType type, Bool normalized,
|
Bool ValidateVertexAttribFormat(Uint index, GLint sizeRaw, DataType type, Bool normalized, Int stride,
|
||||||
Int stride, Bool integerPath) {
|
Bool integerPath) {
|
||||||
constexpr const char* fn = "ValidateVertexAttribFormat";
|
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) {
|
if (type == DataType::Unknown) {
|
||||||
MG_State::pGLContext->RecordError(
|
MG_State::pGLContext->RecordError(
|
||||||
ErrorCode::InvalidEnum,
|
ErrorCode::InvalidEnum,
|
||||||
@@ -204,40 +170,4 @@ namespace MobileGL::MG_Impl::GLImpl::VertexArrayImpl {
|
|||||||
}
|
}
|
||||||
return true;
|
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
|
} // namespace MobileGL::MG_Impl::GLImpl::VertexArrayImpl
|
||||||
|
|||||||
@@ -15,20 +15,6 @@ namespace MobileGL::MG_Impl::GLImpl::VertexArrayImpl {
|
|||||||
// capacity). Falls back to the capacity when no backend is active (unit tests).
|
// capacity). Falls back to the capacity when no backend is active (unit tests).
|
||||||
Uint GetMaxVertexAttribs();
|
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 ValidateVertexArrayName(Uint index);
|
||||||
Bool ValidateVertexArrayObject(Uint index);
|
Bool ValidateVertexArrayObject(Uint index);
|
||||||
Bool ValidateVertexAttributeIndex(Uint index);
|
Bool ValidateVertexAttributeIndex(Uint index);
|
||||||
@@ -36,13 +22,6 @@ namespace MobileGL::MG_Impl::GLImpl::VertexArrayImpl {
|
|||||||
// Full glVertexAttribPointer / glVertexAttribIPointer format validation, including the packed
|
// 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);
|
// 2_10_10_10 types and GL_BGRA size. sizeRaw is the untranslated GL size (possibly GL_BGRA);
|
||||||
// integerPath selects the glVertexAttribIPointer rules.
|
// integerPath selects the glVertexAttribIPointer rules.
|
||||||
Bool ValidateVertexAttribFormat(Uint index, GLint sizeRaw, GLenum glType, DataType type, Bool normalized,
|
Bool ValidateVertexAttribFormat(Uint index, GLint sizeRaw, DataType type, Bool normalized, Int stride,
|
||||||
Int stride, Bool integerPath);
|
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
|
} // namespace MobileGL::MG_Impl::GLImpl::VertexArrayImpl
|
||||||
|
|||||||
@@ -85,8 +85,6 @@ namespace MobileGL::MG_Impl {
|
|||||||
GETPROC(CGLGetPixelFormat, name);
|
GETPROC(CGLGetPixelFormat, name);
|
||||||
GETPROC(CGLSetCurrentContext, name);
|
GETPROC(CGLSetCurrentContext, name);
|
||||||
GETPROC(CGLGetCurrentContext, name);
|
GETPROC(CGLGetCurrentContext, name);
|
||||||
GETPROC(CGLSetVirtualScreen, name);
|
|
||||||
GETPROC(CGLGetVirtualScreen, name);
|
|
||||||
GETPROC(CGLSetParameter, name);
|
GETPROC(CGLSetParameter, name);
|
||||||
GETPROC(CGLGetParameter, name);
|
GETPROC(CGLGetParameter, name);
|
||||||
GETPROC(CGLUpdateContext, name);
|
GETPROC(CGLUpdateContext, name);
|
||||||
|
|||||||
@@ -29,19 +29,10 @@ namespace MobileGL::MG_Impl::NSOpenGLImpl {
|
|||||||
char kContextViewKey;
|
char kContextViewKey;
|
||||||
char kContextLayerKey;
|
char kContextLayerKey;
|
||||||
|
|
||||||
|
std::once_flag g_installOnce;
|
||||||
IMP g_pixelFormatDealloc = nullptr;
|
IMP g_pixelFormatDealloc = nullptr;
|
||||||
IMP g_contextDealloc = nullptr;
|
IMP g_contextDealloc = nullptr;
|
||||||
|
|
||||||
std::mutex& HookInstallMutex() {
|
|
||||||
static auto* mutex = new std::mutex();
|
|
||||||
return *mutex;
|
|
||||||
}
|
|
||||||
|
|
||||||
Bool& HooksInstalled() {
|
|
||||||
static auto* installed = new Bool(false);
|
|
||||||
return *installed;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename Fn>
|
template <typename Fn>
|
||||||
Fn ObjcMsgSend() {
|
Fn ObjcMsgSend() {
|
||||||
return reinterpret_cast<Fn>(objc_msgSend);
|
return reinterpret_cast<Fn>(objc_msgSend);
|
||||||
@@ -440,12 +431,12 @@ namespace MobileGL::MG_Impl::NSOpenGLImpl {
|
|||||||
method_setImplementation(method, replacement);
|
method_setImplementation(method, replacement);
|
||||||
}
|
}
|
||||||
|
|
||||||
Bool InstallHooksOnce() {
|
void InstallHooksOnce() {
|
||||||
Class pixelFormatClass = objc_getClass("NSOpenGLPixelFormat");
|
Class pixelFormatClass = objc_getClass("NSOpenGLPixelFormat");
|
||||||
Class contextClass = objc_getClass("NSOpenGLContext");
|
Class contextClass = objc_getClass("NSOpenGLContext");
|
||||||
if (!pixelFormatClass || !contextClass) {
|
if (!pixelFormatClass || !contextClass) {
|
||||||
MGLOG_W("NSOpenGLImpl: NSOpenGL classes are not loaded; hooks not installed");
|
MGLOG_W("NSOpenGLImpl: NSOpenGL classes are not loaded; hooks not installed");
|
||||||
return false;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ReplaceInstanceMethod(pixelFormatClass, "initWithAttributes:",
|
ReplaceInstanceMethod(pixelFormatClass, "initWithAttributes:",
|
||||||
@@ -480,34 +471,11 @@ namespace MobileGL::MG_Impl::NSOpenGLImpl {
|
|||||||
ReplaceInstanceMethod(contextClass, "dealloc", reinterpret_cast<IMP>(ContextDealloc), &g_contextDealloc);
|
ReplaceInstanceMethod(contextClass, "dealloc", reinterpret_cast<IMP>(ContextDealloc), &g_contextDealloc);
|
||||||
|
|
||||||
MGLOG_I("NSOpenGLImpl hooks installed");
|
MGLOG_I("NSOpenGLImpl hooks installed");
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
void InstallHooks() {
|
void InstallHooks() {
|
||||||
const std::lock_guard<std::mutex> lock(HookInstallMutex());
|
std::call_once(g_installOnce, InstallHooksOnce);
|
||||||
if (!HooksInstalled()) {
|
|
||||||
// Do not permanently consume the install attempt when the OpenGL
|
|
||||||
// framework has not registered its Objective-C classes yet. The
|
|
||||||
// dyld bootstrap normally runs after framework dependencies, but
|
|
||||||
// an explicitly loaded/static-linked MobileGL can arrive earlier.
|
|
||||||
HooksInstalled() = InstallHooksOnce();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} // namespace MobileGL::MG_Impl::NSOpenGLImpl
|
} // namespace MobileGL::MG_Impl::NSOpenGLImpl
|
||||||
|
|
||||||
namespace {
|
|
||||||
// SDL's Cocoa backend creates NSOpenGLPixelFormat/NSOpenGLContext before
|
|
||||||
// its first dlsym("glGetString") or other MobileGL host-API call. Install
|
|
||||||
// only the lightweight Objective-C dispatch hooks while the injected dylib
|
|
||||||
// is loading so those first Cocoa objects are routed through CGLImpl. The
|
|
||||||
// hooked context constructor reaches EGLImpl::GetDisplay(), which performs
|
|
||||||
// the full, thread-safe MobileGL initialization outside this bootstrap.
|
|
||||||
//
|
|
||||||
// There is intentionally no matching destructor: backend teardown remains
|
|
||||||
// owned by the EGL lifecycle and process-exit globals remain leak-at-exit.
|
|
||||||
__attribute__((constructor)) void BootstrapNSOpenGLHooks() {
|
|
||||||
MobileGL::MG_Impl::NSOpenGLImpl::InstallHooks();
|
|
||||||
}
|
|
||||||
} // namespace
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -174,21 +174,6 @@ namespace MobileGL::MG_State::GLState {
|
|||||||
++m_changeSerial;
|
++m_changeSerial;
|
||||||
}
|
}
|
||||||
|
|
||||||
void BufferObject::MarkGpuWritten() {
|
|
||||||
m_gpuWritePending = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void BufferObject::SyncGpuWrites() {
|
|
||||||
if (!m_gpuWritePending) return;
|
|
||||||
// Cleared unconditionally: without a readback op the shadow can never catch up,
|
|
||||||
// and retrying on every subsequent read would only repeat the same no-op.
|
|
||||||
m_gpuWritePending = false;
|
|
||||||
if (m_size == 0 || g_bufferBackendOps == nullptr || g_bufferBackendOps->ReadbackFromGpu == nullptr) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
g_bufferBackendOps->ReadbackFromGpu(*this);
|
|
||||||
}
|
|
||||||
|
|
||||||
void BufferObject::UploadSubData(DataPtr data, SizeT atOffset) {
|
void BufferObject::UploadSubData(DataPtr data, SizeT atOffset) {
|
||||||
MOBILEGL_ASSERT(!m_isMapped || (m_mappingAccess & BufferMappingAccessBit::Persistent),
|
MOBILEGL_ASSERT(!m_isMapped || (m_mappingAccess & BufferMappingAccessBit::Persistent),
|
||||||
"Cannot upload sub data while buffer is non-persistently mapped.");
|
"Cannot upload sub data while buffer is non-persistently mapped.");
|
||||||
@@ -219,13 +204,11 @@ namespace MobileGL::MG_State::GLState {
|
|||||||
"Destination buffer copy out of bounds: dstOffset (%zu) + size (%zu) > m_size (%zu)", dstOffset,
|
"Destination buffer copy out of bounds: dstOffset (%zu) + size (%zu) > m_size (%zu)", dstOffset,
|
||||||
size, m_size);
|
size, m_size);
|
||||||
|
|
||||||
src->SyncGpuWrites();
|
|
||||||
Memcpy(m_resource.Bytes() + dstOffset, src->m_resource.Bytes() + srcOffset, size);
|
Memcpy(m_resource.Bytes() + dstOffset, src->m_resource.Bytes() + srcOffset, size);
|
||||||
NotifyContentWrite(dstOffset, size);
|
NotifyContentWrite(dstOffset, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
void* BufferObject::AcquireMemory(Bool markMapped, Bool read, Bool write) {
|
void* BufferObject::AcquireMemory(Bool markMapped, Bool read, Bool write) {
|
||||||
SyncGpuWrites();
|
|
||||||
if (markMapped) {
|
if (markMapped) {
|
||||||
m_isMapped = true;
|
m_isMapped = true;
|
||||||
m_mappingAccess = (read ? BufferMappingAccessBit::Read : BufferMappingAccessBit::Null) |
|
m_mappingAccess = (read ? BufferMappingAccessBit::Read : BufferMappingAccessBit::Null) |
|
||||||
@@ -248,29 +231,10 @@ namespace MobileGL::MG_State::GLState {
|
|||||||
return m_resource.Bytes();
|
return m_resource.Bytes();
|
||||||
}
|
}
|
||||||
|
|
||||||
Bool BufferObject::EnsureGpuResidentStorage() {
|
|
||||||
if (m_resource.IsGpuResident()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (m_size == 0 || g_bufferBackendOps == nullptr || g_bufferBackendOps->AcquirePersistentMap == nullptr) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
void* base = g_bufferBackendOps->AcquirePersistentMap(*this);
|
|
||||||
if (base == nullptr) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
m_resource.AdoptPersistentMap(base);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void* BufferObject::AcquireMemoryRange(Range1D range, Flags<BufferMappingAccessBit> access) {
|
void* BufferObject::AcquireMemoryRange(Range1D range, Flags<BufferMappingAccessBit> access) {
|
||||||
MOBILEGL_ASSERT(range.end <= m_size && range.start <= range.end,
|
MOBILEGL_ASSERT(range.end <= m_size && range.start <= range.end,
|
||||||
"AcquireMemoryRange out of bounds: range (%zu, %zu) exceeds m_size (%zu)", range.start,
|
"AcquireMemoryRange out of bounds: range (%zu, %zu) exceeds m_size (%zu)", range.start,
|
||||||
range.end, m_size);
|
range.end, m_size);
|
||||||
// The app is about to look at the bytes; a shader may have rewritten them since
|
|
||||||
// the shadow was last authoritative. Also needed for a write map without an
|
|
||||||
// invalidate bit, whose staging copy is seeded from the shadow.
|
|
||||||
SyncGpuWrites();
|
|
||||||
m_isMapped = true;
|
m_isMapped = true;
|
||||||
m_mappingAccess = access;
|
m_mappingAccess = access;
|
||||||
m_mappedRange = range;
|
m_mappedRange = range;
|
||||||
|
|||||||
@@ -99,13 +99,6 @@ namespace MobileGL {
|
|||||||
// Must be idempotent: a second call for an already-backed buffer returns the
|
// Must be idempotent: a second call for an already-backed buffer returns the
|
||||||
// same base pointer.
|
// same base pointer.
|
||||||
void* (*AcquirePersistentMap)(BufferObject& bufferObject) = nullptr;
|
void* (*AcquirePersistentMap)(BufferObject& bufferObject) = nullptr;
|
||||||
// Pulls the backend's current contents for the whole buffer into the shadow
|
|
||||||
// (through WritebackFromBackend). Only ever called for a buffer the GPU may
|
|
||||||
// have written behind the frontend's back - a shader storage or atomic counter
|
|
||||||
// binding of a draw or dispatch - because nothing else can desynchronise the
|
|
||||||
// shadow. Backends that cannot read their storage back leave this null; the
|
|
||||||
// shadow then keeps its pre-dispatch bytes, which is the old behaviour.
|
|
||||||
void (*ReadbackFromGpu)(BufferObject& bufferObject) = nullptr;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Registered by the active backend at init, cleared at shutdown.
|
// Registered by the active backend at init, cleared at shutdown.
|
||||||
@@ -140,11 +133,6 @@ namespace MobileGL {
|
|||||||
|
|
||||||
void* AcquireMemory(Bool markMapped, Bool read, Bool write);
|
void* AcquireMemory(Bool markMapped, Bool read, Bool write);
|
||||||
void* AcquireMemoryRange(Range1D range, Flags<BufferMappingAccessBit> access);
|
void* AcquireMemoryRange(Range1D range, Flags<BufferMappingAccessBit> access);
|
||||||
// Adopt backend host-visible coherent GPU storage as the source of truth
|
|
||||||
// (used for GPU-written targets like transform feedback capture, so
|
|
||||||
// MapBuffer/GetBufferSubData read real GPU results). No-op when already
|
|
||||||
// resident or when the backend declines.
|
|
||||||
Bool EnsureGpuResidentStorage();
|
|
||||||
void ReleaseMemory();
|
void ReleaseMemory();
|
||||||
void FlushMemoryRange(SizeT offset, SizeT length);
|
void FlushMemoryRange(SizeT offset, SizeT length);
|
||||||
|
|
||||||
@@ -156,16 +144,6 @@ namespace MobileGL {
|
|||||||
// backend op: the backend storage already holds these bytes.
|
// backend op: the backend storage already holds these bytes.
|
||||||
void WritebackFromBackend(DataPtr data, SizeT atOffset);
|
void WritebackFromBackend(DataPtr data, SizeT atOffset);
|
||||||
|
|
||||||
// A draw or dispatch just ran with this buffer bound where a shader can write
|
|
||||||
// it (shader storage / atomic counter). The next read has to reconcile with
|
|
||||||
// that: pull the bytes back, or - when the shadow already IS coherent GPU
|
|
||||||
// memory - wait for the work that wrote them to retire. Which of the two is
|
|
||||||
// the backend's business; the flag only says a GPU write is outstanding.
|
|
||||||
void MarkGpuWritten();
|
|
||||||
// Refreshes the shadow from the backend when a GPU write is outstanding. Called
|
|
||||||
// from every path that reads the shadow on the app's behalf.
|
|
||||||
void SyncGpuWrites();
|
|
||||||
|
|
||||||
Bool IsMapped() const;
|
Bool IsMapped() const;
|
||||||
Bool IsImmutableStorage() const;
|
Bool IsImmutableStorage() const;
|
||||||
SizeT GetSize() const;
|
SizeT GetSize() const;
|
||||||
@@ -213,8 +191,6 @@ namespace MobileGL {
|
|||||||
Bool m_isImmutableStorage = false;
|
Bool m_isImmutableStorage = false;
|
||||||
GLbitfield m_storageFlags = 0;
|
GLbitfield m_storageFlags = 0;
|
||||||
Uint64 m_changeSerial = 0;
|
Uint64 m_changeSerial = 0;
|
||||||
// Set by MarkGpuWritten, cleared by SyncGpuWrites once the shadow is refreshed.
|
|
||||||
Bool m_gpuWritePending = false;
|
|
||||||
Range1D m_mappedRange;
|
Range1D m_mappedRange;
|
||||||
Vector<Uint8> m_stagingData;
|
Vector<Uint8> m_stagingData;
|
||||||
Bool m_ownsStagingData;
|
Bool m_ownsStagingData;
|
||||||
|
|||||||
@@ -31,9 +31,6 @@ namespace MobileGL::MG_State::GLState {
|
|||||||
BindingSlot<BufferObject>& GetBindingSlot(BufferTarget target);
|
BindingSlot<BufferObject>& GetBindingSlot(BufferTarget target);
|
||||||
// For glBindBufferBase / glBindBufferRange
|
// For glBindBufferBase / glBindBufferRange
|
||||||
BindingSlotRange1D<BufferObject>& GetBindingPoint(BufferTarget target, Uint index);
|
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 {
|
constexpr SizeT GetBindingPointCount(const BufferTarget target) const {
|
||||||
auto it = std::find(BufferBindPointTargets.begin(), BufferBindPointTargets.end(), target);
|
auto it = std::find(BufferBindPointTargets.begin(), BufferBindPointTargets.end(), target);
|
||||||
auto index = std::distance(BufferBindPointTargets.begin(), it);
|
auto index = std::distance(BufferBindPointTargets.begin(), it);
|
||||||
|
|||||||
@@ -241,32 +241,6 @@ namespace MobileGL::MG_State {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void GLContext::MarkTextureObjectForDeletion(Uint index) {
|
void GLContext::MarkTextureObjectForDeletion(Uint index) {
|
||||||
// GL 3.3 core 4.4.2: deleting a texture whose image is attached to the framebuffer
|
|
||||||
// that is currently bound acts as if FramebufferTexture* had been called with texture
|
|
||||||
// zero for every attachment point it occupied there. Framebuffers that are NOT bound
|
|
||||||
// keep the orphaned attachment, so only the bound ones are touched.
|
|
||||||
//
|
|
||||||
// Without this the framebuffer object goes on holding the deleted texture alive as its
|
|
||||||
// attachment, and a later read through that framebuffer returns the dead texture's
|
|
||||||
// contents rather than those of whatever the application put in its place - the name
|
|
||||||
// it deleted usually comes straight back from the next glGenTextures, so the two are
|
|
||||||
// indistinguishable from the outside (KHR-GL32.packed_pixels read a stale gradient).
|
|
||||||
if (const auto& textureObject = m_textureState.GetTextureObject(index)) {
|
|
||||||
for (SizeT targetIndex = 0; targetIndex < SizeT(FramebufferTarget::FramebufferTargetCount);
|
|
||||||
++targetIndex) {
|
|
||||||
const auto& framebuffer =
|
|
||||||
GetFramebufferBindingSlot(static_cast<FramebufferTarget>(targetIndex)).GetBoundObject();
|
|
||||||
if (!framebuffer || framebuffer->IsDefaultFramebuffer()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const auto& attachments = framebuffer->GetAllAttachmentObjects();
|
|
||||||
for (SizeT i = 0; i < attachments.size(); ++i) {
|
|
||||||
if (attachments[i].IsTexture() && attachments[i].GetTexture() == textureObject) {
|
|
||||||
framebuffer->Detach(static_cast<FramebufferAttachmentType>(i));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
m_textureState.MarkTextureObjectForDeletion(index, IsRelaxedSemanticsActive());
|
m_textureState.MarkTextureObjectForDeletion(index, IsRelaxedSemanticsActive());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -343,60 +317,7 @@ namespace MobileGL::MG_State {
|
|||||||
return m_programState.GetCurrentProgram();
|
return m_programState.GetCurrentProgram();
|
||||||
}
|
}
|
||||||
|
|
||||||
const SharedPtr<ProgramObject>& GLContext::GetProgramForDraw() {
|
|
||||||
static const SharedPtr<ProgramObject> nullProgram = nullptr;
|
|
||||||
const auto& currentProgram = m_programState.GetCurrentProgram();
|
|
||||||
if (currentProgram) return currentProgram;
|
|
||||||
if (m_boundProgramPipeline == 0) return nullProgram;
|
|
||||||
const auto& pipeline = GetBoundProgramPipeline();
|
|
||||||
if (!pipeline) return nullProgram;
|
|
||||||
|
|
||||||
const auto signature = pipeline->ComputeDrawProgramSignature();
|
|
||||||
if (const auto& cached = pipeline->GetCachedDrawProgram(signature)) return cached;
|
|
||||||
|
|
||||||
// Everything downstream of here - the backends, the uniform plumbing, the draw
|
|
||||||
// validation - is written against a single linked program, so the pipeline is
|
|
||||||
// flattened into one. Each stage contributes only the shaders that serve it, so a
|
|
||||||
// program bound to two stages is not pulled in twice and a program bound to a
|
|
||||||
// stage it does not implement contributes nothing.
|
|
||||||
// Deliberately not a named program: it is reachable only through the pipeline, it
|
|
||||||
// must not answer glIsProgram, and it must not consume a name the application
|
|
||||||
// could otherwise be handed. Backend registries key on the object, not the name.
|
|
||||||
auto composite = MakeShared<ProgramObject>(0u);
|
|
||||||
|
|
||||||
Bool anyStage = false;
|
|
||||||
for (SizeT stage = 0; stage < static_cast<SizeT>(ShaderStage::ShaderStageCount); ++stage) {
|
|
||||||
const auto& stageProgram = pipeline->GetStageProgram(static_cast<ShaderStage>(stage));
|
|
||||||
if (!stageProgram) continue;
|
|
||||||
for (const auto& shader : stageProgram->GetAttachedShaders()) {
|
|
||||||
if (!shader || static_cast<SizeT>(shader->GetShaderStage()) != stage) continue;
|
|
||||||
composite->AttachShader(shader);
|
|
||||||
anyStage = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!anyStage) return nullProgram;
|
|
||||||
// A pipeline with no fragment stage still rasterises, so the default fragment
|
|
||||||
// shader is wanted here even though the separable stage programs never get one.
|
|
||||||
composite->Link(true);
|
|
||||||
pipeline->SetCachedDrawProgram(signature, Move(composite));
|
|
||||||
return pipeline->GetCachedDrawProgram(signature);
|
|
||||||
}
|
|
||||||
|
|
||||||
const SharedPtr<ProgramObject>& GLContext::GetProgramForUniform() {
|
|
||||||
const auto& currentProgram = m_programState.GetCurrentProgram();
|
|
||||||
if (currentProgram) return currentProgram;
|
|
||||||
static const SharedPtr<ProgramObject> nullProgram = nullptr;
|
|
||||||
if (m_boundProgramPipeline == 0) return nullProgram;
|
|
||||||
const auto& pipeline = GetBoundProgramPipeline();
|
|
||||||
if (!pipeline) return nullProgram;
|
|
||||||
return pipeline->GetActiveProgram();
|
|
||||||
}
|
|
||||||
|
|
||||||
// RenderState
|
// RenderState
|
||||||
Uint GLContext::GetPipelineStateVersion() const {
|
|
||||||
return m_renderState.GetPipelineStateVersion();
|
|
||||||
}
|
|
||||||
|
|
||||||
Uint GLContext::GetRenderStateParametersVersion() const {
|
Uint GLContext::GetRenderStateParametersVersion() const {
|
||||||
return m_renderState.GetVersion();
|
return m_renderState.GetVersion();
|
||||||
}
|
}
|
||||||
@@ -477,14 +398,6 @@ namespace MobileGL::MG_State {
|
|||||||
m_renderState.SetPointSize(size);
|
m_renderState.SetPointSize(size);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GLContext::SetPatchVertices(Uint vertices) {
|
|
||||||
m_renderState.SetPatchVertices(vertices);
|
|
||||||
}
|
|
||||||
|
|
||||||
Uint GLContext::GetPatchVertices() const {
|
|
||||||
return m_renderState.GetPatchVertices();
|
|
||||||
}
|
|
||||||
|
|
||||||
Float GLContext::GetPointSize() const {
|
Float GLContext::GetPointSize() const {
|
||||||
return m_renderState.GetPointSize();
|
return m_renderState.GetPointSize();
|
||||||
}
|
}
|
||||||
@@ -806,200 +719,6 @@ namespace MobileGL::MG_State {
|
|||||||
Bool GLContext::ValidateRenderbufferObject(Uint index) const {
|
Bool GLContext::ValidateRenderbufferObject(Uint index) const {
|
||||||
return m_renderbufferState.ValidateRenderbufferObject(index);
|
return m_renderbufferState.ValidateRenderbufferObject(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GLContext::SaveBoundTransformFeedbackState() {
|
|
||||||
auto& object = m_transformFeedbackObjects[m_boundTransformFeedback];
|
|
||||||
for (Uint i = 0; i < MAX_TRANSFORM_FEEDBACK_BUFFERS; ++i) {
|
|
||||||
const auto& point = m_bufferState.GetBindingPoint(BufferTarget::TransformFeedback, i);
|
|
||||||
object.bindings[i] = {point.GetBoundObject(), point.GetRange(), point.HasExplicitRange()};
|
|
||||||
}
|
|
||||||
object.active = m_transformFeedbackActive;
|
|
||||||
object.paused = m_transformFeedbackPaused;
|
|
||||||
object.primitiveMode = m_transformFeedbackPrimitiveMode;
|
|
||||||
object.program = m_transformFeedbackProgram;
|
|
||||||
object.generation = m_transformFeedbackGeneration;
|
|
||||||
object.capturedVertices = m_transformFeedbackCapturedVertices;
|
|
||||||
object.inputPrimitives = m_transformFeedbackInputPrimitives;
|
|
||||||
}
|
|
||||||
|
|
||||||
void GLContext::RestoreBoundTransformFeedbackState() {
|
|
||||||
const auto& object = m_transformFeedbackObjects[m_boundTransformFeedback];
|
|
||||||
for (Uint i = 0; i < MAX_TRANSFORM_FEEDBACK_BUFFERS; ++i) {
|
|
||||||
auto& point = m_bufferState.GetBindingPoint(BufferTarget::TransformFeedback, i);
|
|
||||||
point.Bind(object.bindings[i].buffer);
|
|
||||||
if (object.bindings[i].buffer) {
|
|
||||||
point.SetRange(object.bindings[i].range, object.bindings[i].hasExplicitRange);
|
|
||||||
} else {
|
|
||||||
point.ClearRange();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
m_transformFeedbackActive = object.active;
|
|
||||||
m_transformFeedbackPaused = object.paused;
|
|
||||||
m_transformFeedbackPrimitiveMode = object.primitiveMode;
|
|
||||||
m_transformFeedbackProgram = object.program;
|
|
||||||
// The generation identifies one capture span, and a span belongs to the object
|
|
||||||
// that opened it - a backend keys its append state on it, so switching objects
|
|
||||||
// has to bring the right one back.
|
|
||||||
m_transformFeedbackGeneration = object.generation;
|
|
||||||
m_transformFeedbackCapturedVertices = object.capturedVertices;
|
|
||||||
m_transformFeedbackInputPrimitives = object.inputPrimitives;
|
|
||||||
}
|
|
||||||
|
|
||||||
void GLContext::GenTransformFeedbackNames(Uint number, Vector<Uint>& ids) {
|
|
||||||
ids.resize(number);
|
|
||||||
if (number == 0) return;
|
|
||||||
m_transformFeedbackNames.Generate(number, ids.data());
|
|
||||||
// A generated name already denotes an object with the default state, so that a
|
|
||||||
// bind never has to distinguish "first use" from any later one.
|
|
||||||
for (const Uint id : ids) {
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
void GLContext::BindTransformFeedbackObject(Uint index) {
|
|
||||||
if (index == m_boundTransformFeedback) return;
|
|
||||||
SaveBoundTransformFeedbackState();
|
|
||||||
m_boundTransformFeedback = index;
|
|
||||||
m_transformFeedbackObjects[index].everBound = true;
|
|
||||||
RestoreBoundTransformFeedbackState();
|
|
||||||
}
|
|
||||||
|
|
||||||
Bool GLContext::IsTransformFeedbackObject(Uint index) const {
|
|
||||||
if (index == 0 || !m_transformFeedbackNames.IsValid(index)) return false;
|
|
||||||
const auto it = m_transformFeedbackObjects.find(index);
|
|
||||||
return it != m_transformFeedbackObjects.end() && it->second.everBound;
|
|
||||||
}
|
|
||||||
|
|
||||||
void GLContext::MarkTransformFeedbackObjectForDeletion(Uint index) {
|
|
||||||
if (index == 0 || !m_transformFeedbackNames.IsValid(index)) return;
|
|
||||||
// Deleting the bound object reverts to the default one (GL 4.6 core 13.2.1);
|
|
||||||
// its state is dropped rather than saved back into the dying object.
|
|
||||||
if (index == m_boundTransformFeedback) {
|
|
||||||
m_boundTransformFeedback = 0;
|
|
||||||
RestoreBoundTransformFeedbackState();
|
|
||||||
}
|
|
||||||
m_transformFeedbackObjects.erase(index);
|
|
||||||
m_transformFeedbackNames.Delete(index);
|
|
||||||
}
|
|
||||||
|
|
||||||
Uint64 GLContext::GetTransformFeedbackRecordedVertices(Uint index) const {
|
|
||||||
const auto it = m_transformFeedbackObjects.find(index);
|
|
||||||
return it == m_transformFeedbackObjects.end() ? 0 : it->second.recordedVertices;
|
|
||||||
}
|
|
||||||
|
|
||||||
Bool GLContext::HasTransformFeedbackCompletedSpan(Uint index) const {
|
|
||||||
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
|
} // namespace GLState
|
||||||
|
|
||||||
// Leak-at-exit storage; see GlobalObjects.cpp.
|
// Leak-at-exit storage; see GlobalObjects.cpp.
|
||||||
|
|||||||
@@ -14,7 +14,6 @@
|
|||||||
#include "MG_State/GLState/RenderbufferState/RenderbufferState.h"
|
#include "MG_State/GLState/RenderbufferState/RenderbufferState.h"
|
||||||
#include "RenderState/RenderState.h"
|
#include "RenderState/RenderState.h"
|
||||||
#include "ProgramState/ProgramState.h"
|
#include "ProgramState/ProgramState.h"
|
||||||
#include "ProgramState/ProgramPipelineObject.h"
|
|
||||||
#include "SamplerState/SamplerState.h"
|
#include "SamplerState/SamplerState.h"
|
||||||
#include "TextureState/TextureState.h"
|
#include "TextureState/TextureState.h"
|
||||||
#include "FramebufferState/FramebufferState.h"
|
#include "FramebufferState/FramebufferState.h"
|
||||||
@@ -46,14 +45,6 @@ namespace MobileGL {
|
|||||||
// translates the result into its own API call.
|
// translates the result into its own API call.
|
||||||
VertexAttribTypeInfo ClassifyVertexAttribType(GLenum glType);
|
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 {
|
class GLContext {
|
||||||
public:
|
public:
|
||||||
GLContext() = default;
|
GLContext() = default;
|
||||||
@@ -137,30 +128,9 @@ namespace MobileGL {
|
|||||||
const SharedPtr<ShaderObject>& GetShaderObject(Uint index);
|
const SharedPtr<ShaderObject>& GetShaderObject(Uint index);
|
||||||
void UseProgram(Uint program);
|
void UseProgram(Uint program);
|
||||||
const SharedPtr<ProgramObject>& GetCurrentProgram();
|
const SharedPtr<ProgramObject>& GetCurrentProgram();
|
||||||
// What a draw or dispatch actually executes: the program in use, or - when
|
|
||||||
// there is none - the bound pipeline's stages composited into one program.
|
|
||||||
const SharedPtr<ProgramObject>& GetProgramForDraw();
|
|
||||||
// What glUniform* addresses: the program in use, or the bound pipeline's
|
|
||||||
// active program (GL 4.6 core 7.6.1).
|
|
||||||
const SharedPtr<ProgramObject>& GetProgramForUniform();
|
|
||||||
|
|
||||||
// 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
|
// RenderState
|
||||||
Uint GetRenderStateParametersVersion() const;
|
Uint GetRenderStateParametersVersion() const;
|
||||||
// Only the pipeline-relevant subset - see RenderState::m_pipelineStateVersion.
|
|
||||||
Uint GetPipelineStateVersion() const;
|
|
||||||
const RenderStateParameters& GetRenderStateParameters() const;
|
const RenderStateParameters& GetRenderStateParameters() const;
|
||||||
void SetViewport(IntVec4 viewport); // x, y, width, height
|
void SetViewport(IntVec4 viewport); // x, y, width, height
|
||||||
const IntVec4& GetViewport() const; // x, y, width, height
|
const IntVec4& GetViewport() const; // x, y, width, height
|
||||||
@@ -168,8 +138,6 @@ namespace MobileGL {
|
|||||||
Float GetLineWidth() const;
|
Float GetLineWidth() const;
|
||||||
void SetPointSize(Float size);
|
void SetPointSize(Float size);
|
||||||
Float GetPointSize() const;
|
Float GetPointSize() const;
|
||||||
void SetPatchVertices(Uint vertices);
|
|
||||||
Uint GetPatchVertices() const;
|
|
||||||
void SetPolygonOffset(Float factor, Float units);
|
void SetPolygonOffset(Float factor, Float units);
|
||||||
Float GetPolygonOffsetFactor() const;
|
Float GetPolygonOffsetFactor() const;
|
||||||
Float GetPolygonOffsetUnits() const;
|
Float GetPolygonOffsetUnits() const;
|
||||||
@@ -243,103 +211,6 @@ namespace MobileGL {
|
|||||||
void SetScissorBox(IntVec4 box); // x, y, width, height
|
void SetScissorBox(IntVec4 box); // x, y, width, height
|
||||||
const IntVec4& GetScissorBox() const; // x, y, width, height
|
const IntVec4& GetScissorBox() const; // x, y, width, height
|
||||||
|
|
||||||
// Transform feedback. The fields below are the state of the transform
|
|
||||||
// feedback object currently bound to GL_TRANSFORM_FEEDBACK; see the object
|
|
||||||
// block further down for how a bind swaps them.
|
|
||||||
void BeginTransformFeedback(GLenum primitiveMode, const SharedPtr<ProgramObject>& program) {
|
|
||||||
m_transformFeedbackActive = true;
|
|
||||||
m_transformFeedbackPaused = false;
|
|
||||||
m_transformFeedbackPrimitiveMode = primitiveMode;
|
|
||||||
m_transformFeedbackProgram = program;
|
|
||||||
m_transformFeedbackGeneration = ++m_transformFeedbackNextGeneration;
|
|
||||||
m_transformFeedbackCapturedVertices = 0;
|
|
||||||
m_transformFeedbackInputPrimitives = 0;
|
|
||||||
}
|
|
||||||
void EndTransformFeedback() {
|
|
||||||
m_transformFeedbackActive = false;
|
|
||||||
m_transformFeedbackPaused = false;
|
|
||||||
m_transformFeedbackProgram.reset();
|
|
||||||
// What glDrawTransformFeedback on this object replays from now on.
|
|
||||||
auto& object = m_transformFeedbackObjects[m_boundTransformFeedback];
|
|
||||||
object.recordedVertices = m_transformFeedbackCapturedVertices;
|
|
||||||
object.hasCompletedSpan = true;
|
|
||||||
}
|
|
||||||
Bool IsTransformFeedbackActive() const { return m_transformFeedbackActive; }
|
|
||||||
Bool IsTransformFeedbackPaused() const { return m_transformFeedbackPaused; }
|
|
||||||
void SetTransformFeedbackPaused(Bool paused) { m_transformFeedbackPaused = paused; }
|
|
||||||
GLenum GetTransformFeedbackPrimitiveMode() const { return m_transformFeedbackPrimitiveMode; }
|
|
||||||
const SharedPtr<ProgramObject>& GetTransformFeedbackProgram() const {
|
|
||||||
return m_transformFeedbackProgram;
|
|
||||||
}
|
|
||||||
// Bumped on every BeginTransformFeedback; the backend uses it to
|
|
||||||
// distinguish "resume appending" from "fresh capture".
|
|
||||||
Uint64 GetTransformFeedbackGeneration() const { return m_transformFeedbackGeneration; }
|
|
||||||
// CPU-side primitive accounting for the transform feedback queries:
|
|
||||||
// every captured draw adds its primitive count (draws without a
|
|
||||||
// geometry stage write exactly what they generate).
|
|
||||||
void AddTransformFeedbackPrimitives(Uint64 primitives) {
|
|
||||||
m_transformFeedbackPrimitiveCounter += primitives;
|
|
||||||
}
|
|
||||||
Uint64 GetTransformFeedbackPrimitiveCounter() const { return m_transformFeedbackPrimitiveCounter; }
|
|
||||||
// Primitives a draw assembled while the capture was paused. GL counts those in
|
|
||||||
// PRIMITIVES_GENERATED, but a backend that answers the query with its own
|
|
||||||
// transform feedback counter cannot see them - nothing was being captured.
|
|
||||||
void AddTransformFeedbackPausedPrimitives(Uint64 primitives) {
|
|
||||||
m_transformFeedbackPausedPrimitiveCounter += primitives;
|
|
||||||
}
|
|
||||||
Uint64 GetTransformFeedbackPausedPrimitiveCounter() const {
|
|
||||||
return m_transformFeedbackPausedPrimitiveCounter;
|
|
||||||
}
|
|
||||||
// Vertices already captured since BeginTransformFeedback (drives the
|
|
||||||
// buffer-capacity clamp on the primitives-written accounting).
|
|
||||||
void AddTransformFeedbackCapturedVertices(Uint64 vertices) {
|
|
||||||
m_transformFeedbackCapturedVertices += vertices;
|
|
||||||
}
|
|
||||||
Uint64 GetTransformFeedbackCapturedVertices() const { return m_transformFeedbackCapturedVertices; }
|
|
||||||
// Raw assembled input primitives fed to the capture stage since Begin
|
|
||||||
// (pre-clamp; drives the GS strip capture-order fixup at EndTF).
|
|
||||||
void AddTransformFeedbackInputPrimitives(Uint64 primitives) {
|
|
||||||
m_transformFeedbackInputPrimitives += primitives;
|
|
||||||
}
|
|
||||||
Uint64 GetTransformFeedbackInputPrimitives() const { return m_transformFeedbackInputPrimitives; }
|
|
||||||
|
|
||||||
// Transform feedback objects (ARB_transform_feedback2 / GL 4.0 core).
|
|
||||||
// The capture state above and the indexed GL_TRANSFORM_FEEDBACK_BUFFER
|
|
||||||
// binding points are object state, but the context keeps exactly one live
|
|
||||||
// copy of both so that every existing reader - the backends' per-draw sync,
|
|
||||||
// the drawing and getter paths - needs no notion of which object owns them.
|
|
||||||
// A bind therefore saves the live copy into the outgoing object and restores
|
|
||||||
// the incoming one's. Object 0 is the default object and always exists.
|
|
||||||
static constexpr Uint MAX_TRANSFORM_FEEDBACK_BUFFERS = 4;
|
|
||||||
void GenTransformFeedbackNames(Uint number, Vector<Uint>& ids);
|
|
||||||
// A name glGenTransformFeedbacks handed out and glDeleteTransformFeedbacks
|
|
||||||
// has not taken back. Name 0 is always valid.
|
|
||||||
Bool ValidateTransformFeedbackName(Uint index) const;
|
|
||||||
// What glIsTransformFeedback reports: a generated name only becomes the name
|
|
||||||
// of an object once it has been bound at least once (GL 4.6 core 13.2.1).
|
|
||||||
Bool IsTransformFeedbackObject(Uint index) const;
|
|
||||||
void BindTransformFeedbackObject(Uint index);
|
|
||||||
void MarkTransformFeedbackObjectForDeletion(Uint index);
|
|
||||||
Uint GetBoundTransformFeedbackName() const { return m_boundTransformFeedback; }
|
|
||||||
// Vertices the object captured in its last completed span; the vertex count
|
|
||||||
// glDrawTransformFeedback replays.
|
|
||||||
Uint64 GetTransformFeedbackRecordedVertices(Uint index) const;
|
|
||||||
// Whether the object has ever completed a capture span. glDrawTransformFeedback
|
|
||||||
// on an object that has not is INVALID_OPERATION, which a zero vertex count
|
|
||||||
// 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
|
// Framebuffer
|
||||||
void GenFramebufferNames(Uint number, Vector<Uint>& framebuffers);
|
void GenFramebufferNames(Uint number, Vector<Uint>& framebuffers);
|
||||||
const SharedPtr<FramebufferObject>& GetFramebufferObject(Uint index);
|
const SharedPtr<FramebufferObject>& GetFramebufferObject(Uint index);
|
||||||
@@ -372,51 +243,6 @@ namespace MobileGL {
|
|||||||
BufferState m_bufferState;
|
BufferState m_bufferState;
|
||||||
VertexArrayState m_vertexArrayState;
|
VertexArrayState m_vertexArrayState;
|
||||||
Array<CurrentVertexAttributeValue, VertexArrayObject::MAX_VERTEX_ATTRIBS> m_currentVertexAttributes{};
|
Array<CurrentVertexAttributeValue, VertexArrayObject::MAX_VERTEX_ATTRIBS> m_currentVertexAttributes{};
|
||||||
Bool m_transformFeedbackActive = false;
|
|
||||||
Bool m_transformFeedbackPaused = false;
|
|
||||||
GLenum m_transformFeedbackPrimitiveMode = GL_POINTS;
|
|
||||||
SharedPtr<ProgramObject> m_transformFeedbackProgram;
|
|
||||||
Uint64 m_transformFeedbackGeneration = 0;
|
|
||||||
// Source of the per-span ids above; never rolls back with an object switch.
|
|
||||||
Uint64 m_transformFeedbackNextGeneration = 0;
|
|
||||||
// Not object state: the transform feedback queries snapshot it at BeginQuery
|
|
||||||
// and take the delta at EndQuery, which spans whatever objects were used.
|
|
||||||
Uint64 m_transformFeedbackPrimitiveCounter = 0;
|
|
||||||
Uint64 m_transformFeedbackPausedPrimitiveCounter = 0;
|
|
||||||
Uint64 m_transformFeedbackCapturedVertices = 0;
|
|
||||||
Uint64 m_transformFeedbackInputPrimitives = 0;
|
|
||||||
|
|
||||||
// Everything a transform feedback object owns while it is NOT the bound one.
|
|
||||||
struct TransformFeedbackObjectState {
|
|
||||||
struct SavedBufferBinding {
|
|
||||||
SharedPtr<BufferObject> buffer;
|
|
||||||
Range1D range;
|
|
||||||
Bool hasExplicitRange = false;
|
|
||||||
};
|
|
||||||
Array<SavedBufferBinding, MAX_TRANSFORM_FEEDBACK_BUFFERS> bindings;
|
|
||||||
Bool active = false;
|
|
||||||
Bool paused = false;
|
|
||||||
GLenum primitiveMode = GL_POINTS;
|
|
||||||
SharedPtr<ProgramObject> program;
|
|
||||||
Uint64 generation = 0;
|
|
||||||
Uint64 capturedVertices = 0;
|
|
||||||
Uint64 inputPrimitives = 0;
|
|
||||||
Uint64 recordedVertices = 0;
|
|
||||||
Bool hasCompletedSpan = false;
|
|
||||||
Bool everBound = false;
|
|
||||||
};
|
|
||||||
void SaveBoundTransformFeedbackState();
|
|
||||||
void RestoreBoundTransformFeedbackState();
|
|
||||||
// operator[] materialises an entry with the default state on first touch, so
|
|
||||||
// the default object (name 0) needs no seeding here.
|
|
||||||
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;
|
TextureState m_textureState;
|
||||||
ProgramState m_programState;
|
ProgramState m_programState;
|
||||||
RenderState m_renderState;
|
RenderState m_renderState;
|
||||||
|
|||||||
@@ -185,20 +185,6 @@ namespace MobileGL::MG_State::GLState {
|
|||||||
return m_externalIndex;
|
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) {
|
void FramebufferObject::BumpAttachmentVersion(FramebufferAttachmentType type) {
|
||||||
++m_attachmentVersions[static_cast<SizeT>(type)];
|
++m_attachmentVersions[static_cast<SizeT>(type)];
|
||||||
++m_objectVersion;
|
++m_objectVersion;
|
||||||
|
|||||||
@@ -129,19 +129,6 @@ namespace MobileGL {
|
|||||||
void SetReadBuffer(FramebufferAttachmentType buf);
|
void SetReadBuffer(FramebufferAttachmentType buf);
|
||||||
FramebufferAttachmentType GetReadBuffer() const { return m_readBuffer; }
|
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 {
|
FramebufferAttachmentVersionArray GetAllFramebufferAttachmentVersions() const {
|
||||||
return m_attachmentVersions;
|
return m_attachmentVersions;
|
||||||
}
|
}
|
||||||
@@ -161,12 +148,6 @@ namespace MobileGL {
|
|||||||
FramebufferAttachmentArray m_drawBuffers; // Probably no versioning needed for this, just check equality
|
FramebufferAttachmentArray m_drawBuffers; // Probably no versioning needed for this, just check equality
|
||||||
FramebufferAttachmentType m_readBuffer = FramebufferAttachmentType::None;
|
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`)
|
// This version will bump when draw/read buffer changes (by `glDrawBuffer(s)`/`glReadBuffer`)
|
||||||
Uint16 m_objectVersion = 0;
|
Uint16 m_objectVersion = 0;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -170,280 +170,9 @@ namespace MobileGL::MG_State::GLState {
|
|||||||
m_uniformNameMaxLength = 0;
|
m_uniformNameMaxLength = 0;
|
||||||
m_attribInNameMaxLength = 0;
|
m_attribInNameMaxLength = 0;
|
||||||
m_uniformBlockNameMaxLength = 0;
|
m_uniformBlockNameMaxLength = 0;
|
||||||
m_xfbVaryings.clear();
|
|
||||||
m_xfbStrides.clear();
|
|
||||||
m_xfbBufferMode = GL_INTERLEAVED_ATTRIBS;
|
|
||||||
m_xfbVaryingNameMaxLength = 0;
|
|
||||||
m_xfbNeedsScatteredCapture = false;
|
|
||||||
m_xfbPackedStride = 0;
|
|
||||||
m_gsInputPrimitive = GL_NONE;
|
|
||||||
m_linkStatus = false;
|
m_linkStatus = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace {
|
|
||||||
// GL type enum for a vertex-stage output symbol captured by transform
|
|
||||||
// feedback. Covers the scalar/vector/matrix float+integer types transform
|
|
||||||
// feedback may legally capture in GL 3.3.
|
|
||||||
Bool ResolveXfbSymbolType(const glslang::TType& type, GLenum& outType, GLint& outArraySize,
|
|
||||||
Uint32& outBytesPerElement) {
|
|
||||||
outArraySize = type.isArray() ? type.getOuterArraySize() : 1;
|
|
||||||
const Int columns = type.isMatrix() ? type.getMatrixCols() : 1;
|
|
||||||
const Int components = type.isMatrix() ? type.getMatrixRows()
|
|
||||||
: (type.isVector() ? type.getVectorSize() : 1);
|
|
||||||
const glslang::TBasicType basic = type.getBasicType();
|
|
||||||
static constexpr GLenum kFloatTypes[5] = {0, GL_FLOAT, GL_FLOAT_VEC2, GL_FLOAT_VEC3, GL_FLOAT_VEC4};
|
|
||||||
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 && 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 = 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;
|
|
||||||
}
|
|
||||||
// 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
|
|
||||||
|
|
||||||
Bool ProgramObject::ResolveTransformFeedbackVaryings() {
|
|
||||||
m_xfbVaryings.clear();
|
|
||||||
m_xfbStrides.clear();
|
|
||||||
m_xfbBufferMode = m_requestedXfbBufferMode;
|
|
||||||
m_xfbVaryingNameMaxLength = 0;
|
|
||||||
m_xfbNeedsScatteredCapture = false;
|
|
||||||
m_xfbPackedStride = 0;
|
|
||||||
if (m_requestedXfbVaryings.empty()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Capture happens at the last vertex-processing stage (geometry, then
|
|
||||||
// tessellation evaluation, then vertex).
|
|
||||||
const glslang::TIntermediate* captureIntermediate = nullptr;
|
|
||||||
for (EShLanguage stage : {EShLangGeometry, EShLangTessEvaluation, EShLangVertex}) {
|
|
||||||
captureIntermediate = m_program->getIntermediate(stage);
|
|
||||||
if (captureIntermediate != nullptr) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (captureIntermediate == nullptr) {
|
|
||||||
m_infoLog = "Transform feedback varyings requested but the program has no vertex-processing stage.";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const glslang::TIntermAggregate* linkerObjects = captureIntermediate->findLinkerObjects();
|
|
||||||
|
|
||||||
const Bool interleaved = m_xfbBufferMode == GL_INTERLEAVED_ATTRIBS;
|
|
||||||
Uint32 interleavedOffset = 0;
|
|
||||||
// ARB_transform_feedback3 lets an interleaved capture leave holes (gl_SkipComponents1..4)
|
|
||||||
// and move on to the next buffer (gl_NextBuffer). Both only affect where the following
|
|
||||||
// varyings land, so they are consumed here and never become XfbVaryings of their own -
|
|
||||||
// which also keeps them out of the name list a backend declares on its own driver.
|
|
||||||
Uint32 interleavedBufferIndex = 0;
|
|
||||||
Vector<Uint32> interleavedStrides;
|
|
||||||
for (SizeT i = 0; i < m_requestedXfbVaryings.size(); ++i) {
|
|
||||||
const String& name = m_requestedXfbVaryings[i];
|
|
||||||
if (interleaved && name == "gl_NextBuffer") {
|
|
||||||
interleavedStrides.push_back(interleavedOffset);
|
|
||||||
interleavedOffset = 0;
|
|
||||||
++interleavedBufferIndex;
|
|
||||||
m_xfbNeedsScatteredCapture = true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (interleaved && name.size() == 18 && name.compare(0, 17, "gl_SkipComponents") == 0 &&
|
|
||||||
name[17] >= '1' && name[17] <= '4') {
|
|
||||||
interleavedOffset += static_cast<Uint32>(name[17] - '0') * 4;
|
|
||||||
m_xfbNeedsScatteredCapture = true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
for (SizeT j = 0; j < i; ++j) {
|
|
||||||
if (m_requestedXfbVaryings[j] == name) {
|
|
||||||
m_infoLog = "Transform feedback varying '" + name + "' is specified more than once.";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
XfbVarying varying;
|
|
||||||
varying.name = name;
|
|
||||||
Uint32 bytesPerElement = 0;
|
|
||||||
Bool resolved = false;
|
|
||||||
if (name == "gl_Position") {
|
|
||||||
varying.type = GL_FLOAT_VEC4;
|
|
||||||
varying.size = 1;
|
|
||||||
bytesPerElement = 16;
|
|
||||||
resolved = true;
|
|
||||||
} else if (name == "gl_PointSize") {
|
|
||||||
varying.type = GL_FLOAT;
|
|
||||||
varying.size = 1;
|
|
||||||
bytesPerElement = 4;
|
|
||||||
resolved = true;
|
|
||||||
} else if (linkerObjects != nullptr) {
|
|
||||||
for (const auto* node : linkerObjects->getSequence()) {
|
|
||||||
const glslang::TIntermSymbol* symbol = node->getAsSymbolNode();
|
|
||||||
if (symbol == nullptr || symbol->getType().getQualifier().storage != glslang::EvqVaryingOut) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (symbol->getName() != name.c_str()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
resolved = ResolveXfbSymbolType(symbol->getType(), varying.type, varying.size, bytesPerElement);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!resolved) {
|
|
||||||
m_infoLog = "Transform feedback varying '" + name + "' is not an output of the vertex stage.";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
varying.byteSize = bytesPerElement * static_cast<Uint32>(varying.size);
|
|
||||||
varying.packedOffsetBytes = m_xfbPackedStride;
|
|
||||||
m_xfbPackedStride += varying.byteSize;
|
|
||||||
if (interleaved) {
|
|
||||||
varying.bufferIndex = interleavedBufferIndex;
|
|
||||||
varying.offsetBytes = interleavedOffset;
|
|
||||||
interleavedOffset += varying.byteSize;
|
|
||||||
} else {
|
|
||||||
varying.bufferIndex = static_cast<Uint32>(m_xfbVaryings.size());
|
|
||||||
varying.offsetBytes = 0;
|
|
||||||
}
|
|
||||||
m_xfbVaryingNameMaxLength =
|
|
||||||
std::max(m_xfbVaryingNameMaxLength, static_cast<Int>(name.size()) + 1);
|
|
||||||
m_xfbVaryings.push_back(Move(varying));
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr Uint32 kMaxSeparateAttribs = 4;
|
|
||||||
constexpr Uint32 kMaxSeparateComponents = 4;
|
|
||||||
constexpr Uint32 kMaxInterleavedComponents = 64;
|
|
||||||
constexpr Uint32 kMaxTransformFeedbackBuffers = 4;
|
|
||||||
if (interleaved) {
|
|
||||||
interleavedStrides.push_back(interleavedOffset);
|
|
||||||
if (interleavedStrides.size() > kMaxTransformFeedbackBuffers) {
|
|
||||||
m_infoLog = "Transform feedback capture uses more buffers than "
|
|
||||||
"GL_MAX_TRANSFORM_FEEDBACK_BUFFERS.";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
for (const Uint32 stride : interleavedStrides) {
|
|
||||||
if (stride > kMaxInterleavedComponents * 4) {
|
|
||||||
m_infoLog = "Transform feedback interleaved capture exceeds "
|
|
||||||
"GL_MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS.";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
m_xfbStrides = Move(interleavedStrides);
|
|
||||||
} else {
|
|
||||||
if (m_xfbVaryings.size() > kMaxSeparateAttribs) {
|
|
||||||
m_infoLog = "Transform feedback separate capture exceeds "
|
|
||||||
"GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS.";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
m_xfbStrides.resize(m_xfbVaryings.size());
|
|
||||||
for (SizeT i = 0; i < m_xfbVaryings.size(); ++i) {
|
|
||||||
if (m_xfbVaryings[i].byteSize > kMaxSeparateComponents * 4) {
|
|
||||||
m_infoLog = "Transform feedback varying '" + m_xfbVaryings[i].name +
|
|
||||||
"' exceeds GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_COMPONENTS.";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
m_xfbStrides[i] = m_xfbVaryings[i].byteSize;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ResolveGsTriangleStripCapture(captureIntermediate);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
// Extracts a geometry shader's per-invocation EmitVertex/EndPrimitive sequence
|
|
||||||
// when it is statically knowable (no emit inside selection/loop/switch). Vulkan
|
|
||||||
// transform feedback captures triangle strips in plain (i, i+1, i+2) order while
|
|
||||||
// GL decomposes odd strip triangles as (i+1, i, i+2) (GL 4.6 table 10.1); with
|
|
||||||
// the static strip lengths the capture buffer can be reordered after EndTF.
|
|
||||||
class GsEmitSequenceTraverser final : public glslang::TIntermTraverser {
|
|
||||||
public:
|
|
||||||
bool visitAggregate(glslang::TVisit, glslang::TIntermAggregate* node) override {
|
|
||||||
if (node->getOp() == glslang::EOpEmitVertex) {
|
|
||||||
++emitCount;
|
|
||||||
hasEmit = true;
|
|
||||||
} else if (node->getOp() == glslang::EOpEndPrimitive) {
|
|
||||||
FlushStrip();
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
bool visitSelection(glslang::TVisit, glslang::TIntermSelection*) override {
|
|
||||||
inControlFlow = true;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
bool visitLoop(glslang::TVisit, glslang::TIntermLoop*) override {
|
|
||||||
inControlFlow = true;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
bool visitSwitch(glslang::TVisit, glslang::TIntermSwitch*) override {
|
|
||||||
inControlFlow = true;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
void FlushStrip() {
|
|
||||||
if (emitCount >= 3) {
|
|
||||||
stripTriangles.push_back(static_cast<Uint32>(emitCount - 2));
|
|
||||||
}
|
|
||||||
emitCount = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
Vector<Uint32> stripTriangles;
|
|
||||||
Uint32 emitCount = 0;
|
|
||||||
Bool hasEmit = false;
|
|
||||||
Bool inControlFlow = false;
|
|
||||||
};
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
void ProgramObject::ResolveGsTriangleStripCapture(const glslang::TIntermediate* captureIntermediate) {
|
|
||||||
m_gsStripTriangles.clear();
|
|
||||||
m_gsStripCaptureFixup = false;
|
|
||||||
if (captureIntermediate == nullptr || m_program == nullptr) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (m_program->getIntermediate(EShLangGeometry) != captureIntermediate) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (captureIntermediate->getOutputPrimitive() != glslang::ElgTriangleStrip) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
GsEmitSequenceTraverser traverser;
|
|
||||||
const_cast<glslang::TIntermediate*>(captureIntermediate)->getTreeRoot()->traverse(&traverser);
|
|
||||||
traverser.FlushStrip(); // the invocation end acts as an implicit EndPrimitive
|
|
||||||
if (!traverser.hasEmit || traverser.inControlFlow || traverser.stripTriangles.empty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
m_gsStripTriangles = Move(traverser.stripTriangles);
|
|
||||||
m_gsStripCaptureFixup = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ProgramObject::ShaderIsAttached(const SharedPtr<ShaderObject>& shader) {
|
bool ProgramObject::ShaderIsAttached(const SharedPtr<ShaderObject>& shader) {
|
||||||
MGLOG_D("ProgramObject %u: ShaderIsAttached check for shader %p", m_externalIndex, shader.get());
|
MGLOG_D("ProgramObject %u: ShaderIsAttached check for shader %p", m_externalIndex, shader.get());
|
||||||
auto it = std::find_if(m_shaders.begin(), m_shaders.end(),
|
auto it = std::find_if(m_shaders.begin(), m_shaders.end(),
|
||||||
@@ -592,32 +321,12 @@ namespace MobileGL::MG_State::GLState {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// GL_GEOMETRY_INPUT_TYPE. A draw's primitive type has to be compatible with it
|
|
||||||
// (GL 4.6 core 11.3.1), so it is resolved for every link, not only a capturing one.
|
|
||||||
m_gsInputPrimitive = GL_NONE;
|
|
||||||
if (const glslang::TIntermediate* gs = m_program->getIntermediate(EShLangGeometry)) {
|
|
||||||
switch (gs->getInputPrimitive()) {
|
|
||||||
case glslang::ElgPoints: m_gsInputPrimitive = GL_POINTS; break;
|
|
||||||
case glslang::ElgLines: m_gsInputPrimitive = GL_LINES; break;
|
|
||||||
case glslang::ElgLinesAdjacency: m_gsInputPrimitive = GL_LINES_ADJACENCY; break;
|
|
||||||
case glslang::ElgTriangles: m_gsInputPrimitive = GL_TRIANGLES; break;
|
|
||||||
case glslang::ElgTrianglesAdjacency: m_gsInputPrimitive = GL_TRIANGLES_ADJACENCY; break;
|
|
||||||
default: break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MGLOG_D("ProgramObject %u: Starting reflection", m_externalIndex);
|
MGLOG_D("ProgramObject %u: Starting reflection", m_externalIndex);
|
||||||
DoReflection();
|
DoReflection();
|
||||||
MGLOG_D("ProgramObject %u: Reflection done (linkStatus=%d)", m_externalIndex, (int)m_linkStatus);
|
MGLOG_D("ProgramObject %u: Reflection done (linkStatus=%d)", m_externalIndex, (int)m_linkStatus);
|
||||||
if (!ValidateFragmentOutputLocations()) {
|
if (!ValidateFragmentOutputLocations()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!ResolveTransformFeedbackVaryings()) {
|
|
||||||
m_linkStatus = false;
|
|
||||||
MGLOG_E("ProgramObject %u: transform feedback varying resolution failed: %s", m_externalIndex,
|
|
||||||
m_infoLog.c_str());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
MGLOG_D("ProgramObject %u: Starting binary generation", m_externalIndex);
|
MGLOG_D("ProgramObject %u: Starting binary generation", m_externalIndex);
|
||||||
GenerateBinary();
|
GenerateBinary();
|
||||||
|
|||||||
@@ -45,13 +45,6 @@ namespace MobileGL::MG_State::GLState {
|
|||||||
Vector<SharedPtr<ShaderObject>>& GetAttachedShaders();
|
Vector<SharedPtr<ShaderObject>>& GetAttachedShaders();
|
||||||
const Vector<SharedPtr<ShaderObject>>& GetAttachedShaders() const;
|
const Vector<SharedPtr<ShaderObject>>& GetAttachedShaders() const;
|
||||||
const String& GetInfoLog() const { return m_infoLog; }
|
const String& GetInfoLog() const { return m_infoLog; }
|
||||||
// glCreateShaderProgramv folds the shader's compile log into the program's log, which
|
|
||||||
// is the only place a caller can read it from once the shader name is gone.
|
|
||||||
void AppendInfoLog(const String& text) {
|
|
||||||
if (text.empty()) return;
|
|
||||||
if (!m_infoLog.empty() && m_infoLog.back() != '\n') m_infoLog += '\n';
|
|
||||||
m_infoLog += text;
|
|
||||||
}
|
|
||||||
Int GetUniformMaxLength() const { return m_uniformNameMaxLength; }
|
Int GetUniformMaxLength() const { return m_uniformNameMaxLength; }
|
||||||
Uint GetUniformCount() const { return m_activeUniformCount; }
|
Uint GetUniformCount() const { return m_activeUniformCount; }
|
||||||
Uint GetMaxUniformLocation() const { return m_maxUniformLocation; }
|
Uint GetMaxUniformLocation() const { return m_maxUniformLocation; }
|
||||||
@@ -341,32 +334,16 @@ namespace MobileGL::MG_State::GLState {
|
|||||||
// draw. The memo is keyed by (backendStateVersion, flags); ResetLinkArtifacts and
|
// draw. The memo is keyed by (backendStateVersion, flags); ResetLinkArtifacts and
|
||||||
// the binding setters below invalidate it by bumping m_backendStateVersion.
|
// the binding setters below invalidate it by bumping m_backendStateVersion.
|
||||||
Bool GetBackendHashMemo(Uint flags, Uint64& outHash) const {
|
Bool GetBackendHashMemo(Uint flags, Uint64& outHash) const {
|
||||||
if (m_backendHashMemoVersion != m_backendStateVersion) return false;
|
if (m_backendHashMemoVersion != m_backendStateVersion || m_backendHashMemoFlags != flags) {
|
||||||
for (const auto& slot : m_backendHashMemoSlots) {
|
return false;
|
||||||
if (slot.valid && slot.flags == flags) {
|
|
||||||
outHash = slot.hash;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return false;
|
outHash = m_backendHashMemo;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
void SetBackendHashMemo(Uint flags, Uint64 hash) const {
|
void SetBackendHashMemo(Uint flags, Uint64 hash) const {
|
||||||
if (m_backendHashMemoVersion != m_backendStateVersion) {
|
m_backendHashMemo = hash;
|
||||||
for (auto& slot : m_backendHashMemoSlots) slot.valid = false;
|
m_backendHashMemoVersion = m_backendStateVersion;
|
||||||
m_backendHashMemoVersion = m_backendStateVersion;
|
m_backendHashMemoFlags = flags;
|
||||||
m_backendHashMemoNextSlot = 0;
|
|
||||||
}
|
|
||||||
for (auto& slot : m_backendHashMemoSlots) {
|
|
||||||
if (slot.valid && slot.flags == flags) {
|
|
||||||
slot.hash = hash;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
auto& slot = m_backendHashMemoSlots[m_backendHashMemoNextSlot];
|
|
||||||
slot.flags = flags;
|
|
||||||
slot.hash = hash;
|
|
||||||
slot.valid = true;
|
|
||||||
m_backendHashMemoNextSlot = (m_backendHashMemoNextSlot + 1) % kBackendHashMemoSlotCount;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SetUniformSamplerOrImageUnitIndex(Uint location, Int unit) {
|
void SetUniformSamplerOrImageUnitIndex(Uint location, Int unit) {
|
||||||
@@ -384,22 +361,6 @@ namespace MobileGL::MG_State::GLState {
|
|||||||
|
|
||||||
Bool GetDeleteStatus() const { return m_deleteStatus; }
|
Bool GetDeleteStatus() const { return m_deleteStatus; }
|
||||||
Bool GetLinkStatus() const { return m_linkStatus; }
|
Bool GetLinkStatus() const { return m_linkStatus; }
|
||||||
// GL_PROGRAM_BINARY_RETRIEVABLE_HINT. MobileGL exposes no program binary format
|
|
||||||
// (GL_NUM_PROGRAM_BINARY_FORMATS is 0), so the hint is pure state - which is all
|
|
||||||
// ARB_get_program_binary requires of it.
|
|
||||||
Bool GetBinaryRetrievableHint() const { return m_binaryRetrievableHint; }
|
|
||||||
void SetBinaryRetrievableHint(Bool hint) { m_binaryRetrievableHint = hint; }
|
|
||||||
// GL_PROGRAM_SEPARABLE (GL_ARB_separate_shader_objects): the program may supply a
|
|
||||||
// subset of the stages of a program pipeline. Only takes effect on the next link,
|
|
||||||
// which is why it is plain state here rather than something Link() consults.
|
|
||||||
Bool GetSeparable() const { return m_separable; }
|
|
||||||
void SetSeparable(Bool separable) { m_separable = separable; }
|
|
||||||
// glProgramBinary always fails here (there is no format it could accept) and the
|
|
||||||
// spec then requires the program's LINK_STATUS to read FALSE.
|
|
||||||
void MarkLinkFailedByProgramBinary() {
|
|
||||||
ResetLinkArtifacts();
|
|
||||||
m_infoLog = "No program binary format is supported.";
|
|
||||||
}
|
|
||||||
Bool GetValidateStatus() const { return m_validateStatus; }
|
Bool GetValidateStatus() const { return m_validateStatus; }
|
||||||
Int GetActiveAtomicCounterCount() const { return m_program->getNumAtomicCounters(); }
|
Int GetActiveAtomicCounterCount() const { return m_program->getNumAtomicCounters(); }
|
||||||
Int GetActiveAttributesCount() const { return m_program->getNumPipeInputs(); }
|
Int GetActiveAttributesCount() const { return m_program->getNumPipeInputs(); }
|
||||||
@@ -488,55 +449,6 @@ namespace MobileGL::MG_State::GLState {
|
|||||||
return it == m_shaders.end() ? -1 : (Int)std::distance(m_shaders.begin(), it);
|
return it == m_shaders.end() ? -1 : (Int)std::distance(m_shaders.begin(), it);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Transform feedback (GL 3.0 core: glTransformFeedbackVaryings applies on
|
|
||||||
// the NEXT link; the linked snapshot below is what draws and queries see).
|
|
||||||
struct XfbVarying {
|
|
||||||
String name;
|
|
||||||
GLenum type = GL_FLOAT;
|
|
||||||
GLint size = 1; // array element count
|
|
||||||
Uint32 bufferIndex = 0; // capture buffer slot
|
|
||||||
Uint32 offsetBytes = 0; // offset within the capture buffer
|
|
||||||
Uint32 byteSize = 0; // bytes captured per vertex for this varying
|
|
||||||
// Offset within the gap-free record a backend that cannot express the GL
|
|
||||||
// layout captures into; see NeedsScatteredTransformFeedbackCapture.
|
|
||||||
Uint32 packedOffsetBytes = 0;
|
|
||||||
};
|
|
||||||
void SetTransformFeedbackVaryings(Vector<String>&& names, GLenum bufferMode) {
|
|
||||||
m_requestedXfbVaryings = Move(names);
|
|
||||||
m_requestedXfbBufferMode = bufferMode;
|
|
||||||
}
|
|
||||||
GLenum GetTransformFeedbackBufferMode() const { return m_xfbBufferMode; }
|
|
||||||
SizeT GetTransformFeedbackVaryingCount() const { return m_xfbVaryings.size(); }
|
|
||||||
const XfbVarying* GetTransformFeedbackVarying(SizeT index) const {
|
|
||||||
return index < m_xfbVaryings.size() ? &m_xfbVaryings[index] : nullptr;
|
|
||||||
}
|
|
||||||
const Vector<XfbVarying>& GetTransformFeedbackVaryings() const { return m_xfbVaryings; }
|
|
||||||
// Stride of one captured vertex in the given capture buffer slot.
|
|
||||||
Uint32 GetTransformFeedbackStride(Uint32 bufferIndex) const {
|
|
||||||
return bufferIndex < m_xfbStrides.size() ? m_xfbStrides[bufferIndex] : 0;
|
|
||||||
}
|
|
||||||
SizeT GetTransformFeedbackBufferCount() const { return m_xfbStrides.size(); }
|
|
||||||
Int GetTransformFeedbackVaryingMaxLength() const { return m_xfbVaryingNameMaxLength; }
|
|
||||||
// True when the capture layout uses gl_SkipComponents / gl_NextBuffer
|
|
||||||
// (ARB_transform_feedback3), which no ES driver can express: it can only pack every
|
|
||||||
// captured varying into one record with no gaps. A backend that captures through
|
|
||||||
// such a driver has to capture into scratch storage and scatter the records into the
|
|
||||||
// application's buffers itself, using packedOffsetBytes as the source offset and
|
|
||||||
// (bufferIndex, offsetBytes, stride) as the destination.
|
|
||||||
Bool NeedsScatteredTransformFeedbackCapture() const { return m_xfbNeedsScatteredCapture; }
|
|
||||||
// Bytes one gap-free captured record occupies.
|
|
||||||
Uint32 GetTransformFeedbackPackedStride() const { return m_xfbPackedStride; }
|
|
||||||
// True when the capture stage is a triangle-strip geometry shader with a
|
|
||||||
// statically-known emit sequence: the Vulkan capture order then needs the GL
|
|
||||||
// odd-triangle vertex swap after EndTransformFeedback.
|
|
||||||
Bool HasGsTriangleStripCaptureFixup() const { return m_gsStripCaptureFixup; }
|
|
||||||
// Triangles per strip, in emission order, for ONE geometry invocation.
|
|
||||||
const Vector<Uint32>& GetGsStripTriangles() const { return m_gsStripTriangles; }
|
|
||||||
// GL_GEOMETRY_INPUT_TYPE of the linked geometry stage (GL_POINTS, GL_LINES,
|
|
||||||
// GL_LINES_ADJACENCY, GL_TRIANGLES or GL_TRIANGLES_ADJACENCY), or GL_NONE when the
|
|
||||||
// program has no geometry stage. Draws must present a compatible primitive type.
|
|
||||||
GLenum GetGeometryInputType() const { return m_gsInputPrimitive; }
|
|
||||||
|
|
||||||
Uint GetExternalIndex() const { return m_externalIndex; }
|
Uint GetExternalIndex() const { return m_externalIndex; }
|
||||||
// Globally-unique, never-reused id for this program object's lifetime. Unlike the GL
|
// Globally-unique, never-reused id for this program object's lifetime. Unlike the GL
|
||||||
// name (external index), which is freed to a LIFO list and immediately handed back by
|
// name (external index), which is freed to a LIFO list and immediately handed back by
|
||||||
@@ -547,11 +459,6 @@ namespace MobileGL::MG_State::GLState {
|
|||||||
private:
|
private:
|
||||||
void ResetLinkArtifacts();
|
void ResetLinkArtifacts();
|
||||||
void DoReflection();
|
void DoReflection();
|
||||||
// Resolves the requested transform feedback varyings against the linked
|
|
||||||
// vertex stage; fails the link (GL semantics) on unknown or duplicate
|
|
||||||
// names or exceeded capture limits.
|
|
||||||
Bool ResolveTransformFeedbackVaryings();
|
|
||||||
void ResolveGsTriangleStripCapture(const glslang::TIntermediate* captureIntermediate);
|
|
||||||
void GenerateBinary();
|
void GenerateBinary();
|
||||||
void WaitUntilGenerationCompleted() const;
|
void WaitUntilGenerationCompleted() const;
|
||||||
void AddDefaultFragmentShaderIfMissing();
|
void AddDefaultFragmentShaderIfMissing();
|
||||||
@@ -616,39 +523,15 @@ namespace MobileGL::MG_State::GLState {
|
|||||||
String m_infoLog;
|
String m_infoLog;
|
||||||
Bool m_deleteStatus = false;
|
Bool m_deleteStatus = false;
|
||||||
Bool m_linkStatus = false;
|
Bool m_linkStatus = false;
|
||||||
Bool m_binaryRetrievableHint = false;
|
|
||||||
Bool m_separable = false;
|
|
||||||
Bool m_validateStatus = true;
|
Bool m_validateStatus = true;
|
||||||
Uint32 m_backendStateVersion = 0;
|
Uint32 m_backendStateVersion = 0;
|
||||||
|
|
||||||
// Backend-owned content-hash memo (see GetBackendHashMemo): valid only while
|
// Backend-owned content-hash memo (see GetBackendHashMemo): valid only while
|
||||||
// m_backendStateVersion matches. Several slots, not one: a backend may resolve the same
|
// m_backendStateVersion and the compile flags match the recorded values.
|
||||||
// program under more than one compile-flag set within a frame (surface rotation, and the
|
mutable Uint64 m_backendHashMemo = 0;
|
||||||
// explicit-LOD sampling variant), and a single slot would then miss on every lookup and
|
|
||||||
// re-hash the program's whole SPIR-V once per draw.
|
|
||||||
static constexpr SizeT kBackendHashMemoSlotCount = 4;
|
|
||||||
struct BackendHashMemoSlot {
|
|
||||||
Uint64 hash = 0;
|
|
||||||
Uint flags = 0;
|
|
||||||
Bool valid = false;
|
|
||||||
};
|
|
||||||
mutable Array<BackendHashMemoSlot, kBackendHashMemoSlotCount> m_backendHashMemoSlots{};
|
|
||||||
mutable SizeT m_backendHashMemoNextSlot = 0;
|
|
||||||
mutable Uint32 m_backendHashMemoVersion = ~0u;
|
mutable Uint32 m_backendHashMemoVersion = ~0u;
|
||||||
|
mutable Uint m_backendHashMemoFlags = 0;
|
||||||
Uint32 m_uboContentVersion = 0;
|
Uint32 m_uboContentVersion = 0;
|
||||||
Uint32 m_linkVersion = 0;
|
Uint32 m_linkVersion = 0;
|
||||||
|
|
||||||
// Transform feedback: request (applies at next link) and linked snapshot.
|
|
||||||
Vector<String> m_requestedXfbVaryings;
|
|
||||||
GLenum m_requestedXfbBufferMode = GL_INTERLEAVED_ATTRIBS;
|
|
||||||
Vector<XfbVarying> m_xfbVaryings;
|
|
||||||
Vector<Uint32> m_xfbStrides;
|
|
||||||
Vector<Uint32> m_gsStripTriangles;
|
|
||||||
Bool m_gsStripCaptureFixup = false;
|
|
||||||
GLenum m_gsInputPrimitive = GL_NONE;
|
|
||||||
GLenum m_xfbBufferMode = GL_INTERLEAVED_ATTRIBS;
|
|
||||||
Int m_xfbVaryingNameMaxLength = 0;
|
|
||||||
Bool m_xfbNeedsScatteredCapture = false;
|
|
||||||
Uint32 m_xfbPackedStride = 0;
|
|
||||||
};
|
};
|
||||||
} // namespace MobileGL::MG_State::GLState
|
} // namespace MobileGL::MG_State::GLState
|
||||||
|
|||||||
@@ -1,83 +0,0 @@
|
|||||||
// 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; }
|
|
||||||
|
|
||||||
// A draw sees one program, but a pipeline holds one program per stage. The
|
|
||||||
// stages are composited into a single hidden program object, rebuilt whenever
|
|
||||||
// the stage set - or any stage program's own link - changes. The signature is
|
|
||||||
// what that "changes" means: a stage program's lifetime id pins the object and
|
|
||||||
// its backend state version pins the link generation.
|
|
||||||
using DrawProgramSignature =
|
|
||||||
Array<Uint64, static_cast<SizeT>(ShaderStage::ShaderStageCount) * 2>;
|
|
||||||
|
|
||||||
DrawProgramSignature ComputeDrawProgramSignature() const {
|
|
||||||
DrawProgramSignature signature{};
|
|
||||||
for (SizeT stage = 0; stage < static_cast<SizeT>(ShaderStage::ShaderStageCount); ++stage) {
|
|
||||||
const auto& program = m_stagePrograms[stage];
|
|
||||||
if (!program) continue;
|
|
||||||
signature[stage * 2] = program->GetLifetimeId();
|
|
||||||
signature[stage * 2 + 1] = program->GetBackendStateVersion();
|
|
||||||
}
|
|
||||||
return signature;
|
|
||||||
}
|
|
||||||
|
|
||||||
const SharedPtr<ProgramObject>& GetCachedDrawProgram(const DrawProgramSignature& signature) const {
|
|
||||||
static const SharedPtr<ProgramObject> nullProgram = nullptr;
|
|
||||||
if (!m_drawProgram || m_drawProgramSignature != signature) return nullProgram;
|
|
||||||
return m_drawProgram;
|
|
||||||
}
|
|
||||||
void SetCachedDrawProgram(const DrawProgramSignature& signature, SharedPtr<ProgramObject> program) {
|
|
||||||
m_drawProgramSignature = signature;
|
|
||||||
m_drawProgram = Move(program);
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
Array<SharedPtr<ProgramObject>, static_cast<SizeT>(ShaderStage::ShaderStageCount)> m_stagePrograms{};
|
|
||||||
SharedPtr<ProgramObject> m_activeProgram;
|
|
||||||
SharedPtr<ProgramObject> m_drawProgram;
|
|
||||||
DrawProgramSignature m_drawProgramSignature{};
|
|
||||||
String m_infoLog;
|
|
||||||
const Uint m_externalIndex = 0;
|
|
||||||
Bool m_validateStatus = false;
|
|
||||||
};
|
|
||||||
} // namespace GLState
|
|
||||||
} // namespace MG_State
|
|
||||||
} // namespace MobileGL
|
|
||||||
@@ -29,25 +29,17 @@ namespace MobileGL::MG_State::GLState {
|
|||||||
if (!CheckIndexAvail(program, m_programObjects)) return; // FIXME: add error reporting here
|
if (!CheckIndexAvail(program, m_programObjects)) return; // FIXME: add error reporting here
|
||||||
auto& programObject = m_programObjects[program];
|
auto& programObject = m_programObjects[program];
|
||||||
if (programObject != nullptr) {
|
if (programObject != nullptr) {
|
||||||
|
// Snapshot the attachments: deleting the program is a detach point for shaders
|
||||||
|
// that were flagged with glDeleteShader while still attached.
|
||||||
|
const Vector<SharedPtr<ShaderObject>> attachedShaders = programObject->GetAttachedShaders();
|
||||||
programObject->MarkAsDeleted();
|
programObject->MarkAsDeleted();
|
||||||
// A program in use is only FLAGGED: its name (and every program query) stays
|
programObject.reset();
|
||||||
// valid until it stops being current, at which point UseProgram finishes the job.
|
m_programIndexGenerator.Delete(program);
|
||||||
if (programObject == m_currentProgram) return;
|
for (const auto& shader : attachedShaders) {
|
||||||
DestroyProgramSlot(program);
|
const Uint shaderName = shader->GetExternalIndex();
|
||||||
}
|
if (CheckIndexAvail(shaderName, m_shaderObjects) && m_shaderObjects[shaderName] == shader) {
|
||||||
}
|
ReleaseShaderNameIfOrphaned(shaderName);
|
||||||
|
}
|
||||||
void ProgramState::DestroyProgramSlot(const Uint program) {
|
|
||||||
auto& programObject = m_programObjects[program];
|
|
||||||
// Snapshot the attachments: deleting the program is a detach point for shaders
|
|
||||||
// that were flagged with glDeleteShader while still attached.
|
|
||||||
const Vector<SharedPtr<ShaderObject>> attachedShaders = programObject->GetAttachedShaders();
|
|
||||||
programObject.reset();
|
|
||||||
m_programIndexGenerator.Delete(program);
|
|
||||||
for (const auto& shader : attachedShaders) {
|
|
||||||
const Uint shaderName = shader->GetExternalIndex();
|
|
||||||
if (CheckIndexAvail(shaderName, m_shaderObjects) && m_shaderObjects[shaderName] == shader) {
|
|
||||||
ReleaseShaderNameIfOrphaned(shaderName);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -57,22 +49,10 @@ namespace MobileGL::MG_State::GLState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ProgramState::UseProgram(Uint program) {
|
void ProgramState::UseProgram(Uint program) {
|
||||||
const SharedPtr<ProgramObject> previous = m_currentProgram;
|
|
||||||
|
|
||||||
if (program == 0) m_currentProgram.reset();
|
if (program == 0) m_currentProgram.reset();
|
||||||
|
|
||||||
if (CheckIndexAvail(program, m_programObjects)) {
|
if (!CheckIndexAvail(program, m_programObjects)) return;
|
||||||
m_currentProgram = m_programObjects[program];
|
m_currentProgram = m_programObjects[program];
|
||||||
}
|
|
||||||
|
|
||||||
// A deletion flagged while the program was current takes effect the moment it
|
|
||||||
// stops being current.
|
|
||||||
if (previous != nullptr && previous != m_currentProgram && previous->GetDeleteStatus()) {
|
|
||||||
const Uint previousName = previous->GetExternalIndex();
|
|
||||||
if (CheckIndexAvail(previousName, m_programObjects) && m_programObjects[previousName] == previous) {
|
|
||||||
DestroyProgramSlot(previousName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Uint ProgramState::CreateShader(ShaderStage stage) {
|
Uint ProgramState::CreateShader(ShaderStage stage) {
|
||||||
|
|||||||
@@ -35,9 +35,6 @@ namespace MobileGL::MG_State::GLState {
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
Bool ShaderHasGLVisibleAttachment(const SharedPtr<ShaderObject>& shaderObject) const;
|
Bool ShaderHasGLVisibleAttachment(const SharedPtr<ShaderObject>& shaderObject) const;
|
||||||
// Frees the name slot and releases orphaned attached shaders; the immediate half
|
|
||||||
// of glDeleteProgram (deferred while the program is current).
|
|
||||||
void DestroyProgramSlot(Uint program);
|
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
static Bool CheckIndexAvail(const SizeT idx, const Vector<T>& vec) {
|
static Bool CheckIndexAvail(const SizeT idx, const Vector<T>& vec) {
|
||||||
|
|||||||
@@ -169,15 +169,6 @@ namespace MobileGL::MG_State::GLState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::optional<String> reservedError =
|
|
||||||
MG_Util::ShaderTranspiler::FindReservedIdentifierViolation(compileSource);
|
|
||||||
if (reservedError) {
|
|
||||||
m_compileStatus = false;
|
|
||||||
m_shader.reset();
|
|
||||||
m_infoLog = *reservedError;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compile for OpenGL here, so that we can do validation and link
|
// Compile for OpenGL here, so that we can do validation and link
|
||||||
// like a real OpenGL driver at linking stage
|
// like a real OpenGL driver at linking stage
|
||||||
// Will compile for other backends later.
|
// Will compile for other backends later.
|
||||||
|
|||||||
@@ -37,10 +37,6 @@ namespace MobileGL {
|
|||||||
return m_version;
|
return m_version;
|
||||||
}
|
}
|
||||||
|
|
||||||
Uint RenderState::GetPipelineStateVersion() const {
|
|
||||||
return m_pipelineStateVersion;
|
|
||||||
}
|
|
||||||
|
|
||||||
const RenderStateParameters& RenderState::GetAllParameters() const {
|
const RenderStateParameters& RenderState::GetAllParameters() const {
|
||||||
return m_parameters;
|
return m_parameters;
|
||||||
}
|
}
|
||||||
@@ -126,7 +122,7 @@ namespace MobileGL {
|
|||||||
if (m_parameters.PolygonModeFront == front && m_parameters.PolygonModeBack == back) return;
|
if (m_parameters.PolygonModeFront == front && m_parameters.PolygonModeBack == back) return;
|
||||||
m_parameters.PolygonModeFront = front;
|
m_parameters.PolygonModeFront = front;
|
||||||
m_parameters.PolygonModeBack = back;
|
m_parameters.PolygonModeBack = back;
|
||||||
BumpVersions();
|
++m_version;
|
||||||
}
|
}
|
||||||
|
|
||||||
GLenum RenderState::GetPolygonModeFront() const {
|
GLenum RenderState::GetPolygonModeFront() const {
|
||||||
@@ -158,17 +154,6 @@ namespace MobileGL {
|
|||||||
return m_parameters.PointSize;
|
return m_parameters.PointSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
void RenderState::SetPatchVertices(Uint vertices) {
|
|
||||||
if (m_parameters.PatchVertices == vertices) return;
|
|
||||||
|
|
||||||
m_parameters.PatchVertices = vertices;
|
|
||||||
BumpVersions();
|
|
||||||
}
|
|
||||||
|
|
||||||
Uint RenderState::GetPatchVertices() const {
|
|
||||||
return m_parameters.PatchVertices;
|
|
||||||
}
|
|
||||||
|
|
||||||
void RenderState::SetPolygonOffset(Float factor, Float units) {
|
void RenderState::SetPolygonOffset(Float factor, Float units) {
|
||||||
if (m_parameters.PolygonOffsetFactor == factor && m_parameters.PolygonOffsetUnits == units) return;
|
if (m_parameters.PolygonOffsetFactor == factor && m_parameters.PolygonOffsetUnits == units) return;
|
||||||
|
|
||||||
@@ -191,7 +176,7 @@ namespace MobileGL {
|
|||||||
case CapabilityInput::capability: \
|
case CapabilityInput::capability: \
|
||||||
if (m_parameters.capability##Enabled == (flag)) break; \
|
if (m_parameters.capability##Enabled == (flag)) break; \
|
||||||
m_parameters.capability##Enabled = (flag); \
|
m_parameters.capability##Enabled = (flag); \
|
||||||
BumpVersions(); \
|
++m_version; \
|
||||||
break;
|
break;
|
||||||
|
|
||||||
switch (cap) {
|
switch (cap) {
|
||||||
@@ -224,7 +209,7 @@ namespace MobileGL {
|
|||||||
blendState.Enabled = enabled;
|
blendState.Enabled = enabled;
|
||||||
stateChanged = true;
|
stateChanged = true;
|
||||||
}
|
}
|
||||||
if (stateChanged) BumpVersions();
|
if (stateChanged) ++m_version;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default: // not supported currently
|
default: // not supported currently
|
||||||
@@ -280,7 +265,7 @@ namespace MobileGL {
|
|||||||
if (m_parameters.BlendStates[index].Enabled == enabled) return;
|
if (m_parameters.BlendStates[index].Enabled == enabled) return;
|
||||||
|
|
||||||
m_parameters.BlendStates[index].Enabled = enabled;
|
m_parameters.BlendStates[index].Enabled = enabled;
|
||||||
BumpVersions();
|
++m_version;
|
||||||
}
|
}
|
||||||
|
|
||||||
Bool RenderState::IsCapabilityEnabledIndexed(CapabilityInput cap, Uint index) const {
|
Bool RenderState::IsCapabilityEnabledIndexed(CapabilityInput cap, Uint index) const {
|
||||||
@@ -312,7 +297,7 @@ namespace MobileGL {
|
|||||||
stateChanged = true;
|
stateChanged = true;
|
||||||
}
|
}
|
||||||
if (!stateChanged) return;
|
if (!stateChanged) return;
|
||||||
BumpVersions();
|
++m_version;
|
||||||
}
|
}
|
||||||
|
|
||||||
void RenderState::GetBlendFunc(BlendFactor& srcRGB, BlendFactor& dstRGB, BlendFactor& srcAlpha,
|
void RenderState::GetBlendFunc(BlendFactor& srcRGB, BlendFactor& dstRGB, BlendFactor& srcAlpha,
|
||||||
@@ -338,7 +323,7 @@ namespace MobileGL {
|
|||||||
blendState.DstFactorRGB = dstRGB;
|
blendState.DstFactorRGB = dstRGB;
|
||||||
blendState.SrcFactorAlpha = srcAlpha;
|
blendState.SrcFactorAlpha = srcAlpha;
|
||||||
blendState.DstFactorAlpha = dstAlpha;
|
blendState.DstFactorAlpha = dstAlpha;
|
||||||
BumpVersions();
|
++m_version;
|
||||||
}
|
}
|
||||||
|
|
||||||
void RenderState::GetBlendFuncIndexed(Uint index, BlendFactor& srcRGB, BlendFactor& dstRGB,
|
void RenderState::GetBlendFuncIndexed(Uint index, BlendFactor& srcRGB, BlendFactor& dstRGB,
|
||||||
@@ -364,7 +349,7 @@ namespace MobileGL {
|
|||||||
stateChanged = true;
|
stateChanged = true;
|
||||||
}
|
}
|
||||||
if (!stateChanged) return;
|
if (!stateChanged) return;
|
||||||
BumpVersions();
|
++m_version;
|
||||||
}
|
}
|
||||||
|
|
||||||
void RenderState::GetBlendEquation(BlendEquation& color, BlendEquation& alpha) const {
|
void RenderState::GetBlendEquation(BlendEquation& color, BlendEquation& alpha) const {
|
||||||
@@ -383,7 +368,7 @@ namespace MobileGL {
|
|||||||
}
|
}
|
||||||
blendState.ColorEquation = color;
|
blendState.ColorEquation = color;
|
||||||
blendState.AlphaEquation = alpha;
|
blendState.AlphaEquation = alpha;
|
||||||
BumpVersions();
|
++m_version;
|
||||||
}
|
}
|
||||||
|
|
||||||
void RenderState::GetBlendEquationIndexed(Uint index, BlendEquation& color, BlendEquation& alpha) const {
|
void RenderState::GetBlendEquationIndexed(Uint index, BlendEquation& color, BlendEquation& alpha) const {
|
||||||
@@ -399,7 +384,7 @@ namespace MobileGL {
|
|||||||
if (m_parameters.LogicOp == logicOp) return;
|
if (m_parameters.LogicOp == logicOp) return;
|
||||||
|
|
||||||
m_parameters.LogicOp = logicOp;
|
m_parameters.LogicOp = logicOp;
|
||||||
BumpVersions();
|
++m_version;
|
||||||
}
|
}
|
||||||
|
|
||||||
LogicOperation RenderState::GetLogicOp() const {
|
LogicOperation RenderState::GetLogicOp() const {
|
||||||
@@ -411,7 +396,7 @@ namespace MobileGL {
|
|||||||
if (m_parameters.DepthFunc == func) return;
|
if (m_parameters.DepthFunc == func) return;
|
||||||
|
|
||||||
m_parameters.DepthFunc = func;
|
m_parameters.DepthFunc = func;
|
||||||
BumpVersions();
|
++m_version;
|
||||||
}
|
}
|
||||||
|
|
||||||
DepthTestFunc RenderState::GetDepthFunc() const {
|
DepthTestFunc RenderState::GetDepthFunc() const {
|
||||||
@@ -422,7 +407,7 @@ namespace MobileGL {
|
|||||||
if (m_parameters.DepthMask == flag) return;
|
if (m_parameters.DepthMask == flag) return;
|
||||||
|
|
||||||
m_parameters.DepthMask = flag;
|
m_parameters.DepthMask = flag;
|
||||||
BumpVersions();
|
++m_version;
|
||||||
}
|
}
|
||||||
|
|
||||||
Bool RenderState::GetDepthMask() const {
|
Bool RenderState::GetDepthMask() const {
|
||||||
@@ -433,15 +418,10 @@ namespace MobileGL {
|
|||||||
StencilFaceState& state = m_parameters.StencilStates[GetStencilFaceIndex(face)];
|
StencilFaceState& state = m_parameters.StencilStates[GetStencilFaceIndex(face)];
|
||||||
if (state.Func == func && state.Ref == ref && state.ValueMask == mask) return;
|
if (state.Func == func && state.Ref == ref && state.ValueMask == mask) return;
|
||||||
|
|
||||||
// Only Func is baked into the pipeline; Ref and ValueMask are dynamic state
|
|
||||||
// (VK_DYNAMIC_STATE_STENCIL_REFERENCE / _COMPARE_MASK), so glStencilFunc changing
|
|
||||||
// only the reference must not evict a cached pipeline.
|
|
||||||
const Bool pipelineRelevantChange = state.Func != func;
|
|
||||||
state.Func = func;
|
state.Func = func;
|
||||||
state.Ref = ref;
|
state.Ref = ref;
|
||||||
state.ValueMask = mask;
|
state.ValueMask = mask;
|
||||||
++m_version;
|
++m_version;
|
||||||
if (pipelineRelevantChange) ++m_pipelineStateVersion;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void RenderState::SetStencilMask(StencilFace face, Uint32 mask) {
|
void RenderState::SetStencilMask(StencilFace face, Uint32 mask) {
|
||||||
@@ -463,7 +443,7 @@ namespace MobileGL {
|
|||||||
state.FailOp = fail;
|
state.FailOp = fail;
|
||||||
state.PassDepthFailOp = depthFail;
|
state.PassDepthFailOp = depthFail;
|
||||||
state.PassDepthPassOp = depthPass;
|
state.PassDepthPassOp = depthPass;
|
||||||
BumpVersions();
|
++m_version;
|
||||||
}
|
}
|
||||||
|
|
||||||
const StencilFaceState& RenderState::GetStencilState(StencilFace face) const {
|
const StencilFaceState& RenderState::GetStencilState(StencilFace face) const {
|
||||||
@@ -480,7 +460,7 @@ namespace MobileGL {
|
|||||||
changed = true;
|
changed = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (changed) BumpVersions();
|
if (changed) ++m_version;
|
||||||
}
|
}
|
||||||
|
|
||||||
BoolVec4 RenderState::GetColorMask() const {
|
BoolVec4 RenderState::GetColorMask() const {
|
||||||
@@ -491,7 +471,7 @@ namespace MobileGL {
|
|||||||
void RenderState::SetColorMaskIndexed(Uint index, BoolVec4 mask) {
|
void RenderState::SetColorMaskIndexed(Uint index, BoolVec4 mask) {
|
||||||
if (m_parameters.ColorMasks[index] == mask) return;
|
if (m_parameters.ColorMasks[index] == mask) return;
|
||||||
m_parameters.ColorMasks[index] = mask;
|
m_parameters.ColorMasks[index] = mask;
|
||||||
BumpVersions();
|
++m_version;
|
||||||
}
|
}
|
||||||
|
|
||||||
BoolVec4 RenderState::GetColorMaskIndexed(Uint index) const {
|
BoolVec4 RenderState::GetColorMaskIndexed(Uint index) const {
|
||||||
@@ -559,7 +539,7 @@ namespace MobileGL {
|
|||||||
|
|
||||||
m_parameters.SampleCoverageValue = value;
|
m_parameters.SampleCoverageValue = value;
|
||||||
m_parameters.SampleCoverageInvert = invert;
|
m_parameters.SampleCoverageInvert = invert;
|
||||||
BumpVersions();
|
++m_version;
|
||||||
}
|
}
|
||||||
|
|
||||||
Float RenderState::GetSampleCoverageValue() const {
|
Float RenderState::GetSampleCoverageValue() const {
|
||||||
@@ -574,7 +554,7 @@ namespace MobileGL {
|
|||||||
if (m_parameters.SampleMaskValue == mask) return;
|
if (m_parameters.SampleMaskValue == mask) return;
|
||||||
|
|
||||||
m_parameters.SampleMaskValue = mask;
|
m_parameters.SampleMaskValue = mask;
|
||||||
BumpVersions();
|
++m_version;
|
||||||
}
|
}
|
||||||
|
|
||||||
Uint32 RenderState::GetSampleMaskValue() const {
|
Uint32 RenderState::GetSampleMaskValue() const {
|
||||||
@@ -648,7 +628,7 @@ namespace MobileGL {
|
|||||||
if (m_parameters.CullFaceModeSetting == mode) return;
|
if (m_parameters.CullFaceModeSetting == mode) return;
|
||||||
|
|
||||||
m_parameters.CullFaceModeSetting = mode;
|
m_parameters.CullFaceModeSetting = mode;
|
||||||
BumpVersions();
|
++m_version;
|
||||||
}
|
}
|
||||||
|
|
||||||
CullFaceMode RenderState::GetCullFaceMode() const {
|
CullFaceMode RenderState::GetCullFaceMode() const {
|
||||||
@@ -659,7 +639,7 @@ namespace MobileGL {
|
|||||||
if (m_parameters.FrontFaceModeSetting == mode) return;
|
if (m_parameters.FrontFaceModeSetting == mode) return;
|
||||||
|
|
||||||
m_parameters.FrontFaceModeSetting = mode;
|
m_parameters.FrontFaceModeSetting = mode;
|
||||||
BumpVersions();
|
++m_version;
|
||||||
}
|
}
|
||||||
|
|
||||||
FrontFaceMode RenderState::GetFrontFaceMode() const {
|
FrontFaceMode RenderState::GetFrontFaceMode() const {
|
||||||
@@ -670,7 +650,7 @@ namespace MobileGL {
|
|||||||
if (m_parameters.ProvokingVertexModeSetting == mode) return;
|
if (m_parameters.ProvokingVertexModeSetting == mode) return;
|
||||||
|
|
||||||
m_parameters.ProvokingVertexModeSetting = mode;
|
m_parameters.ProvokingVertexModeSetting = mode;
|
||||||
BumpVersions();
|
++m_version;
|
||||||
}
|
}
|
||||||
|
|
||||||
ProvokingVertexMode RenderState::GetProvokingVertexMode() const {
|
ProvokingVertexMode RenderState::GetProvokingVertexMode() const {
|
||||||
|
|||||||
@@ -224,8 +224,6 @@ namespace MobileGL {
|
|||||||
IntVec4 Viewport = IntVec4(0, 0, 0, 0); // x, y, width, height
|
IntVec4 Viewport = IntVec4(0, 0, 0, 0); // x, y, width, height
|
||||||
Float LineWidth = 1.0f;
|
Float LineWidth = 1.0f;
|
||||||
Float PointSize = 1.0f;
|
Float PointSize = 1.0f;
|
||||||
// GL_PATCH_VERTICES: how many vertices one tessellation patch consumes.
|
|
||||||
Uint PatchVertices = 3;
|
|
||||||
Float PolygonOffsetFactor = 0.0f;
|
Float PolygonOffsetFactor = 0.0f;
|
||||||
Float PolygonOffsetUnits = 0.0f;
|
Float PolygonOffsetUnits = 0.0f;
|
||||||
|
|
||||||
@@ -312,8 +310,6 @@ namespace MobileGL {
|
|||||||
RenderState();
|
RenderState();
|
||||||
|
|
||||||
Uint GetVersion() const;
|
Uint GetVersion() const;
|
||||||
// Version of the pipeline-relevant subset only - see m_pipelineStateVersion.
|
|
||||||
Uint GetPipelineStateVersion() const;
|
|
||||||
const RenderStateParameters& GetAllParameters() const;
|
const RenderStateParameters& GetAllParameters() const;
|
||||||
|
|
||||||
// Rasterization
|
// Rasterization
|
||||||
@@ -323,8 +319,6 @@ namespace MobileGL {
|
|||||||
Float GetLineWidth() const;
|
Float GetLineWidth() const;
|
||||||
void SetPointSize(Float size);
|
void SetPointSize(Float size);
|
||||||
Float GetPointSize() const;
|
Float GetPointSize() const;
|
||||||
void SetPatchVertices(Uint vertices);
|
|
||||||
Uint GetPatchVertices() const;
|
|
||||||
void SetPolygonOffset(Float factor, Float units);
|
void SetPolygonOffset(Float factor, Float units);
|
||||||
Float GetPolygonOffsetFactor() const;
|
Float GetPolygonOffsetFactor() const;
|
||||||
Float GetPolygonOffsetUnits() const;
|
Float GetPolygonOffsetUnits() const;
|
||||||
@@ -420,21 +414,7 @@ namespace MobileGL {
|
|||||||
const IntVec4& GetScissorBox() const; // x, y, width, height
|
const IntVec4& GetScissorBox() const; // x, y, width, height
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Bump both: any state change invalidates the draw snapshot, and this one also
|
|
||||||
// changes the VkPipeline (or its DirectGLES equivalent).
|
|
||||||
void BumpVersions() {
|
|
||||||
++m_version;
|
|
||||||
++m_pipelineStateVersion;
|
|
||||||
}
|
|
||||||
|
|
||||||
Uint16 m_version = 0;
|
Uint16 m_version = 0;
|
||||||
// Only the subset of render state that a backend bakes INTO a pipeline object.
|
|
||||||
// Viewport, scissor, depth range, blend colour, line width, polygon offset, stencil
|
|
||||||
// write mask, the clear values, hints and the point-size family are all either
|
|
||||||
// dynamic pipeline state or not pipeline state at all, so changing one of them must
|
|
||||||
// not evict a cached pipeline. Keeping one counter for both made a glViewport call
|
|
||||||
// knock the next draw off the pipeline memo AND the draw fast path.
|
|
||||||
Uint16 m_pipelineStateVersion = 0;
|
|
||||||
RenderStateParameters m_parameters;
|
RenderStateParameters m_parameters;
|
||||||
|
|
||||||
// Pixel Store
|
// Pixel Store
|
||||||
|
|||||||
@@ -139,61 +139,6 @@ namespace MobileGL {
|
|||||||
return m_samplerParameters.maxAnisotropy;
|
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 {
|
SamplerCompareMode SamplerObject::GetCompareMode() const {
|
||||||
return m_samplerParameters.compareMode;
|
return m_samplerParameters.compareMode;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,6 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <Includes.h>
|
#include <Includes.h>
|
||||||
#include <MG_Util/Math/VectorTypes.h>
|
|
||||||
|
|
||||||
namespace MobileGL {
|
namespace MobileGL {
|
||||||
enum class SamplerFilterMode {
|
enum class SamplerFilterMode {
|
||||||
@@ -67,18 +66,8 @@ namespace MobileGL {
|
|||||||
Float maxLod = 1000.0f;
|
Float maxLod = 1000.0f;
|
||||||
Float lodBias = 0.0f;
|
Float lodBias = 0.0f;
|
||||||
Float maxAnisotropy = 1.0f;
|
Float maxAnisotropy = 1.0f;
|
||||||
// GL 4.6 core table 23.18 / GLES 3.2 table 21.16: TEXTURE_COMPARE_FUNC starts at LEQUAL,
|
SamplerCompareFunc compareFunc = SamplerCompareFunc::Always;
|
||||||
// for both sampler objects and the sampler state a texture object carries.
|
|
||||||
SamplerCompareFunc compareFunc = SamplerCompareFunc::LessEqual;
|
|
||||||
SamplerCompareMode compareMode = SamplerCompareMode::None;
|
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 {
|
namespace MG_State {
|
||||||
@@ -98,9 +87,6 @@ namespace MobileGL {
|
|||||||
void SetMaxAnisotropy(Float maxAnisotropy);
|
void SetMaxAnisotropy(Float maxAnisotropy);
|
||||||
void SetSamplerCompareFunc(SamplerCompareFunc func);
|
void SetSamplerCompareFunc(SamplerCompareFunc func);
|
||||||
void SetCompareMode(SamplerCompareMode mode);
|
void SetCompareMode(SamplerCompareMode mode);
|
||||||
void SetBorderColor(const FloatVec4& color);
|
|
||||||
void SetBorderColorI(const IntVec4& color);
|
|
||||||
void SetBorderColorUI(const UintVec4& color);
|
|
||||||
|
|
||||||
SamplerWrapMode GetWrapS() const;
|
SamplerWrapMode GetWrapS() const;
|
||||||
SamplerWrapMode GetWrapT() const;
|
SamplerWrapMode GetWrapT() const;
|
||||||
@@ -114,9 +100,6 @@ namespace MobileGL {
|
|||||||
Float GetMaxAnisotropy() const;
|
Float GetMaxAnisotropy() const;
|
||||||
SamplerCompareMode GetCompareMode() const;
|
SamplerCompareMode GetCompareMode() const;
|
||||||
SamplerCompareFunc GetSamplerCompareFunc() const;
|
SamplerCompareFunc GetSamplerCompareFunc() const;
|
||||||
const FloatVec4& GetBorderColor() const;
|
|
||||||
const IntVec4& GetBorderColorI() const;
|
|
||||||
const UintVec4& GetBorderColorUI() const;
|
|
||||||
Uint GetExternalIndex() const;
|
Uint GetExternalIndex() const;
|
||||||
Uint16 GetVersion() const;
|
Uint16 GetVersion() const;
|
||||||
// Globally-unique, never-reused id for this sampler object's lifetime. Lets a
|
// Globally-unique, never-reused id for this sampler object's lifetime. Lets a
|
||||||
|
|||||||
@@ -27,52 +27,11 @@ namespace MobileGL {
|
|||||||
m_texelSizes.reserve(std::bit_ceil(requiredLevelCount));
|
m_texelSizes.reserve(std::bit_ceil(requiredLevelCount));
|
||||||
m_texelSizes.resize(requiredLevelCount);
|
m_texelSizes.resize(requiredLevelCount);
|
||||||
m_isDirty.resize(requiredLevelCount, false);
|
m_isDirty.resize(requiredLevelCount, false);
|
||||||
m_compressedData.resize(requiredLevelCount);
|
|
||||||
m_compressedFormats.resize(requiredLevelCount, GL_NONE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
m_texelSizes[level] = input.texelSize;
|
m_texelSizes[level] = input.texelSize;
|
||||||
auto& data = m_data[level];
|
auto& data = m_data[level];
|
||||||
data.resize(input.byteSize, 0);
|
data.resize(input.byteSize, 0);
|
||||||
|
|
||||||
// Respecifying a level drops whatever compressed image it used to hold. Without this,
|
|
||||||
// a glTexImage2D or glTexStorage2D over a level a previous glCompressedTexImage2D had
|
|
||||||
// shadowed would leave GL_TEXTURE_COMPRESSED answering true and glGetCompressedTexImage
|
|
||||||
// handing back the stale blob. Every allocation path funnels through here, so clearing
|
|
||||||
// once covers all of them; the compressed path re-arms the tag immediately afterwards
|
|
||||||
// via SetCompressedImage.
|
|
||||||
m_compressedFormats[level] = GL_NONE;
|
|
||||||
m_compressedData[level].clear();
|
|
||||||
m_compressedData[level].shrink_to_fit();
|
|
||||||
}
|
|
||||||
|
|
||||||
void MipmapStorage::SetCompressedImage(Uint level, GLenum internalFormat, const void* data, SizeT size) {
|
|
||||||
MOBILEGL_ASSERT(level < m_compressedData.size(), "SetCompressedImage: level out of range");
|
|
||||||
|
|
||||||
m_compressedFormats[level] = internalFormat;
|
|
||||||
auto& blob = m_compressedData[level];
|
|
||||||
// Zero-filled when data is null: glCompressedTexImage* with a null pointer defines the
|
|
||||||
// level's size and format but leaves its contents undefined, and zeros are the one
|
|
||||||
// reproducible answer a later glGetCompressedTexImage can give.
|
|
||||||
blob.assign(size, 0);
|
|
||||||
if (data != nullptr && size > 0) {
|
|
||||||
Memcpy(blob.data(), data, size);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
GLenum MipmapStorage::GetCompressedFormat(Uint level) const {
|
|
||||||
if (level >= m_compressedFormats.size()) return GL_NONE;
|
|
||||||
return m_compressedFormats[level];
|
|
||||||
}
|
|
||||||
|
|
||||||
SizeT MipmapStorage::GetCompressedByteSize(Uint level) const {
|
|
||||||
if (level >= m_compressedData.size()) return 0;
|
|
||||||
return m_compressedData[level].size();
|
|
||||||
}
|
|
||||||
|
|
||||||
const void* MipmapStorage::MapCompressedData(Uint level) const {
|
|
||||||
if (level >= m_compressedData.size()) return nullptr;
|
|
||||||
return m_compressedData[level].data();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MipmapStorage::TruncateToLevelCount(SizeT levelCount) {
|
void MipmapStorage::TruncateToLevelCount(SizeT levelCount) {
|
||||||
@@ -81,8 +40,6 @@ namespace MobileGL {
|
|||||||
m_data.resize(levelCount);
|
m_data.resize(levelCount);
|
||||||
m_texelSizes.resize(levelCount);
|
m_texelSizes.resize(levelCount);
|
||||||
m_isDirty.resize(levelCount);
|
m_isDirty.resize(levelCount);
|
||||||
m_compressedData.resize(levelCount);
|
|
||||||
m_compressedFormats.resize(levelCount);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MipmapStorage::UpdateSubData(Uint level, DataPtr input) {
|
void MipmapStorage::UpdateSubData(Uint level, DataPtr input) {
|
||||||
|
|||||||
@@ -30,27 +30,10 @@ namespace MobileGL {
|
|||||||
void MarkDirty(Uint level, bool dirty);
|
void MarkDirty(Uint level, bool dirty);
|
||||||
bool IsDirty(Uint level) const;
|
bool IsDirty(Uint level) const;
|
||||||
|
|
||||||
// The bytes an application handed to glCompressedTexImage*, kept verbatim beside the
|
|
||||||
// (uncompressed) texel shadow rather than in place of it. GL 4.6 core 8.11 requires
|
|
||||||
// glGetCompressedTexImage to return the image *as stored*, and no backend here has a
|
|
||||||
// BC/ETC codec, so a re-encode could never be byte-exact; at the same time m_data has
|
|
||||||
// to keep the "width * height * bytes-per-texel" layout that the backend upload
|
|
||||||
// sizing, glGenerateMipmap's bytes-per-texel division and the pixel-store packer all
|
|
||||||
// divide by. Two parallel vectors, one invariant preserved. Call order is
|
|
||||||
// AllocateLevel then SetCompressedImage - AllocateLevel clears the tag, so a plain
|
|
||||||
// glTexImage2D over the level un-compresses it.
|
|
||||||
void SetCompressedImage(Uint level, GLenum internalFormat, const void* data, SizeT size);
|
|
||||||
// GL_NONE when the level is not stored compressed.
|
|
||||||
GLenum GetCompressedFormat(Uint level) const;
|
|
||||||
SizeT GetCompressedByteSize(Uint level) const;
|
|
||||||
const void* MapCompressedData(Uint level) const;
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
Vector<IntVec3> m_texelSizes;
|
Vector<IntVec3> m_texelSizes;
|
||||||
Vector<Vector<Uint8>> m_data;
|
Vector<Vector<Uint8>> m_data;
|
||||||
Vector<bool> m_isDirty;
|
Vector<bool> m_isDirty;
|
||||||
Vector<Vector<Uint8>> m_compressedData;
|
|
||||||
Vector<GLenum> m_compressedFormats;
|
|
||||||
};
|
};
|
||||||
} // namespace GLState
|
} // namespace GLState
|
||||||
} // namespace MG_State
|
} // namespace MG_State
|
||||||
|
|||||||
@@ -74,27 +74,6 @@ namespace MobileGL {
|
|||||||
return m_storage[targetIndex].IsDirty(level);
|
return m_storage[targetIndex].IsDirty(level);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SetCompressedImage(Uint targetIndex, Uint level, GLenum internalFormat, const void* data,
|
|
||||||
SizeT size) {
|
|
||||||
MOBILEGL_ASSERT(targetIndex < TargetCount, "SetCompressedImage: target invalid");
|
|
||||||
m_storage[targetIndex].SetCompressedImage(level, internalFormat, data, size);
|
|
||||||
}
|
|
||||||
|
|
||||||
GLenum GetCompressedFormat(Uint targetIndex, Uint level) const {
|
|
||||||
MOBILEGL_ASSERT(targetIndex < TargetCount, "GetCompressedFormat: target invalid");
|
|
||||||
return m_storage[targetIndex].GetCompressedFormat(level);
|
|
||||||
}
|
|
||||||
|
|
||||||
SizeT GetCompressedByteSize(Uint targetIndex, Uint level) const {
|
|
||||||
MOBILEGL_ASSERT(targetIndex < TargetCount, "GetCompressedByteSize: target invalid");
|
|
||||||
return m_storage[targetIndex].GetCompressedByteSize(level);
|
|
||||||
}
|
|
||||||
|
|
||||||
const void* MapCompressedData(Uint targetIndex, Uint level) const {
|
|
||||||
MOBILEGL_ASSERT(targetIndex < TargetCount, "MapCompressedData: target invalid");
|
|
||||||
return m_storage[targetIndex].MapCompressedData(level);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
Array<MipmapStorage, TargetCount> m_storage;
|
Array<MipmapStorage, TargetCount> m_storage;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -162,7 +162,6 @@ namespace MobileGL {
|
|||||||
DepthComponent32F,
|
DepthComponent32F,
|
||||||
Depth24Stencil8,
|
Depth24Stencil8,
|
||||||
Depth32FStencil8,
|
Depth32FStencil8,
|
||||||
StencilIndex8,
|
|
||||||
|
|
||||||
DepthComponent,
|
DepthComponent,
|
||||||
DepthStencil,
|
DepthStencil,
|
||||||
|
|||||||
@@ -24,19 +24,6 @@ namespace MobileGL {
|
|||||||
TextureObjectBase::TextureObjectBase(TextureTarget target, Uint externalIndex)
|
TextureObjectBase::TextureObjectBase(TextureTarget target, Uint externalIndex)
|
||||||
: m_externalIndex(externalIndex), m_lifetimeId(AllocateLifetimeId()), m_target(target) {
|
: m_externalIndex(externalIndex), m_lifetimeId(AllocateLifetimeId()), m_target(target) {
|
||||||
m_sampler = MakeShared<SamplerObject>(0);
|
m_sampler = MakeShared<SamplerObject>(0);
|
||||||
if (target == TextureTarget::TextureRectangle) {
|
|
||||||
// A rectangle texture has no mip chain, so its initial sampler state is not
|
|
||||||
// the shared one: TEXTURE_MIN_FILTER is LINEAR and TEXTURE_WRAP_S/T are
|
|
||||||
// CLAMP_TO_EDGE (GL 4.6 core table 23.15). Leaving the 2D default of
|
|
||||||
// NEAREST_MIPMAP_LINEAR in place makes the texture mipmap-incomplete from
|
|
||||||
// birth, and every lookup that the application never re-filtered reads
|
|
||||||
// (0, 0, 0, 1) instead of its contents.
|
|
||||||
m_sampler->SetMinFilter(SamplerFilterMode::Linear);
|
|
||||||
m_sampler->SetMipmapMode(SamplerMipmapMode::None);
|
|
||||||
m_sampler->SetWrapS(SamplerWrapMode::ClampToEdge);
|
|
||||||
m_sampler->SetWrapT(SamplerWrapMode::ClampToEdge);
|
|
||||||
m_sampler->SetWrapR(SamplerWrapMode::ClampToEdge);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TextureInternalFormat TextureObjectBase::GetFormat() const {
|
TextureInternalFormat TextureObjectBase::GetFormat() const {
|
||||||
@@ -88,40 +75,48 @@ namespace MobileGL {
|
|||||||
return m_externalIndex;
|
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 {
|
const FloatVec4& TextureObjectBase::GetBorderColor() const {
|
||||||
return m_sampler->GetBorderColor();
|
return m_borderColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
void TextureObjectBase::SetBorderColor(const FloatVec4& color) {
|
void TextureObjectBase::SetBorderColor(const FloatVec4& color) {
|
||||||
if (color == m_sampler->GetBorderColor()) return;
|
if (color == m_borderColor) return;
|
||||||
|
|
||||||
m_sampler->SetBorderColor(color);
|
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_textureParamsVersion;
|
++m_textureParamsVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
const IntVec4& TextureObjectBase::GetBorderColorI() const {
|
const IntVec4& TextureObjectBase::GetBorderColorI() const {
|
||||||
return m_sampler->GetBorderColorI();
|
return m_borderColorI;
|
||||||
}
|
}
|
||||||
|
|
||||||
void TextureObjectBase::SetBorderColorI(const IntVec4& color) {
|
void TextureObjectBase::SetBorderColorI(const IntVec4& color) {
|
||||||
if (color == m_sampler->GetBorderColorI()) return;
|
if (color == m_borderColorI) return;
|
||||||
|
|
||||||
m_sampler->SetBorderColorI(color);
|
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_textureParamsVersion;
|
++m_textureParamsVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
const UintVec4& TextureObjectBase::GetBorderColorUI() const {
|
const UintVec4& TextureObjectBase::GetBorderColorUI() const {
|
||||||
return m_sampler->GetBorderColorUI();
|
return m_borderColorUI;
|
||||||
}
|
}
|
||||||
|
|
||||||
void TextureObjectBase::SetBorderColorUI(const UintVec4& color) {
|
void TextureObjectBase::SetBorderColorUI(const UintVec4& color) {
|
||||||
if (color == m_sampler->GetBorderColorUI()) return;
|
if (color == m_borderColorUI) return;
|
||||||
|
|
||||||
m_sampler->SetBorderColorUI(color);
|
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_textureParamsVersion;
|
++m_textureParamsVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -301,27 +296,6 @@ namespace MobileGL {
|
|||||||
return m_textureStorage.IsDirty(GetIndexOfTextureUploadTarget(uploadTarget), mipmapLevel);
|
return m_textureStorage.IsDirty(GetIndexOfTextureUploadTarget(uploadTarget), mipmapLevel);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TextureObjectWithOneMipmap::SetMipmapCompressedImage(TextureUploadTarget uploadTarget, Uint mipmapLevel,
|
|
||||||
GLenum internalFormat, const void* data, SizeT size) {
|
|
||||||
m_textureStorage.SetCompressedImage(GetIndexOfTextureUploadTarget(uploadTarget), mipmapLevel,
|
|
||||||
internalFormat, data, size);
|
|
||||||
}
|
|
||||||
|
|
||||||
GLenum TextureObjectWithOneMipmap::GetMipmapCompressedFormat(TextureUploadTarget uploadTarget,
|
|
||||||
Uint mipmapLevel) const {
|
|
||||||
return m_textureStorage.GetCompressedFormat(GetIndexOfTextureUploadTarget(uploadTarget), mipmapLevel);
|
|
||||||
}
|
|
||||||
|
|
||||||
SizeT TextureObjectWithOneMipmap::GetMipmapCompressedByteSize(TextureUploadTarget uploadTarget,
|
|
||||||
Uint mipmapLevel) const {
|
|
||||||
return m_textureStorage.GetCompressedByteSize(GetIndexOfTextureUploadTarget(uploadTarget), mipmapLevel);
|
|
||||||
}
|
|
||||||
|
|
||||||
const void* TextureObjectWithOneMipmap::MapMipmapCompressedImage(TextureUploadTarget uploadTarget,
|
|
||||||
Uint mipmapLevel) const {
|
|
||||||
return m_textureStorage.MapCompressedData(GetIndexOfTextureUploadTarget(uploadTarget), mipmapLevel);
|
|
||||||
}
|
|
||||||
|
|
||||||
IntVec3 TextureObjectWithOneMipmap::GetBaseSize() const {
|
IntVec3 TextureObjectWithOneMipmap::GetBaseSize() const {
|
||||||
if (m_textureStorage.GetLevelCount() == 0) {
|
if (m_textureStorage.GetLevelCount() == 0) {
|
||||||
return {0, 0, 0};
|
return {0, 0, 0};
|
||||||
@@ -361,64 +335,6 @@ namespace MobileGL {
|
|||||||
|
|
||||||
// TODO: add other texture types as needed
|
// TODO: add other texture types as needed
|
||||||
|
|
||||||
Bool SamplesAsIncompleteTexture(const ITextureObject* texture, const SamplerObject* effectiveSampler) {
|
|
||||||
const Bool mipmapped =
|
|
||||||
effectiveSampler != nullptr && effectiveSampler->GetMipmapMode() != SamplerMipmapMode::None;
|
|
||||||
return !IsMipmapCompleteForFilter(texture, mipmapped);
|
|
||||||
}
|
|
||||||
|
|
||||||
Bool IsMipmapCompleteForFilter(const ITextureObject* texture, Bool mipmapped) {
|
|
||||||
if (texture == nullptr) return true;
|
|
||||||
if (!texture->IsComplete()) return false;
|
|
||||||
if (!mipmapped) return true;
|
|
||||||
|
|
||||||
const auto* mipmapTexture = AsMipmapTexture(texture);
|
|
||||||
if (mipmapTexture == nullptr) return true; // no mip chain to be incomplete about
|
|
||||||
|
|
||||||
const UintVec2& levelRange = texture->GetLevelRange();
|
|
||||||
const Uint baseLevel = levelRange.x();
|
|
||||||
const Uint storedLevels = mipmapTexture->GetMipmapLevelCount();
|
|
||||||
if (baseLevel >= storedLevels) return false;
|
|
||||||
|
|
||||||
// An array texture's layer count is not a dimension of the image: it stays put all
|
|
||||||
// the way down the chain (GL 4.6 core 8.14.3). GetMipmapTexelSize reports it in the
|
|
||||||
// slot after the image's own dimensions.
|
|
||||||
const TextureTarget target = texture->GetTarget();
|
|
||||||
Int shrinkingComponents = 3;
|
|
||||||
if (target == TextureTarget::Texture1DArray) {
|
|
||||||
shrinkingComponents = 1;
|
|
||||||
} else if (target == TextureTarget::Texture2DArray || target == TextureTarget::TextureCubeMapArray) {
|
|
||||||
shrinkingComponents = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const auto uploadTarget : texture->GetUploadTargets()) {
|
|
||||||
const IntVec3 baseSize = mipmapTexture->GetMipmapTexelSize(uploadTarget, baseLevel);
|
|
||||||
Int largest = 0;
|
|
||||||
for (Int component = 0; component < shrinkingComponents; ++component) {
|
|
||||||
largest = std::max(largest, baseSize[component]);
|
|
||||||
}
|
|
||||||
if (largest <= 0) return false;
|
|
||||||
|
|
||||||
// p = log2 of the largest base dimension: the last level the chain needs
|
|
||||||
// before every dimension has reached 1. TEXTURE_MAX_LEVEL can cut it short.
|
|
||||||
Uint p = 0;
|
|
||||||
for (Int extent = largest; extent > 1; extent >>= 1) ++p;
|
|
||||||
const Uint lastLevel = std::min(baseLevel + p, levelRange.y());
|
|
||||||
|
|
||||||
for (Uint level = baseLevel; level <= lastLevel; ++level) {
|
|
||||||
if (level >= storedLevels) return false;
|
|
||||||
const IntVec3 actual = mipmapTexture->GetMipmapTexelSize(uploadTarget, level);
|
|
||||||
for (Int component = 0; component < 3; ++component) {
|
|
||||||
const Int expected = component < shrinkingComponents
|
|
||||||
? std::max(1, baseSize[component] >> (level - baseLevel))
|
|
||||||
: baseSize[component];
|
|
||||||
if (actual[component] != expected) return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace GLState
|
} // namespace GLState
|
||||||
} // namespace MG_State
|
} // namespace MG_State
|
||||||
} // namespace MobileGL
|
} // namespace MobileGL
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user