Compare commits

..
127 changed files with 809 additions and 11894 deletions
+8 -149
View File
@@ -10,22 +10,11 @@ case_name="$1"
fixture_dir="${2:-tools/trace_replay/fixtures}" fixture_dir="${2:-tools/trace_replay/fixtures}"
python_bin="${PYTHON:-python3}" python_bin="${PYTHON:-python3}"
mirror_base="${MOBILEGL_TRACE_FIXTURE_MIRROR_BASE:-https://repo.miawa.cn/mgl/tools/trace_replay/fixtures}" mirror_base="${MOBILEGL_TRACE_FIXTURE_MIRROR_BASE:-https://repo.miawa.cn/mgl/tools/trace_replay/fixtures}"
download_attempts="${MOBILEGL_TRACE_FIXTURE_DOWNLOAD_ATTEMPTS:-5}"
retry_delay="${MOBILEGL_TRACE_FIXTURE_RETRY_DELAY:-2}"
if ! command -v "${python_bin}" >/dev/null 2>&1 && command -v python >/dev/null 2>&1; then if ! command -v "${python_bin}" >/dev/null 2>&1 && command -v python >/dev/null 2>&1; then
python_bin=python python_bin=python
fi fi
if ! [[ "${download_attempts}" =~ ^[1-9][0-9]*$ ]]; then
echo "MOBILEGL_TRACE_FIXTURE_DOWNLOAD_ATTEMPTS must be a positive integer: ${download_attempts}" >&2
exit 2
fi
if ! [[ "${retry_delay}" =~ ^[0-9]+$ ]]; then
echo "MOBILEGL_TRACE_FIXTURE_RETRY_DELAY must be a non-negative integer: ${retry_delay}" >&2
exit 2
fi
fixture_list="$("${python_bin}" tools/trace_replay/trace_cases.py \ fixture_list="$("${python_bin}" tools/trace_replay/trace_cases.py \
--format fixture-files \ --format fixture-files \
--case "${case_name}" \ --case "${case_name}" \
@@ -45,140 +34,6 @@ if [ "${case_name}" = "OpenRA" ]; then
exit 0 exit 0
fi fi
get_lfs_metadata() {
local file="$1"
local pointer
local expected_oid
local expected_size
if ! pointer="$(git show "HEAD:${file}" 2>/dev/null)"; then
echo "failed to read tracked fixture metadata: ${file}" >&2
return 1
fi
if ! grep -q '^version https://git-lfs.github.com/spec/v1$' <<< "${pointer}"; then
echo "tracked fixture is not a Git LFS pointer: ${file}" >&2
return 1
fi
expected_oid="$(awk '$1 == "oid" && $2 ~ /^sha256:/ { sub(/^sha256:/, "", $2); print $2 }' <<< "${pointer}")"
expected_size="$(awk '$1 == "size" { print $2 }' <<< "${pointer}")"
if ! [[ "${expected_oid}" =~ ^[0-9a-f]{64}$ ]] || ! [[ "${expected_size}" =~ ^[0-9]+$ ]]; then
echo "invalid Git LFS pointer metadata: ${file}" >&2
return 1
fi
printf '%s %s\n' "${expected_oid}" "${expected_size}"
}
verify_fixture_file() {
local downloaded_file="$1"
local display_name="$2"
local expected_oid="$3"
local expected_size="$4"
local actual_oid
local actual_size
if [ ! -f "${downloaded_file}" ]; then
echo "fixture file is missing: ${display_name}" >&2
return 1
fi
actual_size="$(wc -c < "${downloaded_file}" | tr -d '[:space:]')"
if [ "${actual_size}" != "${expected_size}" ]; then
echo "fixture size mismatch for ${display_name}: expected ${expected_size}, got ${actual_size}" >&2
return 1
fi
actual_oid="$(sha256sum "${downloaded_file}" | awk '{ print $1 }')"
if [ "${actual_oid}" != "${expected_oid}" ]; then
echo "fixture SHA-256 mismatch for ${display_name}: expected ${expected_oid}, got ${actual_oid}" >&2
return 1
fi
}
fetch_file_from_mirror() {
local file="$1"
local url="$2"
local metadata
local expected_oid
local expected_size
local tmp_file="${file}.tmp"
local attempt
local partial_size
local curl_status
metadata="$(get_lfs_metadata "${file}")" || return 1
read -r expected_oid expected_size <<< "${metadata}"
if [ -f "${tmp_file}" ]; then
partial_size="$(wc -c < "${tmp_file}" | tr -d '[:space:]')"
if [ "${partial_size}" -gt "${expected_size}" ]; then
echo "Discarding oversized partial fixture ${tmp_file}: ${partial_size} > ${expected_size}" >&2
rm -f "${tmp_file}"
elif [ "${partial_size}" = "${expected_size}" ]; then
if verify_fixture_file "${tmp_file}" "${file}" "${expected_oid}" "${expected_size}"; then
mv "${tmp_file}" "${file}"
return 0
fi
rm -f "${tmp_file}"
fi
fi
for ((attempt = 1; attempt <= download_attempts; attempt++)); do
partial_size=0
if [ -f "${tmp_file}" ]; then
partial_size="$(wc -c < "${tmp_file}" | tr -d '[:space:]')"
fi
if [ "${partial_size}" -gt 0 ]; then
echo "Resuming mirror download for ${file} at byte ${partial_size} (attempt ${attempt}/${download_attempts})"
else
echo "Starting mirror download for ${file} (attempt ${attempt}/${download_attempts})"
fi
if curl -L --fail --show-error --continue-at - --output "${tmp_file}" "${url}"; then
if verify_fixture_file "${tmp_file}" "${file}" "${expected_oid}" "${expected_size}"; then
mv "${tmp_file}" "${file}"
return 0
fi
echo "Mirror download failed integrity verification; retrying from the beginning: ${file}" >&2
rm -f "${tmp_file}"
else
curl_status=$?
partial_size=0
if [ -f "${tmp_file}" ]; then
partial_size="$(wc -c < "${tmp_file}" | tr -d '[:space:]')"
fi
if [ "${partial_size}" = "${expected_size}" ]; then
if verify_fixture_file "${tmp_file}" "${file}" "${expected_oid}" "${expected_size}"; then
mv "${tmp_file}" "${file}"
return 0
fi
rm -f "${tmp_file}"
partial_size=0
elif [ "${partial_size}" -gt "${expected_size}" ]; then
echo "Discarding oversized partial fixture ${tmp_file}: ${partial_size} > ${expected_size}" >&2
rm -f "${tmp_file}"
partial_size=0
elif [ "${curl_status}" -eq 33 ]; then
echo "Mirror refused the resume request; retrying from the beginning: ${file}" >&2
rm -f "${tmp_file}"
partial_size=0
fi
echo "Mirror download attempt ${attempt}/${download_attempts} failed with curl exit ${curl_status}; retained ${partial_size} bytes for resume: ${file}" >&2
fi
if [ "${attempt}" -lt "${download_attempts}" ]; then
sleep "${retry_delay}"
fi
done
rm -f "${tmp_file}"
return 1
}
fetch_from_mirror() { fetch_from_mirror() {
mkdir -p "${fixture_dir}" mkdir -p "${fixture_dir}"
for file in "${files[@]}"; do for file in "${files[@]}"; do
@@ -187,9 +42,11 @@ fetch_from_mirror() {
name="$(basename "${file}")" name="$(basename "${file}")"
url="${mirror_base%/}/${name}" url="${mirror_base%/}/${name}"
echo "Fetching trace fixture from mirror: ${url}" echo "Fetching trace fixture from mirror: ${url}"
if ! fetch_file_from_mirror "${file}" "${url}"; then if ! curl -L --fail --retry 3 --retry-delay 2 -o "${file}.tmp" "${url}"; then
rm -f "${file}.tmp"
return 1 return 1
fi fi
mv "${file}.tmp" "${file}"
done done
} }
@@ -202,7 +59,9 @@ else
fi fi
for file in "${files[@]}"; do for file in "${files[@]}"; do
metadata="$(get_lfs_metadata "${file}")" test -s "${file}"
read -r expected_oid expected_size <<< "${metadata}" if head -n 1 "${file}" | grep -q "version https://git-lfs.github.com/spec/v1"; then
verify_fixture_file "${file}" "${file}" "${expected_oid}" "${expected_size}" echo "failed to hydrate LFS fixture: ${file}" >&2
exit 1
fi
done done
+32 -60
View File
@@ -227,8 +227,6 @@ jobs:
env: env:
AVD_NAME: mobilegl-ci AVD_NAME: mobilegl-ci
ANDROID_AVD_HOME: ${{ github.workspace }}/.android/avd ANDROID_AVD_HOME: ${{ github.workspace }}/.android/avd
ANDROID_HOME: ${{ github.workspace }}/.android/sdk
ANDROID_SDK_ROOT: ${{ github.workspace }}/.android/sdk
steps: steps:
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@v6 uses: actions/checkout@v6
@@ -247,11 +245,11 @@ jobs:
with: with:
path: | path: |
${{ env.ANDROID_AVD_HOME }} ${{ env.ANDROID_AVD_HOME }}
${{ env.ANDROID_SDK_ROOT }}/emulator /usr/local/lib/android/sdk/emulator
${{ env.ANDROID_SDK_ROOT }}/platform-tools /usr/local/lib/android/sdk/platform-tools
${{ env.ANDROID_SDK_ROOT }}/platforms/android-35 /usr/local/lib/android/sdk/platforms/android-35
${{ env.ANDROID_SDK_ROOT }}/system-images/android-35/google_apis/x86_64 /usr/local/lib/android/sdk/system-images/android-35/google_apis/x86_64
key: ${{ runner.os }}-mobilegl-avd-api35-google_apis-x86_64-pixel_6-v2-${{ hashFiles('android-plugin/run-avd-ci.sh') }} key: ${{ runner.os }}-mobilegl-avd-api35-google_apis-x86_64-pixel_6-v1-${{ hashFiles('android-plugin/run-avd-ci.sh') }}
- name: Create AVD - name: Create AVD
if: steps.android-avd-cache.outputs.cache-hit != 'true' if: steps.android-avd-cache.outputs.cache-hit != 'true'
@@ -276,8 +274,6 @@ jobs:
env: env:
AVD_NAME: mobilegl-ci AVD_NAME: mobilegl-ci
ANDROID_AVD_HOME: ${{ github.workspace }}/.android/avd ANDROID_AVD_HOME: ${{ github.workspace }}/.android/avd
ANDROID_HOME: ${{ github.workspace }}/.android/sdk
ANDROID_SDK_ROOT: ${{ github.workspace }}/.android/sdk
strategy: strategy:
fail-fast: false fail-fast: false
max-parallel: 4 max-parallel: 4
@@ -292,7 +288,7 @@ jobs:
- name: Set Swap Space - name: Set Swap Space
uses: pierotofy/set-swap-space@v1.0 uses: pierotofy/set-swap-space@v1.0
with: with:
swap-size-gb: 8 swap-size-gb: 16
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@v6 uses: actions/checkout@v6
@@ -328,11 +324,11 @@ jobs:
with: with:
path: | path: |
${{ env.ANDROID_AVD_HOME }} ${{ env.ANDROID_AVD_HOME }}
${{ env.ANDROID_SDK_ROOT }}/emulator /usr/local/lib/android/sdk/emulator
${{ env.ANDROID_SDK_ROOT }}/platform-tools /usr/local/lib/android/sdk/platform-tools
${{ env.ANDROID_SDK_ROOT }}/platforms/android-35 /usr/local/lib/android/sdk/platforms/android-35
${{ env.ANDROID_SDK_ROOT }}/system-images/android-35/google_apis/x86_64 /usr/local/lib/android/sdk/system-images/android-35/google_apis/x86_64
key: ${{ runner.os }}-mobilegl-avd-api35-google_apis-x86_64-pixel_6-v2-${{ hashFiles('android-plugin/run-avd-ci.sh') }} key: ${{ runner.os }}-mobilegl-avd-api35-google_apis-x86_64-pixel_6-v1-${{ hashFiles('android-plugin/run-avd-ci.sh') }}
- name: Download retrace APK - name: Download retrace APK
uses: actions/download-artifact@v8 uses: actions/download-artifact@v8
@@ -382,51 +378,27 @@ jobs:
if [ "${{ matrix.case.coherent_as_flush || false }}" = "true" ]; then if [ "${{ matrix.case.coherent_as_flush || false }}" = "true" ]; then
extra_retrace_args+=(--coherent-as-flush) extra_retrace_args+=(--coherent-as-flush)
fi fi
timeout "$(( ${{ matrix.case.timeout_seconds }} + 300 ))" sh android-plugin/trace-replay-ci.sh \
run_retrace() { --apk-file "${apk_file}" \
timeout "$(( ${{ matrix.case.timeout_seconds }} + 300 ))" sh android-plugin/trace-replay-ci.sh \ --package top.mobilegl.plugin.trace \
--apk-file "${apk_file}" \ --backend "${{ matrix.backend.name }}" \
--package top.mobilegl.plugin.trace \ --result-root android-retrace-result \
--backend "${{ matrix.backend.name }}" \ --fixture-root android-retrace-fixture \
--result-root android-retrace-result \ --case "${{ matrix.case.name }}" \
--fixture-root android-retrace-fixture \ --trace-archive "${{ matrix.case.trace_archive }}" \
--case "${{ matrix.case.name }}" \ --trace-file "${{ matrix.case.trace_file }}" \
--trace-archive "${{ matrix.case.trace_archive }}" \ --golden "${{ matrix.case.golden }}" \
--trace-file "${{ matrix.case.trace_file }}" \ --alternate-golden "${{ matrix.case.alternate_golden || '' }}" \
--golden "${{ matrix.case.golden }}" \ --target-call "${{ matrix.case.target_call }}" \
--alternate-golden "${{ matrix.case.alternate_golden || '' }}" \ --width "${{ matrix.case.width }}" \
--target-call "${{ matrix.case.target_call }}" \ --height "${{ matrix.case.height }}" \
--width "${{ matrix.case.width }}" \ --ssim-threshold "${{ matrix.case.ssim_threshold || '0.99' }}" \
--height "${{ matrix.case.height }}" \ --crop-x "${{ matrix.case.crop_x }}" \
--ssim-threshold "${{ matrix.case.ssim_threshold || '0.99' }}" \ --crop-y "${{ matrix.case.crop_y }}" \
--crop-x "${{ matrix.case.crop_x }}" \ --crop-width "${{ matrix.case.crop_width }}" \
--crop-y "${{ matrix.case.crop_y }}" \ --crop-height "${{ matrix.case.crop_height }}" \
--crop-width "${{ matrix.case.crop_width }}" \ --timeout-seconds "${{ matrix.case.timeout_seconds }}" \
--crop-height "${{ matrix.case.crop_height }}" \ "${extra_retrace_args[@]}"
--timeout-seconds "${{ matrix.case.timeout_seconds }}" \
"${extra_retrace_args[@]}"
}
retrace_status=0
run_retrace || retrace_status=$?
if [ "${retrace_status}" -eq 75 ]; then
echo "::warning::Android emulator infrastructure failed; restarting it and retrying this retrace once."
sh android-plugin/run-avd-ci.sh stop \
--avd-name "${AVD_NAME}" \
--emulator-log "${EMULATOR_LOG}" \
--pid-file "${EMULATOR_PID_FILE}"
adb kill-server || true
sleep 2
sh android-plugin/run-avd-ci.sh start \
--avd-name "${AVD_NAME}" \
--gpu "${{ matrix.backend.gpu }}" \
--emulator-log "${EMULATOR_LOG}" \
--pid-file "${EMULATOR_PID_FILE}" \
--boot-timeout 300
run_retrace
elif [ "${retrace_status}" -ne 0 ]; then
exit "${retrace_status}"
fi
- name: Collect retrace summary inputs - name: Collect retrace summary inputs
if: always() if: always()
+5 -6
View File
@@ -107,7 +107,6 @@ jobs:
--exclude='build.ninja' \ --exclude='build.ninja' \
--exclude='cmake_install.cmake' \ --exclude='cmake_install.cmake' \
-czf ci-artifacts/mobilegl-linux-runtime.tgz \ -czf ci-artifacts/mobilegl-linux-runtime.tgz \
"${BUILD_DIR}/CTestTestfile.cmake" \
"${BUILD_DIR}/MobileGL/MG_Test" \ "${BUILD_DIR}/MobileGL/MG_Test" \
"${BUILD_DIR}/MobileGL/MG_Benchmark" \ "${BUILD_DIR}/MobileGL/MG_Benchmark" \
"${SHARED_LIBS[@]}" "${SHARED_LIBS[@]}"
@@ -157,12 +156,12 @@ jobs:
PY PY
- name: Test - name: Test
working-directory: build-linux working-directory: build-linux/MobileGL/MG_Test
run: | run: |
if [ "${{ secrets.ACTIONS_STEP_DEBUG }}" = "true" ]; then if [ "${{ secrets.ACTIONS_STEP_DEBUG }}" = "true" ]; then
ctest -V -L unit --no-tests=error ctest -V
else else
ctest --output-on-failure -L unit --no-tests=error ctest --output-on-failure
fi fi
benchmark: benchmark:
@@ -203,8 +202,8 @@ jobs:
PY PY
- name: Benchmark - name: Benchmark
working-directory: build-linux working-directory: build-linux/MobileGL/MG_Benchmark
run: ctest -V -C Release -L benchmark --no-tests=error run: ctest -V -C Release
build-retrace: build-retrace:
runs-on: ubuntu-latest runs-on: ubuntu-latest
+1 -10
View File
@@ -190,7 +190,6 @@ set(SOURCE_FILES
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/DecomposeWorkgroupVec3Pass.cpp MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/DecomposeWorkgroupVec3Pass.cpp
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/LowerDrawParametersPass.cpp MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/LowerDrawParametersPass.cpp
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/RebaseInstanceIndexPass.cpp MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/RebaseInstanceIndexPass.cpp
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/StripUboMemberRelaxedPrecisionPass.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
@@ -476,15 +475,6 @@ if (NOT ANDROID AND NOT MOBILEGL_IOS)
endif () endif ()
if (NOT ANDROID) if (NOT ANDROID)
# Enable testing in the top-level scope so a CTestTestfile.cmake is emitted
# at the build-tree root. This lets `ctest` be invoked from the top-level
# build directory (IDE "run all tests", CI) and discover every test in the
# subdirectories below, instead of having to descend into each
# MG_Test/MG_Benchmark subdirectory. Tests are tagged with CTest labels
# (unit / benchmark / integration), so e.g. `ctest -L unit` selects just
# the unit suite.
enable_testing()
if (MOBILEGL_BUILD_TEST) if (MOBILEGL_BUILD_TEST)
add_subdirectory(MobileGL/MG_Test) add_subdirectory(MobileGL/MG_Test)
endif() endif()
@@ -494,6 +484,7 @@ if (NOT ANDROID)
endif() endif()
if (MOBILEGL_BUILD_TRACE_REPLAY) if (MOBILEGL_BUILD_TRACE_REPLAY)
enable_testing()
add_subdirectory(tools/trace_replay) add_subdirectory(tools/trace_replay)
endif() endif()
endif() endif()
-6
View File
@@ -61,12 +61,6 @@ namespace MobileGL::MG_Config {
// per-draw glBufferSubData path instead of the persistent-mapped ring allocator // per-draw glBufferSubData path instead of the persistent-mapped ring allocator
// (negative control / driver-bug escape hatch). // (negative control / driver-bug escape hatch).
Bool DisableUboRing = false; Bool DisableUboRing = false;
// MOBILEGL_RELAXED_SEMANTICS: relax strict core-profile rules (e.g. VAO-0 draws,
// texture-name reuse after delete) even on contexts that explicitly requested a core
// profile. Without it, relaxed semantics still apply to every context that did not
// explicitly request a core profile via EGL_CONTEXT_OPENGL_PROFILE_MASK / a >=3.1
// version request.
Bool RelaxedSemantics = false;
}; };
extern FeaturesTable Features; extern FeaturesTable Features;
} // namespace MobileGL::MG_Config } // namespace MobileGL::MG_Config
-1
View File
@@ -122,7 +122,6 @@ namespace MobileGL::MG_ConfigLoader {
features.CoherentAsFlush = QueryEnvFlag("MOBILEGL_COHERENT_AS_FLUSH"); features.CoherentAsFlush = QueryEnvFlag("MOBILEGL_COHERENT_AS_FLUSH");
features.TraceSkipAutodestroy = QueryEnvFlag("MOBILEGL_TRACE_SKIP_AUTODESTROY"); features.TraceSkipAutodestroy = QueryEnvFlag("MOBILEGL_TRACE_SKIP_AUTODESTROY");
features.DisableUboRing = QueryEnvFlag("MOBILEGL_DISABLE_UBO_RING"); features.DisableUboRing = QueryEnvFlag("MOBILEGL_DISABLE_UBO_RING");
features.RelaxedSemantics = QueryEnvFlag("MOBILEGL_RELAXED_SEMANTICS");
} }
inline void InitBackendType() { inline void InitBackendType() {
-6
View File
@@ -232,9 +232,6 @@ namespace MobileGL {
struct DynamicBackendParameters { struct DynamicBackendParameters {
SizeT UniformBufferOffsetAlignment = 256; SizeT UniformBufferOffsetAlignment = 256;
// GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT. 1.0 means the backend cannot filter anisotropically,
// which is also why the extension is not advertised in that case.
Float MaxTextureMaxAnisotropy = 1.0f;
Float AliasedLineWidthRangeMin = 1.0f; Float AliasedLineWidthRangeMin = 1.0f;
Float AliasedLineWidthRangeMax = 1.0f; Float AliasedLineWidthRangeMax = 1.0f;
Float SmoothLineWidthRangeMin = 1.0f; Float SmoothLineWidthRangeMin = 1.0f;
@@ -272,9 +269,6 @@ namespace MobileGL {
Int MaxUniformBlockSize = 16384; Int MaxUniformBlockSize = 16384;
Int MaxImageUnits = 8; Int MaxImageUnits = 8;
Int MaxCombinedImageUniforms = 8; Int MaxCombinedImageUniforms = 8;
Int MaxVertexImageUniforms = 0;
Int MaxGeometryImageUniforms = 0;
Int MaxFragmentImageUniforms = 8;
Int MaxComputeImageUniforms = 8; Int MaxComputeImageUniforms = 8;
Int MaxDrawBuffers = 8; Int MaxDrawBuffers = 8;
Int MaxColorAttachments = 8; Int MaxColorAttachments = 8;
@@ -605,9 +605,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
{ {
.TargetGLVersion = {3, 3, 0}, // Target OpenGL 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 yet); reconciled once
// once the ES capabilities exist, see UpdateAdvertisedCapabilityExtensions. // the ES capabilities exist, see UpdateAdvertisedTimerQueryExtension.
.Extensions = BuildAdvertisedExtensions(false, false), .Extensions = BuildAdvertisedExtensions(false),
.IsCompatibilityProfile = false // Is Compatibility Profile .IsCompatibilityProfile = false // Is Compatibility Profile
}, },
.StaticBackendCapability = {.AllowVSOnlyPrograms = false} // Backend Capability .StaticBackendCapability = {.AllowVSOnlyPrograms = false} // Backend Capability
@@ -627,9 +627,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
// thread can only observe the extension string after the // thread can only observe the extension string after the
// advertisement for its context has settled; rebuilding the whole // advertisement for its context has settled; rebuilding the whole
// list keeps the re-run after a context recreation idempotent. // list keeps the re-run after a context recreation idempotent.
void UpdateAdvertisedCapabilityExtensions(Bool anisotropicFilteringSupported) { void UpdateAdvertisedTimerQueryExtension() {
MutableRendererInfo().RendererGLInfo.Extensions = MutableRendererInfo().RendererGLInfo.Extensions = BuildAdvertisedExtensions(AreTimerQueriesSupported());
BuildAdvertisedExtensions(AreTimerQueriesSupported(), anisotropicFilteringSupported);
} }
} // namespace } // namespace
@@ -673,11 +672,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
return false; return false;
} }
DirectGLES::SetGLESCapabilities(m_GLESCapabilities); DirectGLES::SetGLESCapabilities(m_GLESCapabilities);
// Now that g_GLESCapabilities knows about GL_EXT_disjoint_timer_query and // Now that g_GLESCapabilities knows about GL_EXT_disjoint_timer_query,
// GL_EXT_texture_filter_anisotropic, reconcile the advertisement (see the comment on // reconcile the E_GL_ARB_timer_query advertisement (see the comment on
// UpdateAdvertisedCapabilityExtensions for why it cannot happen when the extension // UpdateAdvertisedTimerQueryExtension for why it cannot happen when
// list is first built). // the extension list is first built).
UpdateAdvertisedCapabilityExtensions(m_GLESCapabilities.SupportsTextureFilterAnisotropy); UpdateAdvertisedTimerQueryExtension();
UpdateDynamicBackendParameters(); UpdateDynamicBackendParameters();
PopulateFormatCapabilities(m_GLESFunctions, m_GLESCapabilities, MutableFormatCapabilities()); PopulateFormatCapabilities(m_GLESFunctions, m_GLESCapabilities, MutableFormatCapabilities());
PrintFormatCapabilities(GetFormatCapabilities()); PrintFormatCapabilities(GetFormatCapabilities());
@@ -819,7 +818,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
return MutableRendererInfo(); return MutableRendererInfo();
} }
Vector<GLExtension> BuildAdvertisedExtensions(Bool timerQueriesSupported, Bool anisotropicFilteringSupported) { Vector<GLExtension> BuildAdvertisedExtensions(Bool timerQueriesSupported) {
Vector<GLExtension> extensions = {V_OpenGL30, V_OpenGL31, V_OpenGL32, Vector<GLExtension> extensions = {V_OpenGL30, V_OpenGL31, V_OpenGL32,
V_OpenGL33, E_GL_ARB_draw_buffers_blend, E_GL_ARB_compute_shader, V_OpenGL33, E_GL_ARB_draw_buffers_blend, E_GL_ARB_compute_shader,
E_GL_ARB_shader_storage_buffer_object, E_GL_ARB_shader_image_load_store, E_GL_ARB_shader_storage_buffer_object, E_GL_ARB_shader_image_load_store,
@@ -837,14 +836,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
if (timerQueriesSupported && !MG_Config::Features.DisableTimerQuery) { if (timerQueriesSupported && !MG_Config::Features.DisableTimerQuery) {
extensions.push_back(E_GL_ARB_timer_query); extensions.push_back(E_GL_ARB_timer_query);
} }
// Only advertised when the host ES driver actually filters anisotropically: the sampler
// state is accepted regardless, but forwarding it would be a no-op without the extension,
// and an app that trusts the string (LWJGL builds GLCapabilities from it) would silently
// get plain trilinear.
if (anisotropicFilteringSupported) {
extensions.push_back(E_GL_EXT_texture_filter_anisotropic);
extensions.push_back(E_GL_ARB_texture_filter_anisotropic);
}
return extensions; return extensions;
} }
@@ -947,15 +938,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
return m_dynamicParameters; return m_dynamicParameters;
} }
void BackendObject_DirectGLES::ApplyGLESCapabilitiesForTesting(
const MG_External::GLESCapabilities& capabilities) {
m_GLESCapabilities = capabilities;
UpdateDynamicBackendParameters();
}
void BackendObject_DirectGLES::UpdateDynamicBackendParameters() { void BackendObject_DirectGLES::UpdateDynamicBackendParameters() {
m_dynamicParameters.UniformBufferOffsetAlignment = m_GLESCapabilities.UniformBufferOffsetAlignment; m_dynamicParameters.UniformBufferOffsetAlignment = m_GLESCapabilities.UniformBufferOffsetAlignment;
m_dynamicParameters.MaxTextureMaxAnisotropy = m_GLESCapabilities.MaxTextureMaxAnisotropy;
m_dynamicParameters.AliasedLineWidthRangeMin = m_GLESCapabilities.AliasedLineWidthRangeMin; m_dynamicParameters.AliasedLineWidthRangeMin = m_GLESCapabilities.AliasedLineWidthRangeMin;
m_dynamicParameters.AliasedLineWidthRangeMax = m_GLESCapabilities.AliasedLineWidthRangeMax; m_dynamicParameters.AliasedLineWidthRangeMax = m_GLESCapabilities.AliasedLineWidthRangeMax;
m_dynamicParameters.SmoothLineWidthRangeMin = m_GLESCapabilities.SmoothLineWidthRangeMin; m_dynamicParameters.SmoothLineWidthRangeMin = m_GLESCapabilities.SmoothLineWidthRangeMin;
@@ -1009,21 +993,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
m_dynamicParameters.MaxUniformBlockSize = m_GLESCapabilities.MaxUniformBlockSize; m_dynamicParameters.MaxUniformBlockSize = m_GLESCapabilities.MaxUniformBlockSize;
const Int maxSupportedTextureUnits = const Int maxSupportedTextureUnits =
static_cast<Int>(MG_State::GLState::TextureState::MAX_TEXTURE_IMAGE_UNITS); static_cast<Int>(MG_State::GLState::TextureState::MAX_TEXTURE_IMAGE_UNITS);
m_dynamicParameters.MaxImageUnits = m_dynamicParameters.MaxImageUnits = std::min(m_GLESCapabilities.MaxImageUnits, maxSupportedTextureUnits);
std::max(std::min(m_GLESCapabilities.MaxImageUnits, maxSupportedTextureUnits), 0); m_dynamicParameters.MaxCombinedImageUniforms = m_GLESCapabilities.MaxCombinedImageUniforms;
m_dynamicParameters.MaxCombinedImageUniforms = std::max(m_GLESCapabilities.MaxCombinedImageUniforms, 0); m_dynamicParameters.MaxComputeImageUniforms = m_GLESCapabilities.MaxComputeImageUniforms;
const auto clampStageImageUniforms = [this](Int stageLimit) {
return std::min({std::max(stageLimit, 0), m_dynamicParameters.MaxImageUnits,
m_dynamicParameters.MaxCombinedImageUniforms});
};
m_dynamicParameters.MaxVertexImageUniforms =
clampStageImageUniforms(m_GLESCapabilities.MaxVertexImageUniforms);
m_dynamicParameters.MaxGeometryImageUniforms =
clampStageImageUniforms(m_GLESCapabilities.MaxGeometryImageUniforms);
m_dynamicParameters.MaxFragmentImageUniforms =
clampStageImageUniforms(m_GLESCapabilities.MaxFragmentImageUniforms);
m_dynamicParameters.MaxComputeImageUniforms =
clampStageImageUniforms(m_GLESCapabilities.MaxComputeImageUniforms);
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;
@@ -41,7 +41,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
const MG_External::GLESFunctionsTable& GetGLESFunctions() const; const MG_External::GLESFunctionsTable& GetGLESFunctions() const;
const MG_External::EGLFunctionsTable& GetEGLFunctions() const; const MG_External::EGLFunctionsTable& GetEGLFunctions() const;
void ApplyGLESCapabilitiesForTesting(const MG_External::GLESCapabilities& capabilities);
private: private:
void UpdateDynamicBackendParameters(); void UpdateDynamicBackendParameters();
@@ -67,9 +66,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
const RendererInfo& GetRendererIdentity(); const RendererInfo& GetRendererIdentity();
// The full OpenGL extension list Espryt advertises (glGetString(GL_EXTENSIONS)) // The full OpenGL extension list Espryt advertises (glGetString(GL_EXTENSIONS))
// for a device whose timer queries / anisotropic filtering are (or are not) usable. // for a device whose timer queries are (or are not) usable. The
// The MOBILEGL_DISABLE_TIMERQUERY escape hatch is applied inside. // MOBILEGL_DISABLE_TIMERQUERY escape hatch is applied inside.
Vector<GLExtension> BuildAdvertisedExtensions(Bool timerQueriesSupported, Bool anisotropicFilteringSupported); Vector<GLExtension> BuildAdvertisedExtensions(Bool timerQueriesSupported);
// Format: <OpenGL ES Renderer>, OpenGL ES <Major>.<Minor> — the exact string an // Format: <OpenGL ES Renderer>, OpenGL ES <Major>.<Minor> — the exact string an
// initialized backend returns from GetBackendAPIVersionString (and that ends up // initialized backend returns from GetBackendAPIVersionString (and that ends up
File diff suppressed because it is too large Load Diff
@@ -19,10 +19,6 @@
operation Utils::CheckGLESError(); operation Utils::CheckGLESError();
namespace MobileGL::MG_Backend::DirectGLES { namespace MobileGL::MG_Backend::DirectGLES {
// Re-establishes the frontend texture-unit bindings on the native ES context.
// Content uploads use scratch bindings, so draws and dispatches call this after
// texture synchronization.
void BindCurrentTextures();
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);
+49 -108
View File
@@ -1405,11 +1405,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
m_prevSkipPixels = s_skipPixels; m_prevSkipPixels = s_skipPixels;
m_prevImageHeight = s_imageHeight; m_prevImageHeight = s_imageHeight;
m_prevSkipImages = s_skipImages; m_prevSkipImages = s_skipImages;
// Shadow mip data is tightly packed (ProcessTexturePixelsDataUnpack emits Apply(4, 0, 0, 0, 0, 0);
// width * bpp rows with no padding), so uploads must use UNPACK_ALIGNMENT = 1.
// Alignment 4 made the driver read e.g. 7-byte R8 rows at an 8-byte stride,
// shifting every row of a non-multiple-of-4 upload by one pixel.
Apply(1, 0, 0, 0, 0, 0);
} }
~ScopedDefaultUnpackState() { ~ScopedDefaultUnpackState() {
@@ -1476,13 +1472,10 @@ namespace MobileGL::MG_Backend::DirectGLES {
return 2; return 2;
case TextureInternalFormat::RGB8Snorm: case TextureInternalFormat::RGB8Snorm:
case TextureInternalFormat::RGB16: case TextureInternalFormat::RGB16:
case TextureInternalFormat::RGB10: // stored as RGB16 (UNorm16 shadow)
case TextureInternalFormat::RGB12: // stored as RGB16 (UNorm16 shadow)
case TextureInternalFormat::RGB16Snorm: case TextureInternalFormat::RGB16Snorm:
return 3; return 3;
case TextureInternalFormat::RGBA8Snorm: case TextureInternalFormat::RGBA8Snorm:
case TextureInternalFormat::RGBA16: case TextureInternalFormat::RGBA16:
case TextureInternalFormat::RGBA12: // stored as RGBA16 (UNorm16 shadow)
case TextureInternalFormat::RGBA16Snorm: case TextureInternalFormat::RGBA16Snorm:
return 4; return 4;
default: default:
@@ -1577,7 +1570,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
MGLOG_D("Syncing texture mipmaps with backend ID %u to backend for state ID %u", m_backendTextureId, MGLOG_D("Syncing texture mipmaps with backend ID %u to backend for state ID %u", m_backendTextureId,
stateTextureObject->GetExternalIndex()); stateTextureObject->GetExternalIndex());
GLenum target = ConvertTextureTargetToBackendGLEnum(stateTextureObject->GetTarget()); GLenum target = MG_Util::ConvertTextureTargetToGLEnum(stateTextureObject->GetTarget());
auto targetInternal = stateTextureObject->GetTarget(); auto targetInternal = stateTextureObject->GetTarget();
MGLOG_D(" Texture target for syncing is %s", MGLOG_D(" Texture target for syncing is %s",
MG_Util::ConvertTextureTargetToString(targetInternal).c_str()); MG_Util::ConvertTextureTargetToString(targetInternal).c_str());
@@ -1698,7 +1691,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
auto levelTexelSize = textureMipmapObject->GetMipmapTexelSize(uploadTarget, level); auto levelTexelSize = textureMipmapObject->GetMipmapTexelSize(uploadTarget, level);
auto levelByteSize = textureMipmapObject->GetMipmapByteSize(uploadTarget, level); auto levelByteSize = textureMipmapObject->GetMipmapByteSize(uploadTarget, level);
bool levelDirty = textureMipmapObject->IsStorageDirty(uploadTarget, level); bool levelDirty = textureMipmapObject->IsStorageDirty(uploadTarget, level);
auto glUploadTarget = ConvertTextureUploadTargetToBackendGLEnum(uploadTarget); auto glUploadTarget = MG_Util::ConvertTextureUploadTargetToGLEnum(uploadTarget);
auto* pData = (levelDirty && levelByteSize != 0) auto* pData = (levelDirty && levelByteSize != 0)
? textureMipmapObject->MapMipmapData(uploadTarget, level) ? textureMipmapObject->MapMipmapData(uploadTarget, level)
: nullptr; : nullptr;
@@ -1709,22 +1702,19 @@ namespace MobileGL::MG_Backend::DirectGLES {
DebugImpl::ErrorLopper::Clear(); DebugImpl::ErrorLopper::Clear();
g_GLESFuncs.glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); g_GLESFuncs.glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
const IntVec3 uploadSize = switch (stateTextureObject->GetTarget()) {
GetBackendUploadSize(stateTextureObject->GetTarget(), levelTexelSize);
switch (MapToBackendTextureTarget(stateTextureObject->GetTarget())) {
case TextureTarget::Texture2D: case TextureTarget::Texture2D:
case TextureTarget::TextureCubeMap: case TextureTarget::TextureCubeMap:
g_GLESFuncs.glTexImage2D( g_GLESFuncs.glTexImage2D(
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>(levelTexelSize.x()), static_cast<GLsizei>(levelTexelSize.y()),
0, glFormat, glType, uploadData); 0, glFormat, glType, uploadData);
break; break;
case TextureTarget::Texture3D: case TextureTarget::Texture3D:
case TextureTarget::Texture2DArray:
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>(levelTexelSize.x()), static_cast<GLsizei>(levelTexelSize.y()),
static_cast<GLsizei>(uploadSize.z()), 0, glFormat, glType, uploadData); static_cast<GLsizei>(levelTexelSize.z()), 0, glFormat, glType, uploadData);
break; break;
default: default:
MGLOG_E("Unhandled texture target %s", MGLOG_E("Unhandled texture target %s",
@@ -1788,20 +1778,18 @@ namespace MobileGL::MG_Backend::DirectGLES {
} else if (stateTextureObject->IsImmutable() || m_imageBindableStorageRequired) { } else if (stateTextureObject->IsImmutable() || m_imageBindableStorageRequired) {
DebugImpl::ErrorLopper::Clear(); DebugImpl::ErrorLopper::Clear();
g_GLESFuncs.glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); g_GLESFuncs.glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
const IntVec3 storageSize = GetBackendUploadSize(targetInternal, baseSize); switch (targetInternal) {
switch (MapToBackendTextureTarget(targetInternal)) {
case TextureTarget::Texture2D: case TextureTarget::Texture2D:
case TextureTarget::TextureCubeMap: case TextureTarget::TextureCubeMap:
g_GLESFuncs.glTexStorage2D(target, static_cast<GLsizei>(mipmapCount), glInternalFormat, g_GLESFuncs.glTexStorage2D(target, static_cast<GLsizei>(mipmapCount), glInternalFormat,
static_cast<GLsizei>(storageSize.x()), static_cast<GLsizei>(baseSize.x()),
static_cast<GLsizei>(storageSize.y())); static_cast<GLsizei>(baseSize.y()));
break; break;
case TextureTarget::Texture3D: case TextureTarget::Texture3D:
case TextureTarget::Texture2DArray:
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>(baseSize.x()),
static_cast<GLsizei>(storageSize.y()), static_cast<GLsizei>(baseSize.y()),
static_cast<GLsizei>(storageSize.z())); static_cast<GLsizei>(baseSize.z()));
break; break;
default: default:
MGLOG_E("Unhandled immutable texture target %s", MGLOG_E("Unhandled immutable texture target %s",
@@ -1825,7 +1813,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
if (levelDirty && levelByteSize != 0) { if (levelDirty && levelByteSize != 0) {
auto levelTexelSize = auto levelTexelSize =
textureMipmapObject->GetMipmapTexelSize(uploadTarget, level); textureMipmapObject->GetMipmapTexelSize(uploadTarget, level);
auto glUploadTarget = ConvertTextureUploadTargetToBackendGLEnum(uploadTarget); auto glUploadTarget = MG_Util::ConvertTextureUploadTargetToGLEnum(uploadTarget);
auto* pData = textureMipmapObject->MapMipmapData(uploadTarget, level); auto* pData = textureMipmapObject->MapMipmapData(uploadTarget, level);
Vector<Float> convertedUploadData; Vector<Float> convertedUploadData;
const void* uploadData = PrepareNormFloatFallbackUpload( const void* uploadData = PrepareNormFloatFallbackUpload(
@@ -1834,23 +1822,20 @@ namespace MobileGL::MG_Backend::DirectGLES {
DebugImpl::ErrorLopper::Clear(); DebugImpl::ErrorLopper::Clear();
g_GLESFuncs.glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); g_GLESFuncs.glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
const IntVec3 uploadSize = switch (targetInternal) {
GetBackendUploadSize(targetInternal, levelTexelSize);
switch (MapToBackendTextureTarget(targetInternal)) {
case TextureTarget::Texture2D: case TextureTarget::Texture2D:
case TextureTarget::TextureCubeMap: case TextureTarget::TextureCubeMap:
g_GLESFuncs.glTexSubImage2D( g_GLESFuncs.glTexSubImage2D(
glUploadTarget, static_cast<GLint>(level), 0, 0, glUploadTarget, static_cast<GLint>(level), 0, 0,
static_cast<GLsizei>(uploadSize.x()), static_cast<GLsizei>(levelTexelSize.x()),
static_cast<GLsizei>(uploadSize.y()), glFormat, glType, uploadData); static_cast<GLsizei>(levelTexelSize.y()), glFormat, glType, uploadData);
break; break;
case TextureTarget::Texture3D: case TextureTarget::Texture3D:
case TextureTarget::Texture2DArray:
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>(levelTexelSize.x()),
static_cast<GLsizei>(uploadSize.y()), static_cast<GLsizei>(levelTexelSize.y()),
static_cast<GLsizei>(uploadSize.z()), glFormat, glType, uploadData); static_cast<GLsizei>(levelTexelSize.z()), glFormat, glType, uploadData);
break; break;
default: default:
break; break;
@@ -1877,7 +1862,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
auto levelTexelSize = textureMipmapObject->GetMipmapTexelSize(uploadTarget, level); auto levelTexelSize = textureMipmapObject->GetMipmapTexelSize(uploadTarget, level);
auto levelByteSize = textureMipmapObject->GetMipmapByteSize(uploadTarget, level); auto levelByteSize = textureMipmapObject->GetMipmapByteSize(uploadTarget, level);
bool levelDirty = textureMipmapObject->IsStorageDirty(uploadTarget, level); bool levelDirty = textureMipmapObject->IsStorageDirty(uploadTarget, level);
auto glUploadTarget = ConvertTextureUploadTargetToBackendGLEnum(uploadTarget); auto glUploadTarget = MG_Util::ConvertTextureUploadTargetToGLEnum(uploadTarget);
auto* pData = (levelDirty && levelByteSize != 0) auto* pData = (levelDirty && levelByteSize != 0)
? textureMipmapObject->MapMipmapData(uploadTarget, level) ? textureMipmapObject->MapMipmapData(uploadTarget, level)
: nullptr; : nullptr;
@@ -1894,23 +1879,22 @@ namespace MobileGL::MG_Backend::DirectGLES {
DebugImpl::ErrorLopper::Clear(); DebugImpl::ErrorLopper::Clear();
g_GLESFuncs.glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); g_GLESFuncs.glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
auto textureTarget = stateTextureObject->GetTarget(); auto textureTarget = stateTextureObject->GetTarget();
const IntVec3 uploadSize = GetBackendUploadSize(textureTarget, levelTexelSize); // TODO: handle more texture types
switch (MapToBackendTextureTarget(textureTarget)) { switch (textureTarget) {
case TextureTarget::Texture2D: case TextureTarget::Texture2D:
case TextureTarget::TextureCubeMap: { case TextureTarget::TextureCubeMap: {
g_GLESFuncs.glTexImage2D( g_GLESFuncs.glTexImage2D(
glUploadTarget, static_cast<GLint>(level), (GLint)glInternalFormat, glUploadTarget, static_cast<GLint>(level), (GLint)glInternalFormat,
static_cast<GLsizei>(uploadSize.x()), static_cast<GLsizei>(levelTexelSize.x()),
static_cast<GLsizei>(uploadSize.y()), 0, glFormat, glType, uploadData); static_cast<GLsizei>(levelTexelSize.y()), 0, glFormat, glType, uploadData);
break; break;
} }
case TextureTarget::Texture3D: case TextureTarget::Texture3D: {
case TextureTarget::Texture2DArray: {
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>(levelTexelSize.x()),
static_cast<GLsizei>(uploadSize.y()), static_cast<GLsizei>(levelTexelSize.y()),
static_cast<GLsizei>(uploadSize.z()), 0, glFormat, glType, uploadData); static_cast<GLsizei>(levelTexelSize.z()), 0, glFormat, glType, uploadData);
break; break;
} }
default: { default: {
@@ -1977,7 +1961,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
textureMipmapObject->GetMipmapTexelSize(uploadTarget, level).x(), textureMipmapObject->GetMipmapTexelSize(uploadTarget, level).x(),
textureMipmapObject->GetMipmapTexelSize(uploadTarget, level).y(), byteSize); textureMipmapObject->GetMipmapTexelSize(uploadTarget, level).y(), byteSize);
auto glUploadTarget = ConvertTextureUploadTargetToBackendGLEnum(uploadTarget); auto glUploadTarget = MG_Util::ConvertTextureUploadTargetToGLEnum(uploadTarget);
g_GLESFuncs.glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); g_GLESFuncs.glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
DebugImpl::ErrorLopper::Loop( DebugImpl::ErrorLopper::Loop(
[file = __FILE__, line = __LINE__, func = __func__](GLenum err) { [file = __FILE__, line = __LINE__, func = __func__](GLenum err) {
@@ -1990,22 +1974,19 @@ namespace MobileGL::MG_Backend::DirectGLES {
const void* uploadData = PrepareNormFloatFallbackUpload( const void* uploadData = PrepareNormFloatFallbackUpload(
textureMipmapObject->GetFormat(), texelSize, mipData, byteSize, glType, textureMipmapObject->GetFormat(), texelSize, mipData, byteSize, glType,
convertedUploadData); convertedUploadData);
const IntVec3 uploadSize = switch (stateTextureObject->GetTarget()) {
GetBackendUploadSize(stateTextureObject->GetTarget(), texelSize);
switch (MapToBackendTextureTarget(stateTextureObject->GetTarget())) {
case TextureTarget::Texture2D: case TextureTarget::Texture2D:
case TextureTarget::TextureCubeMap: case TextureTarget::TextureCubeMap:
g_GLESFuncs.glTexSubImage2D(glUploadTarget, static_cast<GLint>(level), 0, 0, g_GLESFuncs.glTexSubImage2D(glUploadTarget, static_cast<GLint>(level), 0, 0,
static_cast<GLsizei>(uploadSize.x()), static_cast<GLsizei>(texelSize.x()),
static_cast<GLsizei>(uploadSize.y()), glFormat, glType, static_cast<GLsizei>(texelSize.y()), glFormat, glType,
uploadData); uploadData);
break; break;
case TextureTarget::Texture3D: case TextureTarget::Texture3D:
case TextureTarget::Texture2DArray:
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>(texelSize.x()),
static_cast<GLsizei>(uploadSize.y()), static_cast<GLsizei>(texelSize.y()),
static_cast<GLsizei>(uploadSize.z()), glFormat, glType, static_cast<GLsizei>(texelSize.z()), glFormat, glType,
uploadData); uploadData);
break; break;
default: default:
@@ -2098,7 +2079,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
MGLOG_D("Syncing texture built-in sampler with backend ID %u to backend for state ID %u", MGLOG_D("Syncing texture built-in sampler with backend ID %u to backend for state ID %u",
m_backendTextureId, stateTextureObject->GetExternalIndex()); m_backendTextureId, stateTextureObject->GetExternalIndex());
GLenum target = ConvertTextureTargetToBackendGLEnum(stateTextureObject->GetTarget()); GLenum target = MG_Util::ConvertTextureTargetToGLEnum(stateTextureObject->GetTarget());
auto targetInternal = stateTextureObject->GetTarget(); auto targetInternal = stateTextureObject->GetTarget();
MGLOG_D(" Texture target for syncing is %s", MGLOG_D(" Texture target for syncing is %s",
MG_Util::ConvertTextureTargetToString(targetInternal).c_str()); MG_Util::ConvertTextureTargetToString(targetInternal).c_str());
@@ -2206,7 +2187,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
MGLOG_D("Syncing texture params with backend ID %u to backend for state ID %u", m_backendTextureId, MGLOG_D("Syncing texture params with backend ID %u to backend for state ID %u", m_backendTextureId,
stateTextureObject->GetExternalIndex()); stateTextureObject->GetExternalIndex());
GLenum target = ConvertTextureTargetToBackendGLEnum(stateTextureObject->GetTarget()); GLenum target = MG_Util::ConvertTextureTargetToGLEnum(stateTextureObject->GetTarget());
auto targetInternal = stateTextureObject->GetTarget(); auto targetInternal = stateTextureObject->GetTarget();
MGLOG_D(" Texture target for syncing is %s", MGLOG_D(" Texture target for syncing is %s",
MG_Util::ConvertTextureTargetToString(targetInternal).c_str()); MG_Util::ConvertTextureTargetToString(targetInternal).c_str());
@@ -2285,11 +2266,14 @@ namespace MobileGL::MG_Backend::DirectGLES {
g_activeTextureUnit = unit; g_activeTextureUnit = unit;
} }
void UnbindTexture(Uint unit, GLenum target) { // Activates `unit` when an unbind is issued void UnbindTexture(Uint unit, GLenum target) { // Active unit will be modified
if (unit != g_activeTextureUnit) {
ActivateTextureUnit(unit);
}
auto targetN = static_cast<SizeT>(MG_Util::ConvertGLEnumToTextureTarget(target)); auto targetN = static_cast<SizeT>(MG_Util::ConvertGLEnumToTextureTarget(target));
if (g_boundTexturesCache[unit][targetN] == nullptr) return; if (g_boundTexturesCache[unit][targetN] == nullptr) return;
ActivateTextureUnit(unit);
g_GLESFuncs.glBindTexture(target, 0); g_GLESFuncs.glBindTexture(target, 0);
g_boundTexturesCache[unit][targetN] = nullptr; g_boundTexturesCache[unit][targetN] = nullptr;
} }
@@ -2360,21 +2344,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
g_GLESFuncs.glFramebufferTexture(glFBOTarget, glBackendAttachment, g_GLESFuncs.glFramebufferTexture(glFBOTarget, glBackendAttachment,
backendTextureObject->GetBackendTextureId(), backendTextureObject->GetBackendTextureId(),
static_cast<GLint>(attachmentObject.GetTextureLevel())); static_cast<GLint>(attachmentObject.GetTextureLevel()));
} else if (const auto uploadTarget = attachmentObject.GetTextureUploadTarget();
uploadTarget == TextureUploadTarget::Texture3D ||
uploadTarget == TextureUploadTarget::Texture2DArray ||
uploadTarget == TextureUploadTarget::Texture2DMultisampleArray) {
// Single slice/layer of a 3D or array texture: ES has no
// glFramebufferTexture3D, layers attach via glFramebufferTextureLayer.
g_GLESFuncs.glFramebufferTextureLayer(glFBOTarget, glBackendAttachment,
backendTextureObject->GetBackendTextureId(),
static_cast<GLint>(attachmentObject.GetTextureLevel()),
static_cast<GLint>(attachmentObject.GetTextureLayer()));
} else { } else {
auto glTextureTarget = TextureImpl::ConvertTextureUploadTargetToBackendGLEnum( auto glTextureTarget =
attachmentObject.GetTextureUploadTarget()); MG_Util::ConvertTextureUploadTargetToGLEnum(attachmentObject.GetTextureUploadTarget());
if (glTextureTarget == GL_UNKNOWN_MGL) { if (glTextureTarget == GL_UNKNOWN_MGL) {
glTextureTarget = TextureImpl::ConvertTextureTargetToBackendGLEnum(textureObject->GetTarget()); glTextureTarget = MG_Util::ConvertTextureTargetToGLEnum(textureObject->GetTarget());
} }
backendTextureObject->Bind(glTextureTarget); backendTextureObject->Bind(glTextureTarget);
g_GLESFuncs.glFramebufferTexture2D(glFBOTarget, glBackendAttachment, glTextureTarget, g_GLESFuncs.glFramebufferTexture2D(glFBOTarget, glBackendAttachment, glTextureTarget,
@@ -2488,14 +2462,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
drawBufferClean = true; drawBufferClean = true;
} }
// glDrawBuffers writes the state of the FBO bound to GL_DRAW_FRAMEBUFFER. if (!drawBufferClean) {
// When this object is only bound as the READ target the call would land on
// whatever framebuffer is draw-bound AND falsely stamp this object's memo,
// so the later draw-target sync skips as "clean" while the real state is
// stale (Minecraft 26.x OIT: the scratch clear-FBO kept draw buffers NONE
// from its blit-destination configuration, silently dropping every
// offscreen color clear).
if (!drawBufferClean && asTarget == FramebufferTarget::Draw) {
memcpy(m_frontendDrawBuffers, stateDrawBuffers.data(), memcpy(m_frontendDrawBuffers, stateDrawBuffers.data(),
FramebufferObject::MAX_DRAW_BUFFERS * sizeof(FramebufferAttachmentType)); FramebufferObject::MAX_DRAW_BUFFERS * sizeof(FramebufferAttachmentType));
std::fill(m_backendDrawBuffers, m_backendDrawBuffers + FramebufferObject::MAX_DRAW_BUFFERS, GL_NONE); std::fill(m_backendDrawBuffers, m_backendDrawBuffers + FramebufferObject::MAX_DRAW_BUFFERS, GL_NONE);
@@ -2520,8 +2487,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
nEffectiveBuffers = i + 1; nEffectiveBuffers = i + 1;
} }
g_GLESFuncs.glDrawBuffers(nEffectiveBuffers, m_backendDrawBuffers); g_GLESFuncs.glDrawBuffers(nEffectiveBuffers, m_backendDrawBuffers);
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]);
} }
if (asTarget == FramebufferTarget::Draw) { if (asTarget == FramebufferTarget::Draw) {
@@ -2544,10 +2509,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
PrgramImpl::g_unormFallbackClampOutputMask = unormClampOutputMask; PrgramImpl::g_unormFallbackClampOutputMask = unormClampOutputMask;
} }
// 2. Remap read buffer. glReadBuffer writes the READ-bound FBO's state, so // 2. Remap read buffer
// only apply (and stamp the memo) when this object is bound as READ.
auto frontendReadBuf = stateFBOObject->GetReadBuffer(); auto frontendReadBuf = stateFBOObject->GetReadBuffer();
if (frontendReadBuf != m_frontendReadBuffer && asTarget == FramebufferTarget::Read) { if (frontendReadBuf != m_frontendReadBuffer) {
m_frontendReadBuffer = frontendReadBuf; m_frontendReadBuffer = frontendReadBuf;
GLenum glBackendReadBuffer = GetBackendAttachmentType(frontendReadBuf); GLenum glBackendReadBuffer = GetBackendAttachmentType(frontendReadBuf);
@@ -2654,12 +2618,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
StateBackendObjectRegistry<MG_State::GLState::FramebufferObject, BackendFramebufferObject> StateBackendObjectRegistry<MG_State::GLState::FramebufferObject, BackendFramebufferObject>
g_backendFramebufferObjects; g_backendFramebufferObjects;
Array<Uint16, SizeT(FramebufferTarget::FramebufferTargetCount)> g_fboBindVersions = {0}; Array<Uint16, SizeT(FramebufferTarget::FramebufferTargetCount)> g_fboBindVersions = {0};
// 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
// must re-sync it even when the binding-slot version has not moved.
Array<Uint16, SizeT(FramebufferTarget::FramebufferTargetCount)> g_fboSyncedObjectVersions = {0};
Array<MG_State::GLState::FramebufferObject*, SizeT(FramebufferTarget::FramebufferTargetCount)>
g_fboSyncedObjects = {};
} // namespace FramebufferImpl } // namespace FramebufferImpl
namespace PrgramImpl { namespace PrgramImpl {
@@ -2770,20 +2728,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
effectiveSpirv = &loweredSpirv; effectiveSpirv = &loweredSpirv;
} }
// ESSL stage-matches uniform blocks by member precision, but SPIRV-Cross prints
// a RelaxedPrecision member as explicit "mediump" in the vertex stage and as
// UNQUALIFIED (mediump-by-default) in the fragment stage; after
// ForceSupporterOutput swaps the fragment header to highp, that member reads
// back as highp and the ES driver refuses to link ("definitions of uniform
// block ... do not match"). Strip the hint from block structs so both stages
// declare the member highp; nothing else about emission changes.
Vector<unsigned int> uboPrecisionSpirv;
if (MG_Util::ShaderTranspiler::ShaderCompiler::StripUboMemberRelaxedPrecisionForEssl(
*effectiveSpirv, uboPrecisionSpirv) &&
!uboPrecisionSpirv.empty()) {
effectiveSpirv = &uboPrecisionSpirv;
}
MG_Util::ShaderTranspiler::SpvcSession spvcSession(*effectiveSpirv, MG_Util::ShaderTranspiler::SpvcSession spvcSession(*effectiveSpirv,
MG_Util::ShaderTranspiler::SessionUsageBit::Transpile); MG_Util::ShaderTranspiler::SessionUsageBit::Transpile);
@@ -2948,9 +2892,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
} }
m_uniformBlockBackendIndices[static_cast<SizeT>(i)] = static_cast<Int>(backendBlkIdx); m_uniformBlockBackendIndices[static_cast<SizeT>(i)] = static_cast<Int>(backendBlkIdx);
g_GLESFuncs.glUniformBlockBinding(m_backendProgramId, backendBlkIdx, lastUBOBinding); g_GLESFuncs.glUniformBlockBinding(m_backendProgramId, backendBlkIdx, lastUBOBinding);
MGLOG_D("CACHE prog=%u beProg=%u blk[%d]='%s' beIdx=%u -> bePoint=%u",
stateProgramObject->GetExternalIndex(), m_backendProgramId, i, name.c_str(), backendBlkIdx,
lastUBOBinding);
} }
m_samplerUniformBindings.clear(); m_samplerUniformBindings.clear();
+4 -49
View File
@@ -15,7 +15,6 @@
#include "MG_State/GLState/TextureState/TextureEnum.h" #include "MG_State/GLState/TextureState/TextureEnum.h"
#include <MG_State/GLState/TextureState/TextureObject.h> #include <MG_State/GLState/TextureState/TextureObject.h>
#include <MG_State/GLState/Core.h> #include <MG_State/GLState/Core.h>
#include <MG_Util/Converters/MGToGL/TextureEnumConverter.h>
namespace MobileGL::MG_Backend::DirectGLES { namespace MobileGL::MG_Backend::DirectGLES {
String EmulateBaseInstanceInVertexShader(String source, GLenum shaderType); String EmulateBaseInstanceInVertexShader(String source, GLenum shaderType);
@@ -255,48 +254,10 @@ namespace MobileGL::MG_Backend::DirectGLES {
namespace TextureImpl { namespace TextureImpl {
inline Bool IsSupportedTextureTarget(TextureTarget target) { inline Bool IsSupportedTextureTarget(TextureTarget target) {
// Rectangle textures need non-normalized sampling ES cannot express; everything else is if (target == TextureTarget::Texture1D || target == TextureTarget::TextureRectangle ||
// either native or emulated (1D -> 2D with height 1, 1D array -> 2D array, see target == TextureTarget::Texture1DArray || target == TextureTarget::Texture2DArray)
// MapToBackendTextureTarget). SPIRV-Cross already emits the matching ESSL samplers and return false;
// coordinate padding for 1D/1D-array shaders. return true;
return target != TextureTarget::TextureRectangle;
}
// ES has no 1D targets: 1D textures are stored as 2D (height 1) and 1D arrays as 2D arrays
// (height 1, layers in depth). Must match SPIRV-Cross's ES 1D-as-2D shader emulation.
inline TextureTarget MapToBackendTextureTarget(TextureTarget target) {
switch (target) {
case TextureTarget::Texture1D:
return TextureTarget::Texture2D;
case TextureTarget::Texture1DArray:
return TextureTarget::Texture2DArray;
default:
return target;
}
}
inline GLenum ConvertTextureTargetToBackendGLEnum(TextureTarget target) {
return MG_Util::ConvertTextureTargetToGLEnum(MapToBackendTextureTarget(target));
}
inline GLenum ConvertTextureUploadTargetToBackendGLEnum(TextureUploadTarget uploadTarget) {
switch (uploadTarget) {
case TextureUploadTarget::Texture1D:
return GL_TEXTURE_2D;
case TextureUploadTarget::Texture1DArray:
return GL_TEXTURE_2D_ARRAY;
default:
return MG_Util::ConvertTextureUploadTargetToGLEnum(uploadTarget);
}
}
// 1D arrays store layers in the state-side height; the ES 2D-array image keeps height 1 and
// moves the layer count into depth.
inline IntVec3 GetBackendUploadSize(TextureTarget stateTarget, const IntVec3& texelSize) {
if (stateTarget == TextureTarget::Texture1DArray) {
return {texelSize.x(), 1, texelSize.y()};
}
return texelSize;
} }
inline Bool IsMultisampleTextureTarget(TextureTarget target) { inline Bool IsMultisampleTextureTarget(TextureTarget target) {
@@ -408,12 +369,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
extern StateBackendObjectRegistry<MG_State::GLState::FramebufferObject, BackendFramebufferObject> extern StateBackendObjectRegistry<MG_State::GLState::FramebufferObject, BackendFramebufferObject>
g_backendFramebufferObjects; g_backendFramebufferObjects;
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)
// per target: re-attaching textures or changing draw buffers on an already-bound FBO
// must re-sync it even when the binding-slot version has not moved.
extern Array<Uint16, SizeT(FramebufferTarget::FramebufferTargetCount)> g_fboSyncedObjectVersions;
extern Array<MG_State::GLState::FramebufferObject*, SizeT(FramebufferTarget::FramebufferTargetCount)>
g_fboSyncedObjects;
} // namespace FramebufferImpl } // namespace FramebufferImpl
// Image uniforms take their unit from the layout(binding=N) qualifier baked into // Image uniforms take their unit from the layout(binding=N) qualifier baked into
-317
View File
@@ -18,10 +18,6 @@
#include <MG_Util/Converters/GLToStr/GLEnumConverter.h> #include <MG_Util/Converters/GLToStr/GLEnumConverter.h>
#include <MG_Util/Converters/MGToGL/TextureEnumConverter.h> #include <MG_Util/Converters/MGToGL/TextureEnumConverter.h>
#include <MG_Util/Converters/MGToGL/FramebufferEnumConverter.h> #include <MG_Util/Converters/MGToGL/FramebufferEnumConverter.h>
#include <MG_Util/Math/HalfFloat.h>
#include <MG_Util/Math/SmallFloat.h>
#include <cmath>
namespace MobileGL::MG_Backend::DirectGLES { namespace MobileGL::MG_Backend::DirectGLES {
namespace { namespace {
@@ -452,317 +448,4 @@ namespace MobileGL::MG_Backend::DirectGLES {
} }
} }
} // namespace Utils } // namespace Utils
// ---- Client-format readback conversion helpers -------------------------------------------------
// ReadPixels/GetTexImage read a guaranteed wide RGBA(_INTEGER) layout from the ES driver and repack
// it on the CPU into the client's (format, type) layout. Everything here is pure byte shuffling so
// unit tests can assert the exact packed words; field positions follow GL 3.3 table 3.6 and mirror
// the GL CTS packed_pixels oracle (glcPackedPixelsTests.cpp pack_UNSIGNED_* helpers).
namespace ReadbackImpl {
using MG_Util::DecodeHalfBitsToFloat;
using MG_Util::EncodeFloatToHalfBits;
Bool GetReadbackChannelMapping(GLenum format, ReadbackChannelMapping& outMapping) {
switch (format) {
case GL_RED: outMapping = {{0, 0, 0, 0}, 1, false}; return true;
case GL_RED_INTEGER: outMapping = {{0, 0, 0, 0}, 1, true}; return true;
// Desktop-GL single-channel client formats (GL CTS packed_pixels rgba8_format_green/blue):
// the destination holds one component sourced from the named channel of the wide RGBA read.
// GL_ALPHA is mapped here from the raw enum because the state layer folds it into Red for the
// legacy alpha-texture upload hack.
case GL_GREEN: outMapping = {{1, 0, 0, 0}, 1, false}; return true;
case GL_GREEN_INTEGER: outMapping = {{1, 0, 0, 0}, 1, true}; return true;
case GL_BLUE: outMapping = {{2, 0, 0, 0}, 1, false}; return true;
case GL_BLUE_INTEGER: outMapping = {{2, 0, 0, 0}, 1, true}; return true;
case GL_ALPHA: outMapping = {{3, 0, 0, 0}, 1, false}; return true;
case GL_ALPHA_INTEGER: outMapping = {{3, 0, 0, 0}, 1, true}; return true;
case GL_RG: outMapping = {{0, 1, 0, 0}, 2, false}; return true;
case GL_RG_INTEGER: outMapping = {{0, 1, 0, 0}, 2, true}; return true;
case GL_RGB: outMapping = {{0, 1, 2, 0}, 3, false}; return true;
case GL_RGB_INTEGER: outMapping = {{0, 1, 2, 0}, 3, true}; return true;
case GL_BGR: outMapping = {{2, 1, 0, 0}, 3, false}; return true;
case GL_BGR_INTEGER: outMapping = {{2, 1, 0, 0}, 3, true}; return true;
case GL_RGBA: outMapping = {{0, 1, 2, 3}, 4, false}; return true;
case GL_RGBA_INTEGER: outMapping = {{0, 1, 2, 3}, 4, true}; return true;
case GL_BGRA: outMapping = {{2, 1, 0, 3}, 4, false}; return true;
case GL_BGRA_INTEGER: outMapping = {{2, 1, 0, 3}, 4, true}; return true;
default:
return false;
}
}
Bool GetPackedReadbackLayout(GLenum type, PackedReadbackLayout& out) {
switch (type) {
// Non-REV types pack the first format component starting at the most significant bit,
// *_REV types starting at the least significant bit (GL CTS pack_UNSIGNED_SHORT_5_6_5:
// R bits 15-11; pack_UNSIGNED_SHORT_1_5_5_5_REV: R bits 4-0, A bit 15).
case GL_UNSIGNED_BYTE_3_3_2: out = {3, {3, 3, 2, 0}, {5, 2, 0, 0}, 1, false}; return true;
case GL_UNSIGNED_BYTE_2_3_3_REV: out = {3, {3, 3, 2, 0}, {0, 3, 6, 0}, 1, false}; return true;
case GL_UNSIGNED_SHORT_5_6_5: out = {3, {5, 6, 5, 0}, {11, 5, 0, 0}, 2, false}; return true;
case GL_UNSIGNED_SHORT_5_6_5_REV: out = {3, {5, 6, 5, 0}, {0, 5, 11, 0}, 2, false}; return true;
case GL_UNSIGNED_SHORT_4_4_4_4: out = {4, {4, 4, 4, 4}, {12, 8, 4, 0}, 2, false}; return true;
case GL_UNSIGNED_SHORT_4_4_4_4_REV: out = {4, {4, 4, 4, 4}, {0, 4, 8, 12}, 2, false}; return true;
case GL_UNSIGNED_SHORT_5_5_5_1: out = {4, {5, 5, 5, 1}, {11, 6, 1, 0}, 2, false}; return true;
case GL_UNSIGNED_SHORT_1_5_5_5_REV: out = {4, {5, 5, 5, 1}, {0, 5, 10, 15}, 2, false}; return true;
case GL_UNSIGNED_INT_8_8_8_8: out = {4, {8, 8, 8, 8}, {24, 16, 8, 0}, 4, false}; return true;
case GL_UNSIGNED_INT_8_8_8_8_REV: out = {4, {8, 8, 8, 8}, {0, 8, 16, 24}, 4, false}; return true;
case GL_UNSIGNED_INT_10_10_10_2: out = {4, {10, 10, 10, 2}, {22, 12, 2, 0}, 4, false}; return true;
case GL_UNSIGNED_INT_2_10_10_10_REV: out = {4, {10, 10, 10, 2}, {0, 10, 20, 30}, 4, false}; return true;
// Packed-float RGB types: fields hold unsigned small floats; 5_9_9_9_REV's shared 5-bit
// exponent (bits 31-27) is emitted by EncodeSharedExponentRGB9E5, not a component field.
case GL_UNSIGNED_INT_10F_11F_11F_REV: out = {3, {11, 11, 10, 0}, {0, 11, 22, 0}, 4, true}; return true;
case GL_UNSIGNED_INT_5_9_9_9_REV: out = {3, {9, 9, 9, 0}, {0, 9, 18, 0}, 4, true}; return true;
default:
return false;
}
}
SizeT GetReadbackComponentSize(GLenum type) {
PackedReadbackLayout packedLayout{};
if (GetPackedReadbackLayout(type, packedLayout)) {
return packedLayout.byteSize;
}
switch (type) {
case GL_UNSIGNED_BYTE:
case GL_BYTE:
return 1;
case GL_UNSIGNED_SHORT:
case GL_SHORT:
case GL_HALF_FLOAT:
return 2;
case GL_UNSIGNED_INT:
case GL_INT:
case GL_FLOAT:
return 4;
default:
return 0;
}
}
SizeT GetReadbackDstPixelSize(const ReadbackChannelMapping& mapping, GLenum type) {
PackedReadbackLayout packedLayout{};
if (GetPackedReadbackLayout(type, packedLayout)) {
if (packedLayout.fieldCount != mapping.channelCount) {
return 0; // 3-field packed types pair with 3-component formats only, 4 with 4
}
if (mapping.isInteger && packedLayout.isFloatPacked) {
return 0; // packed-float RGB types never pair with integer formats
}
return packedLayout.byteSize;
}
if (mapping.isInteger && (type == GL_FLOAT || type == GL_HALF_FLOAT)) {
return 0;
}
const SizeT componentSize = GetReadbackComponentSize(type);
return componentSize == 0 ? 0 : static_cast<SizeT>(mapping.channelCount) * componentSize;
}
namespace {
void WritePackedReadbackWord(Uint8* dst, Uint32 word, SizeT byteSize) {
switch (byteSize) {
case 1: {
const auto out = static_cast<Uint8>(word);
Memcpy(dst, &out, sizeof(out));
break;
}
case 2: {
const auto out = static_cast<Uint16>(word);
Memcpy(dst, &out, sizeof(out));
break;
}
default:
Memcpy(dst, &word, sizeof(word));
break;
}
}
} // namespace
// Shared encoders live in MG_Util/Math/SmallFloat.h so the upload conversion
// (PixelStoreProcessor) uses byte-identical packing; kept exported here for unit tests.
Uint32 EncodeFloatToUnsignedF11(Float value) { return MG_Util::EncodeFloatToUnsignedF11(value); }
Uint32 EncodeFloatToUnsignedF10(Float value) { return MG_Util::EncodeFloatToUnsignedF10(value); }
Uint32 EncodeSharedExponentRGB9E5(const Float rgb[3]) { return MG_Util::EncodeSharedExponentRGB9E5(rgb); }
void ConvertWideReadbackRow(const Uint8* src, Uint8* dst, SizeT width, GLenum wideType,
const ReadbackChannelMapping& mapping, GLenum type) {
PackedReadbackLayout packedLayout{};
const Bool isPacked = GetPackedReadbackLayout(type, packedLayout);
const SizeT dstComponentSize = GetReadbackComponentSize(type);
const SizeT dstPixelBytes = GetReadbackDstPixelSize(mapping, type);
const SizeT srcPixelBytes = 4 * GetReadbackComponentSize(wideType);
for (SizeT col = 0; col < width; ++col) {
const Uint8* srcPixel = src + col * srcPixelBytes;
Uint8* dstPixel = dst + col * dstPixelBytes;
if (mapping.isInteger) {
Int64 srcValues[4];
for (Int c = 0; c < 4; ++c) {
srcValues[c] = wideType == GL_INT
? static_cast<Int64>(reinterpret_cast<const Int32*>(srcPixel)[c])
: static_cast<Int64>(reinterpret_cast<const Uint32*>(srcPixel)[c]);
}
if (isPacked) {
// Integer sources clamp each component to the unsigned range of its field
// (GL 3.3 section 4.3.1 final conversion).
Uint32 word = 0;
for (Int ch = 0; ch < packedLayout.fieldCount; ++ch) {
const Int64 fieldMax = (Int64{1} << packedLayout.width[ch]) - 1;
const auto v = static_cast<Uint32>(
std::clamp<Int64>(srcValues[mapping.sourceChannel[ch]], 0, fieldMax));
word |= v << packedLayout.shift[ch];
}
WritePackedReadbackWord(dstPixel, word, packedLayout.byteSize);
} else {
for (Int ch = 0; ch < mapping.channelCount; ++ch) {
const Int64 v = srcValues[mapping.sourceChannel[ch]];
Uint8* dstComponent = dstPixel + static_cast<SizeT>(ch) * dstComponentSize;
switch (type) {
case GL_UNSIGNED_BYTE:
*dstComponent = static_cast<Uint8>(std::clamp<Int64>(v, 0, 255));
break;
case GL_BYTE: {
const auto out = static_cast<Int8>(std::clamp<Int64>(v, -128, 127));
Memcpy(dstComponent, &out, sizeof(out));
break;
}
case GL_UNSIGNED_SHORT: {
const auto out = static_cast<Uint16>(std::clamp<Int64>(v, 0, 65535));
Memcpy(dstComponent, &out, sizeof(out));
break;
}
case GL_SHORT: {
const auto out = static_cast<Int16>(std::clamp<Int64>(v, -32768, 32767));
Memcpy(dstComponent, &out, sizeof(out));
break;
}
case GL_UNSIGNED_INT: {
const auto out = static_cast<Uint32>(std::clamp<Int64>(v, 0, 4294967295LL));
Memcpy(dstComponent, &out, sizeof(out));
break;
}
case GL_INT: {
const auto out =
static_cast<Int32>(std::clamp<Int64>(v, -2147483648LL, 2147483647LL));
Memcpy(dstComponent, &out, sizeof(out));
break;
}
default:
break;
}
}
}
} else {
Float srcValues[4];
switch (wideType) {
case GL_UNSIGNED_BYTE:
for (Int c = 0; c < 4; ++c) {
srcValues[c] = static_cast<Float>(srcPixel[c]) / 255.0f;
}
break;
case GL_BYTE:
for (Int c = 0; c < 4; ++c) {
srcValues[c] = std::max(
static_cast<Float>(reinterpret_cast<const Int8*>(srcPixel)[c]) / 127.0f, -1.0f);
}
break;
case GL_UNSIGNED_SHORT:
for (Int c = 0; c < 4; ++c) {
srcValues[c] =
static_cast<Float>(reinterpret_cast<const Uint16*>(srcPixel)[c]) / 65535.0f;
}
break;
case GL_SHORT:
for (Int c = 0; c < 4; ++c) {
srcValues[c] = std::max(
static_cast<Float>(reinterpret_cast<const Int16*>(srcPixel)[c]) / 32767.0f, -1.0f);
}
break;
case GL_HALF_FLOAT:
for (Int c = 0; c < 4; ++c) {
srcValues[c] = DecodeHalfBitsToFloat(reinterpret_cast<const Uint16*>(srcPixel)[c]);
}
break;
default: // GL_FLOAT
for (Int c = 0; c < 4; ++c) {
srcValues[c] = reinterpret_cast<const Float*>(srcPixel)[c];
}
break;
}
if (isPacked) {
Uint32 word = 0;
if (packedLayout.isFloatPacked) {
const Float fields[3] = {srcValues[mapping.sourceChannel[0]],
srcValues[mapping.sourceChannel[1]],
srcValues[mapping.sourceChannel[2]]};
word = type == GL_UNSIGNED_INT_5_9_9_9_REV
? EncodeSharedExponentRGB9E5(fields)
: (EncodeFloatToUnsignedF11(fields[0]) << packedLayout.shift[0]) |
(EncodeFloatToUnsignedF11(fields[1]) << packedLayout.shift[1]) |
(EncodeFloatToUnsignedF10(fields[2]) << packedLayout.shift[2]);
} else {
// Normalized encode: round(clamp(v, 0, 1) * (2^bits - 1)) into each field.
for (Int ch = 0; ch < packedLayout.fieldCount; ++ch) {
const auto fieldMax = static_cast<Float>((1u << packedLayout.width[ch]) - 1u);
const auto v = static_cast<Uint32>(std::llround(
std::clamp(srcValues[mapping.sourceChannel[ch]], 0.0f, 1.0f) * fieldMax));
word |= v << packedLayout.shift[ch];
}
}
WritePackedReadbackWord(dstPixel, word, packedLayout.byteSize);
} else {
for (Int ch = 0; ch < mapping.channelCount; ++ch) {
const Float v = srcValues[mapping.sourceChannel[ch]];
Uint8* dstComponent = dstPixel + static_cast<SizeT>(ch) * dstComponentSize;
switch (type) {
case GL_UNSIGNED_BYTE:
*dstComponent =
static_cast<Uint8>(std::llround(std::clamp(v, 0.0f, 1.0f) * 255.0));
break;
case GL_BYTE: {
const auto out =
static_cast<Int8>(std::llround(std::clamp(v, -1.0f, 1.0f) * 127.0));
Memcpy(dstComponent, &out, sizeof(out));
break;
}
case GL_UNSIGNED_SHORT: {
const auto out =
static_cast<Uint16>(std::llround(std::clamp(v, 0.0f, 1.0f) * 65535.0));
Memcpy(dstComponent, &out, sizeof(out));
break;
}
case GL_SHORT: {
const auto out =
static_cast<Int16>(std::llround(std::clamp(v, -1.0f, 1.0f) * 32767.0));
Memcpy(dstComponent, &out, sizeof(out));
break;
}
case GL_UNSIGNED_INT: {
const auto out = static_cast<Uint32>(
std::llround(static_cast<Double>(std::clamp(v, 0.0f, 1.0f)) * 4294967295.0));
Memcpy(dstComponent, &out, sizeof(out));
break;
}
case GL_INT: {
const auto out = static_cast<Int32>(
std::llround(static_cast<Double>(std::clamp(v, -1.0f, 1.0f)) * 2147483647.0));
Memcpy(dstComponent, &out, sizeof(out));
break;
}
case GL_FLOAT:
Memcpy(dstComponent, &v, sizeof(v));
break;
case GL_HALF_FLOAT: {
const Uint16 out = EncodeFloatToHalfBits(v);
Memcpy(dstComponent, &out, sizeof(out));
break;
}
default:
break;
}
}
}
}
}
}
} // namespace ReadbackImpl
} // namespace MobileGL::MG_Backend::DirectGLES } // namespace MobileGL::MG_Backend::DirectGLES
-45
View File
@@ -45,51 +45,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
namespace FramebufferImpl {} // namespace FramebufferImpl namespace FramebufferImpl {} // namespace FramebufferImpl
// Pure CPU helpers of the client-format readback conversion (ReadPixels/GetTexImage repack a wide
// RGBA(_INTEGER) read into the caller's (format, type) layout). Kept context-free so unit tests can
// exercise the exact packing the GL CTS packed_pixels oracle compares against.
namespace ReadbackImpl {
struct ReadbackChannelMapping {
Int sourceChannel[4]; // RGBA source channel feeding each destination component
Int channelCount; // destination component count
Bool isInteger;
};
Bool GetReadbackChannelMapping(GLenum format, ReadbackChannelMapping& outMapping);
// Byte size of one destination component of `type`; packed types report the packed word size.
// 0 = type not supported by the conversion path.
SizeT GetReadbackComponentSize(GLenum type);
// Bit-field layout of a GL packed pixel type. width/shift are indexed in the client format's
// component order (matching ReadbackChannelMapping); shift is the LSB position of the field in
// the packed word: non-REV types pack the first component from the MSB, *_REV types from the
// LSB (GL 3.3 table 3.6; field positions mirror the GL CTS glcPackedPixelsTests pack_* oracle).
struct PackedReadbackLayout {
Int fieldCount; // format components stored in the packed word
Int width[4]; // bit width of each component's field
Int shift[4]; // LSB bit position of each component's field
SizeT byteSize; // packed word size in bytes (1, 2 or 4)
Bool isFloatPacked; // 10F_11F_11F_REV / 5_9_9_9_REV: fields hold unsigned small floats
};
Bool GetPackedReadbackLayout(GLenum type, PackedReadbackLayout& out);
// Unsigned small-float encoders (EXT_packed_float / EXT_texture_shared_exponent semantics).
Uint32 EncodeFloatToUnsignedF11(Float value);
Uint32 EncodeFloatToUnsignedF10(Float value);
Uint32 EncodeSharedExponentRGB9E5(const Float rgb[3]);
// Destination bytes per pixel for a (format mapping, type) readback pair; 0 when the pair is
// not convertible (unknown type, packed field count != format component count, floating-point
// or packed-float type with an integer format).
SizeT GetReadbackDstPixelSize(const ReadbackChannelMapping& mapping, GLenum type);
// Repacks one row of wide RGBA(_INTEGER) texels (4 components of wideType each) into the
// client's (format, type) layout. src holds width * 4 * GetReadbackComponentSize(wideType)
// bytes, dst receives width * GetReadbackDstPixelSize(mapping, type) bytes.
void ConvertWideReadbackRow(const Uint8* src, Uint8* dst, SizeT width, GLenum wideType,
const ReadbackChannelMapping& mapping, GLenum type);
} // namespace ReadbackImpl
namespace PrgramImpl { namespace PrgramImpl {
String ProcessOutColorLocations(const String& glslCode); String ProcessOutColorLocations(const String& glslCode);
String ForceSupporterOutput(const String& glslCode); String ForceSupporterOutput(const String& glslCode);
@@ -488,15 +488,14 @@ namespace MobileGL::MG_Backend::DirectVulkan {
.TargetGLSLVersion = {4, 6, 0}, .TargetGLSLVersion = {4, 6, 0},
// Baseline advertisement (no shader subgroup, no timer queries); a // Baseline advertisement (no shader subgroup, no timer queries); a
// live backend reconciles its copy in UpdateAdvertisedExtensions. // live backend reconciles its copy in UpdateAdvertisedExtensions.
.Extensions = BuildAdvertisedExtensions(false, false, false), .Extensions = BuildAdvertisedExtensions(false, false),
.IsCompatibilityProfile = 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) {
Vector<GLExtension> extensions = {V_OpenGL30, V_OpenGL31, V_OpenGL32, Vector<GLExtension> extensions = {V_OpenGL30, V_OpenGL31, V_OpenGL32,
V_OpenGL33, E_GL_ARB_draw_buffers_blend, E_GL_ARB_compute_shader, V_OpenGL33, E_GL_ARB_draw_buffers_blend, E_GL_ARB_compute_shader,
E_GL_ARB_shader_storage_buffer_object, E_GL_ARB_shader_image_load_store, E_GL_ARB_shader_storage_buffer_object, E_GL_ARB_shader_image_load_store,
@@ -504,7 +503,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
E_GL_ARB_multi_draw_indirect, E_GL_ARB_indirect_parameters, E_GL_ARB_multi_draw_indirect, E_GL_ARB_indirect_parameters,
E_GL_EXT_framebuffer_object, E_GL_ARB_depth_texture, E_GL_ARB_buffer_storage, E_GL_EXT_framebuffer_object, E_GL_ARB_depth_texture, E_GL_ARB_buffer_storage,
E_GL_ARB_texture_storage, E_GL_ARB_texture_storage_multisample, E_GL_ARB_texture_storage, E_GL_ARB_texture_storage_multisample,
E_GL_ARB_clear_texture, E_GL_ARB_direct_state_access, E_GL_ARB_direct_state_access,
E_GL_ARB_shader_draw_parameters, E_GL_ARB_gpu_shader_int64, E_GL_KHR_debug, E_GL_ARB_shader_draw_parameters, E_GL_ARB_gpu_shader_int64, E_GL_KHR_debug,
E_GL_ARB_gpu_shader5, E_GL_ARB_multi_bind, E_GL_ARB_shading_language_420pack, E_GL_ARB_gpu_shader5, E_GL_ARB_multi_bind, E_GL_ARB_shading_language_420pack,
E_GL_ARB_vertex_attrib_binding, E_GL_ARB_shader_image_size}; E_GL_ARB_vertex_attrib_binding, E_GL_ARB_shader_image_size};
@@ -517,13 +516,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
if (timerQueriesSupported && !MG_Config::Features.DisableTimerQuery) { if (timerQueriesSupported && !MG_Config::Features.DisableTimerQuery) {
extensions.push_back(E_GL_ARB_timer_query); extensions.push_back(E_GL_ARB_timer_query);
} }
// Only advertised when the samplerAnisotropy device feature was granted: without it the
// sampler state is accepted but never applied, and an app trusting the string (LWJGL builds
// GLCapabilities from it) would think it enabled anisotropic filtering.
if (anisotropicFilteringSupported) {
extensions.push_back(E_GL_EXT_texture_filter_anisotropic);
extensions.push_back(E_GL_ARB_texture_filter_anisotropic);
}
return extensions; return extensions;
} }
@@ -638,8 +630,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// run without a renderer; no timer query is advertised then. Rebuilding // run without a renderer; no timer query is advertised then. Rebuilding
// the whole list keeps re-runs idempotent. // the whole list keeps re-runs idempotent.
m_rendererInfo.RendererGLInfo.Extensions = BuildAdvertisedExtensions( m_rendererInfo.RendererGLInfo.Extensions = BuildAdvertisedExtensions(
m_vulkanCaps.SupportsShaderSubgroup, pVulkanRenderer && pVulkanRenderer->IsTimerQuerySupported(), m_vulkanCaps.SupportsShaderSubgroup, pVulkanRenderer && pVulkanRenderer->IsTimerQuerySupported());
pVulkanRenderer && pVulkanRenderer->IsSamplerAnisotropySupported());
} }
void BackendObject_DirectVulkan::UpdateDynamicBackendParameters() { void BackendObject_DirectVulkan::UpdateDynamicBackendParameters() {
@@ -689,11 +680,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
m_dynamicParameters.UniformBufferOffsetAlignment = m_vulkanCaps.UniformBufferOffsetAlignment; m_dynamicParameters.UniformBufferOffsetAlignment = m_vulkanCaps.UniformBufferOffsetAlignment;
m_dynamicParameters.AliasedLineWidthRangeMin = m_vulkanCaps.AliasedLineWidthRangeMin; m_dynamicParameters.AliasedLineWidthRangeMin = m_vulkanCaps.AliasedLineWidthRangeMin;
m_dynamicParameters.AliasedLineWidthRangeMax = m_vulkanCaps.AliasedLineWidthRangeMax; m_dynamicParameters.AliasedLineWidthRangeMax = m_vulkanCaps.AliasedLineWidthRangeMax;
// Without the samplerAnisotropy feature the limit is unusable, so report 1.0 (no anisotropy)
// rather than a maximum the sampler manager will never apply.
m_dynamicParameters.MaxTextureMaxAnisotropy =
(pVulkanRenderer && pVulkanRenderer->IsSamplerAnisotropySupported()) ? m_vulkanCaps.MaxSamplerAnisotropy
: 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;
@@ -746,24 +732,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
m_dynamicParameters.MaxTextureBufferSize = m_vulkanCaps.MaxTextureBufferSize; m_dynamicParameters.MaxTextureBufferSize = m_vulkanCaps.MaxTextureBufferSize;
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 = m_dynamicParameters.MaxImageUnits = std::min(m_vulkanCaps.MaxImageUnits, maxSupportedTextureUnits);
std::max(std::min(m_vulkanCaps.MaxImageUnits, maxSupportedTextureUnits), 0); m_dynamicParameters.MaxCombinedImageUniforms = m_vulkanCaps.MaxCombinedImageUniforms;
m_dynamicParameters.MaxCombinedImageUniforms = std::max(m_vulkanCaps.MaxCombinedImageUniforms, 0); m_dynamicParameters.MaxComputeImageUniforms = m_vulkanCaps.MaxComputeImageUniforms;
const Int maxPerStageImageUniforms =
std::min(m_dynamicParameters.MaxImageUnits, m_dynamicParameters.MaxCombinedImageUniforms);
// Vulkan uses one descriptor limit for every stage, but non-compute stores/atomics are
// optional device features. VulkanRenderer enables each feature whenever the physical
// device reports it, so these are the exact limits the logical device can compile and run.
m_dynamicParameters.MaxVertexImageUniforms =
m_vulkanCaps.SupportsVertexPipelineStoresAndAtomics ? maxPerStageImageUniforms : 0;
m_dynamicParameters.MaxGeometryImageUniforms =
m_vulkanCaps.SupportsVertexPipelineStoresAndAtomics && m_vulkanCaps.SupportsGeometryShader
? maxPerStageImageUniforms
: 0;
m_dynamicParameters.MaxFragmentImageUniforms =
m_vulkanCaps.SupportsFragmentStoresAndAtomics ? maxPerStageImageUniforms : 0;
m_dynamicParameters.MaxComputeImageUniforms =
std::min(std::max(m_vulkanCaps.MaxComputeImageUniforms, 0), maxPerStageImageUniforms);
const Int maxSupportedDrawBuffers = const Int maxSupportedDrawBuffers =
static_cast<Int>(MG_State::GLState::FramebufferObject::MAX_DRAW_BUFFERS); 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);
@@ -73,8 +73,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// a device with the given raw capabilities. The MOBILEGL_DISABLE_SUBGROUP and // a device with the given raw capabilities. The MOBILEGL_DISABLE_SUBGROUP and
// MOBILEGL_DISABLE_TIMERQUERY escape hatches are applied inside, so callers pass // MOBILEGL_DISABLE_TIMERQUERY escape hatches are applied inside, so callers pass
// the detected device support (passing an already-gated value is harmless). // the detected device support (passing an already-gated value is harmless).
Vector<GLExtension> BuildAdvertisedExtensions(Bool shaderSubgroupSupported, Bool timerQueriesSupported, Vector<GLExtension> BuildAdvertisedExtensions(Bool shaderSubgroupSupported, Bool timerQueriesSupported);
Bool anisotropicFilteringSupported);
// Format: <GPU Name>, Vulkan <Vulkan Version>, Driver <Driver Version> — the exact // Format: <GPU Name>, Vulkan <Vulkan Version>, Driver <Driver Version> — the exact
// string an initialized backend returns from GetBackendAPIVersionString (and that // string an initialized backend returns from GetBackendAPIVersionString (and that
@@ -1197,25 +1197,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
return TextureTarget::Unknown; return TextureTarget::Unknown;
} }
} }
Bool IsFloatStorageImageUniformType(GLenum uniformType) {
switch (uniformType) {
case GL_IMAGE_1D:
case GL_IMAGE_2D:
case GL_IMAGE_3D:
case GL_IMAGE_2D_RECT:
case GL_IMAGE_CUBE:
case GL_IMAGE_BUFFER:
case GL_IMAGE_1D_ARRAY:
case GL_IMAGE_2D_ARRAY:
case GL_IMAGE_CUBE_MAP_ARRAY:
case GL_IMAGE_2D_MULTISAMPLE:
case GL_IMAGE_2D_MULTISAMPLE_ARRAY:
return true;
default:
return false;
}
}
} // namespace } // namespace
VkShaderStageFlagBits ProgramFactory::ToVkStage(ShaderStage stage) { VkShaderStageFlagBits ProgramFactory::ToVkStage(ShaderStage stage) {
@@ -1237,105 +1218,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
} }
} }
VkFormat ProgramFactory::ConvertSpirvImageFormatToVkFormat(SpvImageFormat format) {
switch (format) {
case SpvImageFormatUnknown: return VK_FORMAT_UNDEFINED;
case SpvImageFormatRgba32f: return VK_FORMAT_R32G32B32A32_SFLOAT;
case SpvImageFormatRgba16f: return VK_FORMAT_R16G16B16A16_SFLOAT;
case SpvImageFormatR32f: return VK_FORMAT_R32_SFLOAT;
case SpvImageFormatRgba8: return VK_FORMAT_R8G8B8A8_UNORM;
case SpvImageFormatRgba8Snorm: return VK_FORMAT_R8G8B8A8_SNORM;
case SpvImageFormatRg32f: return VK_FORMAT_R32G32_SFLOAT;
case SpvImageFormatRg16f: return VK_FORMAT_R16G16_SFLOAT;
case SpvImageFormatR11fG11fB10f: return VK_FORMAT_B10G11R11_UFLOAT_PACK32;
case SpvImageFormatR16f: return VK_FORMAT_R16_SFLOAT;
case SpvImageFormatRgba16: return VK_FORMAT_R16G16B16A16_UNORM;
case SpvImageFormatRgb10A2: return VK_FORMAT_A2R10G10B10_UNORM_PACK32;
case SpvImageFormatRg16: return VK_FORMAT_R16G16_UNORM;
case SpvImageFormatRg8: return VK_FORMAT_R8G8_UNORM;
case SpvImageFormatR16: return VK_FORMAT_R16_UNORM;
case SpvImageFormatR8: return VK_FORMAT_R8_UNORM;
case SpvImageFormatRgba16Snorm: return VK_FORMAT_R16G16B16A16_SNORM;
case SpvImageFormatRg16Snorm: return VK_FORMAT_R16G16_SNORM;
case SpvImageFormatRg8Snorm: return VK_FORMAT_R8G8_SNORM;
case SpvImageFormatR16Snorm: return VK_FORMAT_R16_SNORM;
case SpvImageFormatR8Snorm: return VK_FORMAT_R8_SNORM;
case SpvImageFormatRgba32i: return VK_FORMAT_R32G32B32A32_SINT;
case SpvImageFormatRgba16i: return VK_FORMAT_R16G16B16A16_SINT;
case SpvImageFormatRgba8i: return VK_FORMAT_R8G8B8A8_SINT;
case SpvImageFormatR32i: return VK_FORMAT_R32_SINT;
case SpvImageFormatRg32i: return VK_FORMAT_R32G32_SINT;
case SpvImageFormatRg16i: return VK_FORMAT_R16G16_SINT;
case SpvImageFormatRg8i: return VK_FORMAT_R8G8_SINT;
case SpvImageFormatR16i: return VK_FORMAT_R16_SINT;
case SpvImageFormatR8i: return VK_FORMAT_R8_SINT;
case SpvImageFormatRgba32ui: return VK_FORMAT_R32G32B32A32_UINT;
case SpvImageFormatRgba16ui: return VK_FORMAT_R16G16B16A16_UINT;
case SpvImageFormatRgba8ui: return VK_FORMAT_R8G8B8A8_UINT;
case SpvImageFormatR32ui: return VK_FORMAT_R32_UINT;
case SpvImageFormatRgb10a2ui: return VK_FORMAT_A2R10G10B10_UINT_PACK32;
case SpvImageFormatRg32ui: return VK_FORMAT_R32G32_UINT;
case SpvImageFormatRg16ui: return VK_FORMAT_R16G16_UINT;
case SpvImageFormatRg8ui: return VK_FORMAT_R8G8_UINT;
case SpvImageFormatR16ui: return VK_FORMAT_R16_UINT;
case SpvImageFormatR8ui: return VK_FORMAT_R8_UINT;
case SpvImageFormatR64ui: return VK_FORMAT_R64_UINT;
case SpvImageFormatR64i: return VK_FORMAT_R64_SINT;
case SpvImageFormatMax: return VK_FORMAT_UNDEFINED;
}
return VK_FORMAT_UNDEFINED;
}
SamplerNumericDomain ProgramFactory::UniformTypeToSamplerNumericDomain(GLenum glType) {
switch (glType) {
case GL_INT_SAMPLER_1D:
case GL_INT_SAMPLER_2D:
case GL_INT_SAMPLER_3D:
case GL_INT_SAMPLER_CUBE:
case GL_INT_SAMPLER_2D_RECT:
case GL_INT_SAMPLER_1D_ARRAY:
case GL_INT_SAMPLER_2D_ARRAY:
case GL_INT_SAMPLER_BUFFER:
case GL_INT_SAMPLER_2D_MULTISAMPLE:
case GL_INT_SAMPLER_2D_MULTISAMPLE_ARRAY:
case GL_INT_SAMPLER_CUBE_MAP_ARRAY:
return SamplerNumericDomain::SignedInteger;
case GL_UNSIGNED_INT_SAMPLER_1D:
case GL_UNSIGNED_INT_SAMPLER_2D:
case GL_UNSIGNED_INT_SAMPLER_3D:
case GL_UNSIGNED_INT_SAMPLER_CUBE:
case GL_UNSIGNED_INT_SAMPLER_2D_RECT:
case GL_UNSIGNED_INT_SAMPLER_1D_ARRAY:
case GL_UNSIGNED_INT_SAMPLER_2D_ARRAY:
case GL_UNSIGNED_INT_SAMPLER_BUFFER:
case GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE:
case GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE_ARRAY:
case GL_UNSIGNED_INT_SAMPLER_CUBE_MAP_ARRAY:
return SamplerNumericDomain::UnsignedInteger;
case GL_SAMPLER_1D:
case GL_SAMPLER_2D:
case GL_SAMPLER_3D:
case GL_SAMPLER_CUBE:
case GL_SAMPLER_2D_RECT:
case GL_SAMPLER_1D_ARRAY:
case GL_SAMPLER_2D_ARRAY:
case GL_SAMPLER_BUFFER:
case GL_SAMPLER_2D_MULTISAMPLE:
case GL_SAMPLER_2D_MULTISAMPLE_ARRAY:
case GL_SAMPLER_CUBE_MAP_ARRAY:
case GL_SAMPLER_1D_SHADOW:
case GL_SAMPLER_2D_SHADOW:
case GL_SAMPLER_CUBE_SHADOW:
case GL_SAMPLER_2D_RECT_SHADOW:
case GL_SAMPLER_1D_ARRAY_SHADOW:
case GL_SAMPLER_2D_ARRAY_SHADOW:
case GL_SAMPLER_CUBE_MAP_ARRAY_SHADOW:
return SamplerNumericDomain::Float;
default:
return SamplerNumericDomain::Unknown;
}
}
ProgramFactory::HashType ProgramFactory::ComputeHash(const MG_State::GLState::ProgramObject& program, ProgramFactory::HashType ProgramFactory::ComputeHash(const MG_State::GLState::ProgramObject& program,
CompileOptionFlags flags) const { CompileOptionFlags flags) const {
XXHASH_VERIFY(XXH64_reset(m_hashState, m_config.CacheVersion)); XXHASH_VERIFY(XXH64_reset(m_hashState, m_config.CacheVersion));
@@ -1583,9 +1465,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
entry.samplerNameByBinding.assign(m_maxBindings, String()); entry.samplerNameByBinding.assign(m_maxBindings, String());
entry.samplerUniformLocationByBinding.assign(m_maxBindings, -1); entry.samplerUniformLocationByBinding.assign(m_maxBindings, -1);
entry.samplerTextureTargetByBinding.assign(m_maxBindings, TextureTarget::Texture2D); entry.samplerTextureTargetByBinding.assign(m_maxBindings, TextureTarget::Texture2D);
entry.samplerNumericDomainByBinding.assign(m_maxBindings, SamplerNumericDomain::Unknown);
entry.storageImageFormatByBinding.assign(m_maxBindings, VK_FORMAT_UNDEFINED);
entry.storageImageUsesBindingFormatByBinding.assign(m_maxBindings, false);
entry.storageBlockNameByBinding.assign(m_maxBindings, String()); entry.storageBlockNameByBinding.assign(m_maxBindings, String());
entry.storageBlockIndexByBinding.assign(m_maxBindings, -1); entry.storageBlockIndexByBinding.assign(m_maxBindings, -1);
entry.globalUboBinding = -1; entry.globalUboBinding = -1;
@@ -1713,54 +1592,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
continue; continue;
} }
const GLenum uniformType = program.GetUniformType(static_cast<Uint>(location)); const TextureTarget target = UniformTypeToTextureTarget(program.GetUniformType(static_cast<Uint>(location)));
if (descriptorKind == DescriptorBindingKind::StorageImage) {
const VkFormat reflectedFormat =
ConvertSpirvImageFormatToVkFormat(sampler->image.image_format);
VkFormat& existingFormat = entry.storageImageFormatByBinding[binding];
MOBILEGL_ASSERT(existingFormat == VK_FORMAT_UNDEFINED ||
reflectedFormat == VK_FORMAT_UNDEFINED ||
existingFormat == reflectedFormat,
"ProgramFactory::ReflectLayout: storage image binding %u ('%s') has "
"conflicting reflected formats (%d vs %d)",
binding, uniformName.c_str(), static_cast<Int>(existingFormat),
static_cast<Int>(reflectedFormat));
if (existingFormat == VK_FORMAT_UNDEFINED) {
existingFormat = reflectedFormat;
}
if (m_unformattedFloatStorageImagesEnabled &&
existingFormat == VK_FORMAT_UNDEFINED &&
IsFloatStorageImageUniformType(uniformType)) {
entry.storageImageUsesBindingFormatByBinding[binding] = true;
} else if (reflectedFormat != VK_FORMAT_UNDEFINED) {
// A typed declaration in any stage wins for the entire binding. This is
// required when another stage reaches the same image through an atomic
// path and therefore could not be made formatless.
entry.storageImageUsesBindingFormatByBinding[binding] = false;
}
}
const TextureTarget target = UniformTypeToTextureTarget(uniformType);
MOBILEGL_ASSERT(target != TextureTarget::Unknown, MOBILEGL_ASSERT(target != TextureTarget::Unknown,
"ProgramFactory::ReflectLayout: failed to resolve texture target for '%s'", "ProgramFactory::ReflectLayout: failed to resolve texture target for '%s'",
uniformName.c_str()); uniformName.c_str());
if (descriptorKind == DescriptorBindingKind::CombinedImageSampler) {
const SamplerNumericDomain numericDomain = UniformTypeToSamplerNumericDomain(uniformType);
MOBILEGL_ASSERT(numericDomain != SamplerNumericDomain::Unknown,
"ProgramFactory::ReflectLayout: failed to resolve sampler numeric domain "
"for '%s' (uniformType=0x%x)",
uniformName.c_str(), uniformType);
MOBILEGL_ASSERT(entry.samplerNumericDomainByBinding[binding] ==
SamplerNumericDomain::Unknown ||
entry.samplerNumericDomainByBinding[binding] == numericDomain,
"ProgramFactory::ReflectLayout: sampler binding %u ('%s') has conflicting "
"numeric domains (%d vs %d)",
binding, uniformName.c_str(),
static_cast<Int>(entry.samplerNumericDomainByBinding[binding]),
static_cast<Int>(numericDomain));
entry.samplerNumericDomainByBinding[binding] = numericDomain;
}
MOBILEGL_ASSERT(entry.samplerUniformLocationByBinding[binding] < 0 || location < 0 || MOBILEGL_ASSERT(entry.samplerUniformLocationByBinding[binding] < 0 || location < 0 ||
entry.samplerUniformLocationByBinding[binding] == location, entry.samplerUniformLocationByBinding[binding] == location,
"ProgramFactory::ReflectLayout: texture binding %u maps to conflicting uniform locations (%d vs %d)", "ProgramFactory::ReflectLayout: texture binding %u maps to conflicting uniform locations (%d vs %d)",
@@ -1886,23 +1721,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
} }
} }
} }
// When Vulkan can legally access storage images without a statically declared
// format, let GL's glBindImageTexture format select the runtime image view. This
// provides desktop-driver-compatible behavior for packs such as iterationRP, whose
// float image qualifier can disagree with the bound render-target format. Integer
// storage images remain formatted so r32ui/r32i bit-reinterpretation paths keep the
// exact descriptor format required by their shader operations.
if (m_unformattedFloatStorageImagesEnabled) {
Vector<Uint> unformattedSpirv;
if (MG_Util::ShaderTranspiler::ShaderCompiler::UseUnformattedFloatStorageImagesForVulkan(
moduleSpirvs[i], unformattedSpirv)) {
moduleSpirvs[i] = std::move(unformattedSpirv);
} else {
MGLOG_E("ProgramFactory: failed to make float storage images unformatted for program %u",
program.GetExternalIndex());
}
}
} }
const Bool remapOk = RemapDescriptorBindingsForVulkan(moduleSpirvs, m_maxBindings, moduleSpirvs); const Bool remapOk = RemapDescriptorBindingsForVulkan(moduleSpirvs, m_maxBindings, moduleSpirvs);
@@ -14,16 +14,8 @@
#include "MG_State/GLState/TextureState/TextureEnum.h" #include "MG_State/GLState/TextureState/TextureEnum.h"
#include <Includes.h> #include <Includes.h>
#include <spirv_reflect.h>
namespace MobileGL::MG_Backend::DirectVulkan { namespace MobileGL::MG_Backend::DirectVulkan {
enum class SamplerNumericDomain : Uint8 {
Unknown = 0,
Float,
SignedInteger,
UnsignedInteger,
};
class ProgramFactory { class ProgramFactory {
public: public:
enum class DescriptorBindingKind : Uint8 { enum class DescriptorBindingKind : Uint8 {
@@ -62,9 +54,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
Vector<String> samplerNameByBinding; Vector<String> samplerNameByBinding;
Vector<Int> samplerUniformLocationByBinding; Vector<Int> samplerUniformLocationByBinding;
Vector<TextureTarget> samplerTextureTargetByBinding; Vector<TextureTarget> samplerTextureTargetByBinding;
Vector<SamplerNumericDomain> samplerNumericDomainByBinding;
Vector<VkFormat> storageImageFormatByBinding;
Vector<Bool> storageImageUsesBindingFormatByBinding;
Vector<String> storageBlockNameByBinding; Vector<String> storageBlockNameByBinding;
Vector<Int> storageBlockIndexByBinding; Vector<Int> storageBlockIndexByBinding;
Int globalUboBinding = -1; Int globalUboBinding = -1;
@@ -93,10 +82,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
samplerNameByBinding = std::move(other.samplerNameByBinding); samplerNameByBinding = std::move(other.samplerNameByBinding);
samplerUniformLocationByBinding = std::move(other.samplerUniformLocationByBinding); samplerUniformLocationByBinding = std::move(other.samplerUniformLocationByBinding);
samplerTextureTargetByBinding = std::move(other.samplerTextureTargetByBinding); samplerTextureTargetByBinding = std::move(other.samplerTextureTargetByBinding);
samplerNumericDomainByBinding = std::move(other.samplerNumericDomainByBinding);
storageImageFormatByBinding = std::move(other.storageImageFormatByBinding);
storageImageUsesBindingFormatByBinding =
std::move(other.storageImageUsesBindingFormatByBinding);
storageBlockNameByBinding = std::move(other.storageBlockNameByBinding); storageBlockNameByBinding = std::move(other.storageBlockNameByBinding);
storageBlockIndexByBinding = std::move(other.storageBlockIndexByBinding); storageBlockIndexByBinding = std::move(other.storageBlockIndexByBinding);
globalUboBinding = other.globalUboBinding; globalUboBinding = other.globalUboBinding;
@@ -133,10 +118,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
samplerNameByBinding = std::move(other.samplerNameByBinding); samplerNameByBinding = std::move(other.samplerNameByBinding);
samplerUniformLocationByBinding = std::move(other.samplerUniformLocationByBinding); samplerUniformLocationByBinding = std::move(other.samplerUniformLocationByBinding);
samplerTextureTargetByBinding = std::move(other.samplerTextureTargetByBinding); samplerTextureTargetByBinding = std::move(other.samplerTextureTargetByBinding);
samplerNumericDomainByBinding = std::move(other.samplerNumericDomainByBinding);
storageImageFormatByBinding = std::move(other.storageImageFormatByBinding);
storageImageUsesBindingFormatByBinding =
std::move(other.storageImageUsesBindingFormatByBinding);
storageBlockNameByBinding = std::move(other.storageBlockNameByBinding); storageBlockNameByBinding = std::move(other.storageBlockNameByBinding);
storageBlockIndexByBinding = std::move(other.storageBlockIndexByBinding); storageBlockIndexByBinding = std::move(other.storageBlockIndexByBinding);
globalUboBinding = other.globalUboBinding; globalUboBinding = other.globalUboBinding;
@@ -186,11 +167,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
}; };
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)
: m_device(device), m_maxBindings(maxBindings), m_config(config), : m_device(device), m_maxBindings(maxBindings), m_config(config),
m_shaderDrawParametersEnabled(shaderDrawParametersEnabled), m_shaderDrawParametersEnabled(shaderDrawParametersEnabled) {
m_unformattedFloatStorageImagesEnabled(unformattedFloatStorageImagesEnabled) {
VkProgramObject::s_device = device; VkProgramObject::s_device = device;
} }
~ProgramFactory() = default; ~ProgramFactory() = default;
@@ -201,8 +180,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
const MG_State::GLState::ProgramObject& program, CompileOptionFlags flags); const MG_State::GLState::ProgramObject& program, CompileOptionFlags flags);
static VkShaderStageFlagBits ToVkStage(ShaderStage stage); static VkShaderStageFlagBits ToVkStage(ShaderStage stage);
static VkFormat ConvertSpirvImageFormatToVkFormat(SpvImageFormat format);
static SamplerNumericDomain UniformTypeToSamplerNumericDomain(GLenum glType);
private: private:
struct ProgramLookupCache { struct ProgramLookupCache {
@@ -229,9 +206,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// True when the device enabled shaderDrawParameters; gates the InstanceIndex rebase pass // True when the device enabled shaderDrawParameters; gates the InstanceIndex rebase pass
// (which needs the DrawParameters capability / gl_BaseInstance builtin). // (which needs the DrawParameters capability / gl_BaseInstance builtin).
Bool m_shaderDrawParametersEnabled = false; Bool m_shaderDrawParametersEnabled = false;
// True only when the logical device enabled both
// shaderStorageImageReadWithoutFormat and shaderStorageImageWriteWithoutFormat.
Bool m_unformattedFloatStorageImagesEnabled = false;
mutable ProgramLookupCache m_lastLookup; mutable ProgramLookupCache m_lastLookup;
static inline XXH64_state_t* m_hashState = XXH64_createState(); static inline XXH64_state_t* m_hashState = XXH64_createState();
}; };
@@ -13,7 +13,6 @@
#include "MG_State/GLState/ProgramState/ProgramObject.h" #include "MG_State/GLState/ProgramState/ProgramObject.h"
#include "MG_State/GLState/TextureState/TextureObject2D.h" #include "MG_State/GLState/TextureState/TextureObject2D.h"
#include "MG_State/GLState/TextureState/TextureObjectBuffer.h" #include "MG_State/GLState/TextureState/TextureObjectBuffer.h"
#include "MG_Util/Converters/GLToMG/TextureEnumConverter.h"
#include "MG_Util/Converters/MGToStr/FramebufferEnumConverter.h" #include "MG_Util/Converters/MGToStr/FramebufferEnumConverter.h"
#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"
@@ -79,16 +78,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
return uniformUnit >= 0 ? uniformUnit : 0; return uniformUnit >= 0 ? uniformUnit : 0;
} }
VkFormat UniformManager::ResolveStorageImageViewFormat(VkFormat reflectedFormat, GLenum bindingFormat,
VkFormat resourceFormat, Bool useBindingFormat) {
if (useBindingFormat) {
const TextureInternalFormat bindingInternalFormat =
MG_Util::ConvertGLEnumToTextureInternalFormat(bindingFormat);
return MG_Util::ConvertTextureInternalFormatToVkEnum(bindingInternalFormat);
}
return reflectedFormat != VK_FORMAT_UNDEFINED ? reflectedFormat : resourceFormat;
}
Bool UniformManager::Initialize(VkDevice device, VkBufferManager* bufferManager, Bool UniformManager::Initialize(VkDevice device, VkBufferManager* bufferManager,
ProgramFactory* programFactory, ProgramFactory* programFactory,
VkDeviceSize minUniformBufferOffsetAlignment, Uint32 frameCount, VkDeviceSize minUniformBufferOffsetAlignment, Uint32 frameCount,
@@ -287,36 +276,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
"ResolveSamplerDescriptor: invalid sampled image layout=%d for textureId=%d, binding=%u", "ResolveSamplerDescriptor: invalid sampled image layout=%d for textureId=%d, binding=%u",
static_cast<Int>(resource->layout), texture->GetExternalIndex(), binding); static_cast<Int>(resource->layout), texture->GetExternalIndex(), binding);
} }
MOBILEGL_ASSERT(binding < programObj.samplerNumericDomainByBinding.size(),
"ResolveSamplerDescriptor: sampler numeric-domain binding %u out of range", binding);
const SamplerNumericDomain numericDomain = programObj.samplerNumericDomainByBinding[binding];
// Vulkan forbids linear filtering and anisotropy for integer sampled-image formats.
// Some desktop GL shader packs deliberately bit-read a mutable float texture through a
// usampler and still leave the texture's ordinary linear parameters in place; texelFetch
// ignores filtering, so a nearest VkSampler preserves the operation while keeping the
// descriptor valid.
const Bool forceNearestFiltering = numericDomain == SamplerNumericDomain::SignedInteger ||
numericDomain == SamplerNumericDomain::UnsignedInteger;
const VkFormat sampledViewFormat =
VkTextureManager::ResolveSampledImageViewFormat(resource->format, numericDomain);
if (sampledViewFormat == VK_FORMAT_UNDEFINED) {
MGLOG_E("ResolveSamplerDescriptor: no compatible sampled view for binding=%u ('%s') "
"textureId=%d imageFormat=%d numericDomain=%d",
binding, programObj.samplerNameByBinding[binding].c_str(), texture->GetExternalIndex(),
static_cast<Int>(resource->format), static_cast<Int>(numericDomain));
return false;
}
const VkImageView sampledImageView =
m_textureManager->GetOrCreateSampledImageView(*texture, sampledViewFormat);
if (sampledImageView == VK_NULL_HANDLE) {
MGLOG_E("ResolveSamplerDescriptor: failed to resolve sampled view for binding=%u ('%s') "
"textureId=%d imageFormat=%d viewFormat=%d numericDomain=%d",
binding, programObj.samplerNameByBinding[binding].c_str(), texture->GetExternalIndex(),
static_cast<Int>(resource->format), static_cast<Int>(sampledViewFormat),
static_cast<Int>(numericDomain));
return false;
}
// Skip GetOrCreateSampler's per-draw key hash + map lookup when this binding's // Skip GetOrCreateSampler's per-draw key hash + map lookup when this binding's
// sampler object and texture (both by lifetime id + version) are unchanged from the // sampler object and texture (both by lifetime id + version) are unchanged from the
// last draw that resolved it: the resulting sampler key, and therefore the VkSampler // last draw that resolved it: the resulting sampler key, and therefore the VkSampler
@@ -332,27 +291,23 @@ namespace MobileGL::MG_Backend::DirectVulkan {
const Uint64 textureLifetimeId = texture->GetLifetimeId(); const Uint64 textureLifetimeId = texture->GetLifetimeId();
const Uint16 textureParamsVersion = texture->GetTextureParamsVersion(); const Uint16 textureParamsVersion = texture->GetTextureParamsVersion();
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) {
resolvedSampler = memo.sampler; resolvedSampler = memo.sampler;
} else { } else {
resolvedSampler = resolvedSampler = m_samplerManager->GetOrCreateSampler(*samplerToUse, *texture);
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.sampler = resolvedSampler; memo.sampler = resolvedSampler;
memo.valid = true; memo.valid = true;
} }
} else { } else {
resolvedSampler = resolvedSampler = m_samplerManager->GetOrCreateSampler(*samplerToUse, *texture);
m_samplerManager->GetOrCreateSampler(*samplerToUse, *texture, forceNearestFiltering);
} }
outImageInfo = { outImageInfo = {
.sampler = resolvedSampler, .sampler = resolvedSampler,
.imageView = sampledImageView, .imageView = resource->sampledView != VK_NULL_HANDLE ? resource->sampledView : resource->fullView,
.imageLayout = resource->layout, .imageLayout = resource->layout,
}; };
return outImageInfo.sampler != VK_NULL_HANDLE; return outImageInfo.sampler != VK_NULL_HANDLE;
@@ -405,12 +360,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
auto& textureUnit = MG_State::pGLContext->GetTextureUnitObject(unit); auto& textureUnit = MG_State::pGLContext->GetTextureUnitObject(unit);
const TextureTarget preferredTarget = programObj.samplerTextureTargetByBinding[binding]; const TextureTarget preferredTarget = programObj.samplerTextureTargetByBinding[binding];
outTexture = textureUnit.GetBindingSlot(preferredTarget).GetBoundObject(); outTexture = textureUnit.GetBindingSlot(preferredTarget).GetBoundObject();
// The slot always holds at least the target's default texture (name 0). While that
// default has no image it is unsampleable; report it as "unbound" so callers keep
// taking their fallback paths instead of trying to sync a storage-less texture.
if (MG_State::GLState::IsUndefinedDefaultTexture(outTexture.get())) {
outTexture.reset();
}
return true; return true;
} }
@@ -431,15 +380,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
const TextureTarget preferredTarget = programObj.samplerTextureTargetByBinding[binding]; const TextureTarget preferredTarget = programObj.samplerTextureTargetByBinding[binding];
// GetBoundObject() returns the SharedPtr by const ref; .get() reads the pointer without // GetBoundObject() returns the SharedPtr by const ref; .get() reads the pointer without
// touching the refcount (no atomic inc/dec per binding per draw). // touching the refcount (no atomic inc/dec per binding per draw).
MG_State::GLState::ITextureObject* texture = return textureUnit.GetBindingSlot(preferredTarget).GetBoundObject().get();
textureUnit.GetBindingSlot(preferredTarget).GetBoundObject().get();
// The slot always holds at least the target's default texture (name 0). While that
// default has no image it is unsampleable; report it as "unbound" so the caller
// substitutes its fallback texture exactly like it did for the old null slot.
if (MG_State::GLState::IsUndefinedDefaultTexture(texture)) {
return nullptr;
}
return texture;
} }
Bool UniformManager::ResolveTexelBufferDescriptor(const MG_State::GLState::ProgramObject& program, Bool UniformManager::ResolveTexelBufferDescriptor(const MG_State::GLState::ProgramObject& program,
@@ -617,32 +558,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
} }
const Uint32 mipLevel = static_cast<Uint32>(std::max<GLint>(0, imageBinding.Level)); const Uint32 mipLevel = static_cast<Uint32>(std::max<GLint>(0, imageBinding.Level));
MOBILEGL_ASSERT(binding < programObj.storageImageFormatByBinding.size(), VkImageView view = m_textureManager->GetOrCreateViewAtMipLevel(*imageBinding.Texture, mipLevel);
"ResolveStorageImageDescriptor: storage image format binding %u out of range", binding);
MOBILEGL_ASSERT(binding < programObj.storageImageUsesBindingFormatByBinding.size(),
"ResolveStorageImageDescriptor: storage image format policy binding %u out of range",
binding);
const VkFormat reflectedFormat = programObj.storageImageFormatByBinding[binding];
const Bool useBindingFormat = programObj.storageImageUsesBindingFormatByBinding[binding];
const VkFormat viewFormat = ResolveStorageImageViewFormat(
reflectedFormat, imageBinding.Format, resource->format, useBindingFormat);
if (viewFormat == VK_FORMAT_UNDEFINED) {
MGLOG_E("ResolveStorageImageDescriptor: unsupported glBindImageTexture format=0x%x "
"for binding=%u imageUnit=%d textureId=%d bindingPolicy=%s",
imageBinding.Format, binding, imageUnit, imageBinding.Texture->GetExternalIndex(),
useBindingFormat ? "true" : "false");
return false;
}
const VkImageView view = m_textureManager->GetOrCreateStorageImageView(
*imageBinding.Texture, mipLevel, viewFormat, imageBinding.Layered != GL_FALSE, imageBinding.Layer);
if (view == VK_NULL_HANDLE) { if (view == VK_NULL_HANDLE) {
MGLOG_E("ResolveStorageImageDescriptor: failed to resolve storage view textureId=%d mip=%u " view = resource->fullView;
"bindingFormat=0x%x imageFormat=%d reflectedFormat=%d selectedFormat=%d bindingPolicy=%s",
imageBinding.Texture->GetExternalIndex(), mipLevel, imageBinding.Format,
static_cast<Int>(resource->format), static_cast<Int>(reflectedFormat),
static_cast<Int>(viewFormat),
useBindingFormat ? "true" : "false");
return false;
} }
outImageInfo.sampler = VK_NULL_HANDLE; outImageInfo.sampler = VK_NULL_HANDLE;
outImageInfo.imageView = view; outImageInfo.imageView = view;
@@ -681,15 +599,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
MG_State::GLState::ITextureObject* texture = ResolveSamplerTextureRaw(program, programObj, binding); MG_State::GLState::ITextureObject* texture = ResolveSamplerTextureRaw(program, programObj, binding);
if (!texture) { if (!texture) {
// ResolveSamplerDescriptor will substitute the fallback texture for this binding; continue;
// include it in the sampled set so the pre-render-pass sync/transition pass covers
// its first use instead of leaving that work to happen inside an active pass.
const TextureTarget preferredTarget = programObj.samplerTextureTargetByBinding[binding];
if (preferredTarget != TextureTarget::Texture2D &&
preferredTarget != TextureTarget::TextureRectangle) {
continue;
}
texture = GetFallbackTexture(preferredTarget).get();
} }
auto found = std::find(outTextures.begin(), outTextures.end(), texture); auto found = std::find(outTextures.begin(), outTextures.end(), texture);
@@ -700,50 +610,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
return true; return true;
} }
Bool UniformManager::CollectStorageImageTextures(
const MG_State::GLState::ProgramObject& program,
const ProgramFactory::VkProgramObject& programObj,
Vector<MG_State::GLState::ITextureObject*>& outTextures) const {
outTextures.clear();
MOBILEGL_ASSERT(MG_State::pGLContext != nullptr,
"CollectStorageImageTextures: GL context is null");
const Uint32 bindingCount =
std::min<Uint32>(m_maxBindings, static_cast<Uint32>(programObj.bindingKinds.size()));
for (Uint32 binding = 0; binding < bindingCount; ++binding) {
if (programObj.bindingKinds[binding] != ProgramFactory::DescriptorBindingKind::StorageImage) {
continue;
}
if (binding >= programObj.samplerUniformLocationByBinding.size()) {
MGLOG_E("CollectStorageImageTextures: binding %u has no uniform-location mapping", binding);
return false;
}
const Int location = programObj.samplerUniformLocationByBinding[binding];
if (location < 0) {
MGLOG_E("CollectStorageImageTextures: binding %u has no image uniform location", binding);
return false;
}
const Int imageUnit = program.GetUniformSamplerOrImageUnitIndex(static_cast<Uint>(location));
if (imageUnit < 0 || imageUnit >= MG_State::GLState::TextureState::MAX_TEXTURE_IMAGE_UNITS) {
MGLOG_E("CollectStorageImageTextures: image unit %d is invalid for binding %u",
imageUnit, binding);
return false;
}
auto* texture = MG_State::pGLContext->GetImageTextureBinding(imageUnit).Texture.get();
if (texture == nullptr) {
MGLOG_E("CollectStorageImageTextures: image unit %d is unbound for binding %u",
imageUnit, binding);
return false;
}
if (std::find(outTextures.begin(), outTextures.end(), texture) == outTextures.end()) {
outTextures.push_back(texture);
}
}
return true;
}
Bool UniformManager::ResolveUniformBufferPayload(const MG_State::GLState::ProgramObject& program, Bool UniformManager::ResolveUniformBufferPayload(const MG_State::GLState::ProgramObject& program,
const ProgramFactory::VkProgramObject& programObj, Uint32 binding, const ProgramFactory::VkProgramObject& programObj, Uint32 binding,
UboBindResult& out) const { UboBindResult& out) const {
@@ -42,9 +42,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
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);
Bool CollectStorageImageTextures(const MG_State::GLState::ProgramObject& program,
const ProgramFactory::VkProgramObject& programObj,
Vector<MG_State::GLState::ITextureObject*>& outTextures) const;
Bool BindProgramUniformBuffers(VkCommandBuffer commandBuffer, Bool BindProgramUniformBuffers(VkCommandBuffer commandBuffer,
const MG_State::GLState::ProgramObject& program, const MG_State::GLState::ProgramObject& program,
const ProgramFactory::VkProgramObject& programObj, const ProgramFactory::VkProgramObject& programObj,
@@ -52,12 +49,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
VkPipelineBindPoint bindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS, VkPipelineBindPoint bindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS,
const SamplerBindingOverride* samplerBindingOverride = nullptr); const SamplerBindingOverride* samplerBindingOverride = nullptr);
// Pure format-policy helper kept public for host regression tests. Formatted storage
// images use their shader qualifier; transformed float images use glBindImageTexture's
// format and never silently fall back to the backing image format.
static VkFormat ResolveStorageImageViewFormat(VkFormat reflectedFormat, GLenum bindingFormat,
VkFormat resourceFormat, Bool useBindingFormat);
private: private:
struct DescriptorPoolBucket { struct DescriptorPoolBucket {
VkDescriptorPool handle = VK_NULL_HANDLE; VkDescriptorPool handle = VK_NULL_HANDLE;
@@ -174,9 +165,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
VkSampler sampler = VK_NULL_HANDLE; VkSampler sampler = VK_NULL_HANDLE;
Uint16 samplerVersion = 0; Uint16 samplerVersion = 0;
Uint16 textureParamsVersion = 0; Uint16 textureParamsVersion = 0;
Bool forceNearestFiltering = false;
Bool valid = false; Bool valid = false;
}; };
mutable Vector<SamplerResolveMemo> m_samplerResolveMemo; mutable Vector<SamplerResolveMemo> m_samplerResolveMemo;
}; };
} // namespace MobileGL::MG_Backend::DirectVulkan } // namespace MobileGL::MG_Backend::DirectVulkan
@@ -66,7 +66,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
Vector<SizeT> bindingBaseOffsets; Vector<SizeT> bindingBaseOffsets;
Vector<Uint32> bindingAttributeLocations; Vector<Uint32> bindingAttributeLocations;
Vector<Bool> bindingUsesClientMemory; Vector<Bool> bindingUsesClientMemory;
Vector<VertexStreamConversion> bindingConversions;
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) {
@@ -75,9 +74,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
continue; continue;
} }
const VkFormat sourceVkFormat = const auto vkFormat = ToVkVertexFormat(attr.Type, attr.Size, attr.Normalized, attr.IsInteger, attr.IsBgra);
ToVkVertexFormat(attr.Type, attr.Size, attr.Normalized, attr.IsInteger, attr.IsBgra); if (vkFormat == 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",
location, MG_Util::ConvertDataTypeToString(attr.Type).c_str(), attr.Size); location, MG_Util::ConvertDataTypeToString(attr.Type).c_str(), attr.Size);
@@ -85,33 +83,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
continue; continue;
} }
VkFormat vkFormat = sourceVkFormat;
VertexStreamConversion conversion = VertexStreamConversion::None;
if (!SupportsVertexBufferFormat(vkFormat)) {
if (IsScaledIntegerVertexFormat(vkFormat)) {
const VkFormat fallbackFormat = ToFloat32VertexFormat(attr.Size);
if (fallbackFormat != VK_FORMAT_UNDEFINED && SupportsVertexBufferFormat(fallbackFormat)) {
vkFormat = fallbackFormat;
conversion = VertexStreamConversion::ScaledIntegerToFloat32;
MGLOG_W("Vertex attribute location=%u format=%d lacks "
"VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT; using float32 stream format=%d "
"(type=%s size=%d normalized=%s integer=%s)",
location, static_cast<Int>(sourceVkFormat), static_cast<Int>(vkFormat),
MG_Util::ConvertDataTypeToString(attr.Type).c_str(), attr.Size,
attr.Normalized ? "true" : "false", attr.IsInteger ? "true" : "false");
}
}
if (conversion == VertexStreamConversion::None) {
MGLOG_E("Unsupported Vulkan vertex format (location=%u, format=%d, type=%s, size=%d): "
"VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT is unavailable and no semantic fallback exists",
location, static_cast<Int>(sourceVkFormat),
MG_Util::ConvertDataTypeToString(attr.Type).c_str(), attr.Size);
unsupportedAttribMask |= (1u << location);
continue;
}
}
const SizeT attribByteSize = GetAttributeByteSize(attr.Type, attr.Size, attr.IsBgra); const SizeT attribByteSize = GetAttributeByteSize(attr.Type, attr.Size, attr.IsBgra);
if (attribByteSize == 0) { if (attribByteSize == 0) {
MGLOG_E("Vertex attribute with unknown component size (location=%u, type=%s): the array is " MGLOG_E("Vertex attribute with unknown component size (location=%u, type=%s): the array is "
@@ -121,28 +92,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
continue; continue;
} }
const Uint32 sourceStride = const Uint32 stride =
attr.Stride > 0 ? static_cast<Uint32>(attr.Stride) : static_cast<Uint32>(attribByteSize); attr.Stride > 0 ? static_cast<Uint32>(attr.Stride) : static_cast<Uint32>(attribByteSize);
const Bool packedAttribute = attr.Type == DataType::Int2101010Rev ||
attr.Type == DataType::Uint2101010Rev;
const SizeT requiredAlignment = packedAttribute ? attribByteSize : GetComponentSize(attr.Type);
if (conversion == VertexStreamConversion::None && requiredAlignment > 1 &&
((sourceStride % requiredAlignment) != 0 || (attr.Offset % requiredAlignment) != 0)) {
// GL accepts arbitrary byte strides and offsets. Core Vulkan vertex fetches do not
// unless VK_EXT_legacy_vertex_attributes is available, so deinterleave this one
// attribute into a tightly packed transient stream without changing its format.
conversion = VertexStreamConversion::Repack;
MGLOG_W("Vertex attribute location=%u uses Vulkan-incompatible alignment "
"(offset=%zu stride=%u required=%zu); using a tightly packed stream",
location, attr.Offset, sourceStride, requiredAlignment);
}
Uint32 stride = sourceStride;
if (conversion == VertexStreamConversion::Repack) {
stride = static_cast<Uint32>(attribByteSize);
} else if (conversion == VertexStreamConversion::ScaledIntegerToFloat32) {
stride = static_cast<Uint32>(attr.Size * static_cast<Int>(sizeof(Float)));
}
const VkVertexInputRate inputRate = const VkVertexInputRate inputRate =
(attr.Divisor == 0) ? VK_VERTEX_INPUT_RATE_VERTEX : VK_VERTEX_INPUT_RATE_INSTANCE; (attr.Divisor == 0) ? VK_VERTEX_INPUT_RATE_VERTEX : VK_VERTEX_INPUT_RATE_INSTANCE;
@@ -152,7 +103,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
bindingBaseOffsets.push_back(attr.Buffer ? attr.Offset : 0); bindingBaseOffsets.push_back(attr.Buffer ? attr.Offset : 0);
bindingAttributeLocations.push_back(location); bindingAttributeLocations.push_back(location);
bindingUsesClientMemory.push_back(attr.Buffer == nullptr); bindingUsesClientMemory.push_back(attr.Buffer == nullptr);
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);
} }
@@ -167,7 +117,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
entry.bindingBaseOffsets = std::move(bindingBaseOffsets); entry.bindingBaseOffsets = std::move(bindingBaseOffsets);
entry.bindingAttributeLocations = std::move(bindingAttributeLocations); entry.bindingAttributeLocations = std::move(bindingAttributeLocations);
entry.bindingUsesClientMemory = std::move(bindingUsesClientMemory); entry.bindingUsesClientMemory = std::move(bindingUsesClientMemory);
entry.bindingConversions = std::move(bindingConversions);
entry.unsupportedAttribMask = unsupportedAttribMask; entry.unsupportedAttribMask = unsupportedAttribMask;
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();
@@ -333,47 +282,4 @@ namespace MobileGL::MG_Backend::DirectVulkan {
const SizeT componentSize = GetComponentSize(type); const SizeT componentSize = GetComponentSize(type);
return componentSize == 0 ? 0 : componentSize * static_cast<SizeT>(size); return componentSize == 0 ? 0 : componentSize * static_cast<SizeT>(size);
} }
Bool VertexInputStateFactory::IsScaledIntegerVertexFormat(VkFormat format) {
switch (format) {
case VK_FORMAT_R8_USCALED:
case VK_FORMAT_R8_SSCALED:
case VK_FORMAT_R8G8_USCALED:
case VK_FORMAT_R8G8_SSCALED:
case VK_FORMAT_R8G8B8_USCALED:
case VK_FORMAT_R8G8B8_SSCALED:
case VK_FORMAT_R8G8B8A8_USCALED:
case VK_FORMAT_R8G8B8A8_SSCALED:
case VK_FORMAT_R16_USCALED:
case VK_FORMAT_R16_SSCALED:
case VK_FORMAT_R16G16_USCALED:
case VK_FORMAT_R16G16_SSCALED:
case VK_FORMAT_R16G16B16_USCALED:
case VK_FORMAT_R16G16B16_SSCALED:
case VK_FORMAT_R16G16B16A16_USCALED:
case VK_FORMAT_R16G16B16A16_SSCALED:
return true;
default:
return false;
}
}
VkFormat VertexInputStateFactory::ToFloat32VertexFormat(Int componentCount) {
switch (componentCount) {
case 1: return VK_FORMAT_R32_SFLOAT;
case 2: return VK_FORMAT_R32G32_SFLOAT;
case 3: return VK_FORMAT_R32G32B32_SFLOAT;
case 4: return VK_FORMAT_R32G32B32A32_SFLOAT;
default: return VK_FORMAT_UNDEFINED;
}
}
Bool VertexInputStateFactory::SupportsVertexBufferFormat(VkFormat format) const {
if (m_physicalDevice == VK_NULL_HANDLE || format == VK_FORMAT_UNDEFINED) {
return false;
}
VkFormatProperties properties{};
vkGetPhysicalDeviceFormatProperties(m_physicalDevice, format, &properties);
return (properties.bufferFeatures & VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT) != 0;
}
} // namespace MobileGL::MG_Backend::DirectVulkan } // namespace MobileGL::MG_Backend::DirectVulkan
@@ -19,12 +19,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
public: public:
using HashType = Uint64; using HashType = Uint64;
enum class VertexStreamConversion : Uint8 {
None = 0,
Repack,
ScaledIntegerToFloat32,
};
struct BackendVertexInputState { struct BackendVertexInputState {
HashType hash = 0; HashType hash = 0;
Vector<VkVertexInputBindingDescription> bindings; Vector<VkVertexInputBindingDescription> bindings;
@@ -33,7 +27,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
Vector<SizeT> bindingBaseOffsets; Vector<SizeT> bindingBaseOffsets;
Vector<Uint32> bindingAttributeLocations; Vector<Uint32> bindingAttributeLocations;
Vector<Bool> bindingUsesClientMemory; Vector<Bool> bindingUsesClientMemory;
Vector<VertexStreamConversion> bindingConversions;
// Locations whose array is ENABLED but whose GL format has no VkFormat mapping. They are // Locations whose array is ENABLED but whose GL format has no VkFormat mapping. They are
// 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.
@@ -43,8 +36,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
}; };
}; };
VertexInputStateFactory(const VulkanRendererConfig& config, VkPhysicalDevice physicalDevice): explicit VertexInputStateFactory(const VulkanRendererConfig& config):
m_config(config), m_physicalDevice(physicalDevice) {} m_config(config) {}
~VertexInputStateFactory() = default; ~VertexInputStateFactory() = default;
VertexInputStateFactory(const VertexInputStateFactory&) = delete; VertexInputStateFactory(const VertexInputStateFactory&) = delete;
@@ -63,12 +56,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
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);
static Bool IsScaledIntegerVertexFormat(VkFormat format);
static VkFormat ToFloat32VertexFormat(Int componentCount);
Bool SupportsVertexBufferFormat(VkFormat format) const;
const VulkanRendererConfig& m_config; const VulkanRendererConfig& m_config;
VkPhysicalDevice m_physicalDevice = VK_NULL_HANDLE;
UnorderedMap<HashType, BackendVertexInputState> m_cache; UnorderedMap<HashType, BackendVertexInputState> m_cache;
static inline XXH64_state_t* m_hashState = XXH64_createState(); static inline XXH64_state_t* m_hashState = XXH64_createState();
}; };
@@ -197,20 +197,16 @@ namespace MobileGL::MG_Backend::DirectVulkan {
return static_cast<VkBufferResource*>(bufferObject.GetBackendResource().get()); return static_cast<VkBufferResource*>(bufferObject.GetBackendResource().get());
} }
VkBufferResource* VkBufferManager::GetOrCreateResource( SharedPtr<VkBufferResource> VkBufferManager::GetOrCreateResource(
const SharedPtr<MG_State::GLState::BufferObject>& bufferObject) { const SharedPtr<MG_State::GLState::BufferObject>& bufferObject) {
// Return by raw pointer: the resource is owned for its whole lifetime by the BufferObject's auto existing = std::static_pointer_cast<VkBufferResource>(bufferObject->GetBackendResource());
// backend-resource SharedPtr (already set, or set below), so callers that only dereference
// it avoid a static_pointer_cast + SharedPtr refcount inc/dec on every per-draw buffer bind.
const auto& existing = bufferObject->GetBackendResource();
if (existing) { if (existing) {
return static_cast<VkBufferResource*>(existing.get()); return existing;
} }
auto resource = MakeShared<VkBufferResource>(); auto resource = MakeShared<VkBufferResource>();
VkBufferResource* raw = resource.get();
bufferObject->SetBackendResource(resource); bufferObject->SetBackendResource(resource);
TrackLiveResource(resource); TrackLiveResource(resource);
return raw; return resource;
} }
void VkBufferManager::TrackLiveResource(const SharedPtr<VkBufferResource>& resource) { void VkBufferManager::TrackLiveResource(const SharedPtr<VkBufferResource>& resource) {
@@ -124,7 +124,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
private: private:
Bool InitializeTransientArenas(); Bool InitializeTransientArenas();
static VkBufferUsageFlags GetVkBufferUsage(BufferKind kind); static VkBufferUsageFlags GetVkBufferUsage(BufferKind kind);
VkBufferResource* GetOrCreateResource(const SharedPtr<MG_State::GLState::BufferObject>& bufferObject); SharedPtr<VkBufferResource> GetOrCreateResource(const SharedPtr<MG_State::GLState::BufferObject>& bufferObject);
static VkBufferResource* ResourceOf(MG_State::GLState::BufferObject& bufferObject); static VkBufferResource* ResourceOf(MG_State::GLState::BufferObject& bufferObject);
Bool CreateResidentStorage(VkBufferResource& resource, VkDeviceSize size, VkBufferUsageFlags usage, Bool CreateResidentStorage(VkBufferResource& resource, VkDeviceSize size, VkBufferUsageFlags usage,
VkMemoryPropertyFlags requiredFlags = 0); VkMemoryPropertyFlags requiredFlags = 0);
@@ -157,25 +157,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
return true; return true;
} }
Bool VkBufferObject::Invalidate(VkDeviceSize size, VkDeviceSize offset) {
MOBILEGL_ASSERT(IsValid(), "VkBufferObject::Invalidate called on invalid buffer");
MOBILEGL_ASSERT(IsMapped(), "VkBufferObject::Invalidate requires mapped memory");
MOBILEGL_ASSERT(offset <= m_size, "VkBufferObject::Invalidate offset out of range");
const VkDeviceSize resolvedSize = size == VK_WHOLE_SIZE ? m_size - offset : size;
MOBILEGL_ASSERT(offset + resolvedSize <= m_size, "VkBufferObject::Invalidate range out of bounds");
if (resolvedSize == 0) {
return true;
}
const VkResult result = vmaInvalidateAllocation(m_allocator, m_allocation, offset, resolvedSize);
if (result != VK_SUCCESS) {
MGLOG_E("VkBufferObject::Invalidate failed: vmaInvalidateAllocation returned %d", result);
return false;
}
return true;
}
BufferSlice VkBufferObject::GetSlice(VkDeviceSize offset, VkDeviceSize size) const { BufferSlice VkBufferObject::GetSlice(VkDeviceSize offset, VkDeviceSize size) const {
MOBILEGL_ASSERT(offset <= m_size, "VkBufferObject::GetSlice offset out of range"); MOBILEGL_ASSERT(offset <= m_size, "VkBufferObject::GetSlice offset out of range");
const VkDeviceSize resolvedSize = (size == VK_WHOLE_SIZE) ? (m_size - offset) : size; const VkDeviceSize resolvedSize = (size == VK_WHOLE_SIZE) ? (m_size - offset) : size;
@@ -44,7 +44,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
void* Map(); void* Map();
void Unmap(); void Unmap();
Bool Upload(const void* data, VkDeviceSize size, VkDeviceSize offset = 0); Bool Upload(const void* data, VkDeviceSize size, VkDeviceSize offset = 0);
Bool Invalidate(VkDeviceSize size = VK_WHOLE_SIZE, VkDeviceSize offset = 0);
VkBuffer GetHandle() const { return m_buffer; } VkBuffer GetHandle() const { return m_buffer; }
VkDeviceSize GetSize() const { return m_size; } VkDeviceSize GetSize() const { return m_size; }
@@ -602,7 +602,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
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()) {
activeIt->second.lastUsedFrame = m_frameCounter;
return activeIt->second; return activeIt->second;
} }
} }
@@ -624,15 +623,12 @@ 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;
activeIt->second.lastUsedFrame = m_frameCounter;
return activeIt->second; return activeIt->second;
} }
auto hash = ComputeHash(fbo, swapchainImageIndex, true); 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;
return it->second; return it->second;
}
Bool isDefaultFbo = fbo.IsDefaultFramebuffer(); Bool isDefaultFbo = fbo.IsDefaultFramebuffer();
// Color attachment // Color attachment
@@ -722,7 +718,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
if (hasClear) { if (hasClear) {
pendingClearAttachments.emplace_back(PendingClearAttachmentInfo { pendingClearAttachments.emplace_back(PendingClearAttachmentInfo {
.attachmentIndex = attachmentIndex, .attachmentIndex = attachmentIndex,
.colorAttachmentSlot = i,
.key = VkClearManager::MakePendingClearKey(att) .key = VkClearManager::MakePendingClearKey(att)
}); });
} }
@@ -979,52 +974,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
subpassDesc.preserveAttachmentCount = 0; subpassDesc.preserveAttachmentCount = 0;
subpassDesc.pPreserveAttachments = nullptr; subpassDesc.pPreserveAttachments = nullptr;
// External subpass dependencies. Without them there is NO execution/memory
// dependency between consecutive render passes (or a pass and a transfer)
// touching the same attachments when the image layout does not change — the
// layout-transition helper no-ops on identical layouts and no other barrier
// exists. Tile-based GPUs then race tile loads against the previous pass's
// stores (multi-pass FBO chains like MC 26.3's OIT flicker on Adreno).
// Conservative both-ways dependencies: prior writes (attachment output,
// depth/stencil, transfer, shader) are made visible to this pass's loads,
// and this pass's attachment writes to subsequent sampling/transfer/loads.
VkSubpassDependency subpassDependencies[2];
subpassDependencies[0] = {};
subpassDependencies[0].srcSubpass = VK_SUBPASS_EXTERNAL;
subpassDependencies[0].dstSubpass = 0;
subpassDependencies[0].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT |
VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT |
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_TRANSFER_BIT;
subpassDependencies[0].srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT |
VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT |
VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT;
subpassDependencies[0].dstStageMask = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT |
VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT |
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT |
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
subpassDependencies[0].dstAccessMask =
VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT |
VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT |
VK_ACCESS_SHADER_READ_BIT;
subpassDependencies[0].dependencyFlags = 0;
subpassDependencies[1] = {};
subpassDependencies[1].srcSubpass = 0;
subpassDependencies[1].dstSubpass = VK_SUBPASS_EXTERNAL;
subpassDependencies[1].srcStageMask =
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
subpassDependencies[1].srcAccessMask =
VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
subpassDependencies[1].dstStageMask = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT |
VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT |
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT |
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT |
VK_PIPELINE_STAGE_TRANSFER_BIT;
subpassDependencies[1].dstAccessMask =
VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT |
VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT |
VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT;
subpassDependencies[1].dependencyFlags = 0;
// Render Pass // Render Pass
VkRenderPassCreateInfo renderPassCreateInfo; VkRenderPassCreateInfo renderPassCreateInfo;
renderPassCreateInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; renderPassCreateInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
@@ -1034,8 +983,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
renderPassCreateInfo.pAttachments = attachmentDescriptions.data(); renderPassCreateInfo.pAttachments = attachmentDescriptions.data();
renderPassCreateInfo.subpassCount = 1; renderPassCreateInfo.subpassCount = 1;
renderPassCreateInfo.pSubpasses = &subpassDesc; renderPassCreateInfo.pSubpasses = &subpassDesc;
renderPassCreateInfo.dependencyCount = 2; renderPassCreateInfo.dependencyCount = 0;
renderPassCreateInfo.pDependencies = subpassDependencies; renderPassCreateInfo.pDependencies = nullptr;
VkRenderPass renderPass = VK_NULL_HANDLE; VkRenderPass renderPass = VK_NULL_HANDLE;
VK_VERIFY(vkCreateRenderPass(m_device, &renderPassCreateInfo, nullptr, &renderPass)); VK_VERIFY(vkCreateRenderPass(m_device, &renderPassCreateInfo, nullptr, &renderPass));
@@ -1066,7 +1015,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
hasDepthStencilAttachment, hasDepthStencilAttachment,
renderPassSampleCount, renderPassSampleCount,
extent, extent,
framebufferLayers }; static_cast<Int>(framebufferLayers) };
MGLOG_D("VkRenderPassManager::GetOrCreateRenderPass: hash=0x%llx compatibilityHash=0x%llx attachmentCount=%u colorAttachmentCount=%u samples=%d extent=%dx%d", MGLOG_D("VkRenderPassManager::GetOrCreateRenderPass: hash=0x%llx compatibilityHash=0x%llx attachmentCount=%u colorAttachmentCount=%u samples=%d extent=%dx%d",
static_cast<unsigned long long>(hash), static_cast<unsigned long long>(hash),
static_cast<unsigned long long>(compatibilityHash), static_cast<unsigned long long>(compatibilityHash),
@@ -1076,36 +1025,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
extent.x(), extent.x(),
extent.y()); extent.y());
auto [insertedIt, _] = m_renderPasses.emplace(hash, Move(renderPassEntry)); auto [insertedIt, _] = m_renderPasses.emplace(hash, Move(renderPassEntry));
insertedIt->second.lastUsedFrame = m_frameCounter;
return insertedIt->second; return insertedIt->second;
} }
void VkRenderPassManager::OnPresent() {
++m_frameCounter;
// Sweep occasionally; evict entries whose last use is far past every
// in-flight frame so their VkRenderPass/VkFramebuffer can be destroyed
// safely (RenderPassEntry's destructor releases the handles).
constexpr Uint64 kSweepInterval = 256;
constexpr Uint64 kRetireAgeFrames = 1024;
if ((m_frameCounter % kSweepInterval) != 0) {
return;
}
const Uint64 activeHash = s_hasActiveRenderPass ? s_activeRenderPass.hash : 0;
for (auto it = m_renderPasses.begin(); it != m_renderPasses.end();) {
const Bool isActive = s_hasActiveRenderPass && it->first == activeHash;
if (!isActive && m_frameCounter - it->second.lastUsedFrame > kRetireAgeFrames) {
if (m_rpFastValid && m_rpFastRenderPassHash == it->first) {
m_rpFastValid = false;
}
it = m_renderPasses.erase(it);
} else {
++it;
}
}
}
Bool VkRenderPassManager::BeginRenderPass(VkCommandBuffer commandBuffer, RenderPassEntry& renderPassEntry) { Bool VkRenderPassManager::BeginRenderPass(VkCommandBuffer commandBuffer, RenderPassEntry& renderPassEntry) {
// TODO: Transition all the attachments into proper layout before starting the render pass // TODO: Transition all the attachments into proper layout before starting the render pass
VkRenderPassBeginInfo renderPassBeginInfo; VkRenderPassBeginInfo renderPassBeginInfo;
@@ -27,12 +27,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
}; };
struct PendingClearAttachmentInfo { struct PendingClearAttachmentInfo {
// Index into the render pass attachment descriptions (VkRenderPassBeginInfo::pClearValues space).
Uint32 attachmentIndex = 0; Uint32 attachmentIndex = 0;
// Index into the subpass pColorAttachments (VkClearAttachment::colorAttachment space) — the GL
// draw-buffer slot. Differs from attachmentIndex when earlier slots are GL_NONE/incomplete.
// Only meaningful for color clears.
Uint32 colorAttachmentSlot = 0;
PendingClearKey key{}; PendingClearKey key{};
MG_State::GLState::RenderbufferObject* renderbuffer = nullptr; MG_State::GLState::RenderbufferObject* renderbuffer = nullptr;
Bool hasInlinePayload = false; Bool hasInlinePayload = false;
@@ -73,10 +68,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
Bool hasDepthStencilAttachment = false; Bool hasDepthStencilAttachment = false;
VkSampleCountFlagBits sampleCount = VK_SAMPLE_COUNT_1_BIT; VkSampleCountFlagBits sampleCount = VK_SAMPLE_COUNT_1_BIT;
IntVec2 extent = {0, 0}; IntVec2 extent = {0, 0};
// VkFramebufferCreateInfo::layers of the entry's framebuffer (>1 for layered GL attachments). Uint32 subpass = 0;
Uint32 layers = 1;
// Frame counter value of the last GetOrCreateRenderPass hit; drives cache eviction.
Uint64 lastUsedFrame = 0;
RenderPassEntry() = default; RenderPassEntry() = default;
RenderPassEntry(const RenderPassEntry&) = delete; RenderPassEntry(const RenderPassEntry&) = delete;
@@ -92,8 +84,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
std::swap(hasDepthStencilAttachment, that.hasDepthStencilAttachment); std::swap(hasDepthStencilAttachment, that.hasDepthStencilAttachment);
std::swap(sampleCount, that.sampleCount); std::swap(sampleCount, that.sampleCount);
std::swap(extent, that.extent); std::swap(extent, that.extent);
std::swap(layers, that.layers); std::swap(subpass, that.subpass);
std::swap(lastUsedFrame, that.lastUsedFrame);
} }
RenderPassEntry( RenderPassEntry(
Uint64 hash, Uint64 hash,
@@ -106,7 +97,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
Uint32 colorAttachmentCount, Uint32 colorAttachmentCount,
Bool hasDepthStencilAttachment, Bool hasDepthStencilAttachment,
VkSampleCountFlagBits sampleCount, VkSampleCountFlagBits sampleCount,
IntVec2 extent, Uint32 layers): IntVec2 extent, int subpass):
hash(hash), hash(hash),
renderPass(renderpass), renderPass(renderpass),
framebuffer(framebuffer), framebuffer(framebuffer),
@@ -118,7 +109,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
hasDepthStencilAttachment(hasDepthStencilAttachment), hasDepthStencilAttachment(hasDepthStencilAttachment),
sampleCount(sampleCount), sampleCount(sampleCount),
extent(extent), extent(extent),
layers(layers) subpass(subpass)
{} {}
~RenderPassEntry() { ~RenderPassEntry() {
@@ -175,9 +166,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
void QueueRenderbufferClear(const ClearAttachmentPayload& clearPayload, void QueueRenderbufferClear(const ClearAttachmentPayload& clearPayload,
const MG_State::GLState::FramebufferAttachmentObject& attachment); const MG_State::GLState::FramebufferAttachmentObject& attachment);
void PopPendingRenderbufferClear(MG_State::GLState::RenderbufferObject* renderbuffer); void PopPendingRenderbufferClear(MG_State::GLState::RenderbufferObject* renderbuffer);
// Frame boundary hook: ages the render-pass cache and evicts long-unused
// entries (their command buffers retired many frames ago).
void OnPresent();
static Bool BeginRenderPass(VkCommandBuffer commandBuffer, RenderPassEntry& renderPassEntry); static Bool BeginRenderPass(VkCommandBuffer commandBuffer, RenderPassEntry& renderPassEntry);
static Bool EndRenderPass(VkCommandBuffer commandBuffer); static Bool EndRenderPass(VkCommandBuffer commandBuffer);
static ActiveRenderPassInfo* GetActiveRenderPass(); static ActiveRenderPassInfo* GetActiveRenderPass();
@@ -190,8 +178,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
VkTextureManager& m_textureManager; VkTextureManager& m_textureManager;
SwapchainObject& m_swapchainObject; SwapchainObject& m_swapchainObject;
UnorderedMap<Uint64, RenderPassEntry> m_renderPasses; UnorderedMap<Uint64, RenderPassEntry> m_renderPasses;
// Monotonic frame counter (bumped in OnPresent) for render-pass cache aging.
Uint64 m_frameCounter = 0;
// 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
@@ -58,26 +58,11 @@ namespace MobileGL::MG_Backend::DirectVulkan {
m_device = initInfo.device; m_device = initInfo.device;
m_config = initInfo.config; m_config = initInfo.config;
m_samplerAnisotropySupported = initInfo.samplerAnisotropySupported;
m_maxSamplerAnisotropy = std::max(initInfo.maxSamplerAnisotropy, 1.0f);
MOBILEGL_ASSERT(m_device != VK_NULL_HANDLE && m_config != nullptr, MOBILEGL_ASSERT(m_device != VK_NULL_HANDLE && m_config != nullptr,
"VkSamplerManager::Initialize failed: invalid initialization info"); "VkSamplerManager::Initialize failed: invalid initialization info");
return true; return true;
} }
Float VkSamplerManager::ResolveEffectiveMaxAnisotropy(const MG_State::GLState::SamplerObject& sampler,
Bool forceNearestFiltering) const {
if (!m_samplerAnisotropySupported) return 1.0f;
if (forceNearestFiltering) return 1.0f;
// VUID-VkSamplerCreateInfo-anisotropyEnable-01071/01072: anisotropy requires both filters to
// be LINEAR and the value to sit within [1, limits.maxSamplerAnisotropy].
if (sampler.GetMinFilter() != SamplerFilterMode::Linear ||
sampler.GetMagFilter() != SamplerFilterMode::Linear) {
return 1.0f;
}
return std::clamp(sampler.GetMaxAnisotropy(), 1.0f, m_maxSamplerAnisotropy);
}
void VkSamplerManager::Shutdown() { void VkSamplerManager::Shutdown() {
for (auto& [_, sampler] : m_samplers) { for (auto& [_, sampler] : m_samplers) {
if (m_device != VK_NULL_HANDLE && sampler.handle != VK_NULL_HANDLE) { if (m_device != VK_NULL_HANDLE && sampler.handle != VK_NULL_HANDLE) {
@@ -92,13 +77,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
} }
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) 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)));
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)));
const auto magFilter = sampler.GetMagFilter(); const auto magFilter = sampler.GetMagFilter();
@@ -117,11 +99,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
XXHASH_VERIFY(XXH64_update(m_hashState, &maxLod, sizeof(maxLod))); XXHASH_VERIFY(XXH64_update(m_hashState, &maxLod, sizeof(maxLod)));
const auto lodBias = sampler.GetLodBias(); const auto lodBias = sampler.GetLodBias();
XXHASH_VERIFY(XXH64_update(m_hashState, &lodBias, sizeof(lodBias))); XXHASH_VERIFY(XXH64_update(m_hashState, &lodBias, sizeof(lodBias)));
// The RESOLVED value, not the GL request: samplers that only differ in an anisotropy Vulkan // Anisotropy is currently an accepted frontend-only state on DirectVulkan.
// will not apply (NEAREST filtering, or requests past the device limit) must still share one // Keep it out of the key so changing this no-op does not manufacture duplicate
// VkSampler, while two samplers that really do differ must not collide onto the first one's. // VkSamplers while sampler versioning still exposes the new frontend value.
const auto maxAnisotropy = ResolveEffectiveMaxAnisotropy(sampler, forceNearestFiltering);
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 = ResolveCompareFunc(sampler, texture); const auto compareFunc = ResolveCompareFunc(sampler, texture);
@@ -132,9 +112,8 @@ 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) { const Uint64 key = BuildSamplerKey(sampler, texture);
const Uint64 key = BuildSamplerKey(sampler, texture, forceNearestFiltering);
auto it = m_samplers.find(key); auto it = m_samplers.find(key);
if (it != m_samplers.end()) { if (it != m_samplers.end()) {
return it->second.handle; return it->second.handle;
@@ -142,19 +121,17 @@ namespace MobileGL::MG_Backend::DirectVulkan {
VkSamplerCreateInfo samplerInfo{}; VkSamplerCreateInfo samplerInfo{};
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 = ToVkFilter(sampler.GetMagFilter());
samplerInfo.minFilter = forceNearestFiltering ? VK_FILTER_NEAREST : ToVkFilter(sampler.GetMinFilter()); samplerInfo.minFilter = ToVkFilter(sampler.GetMinFilter());
samplerInfo.mipmapMode = forceNearestFiltering ? VK_SAMPLER_MIPMAP_MODE_NEAREST samplerInfo.mipmapMode = 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());
samplerInfo.mipLodBias = sampler.GetLodBias(); samplerInfo.mipLodBias = sampler.GetLodBias();
// Must use the same resolver as BuildSamplerKey - a divergence would either collide two // DirectVulkan does not yet plumb samplerAnisotropy feature/limit discovery;
// different samplers or silently create duplicates. // preserve the accepted frontend state without requesting an unsupported feature.
const Float maxAnisotropy = ResolveEffectiveMaxAnisotropy(sampler, forceNearestFiltering); samplerInfo.anisotropyEnable = VK_FALSE;
samplerInfo.anisotropyEnable = maxAnisotropy > 1.0f ? VK_TRUE : VK_FALSE; samplerInfo.maxAnisotropy = 1.0f;
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(ResolveCompareFunc(sampler, texture)); samplerInfo.compareOp = ToVkCompareOp(ResolveCompareFunc(sampler, texture));
samplerInfo.maxLod = ResolveEffectiveMaxLod(sampler); samplerInfo.maxLod = ResolveEffectiveMaxLod(sampler);
@@ -24,18 +24,13 @@ public:
struct InitInfo { struct InitInfo {
VkDevice device = VK_NULL_HANDLE; VkDevice device = VK_NULL_HANDLE;
const VulkanRendererConfig* config = nullptr; const VulkanRendererConfig* config = nullptr;
// The samplerAnisotropy device feature was requested and granted at vkCreateDevice.
Bool samplerAnisotropySupported = false;
// VkPhysicalDeviceLimits::maxSamplerAnisotropy.
Float maxSamplerAnisotropy = 1.0f;
}; };
Bool Initialize(const InitInfo& initInfo); Bool Initialize(const InitInfo& initInfo);
void Shutdown(); void Shutdown();
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);
private: private:
struct SamplerCacheEntry { struct SamplerCacheEntry {
@@ -45,8 +40,7 @@ private:
}; };
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) 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);
@@ -55,17 +49,9 @@ private:
const MG_State::GLState::ITextureObject& texture); 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 sampler filters linearly both ways, otherwise the GL request clamped to the device limit.
// GL happily carries GL_TEXTURE_MAX_ANISOTROPY on a NEAREST sampler (Blaze3D's blocks do exactly
// that) while Vulkan forbids anisotropyEnable there, so the GL value must never be forwarded raw.
Float ResolveEffectiveMaxAnisotropy(const MG_State::GLState::SamplerObject& sampler,
Bool forceNearestFiltering) const;
VkDevice m_device = VK_NULL_HANDLE; VkDevice m_device = VK_NULL_HANDLE;
const VulkanRendererConfig* m_config = nullptr; const VulkanRendererConfig* m_config = nullptr;
Bool m_samplerAnisotropySupported = false;
Float m_maxSamplerAnisotropy = 1.0f;
UnorderedMap<Uint64, SamplerCacheEntry> m_samplers; UnorderedMap<Uint64, SamplerCacheEntry> m_samplers;
static inline XXH64_state_t* m_hashState = XXH64_createState(); static inline XXH64_state_t* m_hashState = XXH64_createState();
}; };
@@ -8,8 +8,6 @@
#include "VkTextureManager.h" #include "VkTextureManager.h"
#include "ProgramFactory.h"
#include "MG_State/GLState/Core.h" #include "MG_State/GLState/Core.h"
#include "MG_Util/Converters/MGToStr/TextureEnumConverter.h" #include "MG_Util/Converters/MGToStr/TextureEnumConverter.h"
#include "MG_Util/Converters/MGToVk/TextureEnumConverter.h" #include "MG_Util/Converters/MGToVk/TextureEnumConverter.h"
@@ -19,7 +17,6 @@
#include <cstdlib> #include <cstdlib>
#include <cstring> #include <cstring>
#include <memory> #include <memory>
#include <vulkan/utility/vk_format_utils.h>
namespace MobileGL::MG_Backend::DirectVulkan { namespace MobileGL::MG_Backend::DirectVulkan {
// Compute shaders may legally sample framebuffer-attached textures (the GL feedback-loop rule // Compute shaders may legally sample framebuffer-attached textures (the GL feedback-loop rule
@@ -66,59 +63,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
target == TextureUploadTarget::ProxyTexture2DMultisampleArray; target == TextureUploadTarget::ProxyTexture2DMultisampleArray;
} }
static Bool IsMutableStorageImageFormat(VkFormat format) {
if (!vkuFormatIsColor(format) || vkuFormatIsCompressed(format)) {
return false;
}
// These are the uncompressed color compatibility classes covered by the core GLSL/SPIR-V
// storage-image formats. OpenGL mutable texture storage uses image-format compatibility by
// size, so a shader may legally reinterpret (for example) RGBA16_UNORM storage as rgba16f. Vulkan
// requires the image to be mutable and the view formats to share this exact compatibility
// class for the equivalent operation.
switch (vkuFormatCompatibilityClass(format)) {
case VKU_FORMAT_COMPATIBILITY_CLASS_8BIT:
case VKU_FORMAT_COMPATIBILITY_CLASS_16BIT:
case VKU_FORMAT_COMPATIBILITY_CLASS_32BIT:
case VKU_FORMAT_COMPATIBILITY_CLASS_64BIT:
case VKU_FORMAT_COMPATIBILITY_CLASS_128BIT:
return true;
default:
return false;
}
}
static Bool HasMatchingColorComponentLayout(VkFormat lhs, VkFormat rhs) {
const VKU_FORMAT_INFO lhsInfo = vkuGetFormatInfo(lhs);
const VKU_FORMAT_INFO rhsInfo = vkuGetFormatInfo(rhs);
if (lhsInfo.component_count == 0 || lhsInfo.component_count != rhsInfo.component_count ||
lhsInfo.texel_block_size != rhsInfo.texel_block_size ||
lhsInfo.texels_per_block != 1 || rhsInfo.texels_per_block != 1) {
return false;
}
for (Uint32 component = 0; component < lhsInfo.component_count; ++component) {
if (lhsInfo.components[component].type != rhsInfo.components[component].type ||
lhsInfo.components[component].size != rhsInfo.components[component].size) {
return false;
}
}
return true;
}
static Bool FormatMatchesSamplerNumericDomain(VkFormat format, SamplerNumericDomain numericDomain) {
switch (numericDomain) {
case SamplerNumericDomain::Float:
return vkuFormatIsSampledFloat(format);
case SamplerNumericDomain::SignedInteger:
return vkuFormatIsSINT(format);
case SamplerNumericDomain::UnsignedInteger:
return vkuFormatIsUINT(format);
case SamplerNumericDomain::Unknown:
return true;
}
return false;
}
static Bool TryResolveSampleCountFlagBits(Int requestedSamples, VkSampleCountFlagBits& outSampleCount) { static Bool TryResolveSampleCountFlagBits(Int requestedSamples, VkSampleCountFlagBits& outSampleCount) {
switch (requestedSamples) { switch (requestedSamples) {
case 1: case 1:
@@ -823,180 +767,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
return perMipSampledView; return perMipSampledView;
} }
VkImageView VkTextureManager::GetOrCreateSampledImageView(MG_State::GLState::ITextureObject& texture,
VkFormat format) {
TextureResource* resource = SyncTextureAndGetDescriptor(texture);
if (resource == nullptr || resource->image == VK_NULL_HANDLE ||
resource->sampledView == VK_NULL_HANDLE) {
return VK_NULL_HANDLE;
}
if (format == VK_FORMAT_UNDEFINED || format == resource->format) {
return resource->sampledView;
}
if (!AreSampledImageViewFormatsCompatible(resource->format, format)) {
MGLOG_E("%s: incompatible sampled image view format=%d for textureId=%d imageFormat=%d",
__func__, static_cast<Int>(format), texture.GetExternalIndex(),
static_cast<Int>(resource->format));
return VK_NULL_HANDLE;
}
if ((resource->imageCreateFlags & VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT) == 0) {
MGLOG_E("%s: textureId=%d needs mutable image format=%d for sampled view format=%d",
__func__, texture.GetExternalIndex(), static_cast<Int>(resource->format),
static_cast<Int>(format));
return VK_NULL_HANDLE;
}
const TextureResource::SampledImageViewKey key{
.baseMipLevel = resource->sampledBaseMipLevel,
.levelCount = resource->sampledLevelCount,
.viewType = resource->viewType,
.format = format,
};
const auto existing = resource->alternateSampledViews.find(key);
if (existing != resource->alternateSampledViews.end()) {
return existing->second;
}
VkFormatProperties formatProperties{};
vkGetPhysicalDeviceFormatProperties(m_physicalDevice, format, &formatProperties);
if ((formatProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT) == 0) {
MGLOG_E("%s: sampled image view format=%d lacks VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT "
"for textureId=%d (available=0x%x)",
__func__, static_cast<Int>(format), texture.GetExternalIndex(),
static_cast<Uint32>(formatProperties.optimalTilingFeatures));
return VK_NULL_HANDLE;
}
const TextureFormatInfo formatInfo = ResolveTextureFormatInfo(texture.GetFormat());
const VkComponentMapping sampledComponents = ResolveSampledViewComponents(texture, formatInfo);
const VkImageView view = CreateImageView(
resource->image, format, VK_IMAGE_ASPECT_COLOR_BIT, resource->viewType,
resource->sampledBaseMipLevel, resource->sampledLevelCount, 0, resource->arrayLayers,
&sampledComponents, VK_IMAGE_USAGE_SAMPLED_BIT);
if (view == VK_NULL_HANDLE) {
MGLOG_E("%s: failed to create sampled image view textureId=%d imageFormat=%d viewFormat=%d",
__func__, texture.GetExternalIndex(), static_cast<Int>(resource->format),
static_cast<Int>(format));
return VK_NULL_HANDLE;
}
resource->alternateSampledViews.emplace(key, view);
MGLOG_D("%s: created sampled image view textureId=%d imageFormat=%d viewFormat=%d mip=[%u,%u)",
__func__, texture.GetExternalIndex(), static_cast<Int>(resource->format),
static_cast<Int>(format), resource->sampledBaseMipLevel,
resource->sampledBaseMipLevel + resource->sampledLevelCount);
return view;
}
VkImageView VkTextureManager::GetOrCreateStorageImageView(MG_State::GLState::ITextureObject& texture,
Uint32 mipLevel, VkFormat format,
Bool layered, Int32 layer) {
TextureResource* resource = SyncTextureAndGetDescriptor(texture);
if (resource == nullptr || resource->image == VK_NULL_HANDLE || mipLevel >= resource->mipLevels ||
resource->sampleCount != VK_SAMPLE_COUNT_1_BIT ||
(resource->aspect & VK_IMAGE_ASPECT_COLOR_BIT) == 0) {
return VK_NULL_HANDLE;
}
if (format == VK_FORMAT_UNDEFINED) {
format = resource->format;
}
if (!AreStorageImageViewFormatsCompatible(resource->format, format)) {
MGLOG_E("%s: incompatible storage image view format=%d for textureId=%d imageFormat=%d",
__func__, static_cast<Int>(format), texture.GetExternalIndex(),
static_cast<Int>(resource->format));
return VK_NULL_HANDLE;
}
if (format != resource->format &&
(resource->imageCreateFlags & VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT) == 0) {
MGLOG_E("%s: textureId=%d needs mutable image format=%d for storage view format=%d",
__func__, texture.GetExternalIndex(), static_cast<Int>(resource->format),
static_cast<Int>(format));
return VK_NULL_HANDLE;
}
Uint32 baseArrayLayer = 0;
Uint32 layerCount = resource->arrayLayers;
VkImageViewType viewType = resource->viewType;
if (!layered) {
switch (resource->viewType) {
case VK_IMAGE_VIEW_TYPE_1D_ARRAY:
viewType = VK_IMAGE_VIEW_TYPE_1D;
break;
case VK_IMAGE_VIEW_TYPE_2D_ARRAY:
case VK_IMAGE_VIEW_TYPE_CUBE:
case VK_IMAGE_VIEW_TYPE_CUBE_ARRAY:
viewType = VK_IMAGE_VIEW_TYPE_2D;
break;
case VK_IMAGE_VIEW_TYPE_3D:
MGLOG_E("%s: non-layered 3D storage views are unsupported for textureId=%d",
__func__, texture.GetExternalIndex());
return VK_NULL_HANDLE;
default:
break;
}
if (viewType != resource->viewType) {
if (layer < 0 || static_cast<Uint32>(layer) >= resource->arrayLayers) {
MGLOG_E("%s: storage image layer=%d is out of range for textureId=%d arrayLayers=%u",
__func__, layer, texture.GetExternalIndex(), resource->arrayLayers);
return VK_NULL_HANDLE;
}
baseArrayLayer = static_cast<Uint32>(layer);
layerCount = 1;
}
}
const Bool isFullResourceView = baseArrayLayer == 0 && layerCount == resource->arrayLayers &&
viewType == resource->viewType;
if (format == resource->format && isFullResourceView) {
return GetOrCreateViewAtMipLevel(texture, mipLevel);
}
const TextureResource::StorageImageViewKey key{
.mipLevel = mipLevel,
.baseArrayLayer = baseArrayLayer,
.layerCount = layerCount,
.viewType = viewType,
.format = format,
};
auto it = resource->storageImageViews.find(key);
if (it != resource->storageImageViews.end()) {
return it->second;
}
VkFormatFeatureFlags requiredFormatFeatures = VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT;
if (format != resource->format &&
(format == VK_FORMAT_R32_UINT || format == VK_FORMAT_R32_SINT)) {
requiredFormatFeatures |= VK_FORMAT_FEATURE_STORAGE_IMAGE_ATOMIC_BIT;
}
VkFormatProperties formatProperties{};
vkGetPhysicalDeviceFormatProperties(m_physicalDevice, format, &formatProperties);
if ((formatProperties.optimalTilingFeatures & requiredFormatFeatures) != requiredFormatFeatures) {
MGLOG_E("%s: storage image view format=%d lacks required features=0x%x for textureId=%d "
"(available=0x%x)",
__func__, static_cast<Int>(format), static_cast<Uint32>(requiredFormatFeatures),
texture.GetExternalIndex(), static_cast<Uint32>(formatProperties.optimalTilingFeatures));
return VK_NULL_HANDLE;
}
const VkImageView view = CreateImageView(resource->image, format, VK_IMAGE_ASPECT_COLOR_BIT, viewType,
mipLevel, 1, baseArrayLayer, layerCount, nullptr,
VK_IMAGE_USAGE_STORAGE_BIT);
if (view == VK_NULL_HANDLE) {
MGLOG_E("%s: failed to create storage image view for textureId=%d mip=%u imageFormat=%d viewFormat=%d",
__func__, texture.GetExternalIndex(), mipLevel, static_cast<Int>(resource->format),
static_cast<Int>(format));
return VK_NULL_HANDLE;
}
resource->storageImageViews.emplace(key, view);
MGLOG_D("%s: created storage image view textureId=%d mip=%u imageFormat=%d viewFormat=%d",
__func__, texture.GetExternalIndex(), mipLevel, static_cast<Int>(resource->format),
static_cast<Int>(format));
return view;
}
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));
@@ -1315,18 +1085,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
return false; return false;
} }
const VkImageAspectFlags aspect = GetAspectMaskForFormat(format);
VkFormatProperties formatProperties{};
vkGetPhysicalDeviceFormatProperties(m_physicalDevice, format, &formatProperties);
const Bool supportsStorageImage =
!isMultisampleTexture &&
(aspect & VK_IMAGE_ASPECT_COLOR_BIT) != 0 &&
(formatProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT) != 0;
VkImageCreateFlags imageCreateFlags = shapeInfo.imageFlags;
if (supportsStorageImage && IsMutableStorageImageFormat(format)) {
imageCreateFlags |= VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT;
}
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()) &&
resource.extent.height == static_cast<Uint32>(texelSize.y()) && resource.extent.height == static_cast<Uint32>(texelSize.y()) &&
@@ -1334,7 +1092,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
resource.arrayLayers == shapeInfo.arrayLayers && resource.arrayLayers == shapeInfo.arrayLayers &&
resource.viewType == shapeInfo.viewType && resource.viewType == shapeInfo.viewType &&
resource.sampleCount == resolvedSampleCount && resource.sampleCount == resolvedSampleCount &&
resource.imageCreateFlags == imageCreateFlags &&
resource.mipLevels == backingMipLevels; resource.mipLevels == backingMipLevels;
if (compatible) { if (compatible) {
if (resource.perMipViews.size() != backingMipLevels) { if (resource.perMipViews.size() != backingMipLevels) {
@@ -1355,7 +1112,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
resource.arrayLayers == shapeInfo.arrayLayers && resource.arrayLayers == shapeInfo.arrayLayers &&
resource.viewType == shapeInfo.viewType && resource.viewType == shapeInfo.viewType &&
resource.sampleCount == resolvedSampleCount && resource.sampleCount == resolvedSampleCount &&
resource.imageCreateFlags == imageCreateFlags &&
resolvedSampleCount == VK_SAMPLE_COUNT_1_BIT && resolvedSampleCount == VK_SAMPLE_COUNT_1_BIT &&
resource.mipLevels < backingMipLevels && resource.mipLevels < backingMipLevels &&
resource.layout != VK_IMAGE_LAYOUT_UNDEFINED; resource.layout != VK_IMAGE_LAYOUT_UNDEFINED;
@@ -1367,18 +1123,26 @@ namespace MobileGL::MG_Backend::DirectVulkan {
DeferResourceRelease(Move(resource)); DeferResourceRelease(Move(resource));
} }
auto aspect = GetAspectMaskForFormat(format);
VkImageCreateInfo imageInfo{}; VkImageCreateInfo imageInfo{};
imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
imageInfo.flags = imageCreateFlags; imageInfo.flags = shapeInfo.imageFlags;
imageInfo.imageType = shapeInfo.imageType; imageInfo.imageType = shapeInfo.imageType;
imageInfo.extent.width = static_cast<Uint32>(texelSize.x()); imageInfo.extent.width = static_cast<Uint32>(texelSize.x());
imageInfo.extent.height = static_cast<Uint32>(texelSize.y()); imageInfo.extent.height = static_cast<Uint32>(texelSize.y());
imageInfo.extent.depth = shapeInfo.depth; imageInfo.extent.depth = shapeInfo.depth;
imageInfo.mipLevels = backingMipLevels; imageInfo.mipLevels = backingMipLevels;
imageInfo.arrayLayers = shapeInfo.arrayLayers; imageInfo.arrayLayers = shapeInfo.arrayLayers;
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;
VkFormatProperties formatProperties{};
vkGetPhysicalDeviceFormatProperties(m_physicalDevice, format, &formatProperties);
const Bool supportsStorageImage =
!isMultisampleTexture &&
(aspect & VK_IMAGE_ASPECT_COLOR_BIT) != 0 &&
(formatProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT) != 0;
imageInfo.usage = VK_IMAGE_USAGE_SAMPLED_BIT | imageInfo.usage = VK_IMAGE_USAGE_SAMPLED_BIT |
(supportsStorageImage ? VK_IMAGE_USAGE_STORAGE_BIT : 0) | (supportsStorageImage ? VK_IMAGE_USAGE_STORAGE_BIT : 0) |
((aspect & VK_IMAGE_ASPECT_COLOR_BIT) ? VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT : 0) | ((aspect & VK_IMAGE_ASPECT_COLOR_BIT) ? VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT : 0) |
@@ -1389,17 +1153,15 @@ namespace MobileGL::MG_Backend::DirectVulkan {
imageInfo.usage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT; imageInfo.usage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
} }
imageInfo.samples = resolvedSampleCount; imageInfo.samples = resolvedSampleCount;
if (isMultisampleTexture || (imageInfo.flags & VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT) != 0) { if (isMultisampleTexture) {
VkImageFormatProperties imageFormatProperties{}; VkImageFormatProperties imageFormatProperties{};
const VkResult imageFormatResult = vkGetPhysicalDeviceImageFormatProperties( const VkResult imageFormatResult = vkGetPhysicalDeviceImageFormatProperties(
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 || if (imageFormatResult != VK_SUCCESS ||
(isMultisampleTexture && (imageFormatProperties.sampleCounts & resolvedSampleCount) == 0)) { (imageFormatProperties.sampleCounts & resolvedSampleCount) == 0) {
MGLOG_D("%s: image flags=0x%x sampleCount=%d are unsupported for textureId=%d target=%s " MGLOG_D("%s: sampleCount=%d is unsupported for textureId=%d target=%s format=%d usage=0x%x",
"format=%d usage=0x%x", __func__, texture.GetSamples(), texture.GetExternalIndex(),
__func__, static_cast<Uint32>(imageInfo.flags), texture.GetSamples(),
texture.GetExternalIndex(),
MG_Util::ConvertTextureUploadTargetToString(uploadTarget).c_str(), MG_Util::ConvertTextureUploadTargetToString(uploadTarget).c_str(),
static_cast<Int>(format), static_cast<Uint32>(imageInfo.usage)); static_cast<Int>(format), static_cast<Uint32>(imageInfo.usage));
return false; return false;
@@ -1426,7 +1188,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
resource.aspect = aspect; resource.aspect = aspect;
resource.viewType = shapeInfo.viewType; resource.viewType = shapeInfo.viewType;
resource.sampleCount = resolvedSampleCount; resource.sampleCount = resolvedSampleCount;
resource.imageCreateFlags = imageCreateFlags;
resource.syncedTextureParamsVersion = 0; resource.syncedTextureParamsVersion = 0;
if (preservedResource) { if (preservedResource) {
@@ -1442,9 +1203,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
void VkTextureManager::DeferResourceRelease(TextureResource&& resource) { void VkTextureManager::DeferResourceRelease(TextureResource&& resource) {
if (resource.image == VK_NULL_HANDLE && resource.fullView == VK_NULL_HANDLE && if (resource.image == VK_NULL_HANDLE && resource.fullView == VK_NULL_HANDLE &&
resource.sampledView == VK_NULL_HANDLE && resource.sampledView == VK_NULL_HANDLE &&
resource.perMipViews.empty() && resource.perMipSampledViews.empty() && resource.perMipViews.empty() && resource.perMipSampledViews.empty()) {
resource.attachmentViews.empty() && resource.alternateSampledViews.empty() &&
resource.storageImageViews.empty()) {
return; return;
} }
@@ -1540,10 +1299,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
sampledView = VK_NULL_HANDLE; sampledView = VK_NULL_HANDLE;
} }
} }
for (const auto& [_, sampledView] : resource.alternateSampledViews) {
DeferViewRelease(sampledView);
}
resource.alternateSampledViews.clear();
const TextureFormatInfo formatInfo = ResolveTextureFormatInfo(texture.GetFormat()); const TextureFormatInfo formatInfo = ResolveTextureFormatInfo(texture.GetFormat());
const VkComponentMapping sampledComponents = ResolveSampledViewComponents(texture, formatInfo); const VkComponentMapping sampledComponents = ResolveSampledViewComponents(texture, formatInfo);
@@ -1569,8 +1324,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
VkImageViewType viewType, Uint32 baseMipLevel, Uint32 levelCount, VkImageViewType viewType, Uint32 baseMipLevel, Uint32 levelCount,
Uint32 baseArrayLayer, Uint32 baseArrayLayer,
Uint32 layerCount, Uint32 layerCount,
const VkComponentMapping* components, const VkComponentMapping* components) const {
VkImageUsageFlags viewUsage) const {
VkImageViewCreateInfo viewInfo{}; VkImageViewCreateInfo viewInfo{};
viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
viewInfo.image = image; viewInfo.image = image;
@@ -1586,13 +1340,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
viewInfo.subresourceRange.baseArrayLayer = baseArrayLayer; viewInfo.subresourceRange.baseArrayLayer = baseArrayLayer;
viewInfo.subresourceRange.layerCount = layerCount; viewInfo.subresourceRange.layerCount = layerCount;
VkImageViewUsageCreateInfo usageInfo{};
if (viewUsage != 0) {
usageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_USAGE_CREATE_INFO;
usageInfo.usage = viewUsage;
viewInfo.pNext = &usageInfo;
}
VkImageView view = VK_NULL_HANDLE; VkImageView view = VK_NULL_HANDLE;
VK_VERIFY(vkCreateImageView(m_device, &viewInfo, nullptr, &view), "vkCreateImageView(texture)"); VK_VERIFY(vkCreateImageView(m_device, &viewInfo, nullptr, &view), "vkCreateImageView(texture)");
return view; return view;
@@ -1918,55 +1665,4 @@ namespace MobileGL::MG_Backend::DirectVulkan {
} }
return imageAspect; return imageAspect;
} }
VkFormat VkTextureManager::ResolveSampledImageViewFormat(VkFormat imageFormat,
SamplerNumericDomain numericDomain) {
if (imageFormat == VK_FORMAT_UNDEFINED || numericDomain == SamplerNumericDomain::Unknown ||
FormatMatchesSamplerNumericDomain(imageFormat, numericDomain)) {
return imageFormat;
}
if (!IsMutableStorageImageFormat(imageFormat)) {
return VK_FORMAT_UNDEFINED;
}
// Preserve component ordering and bit widths. This selects R32_UINT for an R32_SFLOAT
// texture sampled by a usampler rather than an arbitrary member (such as
// R8G8B8A8_UINT) of Vulkan's broad 32-bit compatibility class.
for (Int candidateValue = static_cast<Int>(VK_FORMAT_R4G4_UNORM_PACK8);
candidateValue <= static_cast<Int>(VK_FORMAT_ASTC_12x12_SRGB_BLOCK);
++candidateValue) {
const VkFormat candidate = static_cast<VkFormat>(candidateValue);
if (!IsMutableStorageImageFormat(candidate) ||
!FormatMatchesSamplerNumericDomain(candidate, numericDomain) ||
!HasMatchingColorComponentLayout(imageFormat, candidate) ||
!AreSampledImageViewFormatsCompatible(imageFormat, candidate)) {
continue;
}
// If an integer backing is intentionally bit-read through a float sampler, require
// a true floating-point view. Normalized/scaled views satisfy OpTypeFloat but apply
// an unrelated numeric conversion to those bits.
if (numericDomain == SamplerNumericDomain::Float && !vkuFormatIsSFLOAT(candidate)) {
continue;
}
return candidate;
}
return VK_FORMAT_UNDEFINED;
}
Bool VkTextureManager::AreSampledImageViewFormatsCompatible(VkFormat imageFormat, VkFormat viewFormat) {
if (imageFormat == viewFormat) {
return true;
}
return IsMutableStorageImageFormat(imageFormat) && IsMutableStorageImageFormat(viewFormat) &&
vkuFormatCompatibilityClass(imageFormat) == vkuFormatCompatibilityClass(viewFormat);
}
Bool VkTextureManager::AreStorageImageViewFormatsCompatible(VkFormat imageFormat, VkFormat viewFormat) {
if (imageFormat == viewFormat) {
return true;
}
return IsMutableStorageImageFormat(imageFormat) && IsMutableStorageImageFormat(viewFormat) &&
vkuFormatCompatibilityClass(imageFormat) == vkuFormatCompatibilityClass(viewFormat);
}
} // namespace MobileGL::MG_Backend::DirectVulkan } // namespace MobileGL::MG_Backend::DirectVulkan
@@ -19,8 +19,6 @@ class ITextureObject;
} }
namespace MobileGL::MG_Backend::DirectVulkan { namespace MobileGL::MG_Backend::DirectVulkan {
enum class SamplerNumericDomain : Uint8;
class VkTextureManager { class VkTextureManager {
public: public:
// Monotonic epoch bumped whenever a texture VkImage is (re)created. The render-pass // Monotonic epoch bumped whenever a texture VkImage is (re)created. The render-pass
@@ -80,61 +78,6 @@ public:
} }
}; };
struct StorageImageViewKey {
Uint32 mipLevel = 0;
Uint32 baseArrayLayer = 0;
Uint32 layerCount = 1;
VkImageViewType viewType = VK_IMAGE_VIEW_TYPE_2D;
VkFormat format = VK_FORMAT_UNDEFINED;
Bool operator==(const StorageImageViewKey& other) const {
return mipLevel == other.mipLevel &&
baseArrayLayer == other.baseArrayLayer &&
layerCount == other.layerCount &&
viewType == other.viewType &&
format == other.format;
}
};
struct SampledImageViewKey {
Uint32 baseMipLevel = 0;
Uint32 levelCount = 1;
VkImageViewType viewType = VK_IMAGE_VIEW_TYPE_2D;
VkFormat format = VK_FORMAT_UNDEFINED;
Bool operator==(const SampledImageViewKey& other) const {
return baseMipLevel == other.baseMipLevel &&
levelCount == other.levelCount &&
viewType == other.viewType &&
format == other.format;
}
};
struct SampledImageViewKeyHash {
SizeT operator()(const SampledImageViewKey& key) const {
SizeT hash = std::hash<Uint32>{}(key.baseMipLevel);
hash ^= std::hash<Uint32>{}(key.levelCount) + 0x9e3779b9u + (hash << 6) + (hash >> 2);
hash ^= std::hash<Uint32>{}(static_cast<Uint32>(key.viewType)) +
0x9e3779b9u + (hash << 6) + (hash >> 2);
hash ^= std::hash<Uint32>{}(static_cast<Uint32>(key.format)) +
0x9e3779b9u + (hash << 6) + (hash >> 2);
return hash;
}
};
struct StorageImageViewKeyHash {
SizeT operator()(const StorageImageViewKey& key) const {
SizeT hash = std::hash<Uint32>{}(key.mipLevel);
hash ^= std::hash<Uint32>{}(key.baseArrayLayer) + 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)) +
0x9e3779b9u + (hash << 6) + (hash >> 2);
hash ^= std::hash<Uint32>{}(static_cast<Uint32>(key.format)) +
0x9e3779b9u + (hash << 6) + (hash >> 2);
return hash;
}
};
VkImage image = VK_NULL_HANDLE; VkImage image = VK_NULL_HANDLE;
VmaAllocation allocation = nullptr; VmaAllocation allocation = nullptr;
VkImageView fullView = VK_NULL_HANDLE; VkImageView fullView = VK_NULL_HANDLE;
@@ -142,8 +85,6 @@ public:
Vector<VkImageView> perMipViews; Vector<VkImageView> perMipViews;
Vector<VkImageView> perMipSampledViews; Vector<VkImageView> perMipSampledViews;
UnorderedMap<AttachmentViewKey, VkImageView, AttachmentViewKeyHash> attachmentViews; UnorderedMap<AttachmentViewKey, VkImageView, AttachmentViewKeyHash> attachmentViews;
UnorderedMap<SampledImageViewKey, VkImageView, SampledImageViewKeyHash> alternateSampledViews;
UnorderedMap<StorageImageViewKey, VkImageView, StorageImageViewKeyHash> storageImageViews;
VkImageLayout layout = VK_IMAGE_LAYOUT_UNDEFINED; VkImageLayout layout = VK_IMAGE_LAYOUT_UNDEFINED;
VkExtent2D extent = {0, 0}; VkExtent2D extent = {0, 0};
Uint32 depth = 1; Uint32 depth = 1;
@@ -155,7 +96,6 @@ public:
VkImageAspectFlags aspect = VK_IMAGE_ASPECT_NONE; VkImageAspectFlags aspect = VK_IMAGE_ASPECT_NONE;
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;
Uint16 syncedTextureParamsVersion = 0; Uint16 syncedTextureParamsVersion = 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.
@@ -175,8 +115,6 @@ public:
std::swap(this->perMipViews, that.perMipViews); std::swap(this->perMipViews, that.perMipViews);
std::swap(this->perMipSampledViews, that.perMipSampledViews); std::swap(this->perMipSampledViews, that.perMipSampledViews);
std::swap(this->attachmentViews, that.attachmentViews); std::swap(this->attachmentViews, that.attachmentViews);
std::swap(this->alternateSampledViews, that.alternateSampledViews);
std::swap(this->storageImageViews, that.storageImageViews);
std::swap(this->layout, that.layout); std::swap(this->layout, that.layout);
std::swap(this->extent, that.extent); std::swap(this->extent, that.extent);
std::swap(this->depth, that.depth); std::swap(this->depth, that.depth);
@@ -188,7 +126,6 @@ public:
std::swap(this->aspect, that.aspect); std::swap(this->aspect, that.aspect);
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->syncedTextureParamsVersion, that.syncedTextureParamsVersion); std::swap(this->syncedTextureParamsVersion, that.syncedTextureParamsVersion);
std::swap(this->syncedContentVersion, that.syncedContentVersion); std::swap(this->syncedContentVersion, that.syncedContentVersion);
std::swap(this->syncedMipLevelCount, that.syncedMipLevelCount); std::swap(this->syncedMipLevelCount, that.syncedMipLevelCount);
@@ -216,16 +153,6 @@ public:
vkDestroyImageView(s_device, attachmentView, nullptr); vkDestroyImageView(s_device, attachmentView, nullptr);
} }
} }
for (const auto& [_, sampledView] : alternateSampledViews) {
if (sampledView != VK_NULL_HANDLE) {
vkDestroyImageView(s_device, sampledView, nullptr);
}
}
for (const auto& [_, storageImageView] : storageImageViews) {
if (storageImageView != VK_NULL_HANDLE) {
vkDestroyImageView(s_device, storageImageView, nullptr);
}
}
if (image != VK_NULL_HANDLE && allocation != nullptr) { if (image != VK_NULL_HANDLE && allocation != nullptr) {
vmaDestroyImage(s_allocator, image, allocation); vmaDestroyImage(s_allocator, image, allocation);
} }
@@ -234,8 +161,6 @@ public:
perMipViews.clear(); perMipViews.clear();
perMipSampledViews.clear(); perMipSampledViews.clear();
attachmentViews.clear(); attachmentViews.clear();
alternateSampledViews.clear();
storageImageViews.clear();
image = VK_NULL_HANDLE; image = VK_NULL_HANDLE;
allocation = nullptr; allocation = nullptr;
layout = VK_IMAGE_LAYOUT_UNDEFINED; layout = VK_IMAGE_LAYOUT_UNDEFINED;
@@ -249,7 +174,6 @@ public:
aspect = VK_IMAGE_ASPECT_NONE; aspect = VK_IMAGE_ASPECT_NONE;
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;
syncedTextureParamsVersion = 0; syncedTextureParamsVersion = 0;
syncedContentVersion = 0; syncedContentVersion = 0;
syncedMipLevelCount = 0; syncedMipLevelCount = 0;
@@ -274,9 +198,6 @@ public:
Uint32 baseArrayLayer, Uint32 layerCount, Uint32 baseArrayLayer, Uint32 layerCount,
VkImageViewType viewType); VkImageViewType viewType);
VkImageView GetOrCreateSampledViewAtMipLevel(MG_State::GLState::ITextureObject& texture, Uint32 mipLevel); VkImageView GetOrCreateSampledViewAtMipLevel(MG_State::GLState::ITextureObject& texture, Uint32 mipLevel);
VkImageView GetOrCreateSampledImageView(MG_State::GLState::ITextureObject& texture, VkFormat format);
VkImageView GetOrCreateStorageImageView(MG_State::GLState::ITextureObject& texture, Uint32 mipLevel,
VkFormat format, Bool layered, Int32 layer);
void UpdateTrackedImageLayout(MG_State::GLState::ITextureObject* texture, VkImageLayout newLayout); void UpdateTrackedImageLayout(MG_State::GLState::ITextureObject* texture, VkImageLayout newLayout);
void UpdateTrackedImageLayoutAfterAttachmentWrite(VkCommandBuffer commandBuffer, void UpdateTrackedImageLayoutAfterAttachmentWrite(VkCommandBuffer commandBuffer,
MG_State::GLState::ITextureObject* texture, MG_State::GLState::ITextureObject* texture,
@@ -286,9 +207,6 @@ public:
Bool TransitionTextureForStorageImage(VkCommandBuffer commandBuffer, MG_State::GLState::ITextureObject& texture); Bool TransitionTextureForStorageImage(VkCommandBuffer commandBuffer, MG_State::GLState::ITextureObject& texture);
static VkImageAspectFlags ResolveSampledImageViewAspectMask(VkImageAspectFlags imageAspect); static VkImageAspectFlags ResolveSampledImageViewAspectMask(VkImageAspectFlags imageAspect);
static VkFormat ResolveSampledImageViewFormat(VkFormat imageFormat, SamplerNumericDomain numericDomain);
static Bool AreSampledImageViewFormatsCompatible(VkFormat imageFormat, VkFormat viewFormat);
static Bool AreStorageImageViewFormatsCompatible(VkFormat imageFormat, VkFormat viewFormat);
static Bool TransitionImageLayout(VkCommandBuffer commandBuffer, VkImage image, VkImageLayout& trackedLayout, static Bool TransitionImageLayout(VkCommandBuffer commandBuffer, VkImage image, VkImageLayout& trackedLayout,
VkImageLayout newLayout, VkPipelineStageFlags srcStageMask, VkImageLayout newLayout, VkPipelineStageFlags srcStageMask,
@@ -337,8 +255,7 @@ private:
VkImageViewType viewType, Uint32 baseMipLevel, Uint32 levelCount, VkImageViewType viewType, Uint32 baseMipLevel, Uint32 levelCount,
Uint32 baseArrayLayer, Uint32 baseArrayLayer,
Uint32 layerCount, Uint32 layerCount,
const VkComponentMapping* components = nullptr, const VkComponentMapping* components = nullptr) const;
VkImageUsageFlags viewUsage = 0) const;
Bool UploadDirtyMipLevels(MG_State::GLState::TextureObjectMipmap &mipmapTexture, Bool UploadDirtyMipLevels(MG_State::GLState::TextureObjectMipmap &mipmapTexture,
TextureUploadTarget uploadTarget, TextureUploadTarget uploadTarget,
TextureResource &outResource); TextureResource &outResource);
File diff suppressed because it is too large Load Diff
@@ -131,14 +131,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
void ClearAttachmentsOnActiveRenderPass(VkCommandBuffer commandBuffer, void ClearAttachmentsOnActiveRenderPass(VkCommandBuffer commandBuffer,
const RenderPassEntry& compatibleRenderPassEntry); const RenderPassEntry& compatibleRenderPassEntry);
enum class ScissoredClearPrep {
NotNeeded, // scissor covers the whole target — take the deferred whole-surface path instead
NoOp, // nothing to clear (degenerate target or empty scissor rect)
Ready, // a render pass is active; record vkCmdClearAttachments with the returned rect
};
ScissoredClearPrep PrepareScissoredClear(const MG_State::GLState::FramebufferObject& framebuffer,
VkClearRect& outClearRect);
void Clear(GLbitfield mask); void Clear(GLbitfield mask);
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);
@@ -165,11 +157,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);
static SizeT GetReadbackTexelSize(VkFormat sourceFormat);
static Bool ConvertReadbackPixels(const Uint8* sourcePixels, VkFormat sourceFormat,
GLsizei width, GLsizei height, GLenum destinationFormat,
GLenum destinationType, SizeT destinationRowStride,
Uint8* destinationPixels);
void GetTexImage(GLenum target, GLint level, GLenum format, GLenum type, GLvoid* pixels); void GetTexImage(GLenum target, GLint level, GLenum format, GLenum type, GLvoid* pixels);
void GetTextureImage(const SharedPtr<MG_State::GLState::ITextureObject>& texture, void GetTextureImage(const SharedPtr<MG_State::GLState::ITextureObject>& texture,
TextureUploadTarget uploadTarget, GLint level, GLenum format, GLenum type, TextureUploadTarget uploadTarget, GLint level, GLenum format, GLenum type,
@@ -232,9 +219,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// frontend. Timestamp support (queue timestampValidBits > 0 and a // frontend. Timestamp support (queue timestampValidBits > 0 and a
// non-zero timestampPeriod) is cached at device creation. // non-zero timestampPeriod) is cached at device creation.
Bool IsTimerQuerySupported() const; Bool IsTimerQuerySupported() const;
// The samplerAnisotropy device feature was granted, so GL_TEXTURE_MAX_ANISOTROPY_EXT is
// honored rather than accepted-and-ignored.
Bool IsSamplerAnisotropySupported() const { return m_samplerAnisotropyFeatureEnabled; }
// Ensures the frame command buffer is recording (same lazy pattern as // Ensures the frame command buffer is recording (same lazy pattern as
// SetupDraw) and writes a bottom-of-pipe timestamp into the current // SetupDraw) and writes a bottom-of-pipe timestamp into the current
// frame's pool. Null when unsupported or the pool is exhausted. // frame's pool. Null when unsupported or the pool is exhausted.
@@ -291,10 +275,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
void QueueClearBufferPayloadForFramebuffer(const MG_State::GLState::FramebufferObject& framebuffer, void QueueClearBufferPayloadForFramebuffer(const MG_State::GLState::FramebufferObject& framebuffer,
GLenum buffer, GLint drawbuffer, GLenum buffer, GLint drawbuffer,
const ClearAttachmentPayload& clearPayload); const ClearAttachmentPayload& clearPayload);
void RecordScissoredClearBuffer(const MG_State::GLState::FramebufferObject& framebuffer,
GLenum buffer, GLint drawbuffer,
const ClearAttachmentPayload& clearPayload,
const VkClearRect& clearRect);
// ---- Submission fence tracking (GL sync objects) ---- // ---- Submission fence tracking (GL sync objects) ----
// One record per vkQueueSubmit still in flight, in ascending submit // One record per vkQueueSubmit still in flight, in ascending submit
@@ -367,10 +347,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
Bool m_indexTypeUint8ExtensionEnabled = false; Bool m_indexTypeUint8ExtensionEnabled = false;
Bool m_logicOpFeatureEnabled = false; Bool m_logicOpFeatureEnabled = false;
Bool m_multiDrawIndirectFeatureEnabled = false; Bool m_multiDrawIndirectFeatureEnabled = false;
Bool m_samplerAnisotropyFeatureEnabled = false;
Bool m_shaderDrawParametersExtensionEnabled = false; Bool m_shaderDrawParametersExtensionEnabled = false;
Bool m_shaderDrawParametersFeatureEnabled = false; Bool m_shaderDrawParametersFeatureEnabled = false;
Bool m_unformattedFloatStorageImagesEnabled = false;
// fillModeNonSolid gates VK_POLYGON_MODE_LINE/_POINT (glPolygonMode); independentBlend gates // fillModeNonSolid gates VK_POLYGON_MODE_LINE/_POINT (glPolygonMode); independentBlend gates
// per-draw-buffer color write masks (glColorMaski). Both are cached at device creation and // per-draw-buffer color write masks (glColorMaski). Both are cached at device creation and
// drive a runtime fallback when the device lacks them. // drive a runtime fallback when the device lacks them.
@@ -443,53 +421,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// 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;
Vector<MG_State::GLState::ITextureObject*> m_storageImageTexturesScratch;
Vector<VkBuffer> m_vertexBuffersScratch; Vector<VkBuffer> m_vertexBuffersScratch;
Vector<VkDeviceSize> m_vertexOffsetsScratch; Vector<VkDeviceSize> m_vertexOffsetsScratch;
Vector<VkVertexInputAttributeDescription> m_patchedAttributesScratch; Vector<VkVertexInputAttributeDescription> m_patchedAttributesScratch;
Vector<Float> m_vertexConversionScratch;
Vector<Uint8> m_vertexRepackScratch;
struct ConvertedVertexStreamKey {
const MG_State::GLState::BufferObject* buffer = nullptr;
Uint64 changeSerial = 0;
SizeT baseOffset = 0;
Uint32 sourceStride = 0;
DataType type = DataType::Float32;
Int size = 0;
Bool normalized = false;
Bool isInteger = false;
VertexInputStateFactory::VertexStreamConversion conversion =
VertexInputStateFactory::VertexStreamConversion::None;
Bool operator==(const ConvertedVertexStreamKey& other) const {
return buffer == other.buffer && changeSerial == other.changeSerial &&
baseOffset == other.baseOffset && sourceStride == other.sourceStride &&
type == other.type && size == other.size && normalized == other.normalized &&
isInteger == other.isInteger && conversion == other.conversion;
}
};
struct ConvertedVertexStreamKeyHash {
SizeT operator()(const ConvertedVertexStreamKey& key) const {
SizeT hash = std::hash<const void*>{}(key.buffer);
auto combine = [&hash](SizeT value) {
hash ^= value + static_cast<SizeT>(0x9e3779b97f4a7c15ull) + (hash << 6) + (hash >> 2);
};
combine(std::hash<Uint64>{}(key.changeSerial));
combine(std::hash<SizeT>{}(key.baseOffset));
combine(std::hash<Uint32>{}(key.sourceStride));
combine(std::hash<Uint32>{}(static_cast<Uint32>(key.type)));
combine(std::hash<Int>{}(key.size));
combine(std::hash<Bool>{}(key.normalized));
combine(std::hash<Bool>{}(key.isInteger));
combine(std::hash<Uint32>{}(static_cast<Uint32>(key.conversion)));
return hash;
}
};
UnorderedMap<ConvertedVertexStreamKey, BufferSlice, ConvertedVertexStreamKeyHash>
m_convertedVertexStreams;
void CreateInstance(); void CreateInstance();
VkResult SetupDebugMessenger(); VkResult SetupDebugMessenger();
@@ -512,14 +446,10 @@ 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();
Bool PrepareStorageImageTextures(
VkCommandBuffer commandBuffer,
const MG_State::GLState::ProgramObject& program,
const ProgramFactory::VkProgramObject& programObj);
Bool UploadAndBindVertexBuffers(VkCommandBuffer commandBuffer, const MG_State::GLState::VertexArrayObject& vao, Bool UploadAndBindVertexBuffers(VkCommandBuffer commandBuffer, const MG_State::GLState::VertexArrayObject& vao,
const ProgramFactory::VkProgramObject& programObj, const ProgramFactory::VkProgramObject& programObj,
const DrawCmdParam& drawParams, Bool indexedDraw); const DrawCmdParam& drawParams);
Bool UploadAndBindIndexBuffer(FrameContext::FrameData& frame, Bool UploadAndBindIndexBuffer(FrameContext::FrameData& frame,
const MG_State::GLState::VertexArrayObject& vao, const MG_State::GLState::VertexArrayObject& vao,
const IndexBufferView* pIndexBufferView = nullptr); const IndexBufferView* pIndexBufferView = nullptr);
@@ -17,4 +17,3 @@ target_link_libraries(
) )
add_test(NAME BufferBench COMMAND BufferBench --benchmark_counters_tabular=true) add_test(NAME BufferBench COMMAND BufferBench --benchmark_counters_tabular=true)
set_tests_properties(BufferBench PROPERTIES LABELS benchmark)
-1
View File
@@ -38,7 +38,6 @@ target_link_libraries(
) )
add_test(NAME SanityBench COMMAND SanityBench --benchmark_counters_tabular=true) add_test(NAME SanityBench COMMAND SanityBench --benchmark_counters_tabular=true)
set_tests_properties(SanityBench PROPERTIES LABELS benchmark)
add_subdirectory(Program) add_subdirectory(Program)
add_subdirectory(Buffer) add_subdirectory(Buffer)
@@ -17,4 +17,3 @@ target_link_libraries(
) )
add_test(NAME ProgramBench COMMAND ProgramBench --benchmark_counters_tabular=true) add_test(NAME ProgramBench COMMAND ProgramBench --benchmark_counters_tabular=true)
set_tests_properties(ProgramBench PROPERTIES LABELS benchmark)
+1 -4
View File
@@ -441,10 +441,7 @@ namespace MobileGL::MG_Impl::GLImpl {
for (SizeT i = 0; i < static_cast<SizeT>(n); ++i) { for (SizeT i = 0; i < static_cast<SizeT>(n); ++i) {
Uint bufferName = buffers[i]; Uint bufferName = buffers[i];
if (bufferName == 0) continue; if (bufferName == 0) continue;
// GL 3.3 core 2.9: names that do not correspond to an existing buffer are silently if (!BufferImpl::ValidateBufferName(bufferName, true)) continue;
// ignored here, so probe with the non-recording query - the shared validator would
// record INVALID_OPERATION, which is only correct on the bind path.
if (!MG_State::pGLContext->ValidateBufferName(bufferName)) continue;
MG_State::pGLContext->MarkBufferObjectForDeletion(bufferName); MG_State::pGLContext->MarkBufferObjectForDeletion(bufferName);
} }
} }
@@ -67,7 +67,7 @@ namespace MobileGL::MG_Impl::GLImpl {
} }
const auto& vao = MG_State::pGLContext->GetBoundVertexArray(); const auto& vao = MG_State::pGLContext->GetBoundVertexArray();
if (vao && vao->GetExternalIndex() == 0 && !MG_State::IsRelaxedSemanticsActive()) { if (MG_State::pEGLContext->IsCurrentContextOpenGLCoreProfile() && vao && vao->GetExternalIndex() == 0) {
MG_State::pGLContext->RecordError( MG_State::pGLContext->RecordError(
ErrorCode::InvalidOperation, ErrorCode::InvalidOperation,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", functionName, MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", functionName,
@@ -998,8 +998,8 @@ DECLARE_GL_FUNCTION_HEAD(void, ShaderStorageBlockBinding, GLuint program, GLuint
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_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_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_STUB_HEAD(void, ClearTexImage, GLuint texture, GLint level, GLenum format, GLenum type, const void* data) DECLARE_GL_FUNCTION_STUB_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_STUB_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_STUB_END_NO_RETURN(void, ClearTexSubImage, texture, level, xoffset, yoffset, zoffset, width, height, depth, format, type, data)
DECLARE_GL_FUNCTION_HEAD(void, BindBuffersBase, GLenum target, GLuint first, GLsizei count, const GLuint* buffers) DECLARE_GL_FUNCTION_END_NO_RETURN(void, BindBuffersBase, target, first, count, buffers) DECLARE_GL_FUNCTION_HEAD(void, BindBuffersBase, GLenum target, GLuint first, GLsizei count, const GLuint* buffers) DECLARE_GL_FUNCTION_END_NO_RETURN(void, BindBuffersBase, target, first, count, buffers)
DECLARE_GL_FUNCTION_HEAD(void, BindBuffersRange, GLenum target, GLuint first, GLsizei count, const GLuint* buffers, const GLintptr* offsets, const GLsizeiptr* sizes) DECLARE_GL_FUNCTION_END_NO_RETURN(void, BindBuffersRange, target, first, count, buffers, offsets, sizes) DECLARE_GL_FUNCTION_HEAD(void, BindBuffersRange, GLenum target, GLuint first, GLsizei count, const GLuint* buffers, const GLintptr* offsets, const GLsizeiptr* sizes) DECLARE_GL_FUNCTION_END_NO_RETURN(void, BindBuffersRange, target, first, count, buffers, offsets, sizes)
DECLARE_GL_FUNCTION_STUB_HEAD(void, BindTextures, GLuint first, GLsizei count, const GLuint* textures) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, BindTextures, first, count, textures) DECLARE_GL_FUNCTION_STUB_HEAD(void, BindTextures, GLuint first, GLsizei count, const GLuint* textures) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, BindTextures, first, count, textures)
@@ -1063,7 +1063,7 @@ DECLARE_GL_FUNCTION_STUB_HEAD(void, CompressedTextureSubImage1D, GLuint texture,
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_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_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_STUB_HEAD(void, CopyTextureSubImage2D, GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, CopyTextureSubImage2D, texture, level, xoffset, yoffset, x, y, width, height)
DECLARE_GL_FUNCTION_STUB_HEAD(void, CopyTextureSubImage3D, GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, CopyTextureSubImage3D, texture, level, xoffset, yoffset, zoffset, x, y, width, height) DECLARE_GL_FUNCTION_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)
@@ -60,87 +60,6 @@ namespace MobileGL::MG_Impl::GLImpl {
return false; return false;
} }
// Whether the backend can actually attach this color format to a framebuffer. Preferred
// source of truth is the backend's probed format-capability cache (real glCheckFramebufferStatus
// probes, so extensions like EXT_render_snorm are respected). Formats a probe-less backend
// cannot answer for fall back to a conservative static list of formats no ES driver renders to:
// shared-exponent, SNORM, three-channel norm16/float32/sRGB and three-channel integer formats.
// Desktop GL treats those as texture-only too (not in the GL 3.3 required-renderable list), so
// reporting GL_FRAMEBUFFER_UNSUPPORTED for them is legal.
Bool IsColorInternalFormatRenderable(TextureInternalFormat format) {
const SizeT formatIndex = static_cast<SizeT>(format);
if (MG_Backend::pActiveBackendObject && formatIndex < MG_Backend::kFormatCapabilityFormatCount) {
const auto& cache = MG_Backend::pActiveBackendObject->GetFormatCapabilities();
const SizeT sentinelFormat = static_cast<SizeT>(TextureInternalFormat::RGBA8);
Bool cachePopulated = false;
for (SizeT targetIndex = 0; targetIndex < MG_Backend::kFormatCapabilityTargetCount && !cachePopulated;
++targetIndex) {
cachePopulated = MG_Backend::HasFormatCapability(cache.FullCaps[targetIndex][sentinelFormat],
MG_Backend::FormatCapability::Creatable);
}
if (cachePopulated) {
for (SizeT targetIndex = 0; targetIndex < MG_Backend::kFormatCapabilityTargetCount;
++targetIndex) {
if (MG_Backend::HasFormatCapability(cache.FullCaps[targetIndex][formatIndex],
MG_Backend::FormatCapability::FramebufferRenderable) ||
MG_Backend::HasFormatCapability(cache.CaveatCaps[targetIndex][formatIndex],
MG_Backend::FormatCapability::FramebufferRenderable)) {
return true;
}
}
return false;
}
}
switch (format) {
case TextureInternalFormat::RGB9E5:
case TextureInternalFormat::R8Snorm:
case TextureInternalFormat::RG8Snorm:
case TextureInternalFormat::RGB8Snorm:
case TextureInternalFormat::RGBA8Snorm:
case TextureInternalFormat::R16Snorm:
case TextureInternalFormat::RG16Snorm:
case TextureInternalFormat::RGB16Snorm:
case TextureInternalFormat::RGBA16Snorm:
case TextureInternalFormat::RGB16:
case TextureInternalFormat::RGB10: // stored as RGB16
case TextureInternalFormat::RGB12: // stored as RGB16
case TextureInternalFormat::RGB16F:
case TextureInternalFormat::RGB32F:
case TextureInternalFormat::RGB8I:
case TextureInternalFormat::RGB8UI:
case TextureInternalFormat::RGB16I:
case TextureInternalFormat::RGB16UI:
case TextureInternalFormat::RGB32I:
case TextureInternalFormat::RGB32UI:
case TextureInternalFormat::SRGB8:
return false;
default:
return true;
}
}
Bool HasNonRenderableColorAttachment(const MG_State::GLState::FramebufferObject& framebufferObject) {
const auto& attachments = framebufferObject.GetAllAttachmentObjects();
for (SizeT i = 0; i < attachments.size(); ++i) {
const auto type = static_cast<FramebufferAttachmentType>(i);
if (type < FramebufferAttachmentType::Color0 || type > FramebufferAttachmentType::Color31) {
continue;
}
const auto& attachment = attachments[i];
if (!attachment.IsValid()) continue;
TextureInternalFormat format = TextureInternalFormat::Unknown;
if (attachment.IsTexture() && attachment.GetTexture()) {
format = attachment.GetTexture()->GetFormat();
} else if (attachment.IsRenderbuffer() && attachment.GetRenderbuffer()) {
format = attachment.GetRenderbuffer()->GetInternalFormat();
}
if (format != TextureInternalFormat::Unknown && !IsColorInternalFormatRenderable(format)) {
return true;
}
}
return false;
}
void RecordUnsupportedFramebufferTextureAttachmentError(const char* functionName, const char* detail) { void RecordUnsupportedFramebufferTextureAttachmentError(const char* functionName, const char* detail) {
MG_State::pGLContext->RecordError( MG_State::pGLContext->RecordError(
ErrorCode::InvalidOperation, ErrorCode::InvalidOperation,
@@ -637,62 +556,6 @@ namespace MobileGL::MG_Impl::GLImpl {
return framebufferObject; return framebufferObject;
} }
// Attaches a single layer/slice of a 3D or array texture. The attachment model stores the layer
// index; the DirectGLES backend attaches it with glFramebufferTextureLayer.
static void AttachFramebufferTextureLayer(const char* functionName, GLenum target, GLenum attachment,
GLuint texture, GLint level, GLint layer,
TextureUploadTarget textureUploadTarget) {
if (target == GL_FRAMEBUFFER) {
target = GL_DRAW_FRAMEBUFFER;
}
if (attachment == GL_DEPTH_STENCIL_ATTACHMENT) {
AttachFramebufferTextureLayer(functionName, target, GL_DEPTH_ATTACHMENT, texture, level, layer,
textureUploadTarget);
AttachFramebufferTextureLayer(functionName, target, GL_STENCIL_ATTACHMENT, texture, level, layer,
textureUploadTarget);
return;
}
const FramebufferAttachmentType attachmentType = MG_Util::ConvertGLEnumToFramebufferAttachmentType(attachment);
const FramebufferTarget framebufferTarget = MG_Util::ConvertGLEnumToFramebufferTarget(target);
if (!FramebufferImpl::ValidateFramebufferAttachmentType(attachmentType)) return;
if (!FramebufferImpl::ValidateFramebufferTarget(framebufferTarget)) return;
if (!TextureImpl::ValidateTextureName(texture, true)) return;
auto& bindingSlot = MG_State::pGLContext->GetFramebufferBindingSlot(framebufferTarget);
auto& framebufferObject = bindingSlot.GetBoundObject();
if (!framebufferObject) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidOperation,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", functionName,
"Framebuffer target is bound to no framebuffer object."));
return;
}
if (texture == 0) {
framebufferObject->Detach(attachmentType);
return;
}
auto& textureObject = MG_State::pGLContext->GetTextureObject(texture);
if (!textureObject) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidOperation,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", functionName,
std::format("Texture object {} is not valid.", texture)));
return;
}
if (layer < 0) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidValue,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", functionName, "Layer must be non-negative."));
return;
}
framebufferObject->AttachTexture(attachmentType, textureObject, textureUploadTarget, level, layer,
/*layered=*/false);
}
void FramebufferTextureLayer_State(GLenum target, GLenum attachment, GLuint texture, GLint level, GLint layer) { void FramebufferTextureLayer_State(GLenum target, GLenum attachment, GLuint texture, GLint level, GLint layer) {
if (texture == 0) { if (texture == 0) {
const TextureUploadTarget detachTarget = TextureUploadTarget::Texture2D; const TextureUploadTarget detachTarget = TextureUploadTarget::Texture2D;
@@ -700,31 +563,10 @@ namespace MobileGL::MG_Impl::GLImpl {
return; return;
} }
auto& textureObject = MG_State::pGLContext->GetTextureObject(texture); static_cast<void>(layer);
if (!textureObject) { RecordUnsupportedFramebufferTextureAttachmentError(
MG_State::pGLContext->RecordError( __func__,
ErrorCode::InvalidOperation, "Layered framebuffer texture attachments are not represented by the current framebuffer attachment model.");
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
std::format("Texture object {} is not valid.", texture)));
return;
}
TextureUploadTarget textureUploadTarget = TextureUploadTarget::Unknown;
switch (textureObject->GetTarget()) {
case TextureTarget::Texture3D:
textureUploadTarget = TextureUploadTarget::Texture3D;
break;
case TextureTarget::Texture2DArray:
textureUploadTarget = TextureUploadTarget::Texture2DArray;
break;
case TextureTarget::Texture2DMultisampleArray:
textureUploadTarget = TextureUploadTarget::Texture2DMultisampleArray;
break;
default:
RecordUnsupportedFramebufferTextureAttachmentError(
__func__, "FramebufferTextureLayer requires a 3D, 2D array or 2D multisample array texture.");
return;
}
AttachFramebufferTextureLayer(__func__, target, attachment, texture, level, layer, textureUploadTarget);
} }
void FramebufferTexture3D_State(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level, void FramebufferTexture3D_State(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level,
@@ -736,15 +578,10 @@ namespace MobileGL::MG_Impl::GLImpl {
return; return;
} }
if (textarget != GL_TEXTURE_3D) { static_cast<void>(zoffset);
MG_State::pGLContext->RecordError( RecordUnsupportedFramebufferTextureAttachmentError(
ErrorCode::InvalidEnum, __func__,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "3D framebuffer texture slice attachments are not represented by the current framebuffer attachment model.");
"FramebufferTexture3D requires GL_TEXTURE_3D."));
return;
}
AttachFramebufferTextureLayer(__func__, target, attachment, texture, level, zoffset,
TextureUploadTarget::Texture3D);
} }
void FramebufferTexture2D_State(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level) { void FramebufferTexture2D_State(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level) {
@@ -1177,7 +1014,7 @@ namespace MobileGL::MG_Impl::GLImpl {
auto& bindingSlot = MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Draw); auto& bindingSlot = MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Draw);
auto& fbo = bindingSlot.GetBoundObject(); auto& fbo = bindingSlot.GetBoundObject();
const bool isDefaultFBO = (fbo == FramebufferImpl::pDefaultFramebufferInfo->defaultFBO); const bool isDefaultFBO = (fbo == FramebufferImpl::pDefaultFramebufferInfo->defaultFBO);
const GLenum bufs[] = {buf}; static GLenum bufs[] = {buf};
DrawBuffersForFramebuffer_State(fbo, isDefaultFBO, 1, bufs, true); DrawBuffersForFramebuffer_State(fbo, isDefaultFBO, 1, bufs, true);
} }
} }
@@ -1364,9 +1201,7 @@ namespace MobileGL::MG_Impl::GLImpl {
for (SizeT i = 0; i < static_cast<SizeT>(n); ++i) { for (SizeT i = 0; i < static_cast<SizeT>(n); ++i) {
Uint bufferName = renderbuffers[i]; Uint bufferName = renderbuffers[i];
if (bufferName == 0) continue; if (bufferName == 0) continue;
// GL 3.3 core 4.4.2: unknown names are silently ignored on delete; the shared bind-path if (!FramebufferImpl::ValidateRenderbufferName(bufferName)) continue;
// validator would record INVALID_OPERATION instead.
if (!MG_State::pGLContext->ValidateRenderbufferName(bufferName)) continue;
MG_State::pGLContext->MarkRenderbufferObjectForDeletion(bufferName); MG_State::pGLContext->MarkRenderbufferObjectForDeletion(bufferName);
} }
} }
@@ -1389,9 +1224,7 @@ namespace MobileGL::MG_Impl::GLImpl {
for (SizeT i = 0; i < static_cast<SizeT>(n); ++i) { for (SizeT i = 0; i < static_cast<SizeT>(n); ++i) {
Uint bufferName = framebuffers[i]; Uint bufferName = framebuffers[i];
if (bufferName == 0) continue; if (bufferName == 0) continue;
// GL 3.3 core 4.4.1: unknown names are silently ignored on delete; the shared bind-path if (!FramebufferImpl::ValidateFramebufferName(bufferName)) continue;
// validator would record INVALID_OPERATION instead.
if (!MG_State::pGLContext->ValidateFramebufferName(bufferName)) continue;
MG_State::pGLContext->MarkFramebufferObjectForDeletion(bufferName); MG_State::pGLContext->MarkFramebufferObjectForDeletion(bufferName);
} }
} }
@@ -1419,9 +1252,6 @@ namespace MobileGL::MG_Impl::GLImpl {
GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT : GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT :
GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT; GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT;
} }
if (HasNonRenderableColorAttachment(*framebufferObject)) {
return GL_FRAMEBUFFER_UNSUPPORTED;
}
if (IsActiveBackendDirectVulkan() && if (IsActiveBackendDirectVulkan() &&
IsUnsupportedFramebufferForDirectVulkan(*framebufferObject)) { IsUnsupportedFramebufferForDirectVulkan(*framebufferObject)) {
return GL_FRAMEBUFFER_UNSUPPORTED; return GL_FRAMEBUFFER_UNSUPPORTED;
@@ -1447,9 +1277,6 @@ namespace MobileGL::MG_Impl::GLImpl {
GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT : GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT :
GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT; GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT;
} }
if (HasNonRenderableColorAttachment(*framebufferObject)) {
return GL_FRAMEBUFFER_UNSUPPORTED;
}
if (IsActiveBackendDirectVulkan() && if (IsActiveBackendDirectVulkan() &&
IsUnsupportedFramebufferForDirectVulkan(*framebufferObject)) { IsUnsupportedFramebufferForDirectVulkan(*framebufferObject)) {
return GL_FRAMEBUFFER_UNSUPPORTED; return GL_FRAMEBUFFER_UNSUPPORTED;
@@ -1799,8 +1626,8 @@ namespace MobileGL::MG_Impl::GLImpl {
return false; return false;
} }
// Check framebuffer completeness (including formats the ES pipeline cannot attach) // Check framebuffer completeness
if (!framebufferObject->CheckCompleteness() || HasNonRenderableColorAttachment(*framebufferObject)) { if (!framebufferObject->CheckCompleteness()) {
MG_State::pGLContext->RecordError( MG_State::pGLContext->RecordError(
ErrorCode::InvalidFramebufferOperation, ErrorCode::InvalidFramebufferOperation,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", "ReadPixels_State", "Framebuffer is incomplete")); MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", "ReadPixels_State", "Framebuffer is incomplete"));
@@ -1852,40 +1679,6 @@ namespace MobileGL::MG_Impl::GLImpl {
"No color buffer for color format")); "No color buffer for color format"));
return false; return false;
} }
// GL 3.3 section 4.3.1: GL_INVALID_OPERATION if format is an integer format and the read
// buffer is not an integer format, or vice versa (GL CTS packed_pixels expects the error
// for every *_INTEGER readback from a normalized attachment).
const auto& readAttachment = framebufferObject->GetAttachment(readBuffer);
TextureInternalFormat attachmentFormat = TextureInternalFormat::Unknown;
if (readAttachment.IsTexture() && readAttachment.GetTexture()) {
attachmentFormat = readAttachment.GetTexture()->GetFormat();
} else if (readAttachment.IsRenderbuffer() && readAttachment.GetRenderbuffer()) {
attachmentFormat = readAttachment.GetRenderbuffer()->GetInternalFormat();
}
if (attachmentFormat != TextureInternalFormat::Unknown &&
TextureImpl::IsIntegerColorInputFormat(textureInputFormat) !=
TextureImpl::IsIntegerColorInternalFormat(attachmentFormat)) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidOperation,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", "ReadPixels_State",
"Integer-ness of format does not match the read buffer"));
return false;
}
}
// Packed-type/format pairing (GL CTS packed_pixels: e.g. GL_RED with GL_UNSIGNED_SHORT_5_6_5 must
// raise an error instead of reaching the backend). Shared with the TexImage/GetTexImage validators;
// runs after the depth-stencil branch above so DEPTH_STENCIL with a wrong type keeps GL_INVALID_ENUM.
if (!TextureImpl::ValidateClientFormatTypePairing(textureInputFormat, texturePixelDataType)) {
return false;
}
// Packed-type/format pairing (GL CTS packed_pixels: e.g. GL_RED with GL_UNSIGNED_SHORT_5_6_5 must
// raise an error instead of reaching the backend). Shared with the TexImage/GetTexImage validators;
// runs after the depth-stencil branch above so DEPTH_STENCIL with a wrong type keeps GL_INVALID_ENUM.
if (!TextureImpl::ValidateClientFormatTypePairing(textureInputFormat, texturePixelDataType)) {
return false;
} }
// Check PBO state // Check PBO state
@@ -76,13 +76,7 @@ namespace MobileGL::MG_Impl::GLImpl::FramebufferImpl {
} }
Bool ValidateRenderbufferName(Uint index, Bool allowZero) { Bool ValidateRenderbufferName(Uint index, Bool allowZero) {
if (index == 0) { if (index == 0 && !allowZero) {
// Zero is never a GenRenderbuffers name, so it must not reach the name-table lookup
// below: where it is allowed (glBindRenderbuffer / FramebufferRenderbuffer detach) it
// means "unbind", and looking it up would record a bogus INVALID_OPERATION - GL CTS's
// per-case state reset calls glBindRenderbuffer(GL_RENDERBUFFER, 0) after every case.
if (allowZero) return true;
MG_State::pGLContext->RecordError( MG_State::pGLContext->RecordError(
ErrorCode::InvalidValue, ErrorCode::InvalidValue,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl/FramebufferImpl", "ValidateRenderbufferName", MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl/FramebufferImpl", "ValidateRenderbufferName",
+7 -31
View File
@@ -529,13 +529,6 @@ namespace MobileGL::MG_Impl::GLImpl {
params[1] = dynamicParameters.AliasedLineWidthRangeMax; params[1] = dynamicParameters.AliasedLineWidthRangeMax;
return; return;
} }
case GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT: {
// EXT_texture_filter_anisotropic queries this as a float; the integer path below widens
// from here, so this case is the authoritative one.
const auto& dynamicParameters = MG_Backend::pActiveBackendObject->GetDynamicParameters();
params[0] = dynamicParameters.MaxTextureMaxAnisotropy;
return;
}
case GL_ALIASED_POINT_SIZE_RANGE: case GL_ALIASED_POINT_SIZE_RANGE:
case GL_POINT_SIZE_RANGE: { case GL_POINT_SIZE_RANGE: {
const auto& dynamicParameters = MG_Backend::pActiveBackendObject->GetDynamicParameters(); const auto& dynamicParameters = MG_Backend::pActiveBackendObject->GetDynamicParameters();
@@ -1175,9 +1168,10 @@ namespace MobileGL::MG_Impl::GLImpl {
*params = kFrontendMaxFragmentInputComponents; *params = kFrontendMaxFragmentInputComponents;
return; return;
case GL_MAX_FRAGMENT_IMAGE_UNIFORMS: case GL_MAX_FRAGMENT_IMAGE_UNIFORMS:
// TODO: Track per-stage image uniform limits separately instead of reusing the compute/backend stage cap.
*params = MG_Backend::pActiveBackendObject *params = MG_Backend::pActiveBackendObject
? MG_Backend::pActiveBackendObject->GetDynamicParameters().MaxFragmentImageUniforms ? MG_Backend::pActiveBackendObject->GetDynamicParameters().MaxComputeImageUniforms
: MG_Backend::DynamicBackendParameters{}.MaxFragmentImageUniforms; : MG_Backend::DynamicBackendParameters{}.MaxComputeImageUniforms;
return; return;
case GL_MAX_FRAGMENT_UNIFORM_COMPONENTS: case GL_MAX_FRAGMENT_UNIFORM_COMPONENTS:
*params = kFrontendMaxFragmentUniformComponents; *params = kFrontendMaxFragmentUniformComponents;
@@ -1207,9 +1201,7 @@ namespace MobileGL::MG_Impl::GLImpl {
*params = kFrontendMaxGeometryTextureImageUnits; *params = kFrontendMaxGeometryTextureImageUnits;
return; return;
case GL_MAX_GEOMETRY_IMAGE_UNIFORMS: case GL_MAX_GEOMETRY_IMAGE_UNIFORMS:
*params = MG_Backend::pActiveBackendObject *params = 0;
? MG_Backend::pActiveBackendObject->GetDynamicParameters().MaxGeometryImageUniforms
: MG_Backend::DynamicBackendParameters{}.MaxGeometryImageUniforms;
return; return;
case GL_MAX_GEOMETRY_TOTAL_OUTPUT_COMPONENTS: case GL_MAX_GEOMETRY_TOTAL_OUTPUT_COMPONENTS:
*params = kFrontendMaxGeometryTotalOutputComponents; *params = kFrontendMaxGeometryTotalOutputComponents;
@@ -1278,9 +1270,7 @@ namespace MobileGL::MG_Impl::GLImpl {
*params = kFrontendMaxVertexAtomicCounters; *params = kFrontendMaxVertexAtomicCounters;
return; return;
case GL_MAX_VERTEX_IMAGE_UNIFORMS: case GL_MAX_VERTEX_IMAGE_UNIFORMS:
*params = MG_Backend::pActiveBackendObject *params = 0;
? MG_Backend::pActiveBackendObject->GetDynamicParameters().MaxVertexImageUniforms
: MG_Backend::DynamicBackendParameters{}.MaxVertexImageUniforms;
return; return;
case GL_MAX_VERTEX_SHADER_STORAGE_BLOCKS: case GL_MAX_VERTEX_SHADER_STORAGE_BLOCKS:
*params = 16; // TODO *params = 16; // TODO
@@ -1737,11 +1727,7 @@ namespace MobileGL::MG_Impl::GLImpl {
*params = 1024 * 1024; // TODO *params = 1024 * 1024; // TODO
return; return;
case GL_CONTEXT_PROFILE_MASK: case GL_CONTEXT_PROFILE_MASK:
// Reports the requested context profile (EGL defaults 3.x contexts to core); *params = GL_CONTEXT_CORE_PROFILE_BIT;
// MOBILEGL_RELAXED_SEMANTICS loosens behavior without changing the identity.
*params = MG_State::pEGLContext && MG_State::pEGLContext->IsCurrentContextOpenGLCompatibilityProfile()
? GL_CONTEXT_COMPATIBILITY_PROFILE_BIT
: GL_CONTEXT_CORE_PROFILE_BIT;
return; return;
default: default:
break; break;
@@ -1908,13 +1894,7 @@ namespace MobileGL::MG_Impl::GLImpl {
*params = dynamicParameters.MaxTextureSize; *params = dynamicParameters.MaxTextureSize;
break; break;
case GL_MAX_UNIFORM_BUFFER_BINDINGS: case GL_MAX_UNIFORM_BUFFER_BINDINGS:
// Never advertise more bindings than the state layer's indexed-binding array can track *params = std::max(dynamicParameters.MaxUniformBufferBindings, kFrontendMinUniformBufferBindings);
// (BufferState::BufferBindingPointCount): glBindBufferBase rejects indices past that
// capacity, and the GL CTS per-case state reset calls glBindBufferBase on every
// advertised index and expects no error. The floor equals the GL 3.3 core minimum
// (36), so the clamp never under-advertises.
*params = std::clamp(dynamicParameters.MaxUniformBufferBindings, kFrontendMinUniformBufferBindings,
static_cast<GLint>(MG_State::GLState::BufferBindingPointCount));
break; break;
case GL_MAX_UNIFORM_BLOCK_SIZE: case GL_MAX_UNIFORM_BLOCK_SIZE:
*params = dynamicParameters.MaxUniformBlockSize; *params = dynamicParameters.MaxUniformBlockSize;
@@ -1975,10 +1955,6 @@ namespace MobileGL::MG_Impl::GLImpl {
case GL_MAX_SAMPLES: case GL_MAX_SAMPLES:
*params = std::max(dynamicParameters.MaxSamples, kFrontendMaxSamples); *params = std::max(dynamicParameters.MaxSamples, kFrontendMaxSamples);
break; break;
case GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT:
// Float state (see GetFloatv); rounded to nearest for the integer query per GL 3.3 6.1.2.
*params = static_cast<GLint>(std::lround(dynamicParameters.MaxTextureMaxAnisotropy));
break;
default: default:
MGLOG_E("glGetIntegerv: Invalid enum %s (0x%X)", MG_Util::ConvertGLEnumToString(pname).c_str(), pname); MGLOG_E("glGetIntegerv: Invalid enum %s (0x%X)", MG_Util::ConvertGLEnumToString(pname).c_str(), pname);
MG_State::pGLContext->RecordError(ErrorCode::InvalidEnum, MG_State::pGLContext->RecordError(ErrorCode::InvalidEnum,
+7 -78
View File
@@ -339,9 +339,6 @@ namespace MobileGL::MG_Impl::GLImpl {
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "Shader is not attached to program.")); MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "Shader is not attached to program."));
return; return;
} }
// A shader flagged with glDeleteShader lives on while attached; this detach may
// have been its last GL-visible attachment.
MG_State::pGLContext->ReleaseShaderNameIfOrphaned(shader);
} }
void GetActiveAttrib_State(GLuint program, GLuint index, GLsizei bufSize, GLsizei* length, GLint* size, void GetActiveAttrib_State(GLuint program, GLuint index, GLsizei bufSize, GLsizei* length, GLint* size,
@@ -738,12 +735,6 @@ namespace MobileGL::MG_Impl::GLImpl {
auto size = programObject->GetUniformSizesInBytes(location); auto size = programObject->GetUniformSizesInBytes(location);
char* pUBO = (char*)programObject->MapUBO(); char* pUBO = (char*)programObject->MapUBO();
auto* ttype = programObject->GetUniformTType(location); auto* ttype = programObject->GetUniformTType(location);
if (pUBO == nullptr || offset == MG_State::GLState::ProgramObject::kInvalidUniformOffset ||
offset + size > programObject->GetUBOSize()) {
MGLOG_E("%s: uniform at program %u location %d has no backing storage; returning nothing", __func__,
program, location);
return;
}
if (!ttype->isMatrix() || ttype->getMatrixCols() != 3) if (!ttype->isMatrix() || ttype->getMatrixCols() != 3)
Memcpy(params, pUBO + offset, size); Memcpy(params, pUBO + offset, size);
@@ -793,12 +784,6 @@ namespace MobileGL::MG_Impl::GLImpl {
auto size = programObject->GetUniformSizesInBytes(location); auto size = programObject->GetUniformSizesInBytes(location);
char* pUBO = static_cast<char*>(programObject->MapUBO()); char* pUBO = static_cast<char*>(programObject->MapUBO());
auto* ttype = programObject->GetUniformTType(location); auto* ttype = programObject->GetUniformTType(location);
if (pUBO == nullptr || offset == MG_State::GLState::ProgramObject::kInvalidUniformOffset ||
offset + size > programObject->GetUBOSize()) {
MGLOG_E("%s: uniform at program %u location %d has no backing storage; returning nothing", __func__,
program, location);
return;
}
if constexpr (std::is_same_v<T, GLfloat>) { if constexpr (std::is_same_v<T, GLfloat>) {
if (ttype->isMatrix() && ttype->getMatrixCols() == 3) { if (ttype->isMatrix() && ttype->getMatrixCols() == 3) {
@@ -915,31 +900,14 @@ namespace MobileGL::MG_Impl::GLImpl {
if (!programObject.IsUniformOpaqueAtLocation(location)) { if (!programObject.IsUniformOpaqueAtLocation(location)) {
MGLOG_D("%s: program = %d, location = %d, maxLocation = %d", __func__, programObject.GetExternalIndex(), MGLOG_D("%s: program = %d, location = %d, maxLocation = %d", __func__, programObject.GetExternalIndex(),
location, programObject.GetMaxUniformLocation()); location, programObject.GetMaxUniformLocation());
const SizeT size = programObject.GetUniformSizesInBytes(location); auto size = programObject.GetUniformSizesInBytes(location);
const Uint offset = programObject.GetUniformOffset(location); auto offset = programObject.GetUniformOffset(location);
char* pUBO = static_cast<char*>(programObject.MapUBO()); MOBILEGL_ASSERT(size >= ItemCount * sizeof(T),
const SizeT uboSize = programObject.GetUBOSize(); "Uniform size mismatch, expected at least %zu bytes, got %zu bytes.", ItemCount * sizeof(T),
SizeT writeSize = ItemCount * sizeof(T); size);
if (size < writeSize) {
// Metadata bug: degrade to a clamped copy instead of killing the process.
MGLOG_E("%s: uniform size mismatch at program %u location %u: expected at least %zu bytes, got %zu "
"bytes; clamping",
__func__, programObject.GetExternalIndex(), location, ItemCount * sizeof(T), size);
writeSize = size;
}
if (pUBO == nullptr || offset == MG_State::GLState::ProgramObject::kInvalidUniformOffset ||
offset + byteOffsetInsideUniform + writeSize > uboSize) {
// Should not happen: linking gives every settable uniform backing
// storage. Log and drop the write instead of faulting.
MGLOG_E("%s: uniform at program %u location %u has no backing storage (ubo=%p offset=%u size=%zu "
"uboSize=%zu); dropping write",
__func__, programObject.GetExternalIndex(), location, static_cast<void*>(pUBO), offset,
writeSize, uboSize);
return;
}
MGLOG_D("%s: program = %d, location = %d, byteOffset = %d", __func__, programObject.GetExternalIndex(), MGLOG_D("%s: program = %d, location = %d, byteOffset = %d", __func__, programObject.GetExternalIndex(),
location, offset + byteOffsetInsideUniform); location, offset + byteOffsetInsideUniform);
Memcpy(pUBO + offset + byteOffsetInsideUniform, value, writeSize); Memcpy((char*)programObject.MapUBO() + offset + byteOffsetInsideUniform, value, ItemCount * sizeof(T));
programObject.MarkUBOContentDirty(); programObject.MarkUBOContentDirty();
} else { } else {
auto* ttype = programObject.GetUniformTType(location); auto* ttype = programObject.GetUniformTType(location);
@@ -972,11 +940,6 @@ namespace MobileGL::MG_Impl::GLImpl {
} }
for (GLint offset = 0; offset < count; offset++) { for (GLint offset = 0; offset < count; offset++) {
if (offset > 0 && !programObject->UniformLocationsAliasSameUniform(location, location + offset)) {
// GL 3.3 §2.11.4: values for elements beyond the end of the uniform
// array are ignored. Never step onto a neighboring uniform's location.
break;
}
if (!programObject->IsValidUniformLocation(location + offset)) { if (!programObject->IsValidUniformLocation(location + offset)) {
RecordInvalidUniformLocationError(__func__, location + offset, "the current program object"); RecordInvalidUniformLocationError(__func__, location + offset, "the current program object");
return; return;
@@ -1001,10 +964,6 @@ namespace MobileGL::MG_Impl::GLImpl {
} }
for (GLint offset = 0; offset < count; offset++) { for (GLint offset = 0; offset < count; offset++) {
if (offset > 0 && !programObject->UniformLocationsAliasSameUniform(location, location + offset)) {
// Values for elements beyond the end of the uniform array are ignored.
break;
}
if (!programObject->IsValidUniformLocation(location + offset)) { if (!programObject->IsValidUniformLocation(location + offset)) {
RecordInvalidUniformLocationError(__func__, location + offset, RecordInvalidUniformLocationError(__func__, location + offset,
"program " + std::to_string(program)); "program " + std::to_string(program));
@@ -1133,10 +1092,6 @@ namespace MobileGL::MG_Impl::GLImpl {
// For matrix uniforms, we handle each matrix individually // For matrix uniforms, we handle each matrix individually
for (GLint i = 0; i < count; i++) { for (GLint i = 0; i < count; i++) {
if (i > 0 && !programObject->UniformLocationsAliasSameUniform(location, location + i)) {
// Values for elements beyond the end of the uniform array are ignored.
break;
}
if (!programObject->IsValidUniformLocation(location + i)) { if (!programObject->IsValidUniformLocation(location + i)) {
RecordInvalidUniformLocationError(__func__, location + i, "the current program object"); RecordInvalidUniformLocationError(__func__, location + i, "the current program object");
return; return;
@@ -1169,10 +1124,6 @@ namespace MobileGL::MG_Impl::GLImpl {
// For matrix uniforms, we handle each matrix individually // For matrix uniforms, we handle each matrix individually
// Handle padding in mat3 correctly!! // Handle padding in mat3 correctly!!
for (GLint i = 0; i < count; i++) { for (GLint i = 0; i < count; i++) {
if (i > 0 && !programObject->UniformLocationsAliasSameUniform(location, location + i)) {
// Values for elements beyond the end of the uniform array are ignored.
break;
}
if (!programObject->IsValidUniformLocation(location + i)) { if (!programObject->IsValidUniformLocation(location + i)) {
RecordInvalidUniformLocationError(__func__, location + i, "the current program object"); RecordInvalidUniformLocationError(__func__, location + i, "the current program object");
return; return;
@@ -1208,10 +1159,6 @@ namespace MobileGL::MG_Impl::GLImpl {
// For matrix uniforms, we handle each matrix individually // For matrix uniforms, we handle each matrix individually
for (GLint i = 0; i < count; i++) { for (GLint i = 0; i < count; i++) {
if (i > 0 && !programObject->UniformLocationsAliasSameUniform(location, location + i)) {
// Values for elements beyond the end of the uniform array are ignored.
break;
}
if (!programObject->IsValidUniformLocation(location + i)) { if (!programObject->IsValidUniformLocation(location + i)) {
RecordInvalidUniformLocationError(__func__, location + i, "the current program object"); RecordInvalidUniformLocationError(__func__, location + i, "the current program object");
return; return;
@@ -1272,10 +1219,6 @@ namespace MobileGL::MG_Impl::GLImpl {
} }
for (GLint i = 0; i < count; i++) { for (GLint i = 0; i < count; i++) {
if (i > 0 && !programObject->UniformLocationsAliasSameUniform(location, location + i)) {
// Values for elements beyond the end of the uniform array are ignored.
break;
}
if (!programObject->IsValidUniformLocation(location + i)) { if (!programObject->IsValidUniformLocation(location + i)) {
RecordInvalidUniformLocationError(__func__, location + i, "program " + std::to_string(program)); RecordInvalidUniformLocationError(__func__, location + i, "program " + std::to_string(program));
return; return;
@@ -1306,10 +1249,6 @@ namespace MobileGL::MG_Impl::GLImpl {
} }
for (GLint i = 0; i < count; i++) { for (GLint i = 0; i < count; i++) {
if (i > 0 && !programObject->UniformLocationsAliasSameUniform(location, location + i)) {
// Values for elements beyond the end of the uniform array are ignored.
break;
}
if (!programObject->IsValidUniformLocation(location + i)) { if (!programObject->IsValidUniformLocation(location + i)) {
RecordInvalidUniformLocationError(__func__, location + i, "program " + std::to_string(program)); RecordInvalidUniformLocationError(__func__, location + i, "program " + std::to_string(program));
return; return;
@@ -1344,10 +1283,6 @@ namespace MobileGL::MG_Impl::GLImpl {
} }
for (GLint i = 0; i < count; i++) { for (GLint i = 0; i < count; i++) {
if (i > 0 && !programObject->UniformLocationsAliasSameUniform(location, location + i)) {
// Values for elements beyond the end of the uniform array are ignored.
break;
}
if (!programObject->IsValidUniformLocation(location + i)) { if (!programObject->IsValidUniformLocation(location + i)) {
RecordInvalidUniformLocationError(__func__, location + i, "program " + std::to_string(program)); RecordInvalidUniformLocationError(__func__, location + i, "program " + std::to_string(program));
return; return;
@@ -1406,7 +1341,6 @@ namespace MobileGL::MG_Impl::GLImpl {
} }
const auto& index = programObject->GetUniformBlockIndex(uniformBlockName); const auto& index = programObject->GetUniformBlockIndex(uniformBlockName);
MGLOG_D("GBI prog=%u name='%s' -> %d", program, uniformBlockName ? uniformBlockName : "(null)", (Int)index);
return index; return index;
} }
@@ -1430,7 +1364,6 @@ namespace MobileGL::MG_Impl::GLImpl {
std::to_string(program) + ".")); std::to_string(program) + "."));
return; return;
} }
MGLOG_D("UBB prog=%u idx=%u binding=%u", program, uniformBlockIndex, uniformBlockBinding);
programObject->SetUniformBlockBinding(uniformBlockIndex, uniformBlockBinding); programObject->SetUniformBlockBinding(uniformBlockIndex, uniformBlockBinding);
} }
@@ -1502,13 +1435,9 @@ namespace MobileGL::MG_Impl::GLImpl {
MGLOG_D("%s: GL_UNIFORM_BLOCK_REFERENCED_BY_COMPUTE_SHADER = %d", __func__, *params); MGLOG_D("%s: GL_UNIFORM_BLOCK_REFERENCED_BY_COMPUTE_SHADER = %d", __func__, *params);
break; break;
case GL_UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES: { case GL_UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES: {
// Member entries of an arrayed block are recorded against the first instance;
// every instance of the array reports that shared member set (matches
// GL_UNIFORM_BLOCK_ACTIVE_UNIFORMS, which scans with the same owner index).
const Int ownerIndex = static_cast<Int>(programObject->GetUniformBlockMemberOwnerIndex(uniformBlockIndex));
GLint uniformIndexCount = 0; GLint uniformIndexCount = 0;
for (Uint uniformIndex = 0; uniformIndex < programObject->GetUniformCount(); ++uniformIndex) { for (Uint uniformIndex = 0; uniformIndex < programObject->GetUniformCount(); ++uniformIndex) {
if (programObject->GetActiveUniformBlockIndex(uniformIndex) != ownerIndex) { if (programObject->GetActiveUniformBlockIndex(uniformIndex) != static_cast<Int>(uniformBlockIndex)) {
continue; continue;
} }
params[uniformIndexCount++] = static_cast<GLint>(uniformIndex); params[uniformIndexCount++] = static_cast<GLint>(uniformIndex);
@@ -649,9 +649,7 @@ namespace MobileGL::MG_Impl::GLImpl {
} }
void ClearDepth_State(GLclampd depth) { void ClearDepth_State(GLclampd depth) {
// GL 3.3 §4.2.3: the clear depth is clamped to [0,1] at specification time (Vulkan clear MG_State::pGLContext->SetClearDepth(static_cast<Float>(depth));
// values additionally require it: VUID-VkClearDepthStencilValue-depth-00022).
MG_State::pGLContext->SetClearDepth(ClampUnitFloat(static_cast<Float>(depth)));
} }
void ClearColor_State(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha) { void ClearColor_State(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha) {
+1 -10
View File
@@ -233,16 +233,7 @@ namespace MobileGL::MG_Impl::GLImpl {
if (sampler == 0) { if (sampler == 0) {
textureUnit.SetSamplerObject(nullptr); textureUnit.SetSamplerObject(nullptr);
} else { } else {
// GL 3.3 core 3.8.2: BindSampler on a name GenSamplers never returned - or one already if (!SamplerImpl::ValidateSamplerName(sampler)) return;
// deleted - is INVALID_OPERATION. SamplerParameter* raises INVALID_VALUE for the same
// name, which is why this cannot go through the shared SamplerImpl validator.
if (!MG_State::pGLContext->ValidateSamplerName(sampler)) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidOperation,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", "BindSampler_State",
std::format("Invalid sampler name {}", sampler)));
return;
}
Bool doesSamplerObjectCreated = MG_State::pGLContext->ValidateSamplerObject(sampler); Bool doesSamplerObjectCreated = MG_State::pGLContext->ValidateSamplerObject(sampler);
if (!doesSamplerObjectCreated) { if (!doesSamplerObjectCreated) {
MG_State::pGLContext->CreateSamplerObject(sampler); MG_State::pGLContext->CreateSamplerObject(sampler);
+34 -350
View File
@@ -294,16 +294,10 @@ namespace MobileGL::MG_Impl::GLImpl {
MG_State::GLState::TextureState::MAX_TEXTURE_IMAGE_UNITS)); MG_State::GLState::TextureState::MAX_TEXTURE_IMAGE_UNITS));
} }
// Array targets store their layer count in z; layers never participate in mip Uint ComputeFullMipmapLevelCount(const IntVec3& baseTexelSize) {
// reduction (GL 3.3 §3.8.14), only true 3D textures halve their depth per level.
Bool DepthParticipatesInMipmapping(TextureTarget target) {
return target == TextureTarget::Texture3D;
}
Uint ComputeFullMipmapLevelCount(const IntVec3& baseTexelSize, Bool depthMips) {
Int maxDimension = std::max<Int>( Int maxDimension = std::max<Int>(
baseTexelSize.x(), baseTexelSize.x(),
std::max<Int>(baseTexelSize.y(), depthMips ? std::max<Int>(baseTexelSize.z(), 1) : 1)); std::max<Int>(baseTexelSize.y(), std::max<Int>(baseTexelSize.z(), 1)));
Uint mipLevelCount = 1; Uint mipLevelCount = 1;
while (maxDimension > 1) { while (maxDimension > 1) {
maxDimension = std::max<Int>(maxDimension / 2, 1); maxDimension = std::max<Int>(maxDimension / 2, 1);
@@ -312,12 +306,11 @@ namespace MobileGL::MG_Impl::GLImpl {
return mipLevelCount; return mipLevelCount;
} }
IntVec3 ComputeMipmapTexelSize(const IntVec3& baseTexelSize, Uint relativeLevel, Bool depthMips) { IntVec3 ComputeMipmapTexelSize(const IntVec3& baseTexelSize, Uint relativeLevel) {
return { return {
std::max<Int>(baseTexelSize.x() >> static_cast<Int>(relativeLevel), 1), std::max<Int>(baseTexelSize.x() >> static_cast<Int>(relativeLevel), 1),
std::max<Int>(baseTexelSize.y() >> static_cast<Int>(relativeLevel), 1), std::max<Int>(baseTexelSize.y() >> static_cast<Int>(relativeLevel), 1),
depthMips ? std::max<Int>(baseTexelSize.z() >> static_cast<Int>(relativeLevel), 1) std::max<Int>(baseTexelSize.z() >> static_cast<Int>(relativeLevel), 1),
: std::max<Int>(baseTexelSize.z(), 1),
}; };
} }
@@ -340,10 +333,9 @@ namespace MobileGL::MG_Impl::GLImpl {
} }
const SizeT bytesPerTexel = baseByteSize / baseTexelCount; const SizeT bytesPerTexel = baseByteSize / baseTexelCount;
const Bool depthMips = DepthParticipatesInMipmapping(texture.GetTarget()); const Uint requiredLevelCount = ComputeFullMipmapLevelCount(baseTexelSize);
const Uint requiredLevelCount = ComputeFullMipmapLevelCount(baseTexelSize, depthMips);
for (Uint level = 1; level < requiredLevelCount; ++level) { for (Uint level = 1; level < requiredLevelCount; ++level) {
const IntVec3 levelTexelSize = ComputeMipmapTexelSize(baseTexelSize, level, depthMips); const IntVec3 levelTexelSize = ComputeMipmapTexelSize(baseTexelSize, level);
const SizeT levelByteSize = bytesPerTexel * static_cast<SizeT>(levelTexelSize.x()) * const SizeT levelByteSize = bytesPerTexel * static_cast<SizeT>(levelTexelSize.x()) *
static_cast<SizeT>(levelTexelSize.y()) * static_cast<SizeT>(levelTexelSize.y()) *
static_cast<SizeT>(levelTexelSize.z()); static_cast<SizeT>(levelTexelSize.z());
@@ -422,10 +414,13 @@ namespace MobileGL::MG_Impl::GLImpl {
"2D multisample textures must use depth 1.")); "2D multisample textures must use depth 1."));
return false; return false;
} }
// Zero layers is NOT an error for multisample arrays: depth == 0 (like width/height if (textureTarget == TextureTarget::Texture2DMultisampleArray && depth == 0) {
// == 0) deallocates the image - GL 4.5 8.8 only raises INVALID_VALUE for negative MG_State::pGLContext->RecordError(
// dimensions, and GL CTS's per-case state reset (gluStateReset) clears the default ErrorCode::InvalidValue,
// GL_TEXTURE_2D_MULTISAMPLE_ARRAY texture with glTexImage3DMultisample(..., 0, 0, 0). MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller,
"2D multisample array textures must have at least one layer."));
return false;
}
const Int maxSamples = GetMaxSupportedTextureSamples(textureInternalFormat); const Int maxSamples = GetMaxSupportedTextureSamples(textureInternalFormat);
if (samples > maxSamples) { if (samples > maxSamples) {
@@ -470,210 +465,6 @@ namespace MobileGL::MG_Impl::GLImpl {
return textureObject; return textureObject;
} }
namespace {
void RecordClearTextureError(const char* caller, ErrorCode code, const String& message) {
MG_State::pGLContext->RecordError(
code, MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller, message));
}
SharedPtr<MG_State::GLState::TextureObjectMipmap> GetClearTextureObject(GLuint texture, GLint level,
const char* caller) {
if (texture == 0) {
RecordClearTextureError(caller, ErrorCode::InvalidOperation,
"Clear texture operations require a non-zero texture name.");
return nullptr;
}
auto textureObject = GetTextureObjectByName(texture, caller);
if (!textureObject) return nullptr;
if (textureObject->GetStorageType() != TextureStorageType::Mipmap) {
RecordClearTextureError(caller, ErrorCode::InvalidOperation,
"Buffer textures cannot be cleared with glClearTexImage.");
return nullptr;
}
auto mipmapTexture = std::static_pointer_cast<MG_State::GLState::TextureObjectMipmap>(textureObject);
if (level < 0 || static_cast<Uint>(level) >= mipmapTexture->GetMipmapLevelCount()) {
RecordClearTextureError(caller, ErrorCode::InvalidValue,
std::format("Texture level {} is not defined.", level));
return nullptr;
}
return mipmapTexture;
}
Bool BuildClearPixel(const SharedPtr<MG_State::GLState::TextureObjectMipmap>& textureObject,
GLenum format, GLenum type, const void* data, Vector<Uint8>& clearPixel) {
const TextureInputFormat inputFormat = MG_Util::ConvertGLEnumToTextureInputFormat(format);
const TexturePixelDataType inputType = MG_Util::ConvertGLEnumToTexturePixelDataType(type);
if (!TextureImpl::ValidateTextureInputFormat(inputFormat) ||
!TextureImpl::ValidateTexturePixelDataType(inputType) ||
!TextureImpl::ValidateTextureInternalFormatCompatibleWithInput(
inputFormat, textureObject->GetFormat(), inputType)) {
return false;
}
clearPixel.clear();
if (data == nullptr) {
// ARB_clear_texture defines a null clear value as all zeroes. Keeping the
// pattern empty lets the region writer use a fast memset path.
return true;
}
PixelStoreParameters clearPixelStore{};
clearPixelStore.Alignment = 1;
SizeT clearPixelSize = 0;
void* converted = MG_Util::PixelStoreProcessor::ProcessTexturePixelsDataUnpack(
data, clearPixelStore, textureObject->GetFormat(), inputFormat, inputType,
{1, 1, 1}, false, clearPixelSize);
if (!converted || clearPixelSize == 0) {
if (converted) free(converted);
return false;
}
clearPixel.resize(clearPixelSize);
Memcpy(clearPixel.data(), converted, clearPixelSize);
free(converted);
return true;
}
Bool ClearMipmapRegion(const SharedPtr<MG_State::GLState::TextureObjectMipmap>& textureObject,
TextureUploadTarget uploadTarget, GLint level,
GLint xoffset, GLint yoffset, GLint zoffset,
GLsizei width, GLsizei height, GLsizei depth,
const Vector<Uint8>& clearPixel, const char* caller) {
const IntVec3 texelSize = textureObject->GetMipmapTexelSize(uploadTarget, static_cast<Uint>(level));
if (texelSize.x() <= 0 || texelSize.y() <= 0 || texelSize.z() <= 0) {
RecordClearTextureError(caller, ErrorCode::InvalidOperation,
"The requested texture level has no storage.");
return false;
}
if (xoffset < 0 || yoffset < 0 || zoffset < 0 ||
width < 0 || height < 0 || depth < 0 ||
width > texelSize.x() - xoffset ||
height > texelSize.y() - yoffset ||
depth > texelSize.z() - zoffset) {
RecordClearTextureError(caller, ErrorCode::InvalidValue,
"The clear region lies outside the requested texture level.");
return false;
}
if (width == 0 || height == 0 || depth == 0) return true;
const SizeT texelCount = static_cast<SizeT>(texelSize.x()) *
static_cast<SizeT>(texelSize.y()) *
static_cast<SizeT>(texelSize.z());
const SizeT byteSize = textureObject->GetMipmapByteSize(uploadTarget, static_cast<Uint>(level));
if (byteSize == 0 || byteSize % texelCount != 0) {
RecordClearTextureError(caller, ErrorCode::InvalidOperation,
"The requested texture storage cannot be cleared.");
return false;
}
const SizeT bytesPerTexel = byteSize / texelCount;
if (!clearPixel.empty() && clearPixel.size() != bytesPerTexel) {
RecordClearTextureError(
caller, ErrorCode::InvalidOperation,
std::format("Converted clear value is {} bytes, but the texture stores {} bytes per texel.",
clearPixel.size(), bytesPerTexel));
return false;
}
auto* destination = static_cast<Uint8*>(
textureObject->MapMipmapData(uploadTarget, static_cast<Uint>(level)));
if (!destination) {
RecordClearTextureError(caller, ErrorCode::InvalidOperation,
"The requested texture level could not be mapped.");
return false;
}
const SizeT fullRowBytes = static_cast<SizeT>(texelSize.x()) * bytesPerTexel;
const SizeT fullSliceBytes = static_cast<SizeT>(texelSize.y()) * fullRowBytes;
const SizeT clearRowBytes = static_cast<SizeT>(width) * bytesPerTexel;
Uint8* firstClearRow = nullptr;
for (GLsizei z = 0; z < depth; ++z) {
for (GLsizei y = 0; y < height; ++y) {
Uint8* row = destination +
static_cast<SizeT>(zoffset + z) * fullSliceBytes +
static_cast<SizeT>(yoffset + y) * fullRowBytes +
static_cast<SizeT>(xoffset) * bytesPerTexel;
if (firstClearRow) {
Memcpy(row, firstClearRow, clearRowBytes);
continue;
}
firstClearRow = row;
if (clearPixel.empty()) {
Memset(row, 0, clearRowBytes);
continue;
}
Memcpy(row, clearPixel.data(), bytesPerTexel);
SizeT filled = bytesPerTexel;
while (filled < clearRowBytes) {
const SizeT copySize = std::min(filled, clearRowBytes - filled);
Memcpy(row + filled, row, copySize);
filled += copySize;
}
}
}
textureObject->MarkStorageDirty(uploadTarget, static_cast<Uint>(level), true);
return true;
}
} // namespace
void ClearTexImage(GLuint texture, GLint level, GLenum format, GLenum type, const void* data) {
auto textureObject = GetClearTextureObject(texture, level, __func__);
if (!textureObject) return;
Vector<Uint8> clearPixel;
if (!BuildClearPixel(textureObject, format, type, data, clearPixel)) return;
for (TextureUploadTarget uploadTarget : textureObject->GetUploadTargets()) {
const IntVec3 size = textureObject->GetMipmapTexelSize(uploadTarget, static_cast<Uint>(level));
if (!ClearMipmapRegion(textureObject, uploadTarget, level, 0, 0, 0,
size.x(), size.y(), size.z(), clearPixel, __func__)) {
return;
}
}
}
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) {
auto textureObject = GetClearTextureObject(texture, level, __func__);
if (!textureObject) return;
Vector<Uint8> clearPixel;
if (!BuildClearPixel(textureObject, format, type, data, clearPixel)) return;
const auto& uploadTargets = textureObject->GetUploadTargets();
if (textureObject->GetTarget() == TextureTarget::TextureCubeMap) {
if (zoffset < 0 || depth < 0 ||
static_cast<SizeT>(zoffset) > uploadTargets.size() ||
static_cast<SizeT>(depth) > uploadTargets.size() - static_cast<SizeT>(zoffset)) {
RecordClearTextureError(__func__, ErrorCode::InvalidValue,
"The cube-map clear region selects invalid faces.");
return;
}
for (GLsizei face = 0; face < depth; ++face) {
if (!ClearMipmapRegion(textureObject, uploadTargets[static_cast<SizeT>(zoffset + face)], level,
xoffset, yoffset, 0, width, height, 1, clearPixel, __func__)) {
return;
}
}
return;
}
if (uploadTargets.empty()) {
RecordClearTextureError(__func__, ErrorCode::InvalidOperation,
"The requested texture has no upload target.");
return;
}
ClearMipmapRegion(textureObject, uploadTargets.front(), level, xoffset, yoffset, zoffset,
width, height, depth, clearPixel, __func__);
}
Bool ValidateTextureParameterForTarget(const SharedPtr<MG_State::GLState::ITextureObject>& textureObject, Bool ValidateTextureParameterForTarget(const SharedPtr<MG_State::GLState::ITextureObject>& textureObject,
GLenum pname, GLint param, const char* caller) { GLenum pname, GLint param, const char* caller) {
const auto target = textureObject->GetTarget(); const auto target = textureObject->GetTarget();
@@ -780,14 +571,6 @@ namespace MobileGL::MG_Impl::GLImpl {
case GL_TEXTURE_SWIZZLE_A: { case GL_TEXTURE_SWIZZLE_A: {
auto swizzleParam = MG_Util::ConvertGLEnumPnameToTextureSwizzleParam(pname); auto swizzleParam = MG_Util::ConvertGLEnumPnameToTextureSwizzleParam(pname);
auto swizzleValue = MG_Util::ConvertGLEnumToTextureSwizzleParam(param); auto swizzleValue = MG_Util::ConvertGLEnumToTextureSwizzleParam(param);
if (swizzleValue == TextureSwizzleParam::Unknown) {
// GL CTS texture_swizzle.api_errors: single-value TexParameter* with a value outside
// [RED, GREEN, BLUE, ALPHA, ZERO, ONE] must raise GL_INVALID_ENUM.
MG_State::pGLContext->RecordError(
ErrorCode::InvalidEnum,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller, "Invalid texture swizzle value."));
return;
}
textureObject->SetSwizzleParam(swizzleParam, swizzleValue); textureObject->SetSwizzleParam(swizzleParam, swizzleValue);
break; break;
} }
@@ -977,23 +760,6 @@ namespace MobileGL::MG_Impl::GLImpl {
} }
} }
// Texture-parameter lookups must not raise GL_INVALID_OPERATION when the default texture
// (name 0) is bound: glTexParameter* on default textures is legal GL (the GL CTS state reset
// sets swizzles/levels on texture 0 for every unit x target and expects glGetError() to stay
// clean). Name 0 resolves to the target's real default texture object, so parameters set on
// it are stored and queryable like on any texture.
const SharedPtr<MG_State::GLState::ITextureObject>& GetTextureObjectByTargetForParameter(
TextureUploadTarget textureUploadTarget, TextureTarget textureTarget) {
if (TextureImpl::IsProxyTextureTarget(textureUploadTarget)) {
return TextureImpl::pProxyTextureManager->GetProxyTextureObject(textureUploadTarget);
}
if (textureTarget == TextureTarget::Unknown) {
return nullTextureObject;
}
auto& activeUnit = MG_State::pGLContext->GetTextureUnitObject(MG_State::pGLContext->GetActiveTextureUnit());
return activeUnit.GetBindingSlot(textureTarget).GetBoundObject();
}
void GenerateMipmap_Backend(GLenum target) { void GenerateMipmap_Backend(GLenum target) {
MG_Backend::gBackendFunctionsTable.GL.GenerateMipmap(target); MG_Backend::gBackendFunctionsTable.GL.GenerateMipmap(target);
} }
@@ -1039,12 +805,6 @@ namespace MobileGL::MG_Impl::GLImpl {
MOBILEGL_ASSERT(nullptr != static_cast<MG_State::GLState::TextureObjectMipmap*>(textureObject.get()), MOBILEGL_ASSERT(nullptr != static_cast<MG_State::GLState::TextureObjectMipmap*>(textureObject.get()),
"Texture object here should always be an object with mipmap"); "Texture object here should always be an object with mipmap");
auto textureMipmapObject = static_cast<MG_State::GLState::TextureObjectMipmap*>(textureObject.get()); auto textureMipmapObject = static_cast<MG_State::GLState::TextureObjectMipmap*>(textureObject.get());
if (static_cast<Uint>(level) >= textureMipmapObject->GetMipmapLevelCount()) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidValue,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "Texture level is out of range."));
return;
}
const void* originalPixels = pixels; const void* originalPixels = pixels;
const auto& pixelUnpackBufferObject = const auto& pixelUnpackBufferObject =
@@ -1077,14 +837,6 @@ namespace MobileGL::MG_Impl::GLImpl {
const SizeT destRowSize = static_cast<SizeT>(texelSize.x()) * internalBpp; const SizeT destRowSize = static_cast<SizeT>(texelSize.x()) * internalBpp;
const SizeT destSliceSize = static_cast<SizeT>(texelSize.y()) * destRowSize; const SizeT destSliceSize = static_cast<SizeT>(texelSize.y()) * destRowSize;
if (xoffset + width > static_cast<GLsizei>(texelSize.x()) ||
yoffset + height > static_cast<GLsizei>(texelSize.y()) ||
zoffset + depth > static_cast<GLsizei>(texelSize.z())) {
MGLOG_E("TexSubImage3D_State: Specified region exceeds texture level dimensions");
free(processedPixels);
return;
}
const auto* srcData = static_cast<const Uint8*>(processedPixels); const auto* srcData = static_cast<const Uint8*>(processedPixels);
Uint8* destData = static_cast<Uint8*>(textureMipmapObject->MapMipmapData(textureUploadTarget, level)); Uint8* destData = static_cast<Uint8*>(textureMipmapObject->MapMipmapData(textureUploadTarget, level));
if (destData) { if (destData) {
@@ -1301,7 +1053,7 @@ namespace MobileGL::MG_Impl::GLImpl {
TextureTarget textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target); TextureTarget textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target);
// ======================= Processing ================================ // ======================= Processing ================================
auto& textureObject = GetTextureObjectByTargetForParameter(textureUploadTarget, textureTarget); auto& textureObject = GetTextureObjectByTarget(textureUploadTarget, textureTarget);
if (!textureObject) return; if (!textureObject) return;
switch (pname) { switch (pname) {
@@ -1334,12 +1086,6 @@ namespace MobileGL::MG_Impl::GLImpl {
case GL_TEXTURE_SWIZZLE_A: { case GL_TEXTURE_SWIZZLE_A: {
auto swizzleParam = MG_Util::ConvertGLEnumPnameToTextureSwizzleParam(pname); auto swizzleParam = MG_Util::ConvertGLEnumPnameToTextureSwizzleParam(pname);
auto swizzleValue = MG_Util::ConvertGLEnumToTextureSwizzleParam((GLenum)param); auto swizzleValue = MG_Util::ConvertGLEnumToTextureSwizzleParam((GLenum)param);
if (swizzleValue == TextureSwizzleParam::Unknown) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidEnum,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "Invalid texture swizzle value."));
return;
}
textureObject->SetSwizzleParam(swizzleParam, swizzleValue); textureObject->SetSwizzleParam(swizzleParam, swizzleValue);
break; break;
} }
@@ -1390,7 +1136,7 @@ namespace MobileGL::MG_Impl::GLImpl {
TextureTarget textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target); TextureTarget textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target);
// ======================= Processing ================================ // ======================= Processing ================================
auto& textureObject = GetTextureObjectByTargetForParameter(textureUploadTarget, textureTarget); auto& textureObject = GetTextureObjectByTarget(textureUploadTarget, textureTarget);
if (!textureObject) return; if (!textureObject) return;
TextureParameterObject_State(textureObject, pname, param, __func__); TextureParameterObject_State(textureObject, pname, param, __func__);
@@ -1406,7 +1152,7 @@ namespace MobileGL::MG_Impl::GLImpl {
TextureTarget textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target); TextureTarget textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target);
// ======================= Processing ================================ // ======================= Processing ================================
auto& textureObject = GetTextureObjectByTargetForParameter(textureUploadTarget, textureTarget); auto& textureObject = GetTextureObjectByTarget(textureUploadTarget, textureTarget);
if (!textureObject) return; if (!textureObject) return;
SetTextureBorderColorFromFloats(textureObject, params); SetTextureBorderColorFromFloats(textureObject, params);
break; break;
@@ -1414,7 +1160,7 @@ namespace MobileGL::MG_Impl::GLImpl {
case GL_TEXTURE_SWIZZLE_RGBA: { case GL_TEXTURE_SWIZZLE_RGBA: {
TextureUploadTarget textureUploadTarget = MG_Util::ConvertGLEnumToTextureUploadTarget(target); TextureUploadTarget textureUploadTarget = MG_Util::ConvertGLEnumToTextureUploadTarget(target);
TextureTarget textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target); TextureTarget textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target);
auto& textureObject = GetTextureObjectByTargetForParameter(textureUploadTarget, textureTarget); auto& textureObject = GetTextureObjectByTarget(textureUploadTarget, textureTarget);
if (!textureObject) return; if (!textureObject) return;
GLint signedParams[4] = {static_cast<GLint>(params[0]), static_cast<GLint>(params[1]), GLint signedParams[4] = {static_cast<GLint>(params[0]), static_cast<GLint>(params[1]),
static_cast<GLint>(params[2]), static_cast<GLint>(params[3])}; static_cast<GLint>(params[2]), static_cast<GLint>(params[3])};
@@ -1437,7 +1183,7 @@ namespace MobileGL::MG_Impl::GLImpl {
TextureTarget textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target); TextureTarget textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target);
// ======================= Processing ================================ // ======================= Processing ================================
auto& textureObject = GetTextureObjectByTargetForParameter(textureUploadTarget, textureTarget); auto& textureObject = GetTextureObjectByTarget(textureUploadTarget, textureTarget);
if (!textureObject) return; if (!textureObject) return;
SetTextureBorderColorFromInts(textureObject, params); SetTextureBorderColorFromInts(textureObject, params);
break; break;
@@ -1445,7 +1191,7 @@ namespace MobileGL::MG_Impl::GLImpl {
case GL_TEXTURE_SWIZZLE_RGBA: { case GL_TEXTURE_SWIZZLE_RGBA: {
TextureUploadTarget textureUploadTarget = MG_Util::ConvertGLEnumToTextureUploadTarget(target); TextureUploadTarget textureUploadTarget = MG_Util::ConvertGLEnumToTextureUploadTarget(target);
TextureTarget textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target); TextureTarget textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target);
auto& textureObject = GetTextureObjectByTargetForParameter(textureUploadTarget, textureTarget); auto& textureObject = GetTextureObjectByTarget(textureUploadTarget, textureTarget);
if (!textureObject) return; if (!textureObject) return;
if (!SetTextureSwizzleParamsFromInts(textureObject, params, __func__)) { if (!SetTextureSwizzleParamsFromInts(textureObject, params, __func__)) {
return; return;
@@ -1463,7 +1209,7 @@ namespace MobileGL::MG_Impl::GLImpl {
case GL_TEXTURE_BORDER_COLOR: { case GL_TEXTURE_BORDER_COLOR: {
TextureUploadTarget textureUploadTarget = MG_Util::ConvertGLEnumToTextureUploadTarget(target); TextureUploadTarget textureUploadTarget = MG_Util::ConvertGLEnumToTextureUploadTarget(target);
TextureTarget textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target); TextureTarget textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target);
auto& textureObject = GetTextureObjectByTargetForParameter(textureUploadTarget, textureTarget); auto& textureObject = GetTextureObjectByTarget(textureUploadTarget, textureTarget);
if (!textureObject) return; if (!textureObject) return;
SetTextureBorderColorFromIntegerInts(textureObject, params); SetTextureBorderColorFromIntegerInts(textureObject, params);
break; break;
@@ -1471,7 +1217,7 @@ namespace MobileGL::MG_Impl::GLImpl {
case GL_TEXTURE_SWIZZLE_RGBA: { case GL_TEXTURE_SWIZZLE_RGBA: {
TextureUploadTarget textureUploadTarget = MG_Util::ConvertGLEnumToTextureUploadTarget(target); TextureUploadTarget textureUploadTarget = MG_Util::ConvertGLEnumToTextureUploadTarget(target);
TextureTarget textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target); TextureTarget textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target);
auto& textureObject = GetTextureObjectByTargetForParameter(textureUploadTarget, textureTarget); auto& textureObject = GetTextureObjectByTarget(textureUploadTarget, textureTarget);
if (!textureObject) return; if (!textureObject) return;
GLint signedParams[4] = {static_cast<GLint>(params[0]), static_cast<GLint>(params[1]), GLint signedParams[4] = {static_cast<GLint>(params[0]), static_cast<GLint>(params[1]),
static_cast<GLint>(params[2]), static_cast<GLint>(params[3])}; static_cast<GLint>(params[2]), static_cast<GLint>(params[3])};
@@ -1491,7 +1237,7 @@ namespace MobileGL::MG_Impl::GLImpl {
case GL_TEXTURE_BORDER_COLOR: { case GL_TEXTURE_BORDER_COLOR: {
TextureUploadTarget textureUploadTarget = MG_Util::ConvertGLEnumToTextureUploadTarget(target); TextureUploadTarget textureUploadTarget = MG_Util::ConvertGLEnumToTextureUploadTarget(target);
TextureTarget textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target); TextureTarget textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target);
auto& textureObject = GetTextureObjectByTargetForParameter(textureUploadTarget, textureTarget); auto& textureObject = GetTextureObjectByTarget(textureUploadTarget, textureTarget);
if (!textureObject) return; if (!textureObject) return;
SetTextureBorderColorFromUnsignedInts(textureObject, params); SetTextureBorderColorFromUnsignedInts(textureObject, params);
break; break;
@@ -1502,7 +1248,7 @@ namespace MobileGL::MG_Impl::GLImpl {
TextureTarget textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target); TextureTarget textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target);
// ======================= Processing ================================ // ======================= Processing ================================
auto& textureObject = GetTextureObjectByTargetForParameter(textureUploadTarget, textureTarget); auto& textureObject = GetTextureObjectByTarget(textureUploadTarget, textureTarget);
if (!textureObject) return; if (!textureObject) return;
Vec4<TextureSwizzleParam> swizzleParams; Vec4<TextureSwizzleParam> swizzleParams;
@@ -1553,8 +1299,6 @@ namespace MobileGL::MG_Impl::GLImpl {
auto& textureObject = auto& textureObject =
isProxy ? TextureImpl::pProxyTextureManager->CreateOrReplaceProxyTextureObject(textureUploadTarget) isProxy ? TextureImpl::pProxyTextureManager->CreateOrReplaceProxyTextureObject(textureUploadTarget)
: bindingSlot.GetBoundObject(); : bindingSlot.GetBoundObject();
// Name 0 resolves to the target's default texture object - a real texture this call
// (re)specifies like any other; the slot is never empty anymore.
if (!TextureImpl::ValidateTextureObject(textureObject)) return; if (!TextureImpl::ValidateTextureObject(textureObject)) return;
if (textureObject->GetStorageType() != TextureStorageType::Mipmap) { if (textureObject->GetStorageType() != TextureStorageType::Mipmap) {
MG_State::pGLContext->RecordError( MG_State::pGLContext->RecordError(
@@ -1596,8 +1340,6 @@ namespace MobileGL::MG_Impl::GLImpl {
auto& textureObject = auto& textureObject =
isProxy ? TextureImpl::pProxyTextureManager->CreateOrReplaceProxyTextureObject(textureUploadTarget) isProxy ? TextureImpl::pProxyTextureManager->CreateOrReplaceProxyTextureObject(textureUploadTarget)
: bindingSlot.GetBoundObject(); : bindingSlot.GetBoundObject();
// Name 0 resolves to the target's default texture object - a real texture this call
// (re)specifies like any other; the slot is never empty anymore.
if (!TextureImpl::ValidateTextureObject(textureObject)) return; if (!TextureImpl::ValidateTextureObject(textureObject)) return;
if (textureObject->GetStorageType() != TextureStorageType::Mipmap) { if (textureObject->GetStorageType() != TextureStorageType::Mipmap) {
MG_State::pGLContext->RecordError( MG_State::pGLContext->RecordError(
@@ -1666,8 +1408,6 @@ namespace MobileGL::MG_Impl::GLImpl {
// target and data is not evenly divisible into the number of bytes needed to store in memory a datum // target and data is not evenly divisible into the number of bytes needed to store in memory a datum
// indicated by type. // indicated by type.
// ======================= Processing ================================ // ======================= Processing ================================
textureInternalFormat =
MG_Util::ConvertInternalFormatToSized(textureInternalFormat, textureInputFormat, texturePixelDataType);
auto& activeUnit = MG_State::pGLContext->GetTextureUnitObject(MG_State::pGLContext->GetActiveTextureUnit()); auto& activeUnit = MG_State::pGLContext->GetTextureUnitObject(MG_State::pGLContext->GetActiveTextureUnit());
auto& bindingSlot = activeUnit.GetBindingSlot(textureTarget); auto& bindingSlot = activeUnit.GetBindingSlot(textureTarget);
Bool isProxy = TextureImpl::IsProxyTextureTarget(textureUploadTarget); Bool isProxy = TextureImpl::IsProxyTextureTarget(textureUploadTarget);
@@ -1676,8 +1416,6 @@ namespace MobileGL::MG_Impl::GLImpl {
: bindingSlot.GetBoundObject(); : bindingSlot.GetBoundObject();
// ===================== Error Checking ============================== // ===================== Error Checking ==============================
// Name 0 resolves to the target's default texture object - a real texture this call
// (re)specifies like any other; the slot is never empty anymore.
if (!TextureImpl::ValidateTextureObject(textureObject)) return; if (!TextureImpl::ValidateTextureObject(textureObject)) return;
if (!ValidateTextureMutable(textureObject, __func__)) return; if (!ValidateTextureMutable(textureObject, __func__)) return;
@@ -1711,11 +1449,7 @@ namespace MobileGL::MG_Impl::GLImpl {
auto textureMipmapObject = static_cast<MG_State::GLState::TextureObjectMipmap*>(textureObject.get()); auto textureMipmapObject = static_cast<MG_State::GLState::TextureObjectMipmap*>(textureObject.get());
// Allocate in TextureObject // Allocate in TextureObject
if (isProxy) { textureMipmapObject->AllocateStorage(textureUploadTarget, level, {{width, height, depth}, internalBytes});
MGLOG_D("%s: isProxy = true, not allocating", __func__);
} else {
textureMipmapObject->AllocateStorage(textureUploadTarget, level, {{width, height, depth}, internalBytes});
}
if (!originalPixels) { if (!originalPixels) {
MGLOG_D("%s: No input pixel and no PBO bound, no pixel transfer", __func__); MGLOG_D("%s: No input pixel and no PBO bound, no pixel transfer", __func__);
@@ -1742,7 +1476,6 @@ namespace MobileGL::MG_Impl::GLImpl {
textureMipmapObject->MarkStorageDirty(textureUploadTarget, level, true); textureMipmapObject->MarkStorageDirty(textureUploadTarget, level, true);
free(processedPixels); free(processedPixels);
MaybeAutoGenerateMipmap(target, textureObject, isProxy, level);
} }
void TexImage2D_State(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, void TexImage2D_State(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border,
@@ -1797,8 +1530,6 @@ namespace MobileGL::MG_Impl::GLImpl {
: bindingSlot.GetBoundObject(); : bindingSlot.GetBoundObject();
// ===================== Error Checking ============================== // ===================== Error Checking ==============================
// Name 0 resolves to the target's default texture object - a real texture this call
// (re)specifies like any other; the slot is never empty anymore.
if (!TextureImpl::ValidateTextureObject(textureObject)) return; if (!TextureImpl::ValidateTextureObject(textureObject)) return;
if (!ValidateTextureMutable(textureObject, __func__)) return; if (!ValidateTextureMutable(textureObject, __func__)) return;
@@ -1903,8 +1634,6 @@ namespace MobileGL::MG_Impl::GLImpl {
auto& textureObject = auto& textureObject =
isProxy ? TextureImpl::pProxyTextureManager->CreateOrReplaceProxyTextureObject(textureUploadTarget) isProxy ? TextureImpl::pProxyTextureManager->CreateOrReplaceProxyTextureObject(textureUploadTarget)
: bindingSlot.GetBoundObject(); : bindingSlot.GetBoundObject();
// Name 0 resolves to the target's default texture object - a real texture this call
// (re)specifies like any other; the slot is never empty anymore.
if (!TextureImpl::ValidateTextureObject(textureObject)) return; if (!TextureImpl::ValidateTextureObject(textureObject)) return;
if (!ValidateTextureMutable(textureObject, __func__)) return; if (!ValidateTextureMutable(textureObject, __func__)) return;
@@ -1960,12 +1689,8 @@ namespace MobileGL::MG_Impl::GLImpl {
if (!TextureImpl::ValidateTextureUploadTarget(textureUploadTarget)) return; if (!TextureImpl::ValidateTextureUploadTarget(textureUploadTarget)) return;
if (!TextureImpl::ValidateTextureInternalFormat(textureInternalFormat)) return; if (!TextureImpl::ValidateTextureInternalFormat(textureInternalFormat)) return;
// TODO: make sure `internalformat` is in one of supported format for TexBuffer // TODO: make sure `internalformat` is in one of supported format for TexBuffer
// GL 3.3 core 3.8.5: buffer zero detaches any buffer from the buffer texture - only a
// nonzero name that is not an existing buffer object is an error. This is reachable on
// the default buffer texture (bound whenever texture 0 is bound to GL_TEXTURE_BUFFER),
// which the GL CTS state reset detaches with glTexBuffer(..., 0) after every case.
auto& bufferObject = MG_State::pGLContext->GetBufferObject(buffer); auto& bufferObject = MG_State::pGLContext->GetBufferObject(buffer);
if (buffer != 0 && !bufferObject) { if (!bufferObject) {
MG_State::pGLContext->RecordError( MG_State::pGLContext->RecordError(
ErrorCode::InvalidOperation, ErrorCode::InvalidOperation,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
@@ -1979,8 +1704,6 @@ namespace MobileGL::MG_Impl::GLImpl {
auto& textureObject = bindingSlot.GetBoundObject(); auto& textureObject = bindingSlot.GetBoundObject();
// ===================== Error Checking ============================== // ===================== Error Checking ==============================
// Name 0 is the default buffer texture - a real object the (de)attach operates on, not a
// silent no-op; the slot is never empty now that every unit/target holds its default.
if (!TextureImpl::ValidateTextureObject(textureObject)) return; if (!TextureImpl::ValidateTextureObject(textureObject)) return;
if (textureObject->GetStorageType() != TextureStorageType::Buffer) { if (textureObject->GetStorageType() != TextureStorageType::Buffer) {
MG_State::pGLContext->RecordError( MG_State::pGLContext->RecordError(
@@ -2001,9 +1724,7 @@ namespace MobileGL::MG_Impl::GLImpl {
GLboolean IsTexture_State(GLuint texture) { GLboolean IsTexture_State(GLuint texture) {
// ======================= Processing ================================ // ======================= Processing ================================
// GL 3.3 core 6.1.4: IsTexture generates no error - an unknown, deleted or merely reserved if (!TextureImpl::ValidateTextureName(texture, true)) return GL_FALSE;
// name is just GL_FALSE. Probing with the recording validator (as every other Is* entry
// point already avoids doing) would leave a spurious INVALID_VALUE behind.
return MG_State::pGLContext->ValidateTextureObject(texture) ? GL_TRUE : GL_FALSE; return MG_State::pGLContext->ValidateTextureObject(texture) ? GL_TRUE : GL_FALSE;
} }
@@ -2826,13 +2547,11 @@ namespace MobileGL::MG_Impl::GLImpl {
// ===================== Error Checking ============================== // ===================== Error Checking ==============================
if (!TextureImpl::ValidateTextureTarget(textureTarget)) return; if (!TextureImpl::ValidateTextureTarget(textureTarget)) return;
// GL 3.3 core 3.8: name 0 is the target's default texture object - a real texture that // Name 0 unbinds the current target from the active texture unit.
// glTexImage*/glTexParameter*/glGetTex* must operate on - not "nothing bound". Binding it
// restores the unit/target slot to its initial state.
if (texture == 0) { if (texture == 0) {
auto& currentUnit = MG_State::pGLContext->GetTextureUnitObject(activeUnit); auto& currentUnit = MG_State::pGLContext->GetTextureUnitObject(activeUnit);
auto& bindingSlot = currentUnit.GetBindingSlot(textureTarget); auto& bindingSlot = currentUnit.GetBindingSlot(textureTarget);
bindingSlot.Bind(MG_State::pGLContext->GetDefaultTextureObject(textureTarget)); bindingSlot.Bind(nullptr);
MG_State::pGLContext->NoteTextureUnitTouched(activeUnit); MG_State::pGLContext->NoteTextureUnitTouched(activeUnit);
return; return;
} }
@@ -2844,15 +2563,7 @@ namespace MobileGL::MG_Impl::GLImpl {
return; return;
} }
// GL 3.3 core 3.8.1: a name that GenTextures never returned - or that has since been deleted - if (!TextureImpl::ValidateTextureName(texture)) return;
// is not a legal bind target in the core profile (no application-generated names), and the error
// is INVALID_OPERATION, not INVALID_VALUE.
if (!MG_State::pGLContext->ValidateTextureName(texture)) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidOperation,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", "BindTexture_State", "Invalid texture name"));
return;
}
// ======================= Processing ================================ // ======================= Processing ================================
Bool doesTextureExist = MG_State::pGLContext->ValidateTextureObject(texture); Bool doesTextureExist = MG_State::pGLContext->ValidateTextureObject(texture);
@@ -2873,25 +2584,14 @@ namespace MobileGL::MG_Impl::GLImpl {
void ActiveTexture_State(GLenum texture) { void ActiveTexture_State(GLenum texture) {
// ===================== Error Checking ============================== // ===================== Error Checking ==============================
// GL 3.3 core 3.8: the valid range is [GL_TEXTURE0, GL_TEXTURE0 + if (texture < GL_TEXTURE0 || texture > GL_TEXTURE31) {
// GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS) - NOT a fixed 0..31 range. GL CTS's per-case
// state reset iterates every advertised combined unit, so rejecting units the
// implementation itself reports would leave a sticky GL_INVALID_ENUM behind and abort
// whole test batches. The backend already clamps its advertised value to the state
// layer's MAX_TEXTURE_IMAGE_UNITS capacity.
Int maxCombinedUnits = static_cast<Int>(MG_State::GLState::TextureState::MAX_TEXTURE_IMAGE_UNITS);
if (MG_Backend::pActiveBackendObject) {
maxCombinedUnits = std::min(
maxCombinedUnits, MG_Backend::pActiveBackendObject->GetDynamicParameters().MaxCombinedTextureImageUnits);
}
if (texture < GL_TEXTURE0 || static_cast<Int>(texture - GL_TEXTURE0) >= maxCombinedUnits) {
MG_State::pGLContext->RecordError( MG_State::pGLContext->RecordError(
ErrorCode::InvalidEnum, ErrorCode::InvalidEnum,
MakeUnique<GenericErrorInfo>( MakeUnique<GenericErrorInfo>(
"MG_Impl/GLImpl", "ActiveTexture_State", "MG_Impl/GLImpl", "ActiveTexture_State",
std::format("Texture must be one of GL_TEXTUREi, where i is in the range 0 to {}, but got " std::format("Texture must be one of GL_TEXTUREi, where i is in the range 0 to 31, but got "
"invalid enum: 0x{:X}, which may stand for unit {}.", "invalid enum: 0x{:X}, which may stand for unit {}.",
maxCombinedUnits - 1, texture, texture - GL_TEXTURE0))); texture, texture - GL_TEXTURE0)));
return; return;
} }
@@ -3251,13 +2951,10 @@ namespace MobileGL::MG_Impl::GLImpl {
auto* textureMipmapObject = static_cast<MG_State::GLState::TextureObjectMipmap*>(textureObject.get()); auto* textureMipmapObject = static_cast<MG_State::GLState::TextureObjectMipmap*>(textureObject.get());
textureObject->SetInternalFormat(textureInternalFormat); textureObject->SetInternalFormat(textureInternalFormat);
// Array targets keep their layer count constant across levels; only true 3D
// textures halve depth per level (GL 3.3 §3.9 glTexStorage3D).
const Bool depthMips = DepthParticipatesInMipmapping(textureObject->GetTarget());
for (GLsizei level = 0; level < levels; ++level) { for (GLsizei level = 0; level < levels; ++level) {
const GLsizei levelWidth = std::max<GLsizei>(1, width >> level); const GLsizei levelWidth = std::max<GLsizei>(1, width >> level);
const GLsizei levelHeight = std::max<GLsizei>(1, height >> level); const GLsizei levelHeight = std::max<GLsizei>(1, height >> level);
const GLsizei levelDepth = depthMips ? std::max<GLsizei>(1, depth >> level) : depth; const GLsizei levelDepth = std::max<GLsizei>(1, depth >> level);
const SizeT byteSize = ComputeTextureStorageByteSize(textureInternalFormat, levelWidth, levelHeight, const SizeT byteSize = ComputeTextureStorageByteSize(textureInternalFormat, levelWidth, levelHeight,
levelDepth); levelDepth);
textureMipmapObject->AllocateStorage(textureUploadTarget, level, textureMipmapObject->AllocateStorage(textureUploadTarget, level,
@@ -3293,7 +2990,6 @@ namespace MobileGL::MG_Impl::GLImpl {
auto& bindingSlot = activeUnit.GetBindingSlot(textureTarget); auto& bindingSlot = activeUnit.GetBindingSlot(textureTarget);
auto& textureObject = bindingSlot.GetBoundObject(); auto& textureObject = bindingSlot.GetBoundObject();
if (!TextureImpl::ValidateTextureObject(textureObject)) return; if (!TextureImpl::ValidateTextureObject(textureObject)) return;
if (!TextureImpl::ValidateTextureNotDefault(textureObject, __func__)) return;
TextureStorage1D(textureObject->GetExternalIndex(), levels, internalformat, width); TextureStorage1D(textureObject->GetExternalIndex(), levels, internalformat, width);
} }
@@ -3308,7 +3004,6 @@ namespace MobileGL::MG_Impl::GLImpl {
auto& bindingSlot = activeUnit.GetBindingSlot(textureTarget); auto& bindingSlot = activeUnit.GetBindingSlot(textureTarget);
auto& textureObject = bindingSlot.GetBoundObject(); auto& textureObject = bindingSlot.GetBoundObject();
if (!TextureImpl::ValidateTextureObject(textureObject)) return; if (!TextureImpl::ValidateTextureObject(textureObject)) return;
if (!TextureImpl::ValidateTextureNotDefault(textureObject, __func__)) return;
TextureStorage2D(textureObject->GetExternalIndex(), levels, internalformat, width, height); TextureStorage2D(textureObject->GetExternalIndex(), levels, internalformat, width, height);
} }
@@ -3324,7 +3019,6 @@ namespace MobileGL::MG_Impl::GLImpl {
auto& bindingSlot = activeUnit.GetBindingSlot(textureTarget); auto& bindingSlot = activeUnit.GetBindingSlot(textureTarget);
auto& textureObject = bindingSlot.GetBoundObject(); auto& textureObject = bindingSlot.GetBoundObject();
if (!TextureImpl::ValidateTextureObject(textureObject)) return; if (!TextureImpl::ValidateTextureObject(textureObject)) return;
if (!TextureImpl::ValidateTextureNotDefault(textureObject, __func__)) return;
TextureStorage3D(textureObject->GetExternalIndex(), levels, internalformat, width, height, depth); TextureStorage3D(textureObject->GetExternalIndex(), levels, internalformat, width, height, depth);
} }
@@ -3480,10 +3174,8 @@ namespace MobileGL::MG_Impl::GLImpl {
auto& textureUnit = MG_State::pGLContext->GetTextureUnitObject(static_cast<Int>(unit)); auto& textureUnit = MG_State::pGLContext->GetTextureUnitObject(static_cast<Int>(unit));
MG_State::pGLContext->NoteTextureUnitTouched(static_cast<Int>(unit)); MG_State::pGLContext->NoteTextureUnitTouched(static_cast<Int>(unit));
if (texture == 0) { if (texture == 0) {
// GL 4.5 8.1: texture zero unbinds every target of the unit, i.e. rebinds each
// target's default texture object (the unit's initial state).
for (auto& slot : textureUnit.GetAllBindingSlots()) { for (auto& slot : textureUnit.GetAllBindingSlots()) {
slot.Bind(MG_State::pGLContext->GetDefaultTextureObject(slot.GetTarget())); slot.Bind(nullptr);
} }
return; return;
} }
@@ -4022,14 +3714,6 @@ namespace MobileGL::MG_Impl::GLImpl {
CopyTexSubImage2D_Backend(target, level, xoffset, yoffset, x, y, width, height); CopyTexSubImage2D_Backend(target, level, xoffset, yoffset, x, y, width, height);
} }
void CopyTextureSubImage2D(GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y,
GLsizei width, GLsizei height) {
auto textureObject = GetTextureObjectByName(texture, __func__);
WithTemporarilyBoundNamedTexture(textureObject, [&](GLenum target) {
CopyTexSubImage2D_Backend(target, level, xoffset, yoffset, x, y, width, 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) {
CopyTexSubImage1D_State(target, level, xoffset, x, y, width); CopyTexSubImage1D_State(target, level, xoffset, x, y, width);
} }
@@ -11,9 +11,6 @@
namespace MobileGL::MG_Impl::GLImpl { namespace MobileGL::MG_Impl::GLImpl {
/* @INSERTION_POINT:FUNCTION_DECLARATION@ */ /* @INSERTION_POINT:FUNCTION_DECLARATION@ */
void ClearTexImage(GLuint texture, GLint level, GLenum format, GLenum type, const void* data);
void ClearTexSubImage(GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width,
GLsizei height, GLsizei depth, GLenum format, GLenum type, const void* data);
void BindImageTexture(GLuint unit, GLuint texture, GLint level, GLboolean layered, GLint layer, GLenum access, void BindImageTexture(GLuint unit, GLuint texture, GLint level, GLboolean layered, GLint layer, GLenum access,
GLenum format); GLenum format);
void GenerateMipmap(GLenum target); void GenerateMipmap(GLenum target);
@@ -98,8 +95,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 CopyTextureSubImage2D(GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y,
GLsizei width, GLsizei height);
void CopyTexSubImage1D(GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width); void CopyTexSubImage1D(GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width);
void CopyTexImage2D(GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, void CopyTexImage2D(GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width,
GLsizei height, GLint border); GLsizei height, GLint border);
+11 -45
View File
@@ -175,15 +175,13 @@ namespace MobileGL::MG_Impl::GLImpl::TextureImpl {
return true; return true;
} }
Bool IsIntegerColorInputFormat(TextureInputFormat format) { static Bool IsIntegerColorInputFormat(TextureInputFormat format) {
return format == TextureInputFormat::RInteger || format == TextureInputFormat::RGInteger || return format == TextureInputFormat::RInteger || format == TextureInputFormat::RGInteger ||
format == TextureInputFormat::RGBInteger || format == TextureInputFormat::BGRInteger || format == TextureInputFormat::RGBInteger || format == TextureInputFormat::BGRInteger ||
format == TextureInputFormat::RGBAInteger || format == TextureInputFormat::BGRAInteger || format == TextureInputFormat::RGBAInteger || format == TextureInputFormat::BGRAInteger;
format == TextureInputFormat::GreenInteger || format == TextureInputFormat::BlueInteger ||
format == TextureInputFormat::AlphaInteger;
} }
Bool IsIntegerColorInternalFormat(TextureInternalFormat internalFormat) { static Bool IsIntegerColorInternalFormat(TextureInternalFormat internalFormat) {
switch (internalFormat) { switch (internalFormat) {
case TextureInternalFormat::R8I: case TextureInternalFormat::R8I:
case TextureInternalFormat::R8UI: case TextureInternalFormat::R8UI:
@@ -237,15 +235,17 @@ namespace MobileGL::MG_Impl::GLImpl::TextureImpl {
format == TextureInputFormat::StencilIndex; format == TextureInputFormat::StencilIndex;
} }
// Client-memory format<->type pairing rules shared by pixel uploads (TexImage*) and readbacks // Mirrors the desktop-GL validity matrix used by GL CTS packed_pixels (glcPackedPixelsTests
// (ReadPixels, GetTexImage). Mirrors the desktop-GL validity matrix used by GL CTS packed_pixels // isFormatValid, INPUT_TEXIMAGE): packed-type/format pairing, depth-vs-color mismatch, and
// (glcPackedPixelsTests isFormatValid): packed types constrain the formats they may pair with, and // integer-ness matching all raise GL_INVALID_OPERATION instead of reaching the upload path.
// integer formats reject floating-point types; violations raise GL_INVALID_OPERATION. Bool ValidateTextureInternalFormatCompatibleWithInput(TextureInputFormat format,
Bool ValidateClientFormatTypePairing(TextureInputFormat format, TexturePixelDataType type) { TextureInternalFormat internalFormat,
TexturePixelDataType type) {
const auto recordInvalidOperation = [](const char* message) { const auto recordInvalidOperation = [](const char* message) {
MG_State::pGLContext->RecordError( MG_State::pGLContext->RecordError(
ErrorCode::InvalidOperation, ErrorCode::InvalidOperation,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", "ValidateClientFormatTypePairing", message)); MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", "ValidateTextureInternalFormatCompatibleWithInput",
message));
return false; return false;
}; };
@@ -288,27 +288,6 @@ namespace MobileGL::MG_Impl::GLImpl::TextureImpl {
return recordInvalidOperation("Integer format cannot be used with a floating-point type"); return recordInvalidOperation("Integer format cannot be used with a floating-point type");
} }
return true;
}
// Mirrors the desktop-GL validity matrix used by GL CTS packed_pixels (glcPackedPixelsTests
// isFormatValid, INPUT_TEXIMAGE): packed-type/format pairing, depth-vs-color mismatch, and
// integer-ness matching all raise GL_INVALID_OPERATION instead of reaching the upload path.
Bool ValidateTextureInternalFormatCompatibleWithInput(TextureInputFormat format,
TextureInternalFormat internalFormat,
TexturePixelDataType type) {
const auto recordInvalidOperation = [](const char* message) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidOperation,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", "ValidateTextureInternalFormatCompatibleWithInput",
message));
return false;
};
if (!ValidateClientFormatTypePairing(format, type)) {
return false;
}
// TexImage in core 3.3 has no stencil-only upload path (that arrived with GL 4.4). // TexImage in core 3.3 has no stencil-only upload path (that arrived with GL 4.4).
if (format == TextureInputFormat::StencilIndex) { if (format == TextureInputFormat::StencilIndex) {
return recordInvalidOperation("STENCIL_INDEX is not a valid texture upload format"); return recordInvalidOperation("STENCIL_INDEX is not a valid texture upload format");
@@ -360,19 +339,6 @@ namespace MobileGL::MG_Impl::GLImpl::TextureImpl {
return true; return true;
} }
Bool ValidateTextureNotDefault(const SharedPtr<MG_State::GLState::ITextureObject>& textureObject,
const char* caller) {
if (textureObject && textureObject->GetExternalIndex() == 0) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidOperation,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller,
"This operation is not allowed on the default texture (zero is "
"bound to the target)."));
return false;
}
return true;
}
Bool ValidateTextureTargetUniformity(SharedPtr<MG_State::GLState::ITextureObject> textureObject, Bool ValidateTextureTargetUniformity(SharedPtr<MG_State::GLState::ITextureObject> textureObject,
TextureTarget target) { TextureTarget target) {
if (!textureObject) return true; // should be created later if (!textureObject) return true; // should be created later
@@ -23,19 +23,11 @@ namespace MobileGL::MG_Impl::GLImpl::TextureImpl {
Bool ValidateTextureSizeRange(Int width, Int height, Int depth); Bool ValidateTextureSizeRange(Int width, Int height, Int depth);
Bool ValidateTextureInternalFormat(TextureInternalFormat format); Bool ValidateTextureInternalFormat(TextureInternalFormat format);
Bool ValidateTextureBorderNumber(Int border); Bool ValidateTextureBorderNumber(Int border);
Bool IsIntegerColorInputFormat(TextureInputFormat format);
Bool IsIntegerColorInternalFormat(TextureInternalFormat internalFormat);
Bool ValidateClientFormatTypePairing(TextureInputFormat format, TexturePixelDataType type);
Bool ValidateTextureInternalFormatCompatibleWithInput(TextureInputFormat format, Bool ValidateTextureInternalFormatCompatibleWithInput(TextureInputFormat format,
TextureInternalFormat internalFormat, TextureInternalFormat internalFormat,
TexturePixelDataType type); TexturePixelDataType type);
Bool ValidateTextureLevelWithUploadTarget(TextureUploadTarget target, Int level); Bool ValidateTextureLevelWithUploadTarget(TextureUploadTarget target, Int level);
Bool ValidateTextureObject(SharedPtr<MG_State::GLState::ITextureObject> textureObject); Bool ValidateTextureObject(SharedPtr<MG_State::GLState::ITextureObject> textureObject);
// Rejects the per-target default texture objects (name 0) with GL_INVALID_OPERATION for entry
// points that require a GenTextures-created texture, e.g. TexStorage* ("An INVALID_OPERATION
// error is generated if zero is bound to target", ARB_texture_storage).
Bool ValidateTextureNotDefault(const SharedPtr<MG_State::GLState::ITextureObject>& textureObject,
const char* caller);
Bool ValidateTextureTargetUniformity(SharedPtr<MG_State::GLState::ITextureObject> textureObject, Bool ValidateTextureTargetUniformity(SharedPtr<MG_State::GLState::ITextureObject> textureObject,
TextureTarget target); TextureTarget target);
Bool ValidateTextureSubImageOffsets(SharedPtr<MG_State::GLState::ITextureObject> textureObject, Int xoffset, Bool ValidateTextureSubImageOffsets(SharedPtr<MG_State::GLState::ITextureObject> textureObject, Int xoffset,
@@ -78,12 +78,15 @@ namespace MobileGL::MG_Impl::GLImpl {
} }
static bool ValidateCurrentVertexAttribIndex(GLuint index, const char* funcName) { static bool ValidateCurrentVertexAttribIndex(GLuint index, const char* funcName) {
// GL 3.3 core 2.7: VertexAttrib* sets the current value of ANY generic attribute, if (!VertexArrayImpl::ValidateVertexAttributeIndex(index)) return false;
// including index 0 - only an out-of-range index is an error (INVALID_VALUE). if (index == 0) {
// "Attribute 0 is immutable" was legacy immediate-mode lore; rejecting it broke GL MG_State::pGLContext->RecordError(
// CTS's per-case state reset, which writes vertexAttrib4f(0, 0,0,0,1) after every case. ErrorCode::InvalidOperation,
static_cast<void>(funcName); MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", funcName,
return VertexArrayImpl::ValidateVertexAttributeIndex(index); "Generic vertex attribute 0 current value cannot be modified."));
return false;
}
return true;
} }
static bool TryGetVertexAttribute(GLuint index, const MG_State::GLState::VertexAttribute** outAttr) { static bool TryGetVertexAttribute(GLuint index, const MG_State::GLState::VertexAttribute** outAttr) {
@@ -285,9 +288,7 @@ namespace MobileGL::MG_Impl::GLImpl {
GLuint vao = arrays[i]; GLuint vao = arrays[i];
if (vao == 0) continue; if (vao == 0) continue;
// GL 3.3 core 2.10: unknown names are silently ignored on delete; the shared bind-path if (!VertexArrayImpl::ValidateVertexArrayName(vao)) continue;
// validator would record INVALID_OPERATION instead.
if (!MG_State::pGLContext->ValidateVertexArrayName(vao)) continue;
if (MG_State::pGLContext->GetBoundVertexArray() && if (MG_State::pGLContext->GetBoundVertexArray() &&
MG_State::pGLContext->GetBoundVertexArray() == MG_State::pGLContext->GetVertexArrayObject(vao)) { MG_State::pGLContext->GetBoundVertexArray() == MG_State::pGLContext->GetVertexArrayObject(vao)) {
-14
View File
@@ -764,20 +764,6 @@ namespace MobileGL {
ctx->MajorVersion > 3 || (ctx->MajorVersion == 3 && ctx->MinorVersion >= 1); ctx->MajorVersion > 3 || (ctx->MajorVersion == 3 && ctx->MinorVersion >= 1);
} }
Bool EGLContext::IsCurrentContextOpenGLCompatibilityProfile() const {
const std::lock_guard<std::recursive_mutex> lock(m_mutex);
auto currentIt = m_threadCurrents.find(CurrentThreadKey());
if (currentIt == m_threadCurrents.end()) {
return false;
}
const auto* ctx = TryGetContext(currentIt->second.Context);
// Affirmative check: true only when the host explicitly requested the
// compatibility bit. Attrib-less contexts report false and thus read as core
// for GL_CONTEXT_PROFILE_MASK (EGL defaults 3.x contexts to the core profile).
return ctx && ctx->ClientAPI == EGL_OPENGL_API &&
(ctx->OpenGLProfileMask & EGL_CONTEXT_OPENGL_COMPATIBILITY_PROFILE_BIT);
}
EGLint EGLContext::GetCurrentContextFlags() const { EGLint EGLContext::GetCurrentContextFlags() const {
const std::lock_guard<std::recursive_mutex> lock(m_mutex); const std::lock_guard<std::recursive_mutex> lock(m_mutex);
auto currentIt = m_threadCurrents.find(CurrentThreadKey()); auto currentIt = m_threadCurrents.find(CurrentThreadKey());
-1
View File
@@ -59,7 +59,6 @@ namespace MobileGL {
Bool ValidateContext(EGLContextHandle context) const; Bool ValidateContext(EGLContextHandle context) const;
Bool ValidateContextOnDisplay(EGLDisplayHandle display, EGLContextHandle context) const; Bool ValidateContextOnDisplay(EGLDisplayHandle display, EGLContextHandle context) const;
Bool IsCurrentContextOpenGLCoreProfile() const; Bool IsCurrentContextOpenGLCoreProfile() const;
Bool IsCurrentContextOpenGLCompatibilityProfile() const;
EGLint GetCurrentContextFlags() const; EGLint GetCurrentContextFlags() const;
// Surface // Surface
+1 -15
View File
@@ -9,7 +9,6 @@
#include "Core.h" #include "Core.h"
#include "MG_State/GLState/RenderbufferState/RenderbufferObject.h" #include "MG_State/GLState/RenderbufferState/RenderbufferObject.h"
#include "MG_State/EGLState/Core.h" #include "MG_State/EGLState/Core.h"
#include <Config.h>
namespace MobileGL::MG_State { namespace MobileGL::MG_State {
void Init() { void Init() {
@@ -18,11 +17,6 @@ namespace MobileGL::MG_State {
pEGLContext = MakeUnique<EGLState::EGLContext>(); pEGLContext = MakeUnique<EGLState::EGLContext>();
} }
Bool IsRelaxedSemanticsActive() {
return MG_Config::Features.RelaxedSemantics ||
!(pEGLContext && pEGLContext->IsCurrentContextOpenGLCoreProfile());
}
namespace GLState { namespace GLState {
// Error // Error
void GLContext::RecordError(ErrorCode code, UniquePtr<ErrorInfo> info) { void GLContext::RecordError(ErrorCode code, UniquePtr<ErrorInfo> info) {
@@ -232,16 +226,12 @@ namespace MobileGL::MG_State {
return m_textureState.GetTextureObject(index); return m_textureState.GetTextureObject(index);
} }
const SharedPtr<ITextureObject>& GLContext::GetDefaultTextureObject(TextureTarget target) const {
return m_textureState.GetDefaultTextureObject(target);
}
const SharedPtr<ITextureObject>& GLContext::CreateTextureObject(Uint index, TextureTarget target) { const SharedPtr<ITextureObject>& GLContext::CreateTextureObject(Uint index, TextureTarget target) {
return m_textureState.CreateTextureObject(index, target); return m_textureState.CreateTextureObject(index, target);
} }
void GLContext::MarkTextureObjectForDeletion(Uint index) { void GLContext::MarkTextureObjectForDeletion(Uint index) {
m_textureState.MarkTextureObjectForDeletion(index, IsRelaxedSemanticsActive()); m_textureState.MarkTextureObjectForDeletion(index);
} }
TextureUnit& GLContext::GetTextureUnitObject(Int unit) { TextureUnit& GLContext::GetTextureUnitObject(Int unit) {
@@ -289,10 +279,6 @@ namespace MobileGL::MG_State {
return m_programState.MarkShaderObjectForDeletion(index); return m_programState.MarkShaderObjectForDeletion(index);
} }
void GLContext::ReleaseShaderNameIfOrphaned(const Uint index) {
return m_programState.ReleaseShaderNameIfOrphaned(index);
}
Bool GLContext::ValidateProgramName(const Uint index) const { Bool GLContext::ValidateProgramName(const Uint index) const {
return m_programState.ValidateProgramObject(index); return m_programState.ValidateProgramObject(index);
} }
-13
View File
@@ -95,8 +95,6 @@ namespace MobileGL {
// Texture // Texture
void GenTextureNames(Uint number, Vector<Uint>& textures); void GenTextureNames(Uint number, Vector<Uint>& textures);
const SharedPtr<ITextureObject>& GetTextureObject(Uint index); const SharedPtr<ITextureObject>& GetTextureObject(Uint index);
// Per-target default texture object (name 0); see TextureState::GetDefaultTextureObject.
const SharedPtr<ITextureObject>& GetDefaultTextureObject(TextureTarget target) const;
const SharedPtr<ITextureObject>& CreateTextureObject(Uint index, TextureTarget target); const SharedPtr<ITextureObject>& CreateTextureObject(Uint index, TextureTarget target);
void MarkTextureObjectForDeletion(Uint index); void MarkTextureObjectForDeletion(Uint index);
TextureUnit& GetTextureUnitObject(Int unit); TextureUnit& GetTextureUnitObject(Int unit);
@@ -108,7 +106,6 @@ namespace MobileGL {
// texture is bound at a unit; lets a backend skip re-resolving an unchanged // texture is bound at a unit; lets a backend skip re-resolving an unchanged
// per-draw sampled-texture set. // per-draw sampled-texture set.
Uint64 GetTextureBindGeneration() const { return m_textureState.GetTextureBindGeneration(); } Uint64 GetTextureBindGeneration() const { return m_textureState.GetTextureBindGeneration(); }
void BumpTextureBindGeneration() { m_textureState.BumpTextureBindGeneration(); }
Bool ValidateTextureName(Uint index) const; Bool ValidateTextureName(Uint index) const;
Bool ValidateTextureObject(Uint index) const; Bool ValidateTextureObject(Uint index) const;
Int GetActiveTextureUnit() const; Int GetActiveTextureUnit() const;
@@ -119,9 +116,6 @@ namespace MobileGL {
Uint CreateShader(ShaderStage stage); Uint CreateShader(ShaderStage stage);
void MarkProgramForDeletion(Uint index); void MarkProgramForDeletion(Uint index);
void MarkShaderForDeletion(Uint index); void MarkShaderForDeletion(Uint index);
// Frees a deletion-flagged shader's name once it lost its last GL-visible
// attachment (call after glDetachShader).
void ReleaseShaderNameIfOrphaned(Uint index);
Bool ValidateProgramName(Uint index) const; Bool ValidateProgramName(Uint index) const;
Bool ValidateShaderName(Uint index) const; Bool ValidateShaderName(Uint index) const;
const SharedPtr<ProgramObject>& GetProgramObject(Uint index); const SharedPtr<ProgramObject>& GetProgramObject(Uint index);
@@ -253,12 +247,5 @@ namespace MobileGL {
} // namespace GLState } // namespace GLState
extern UniquePtr<GLState::GLContext> pGLContext; extern UniquePtr<GLState::GLContext> pGLContext;
// True when relaxed GL semantics apply. Strict core rules are enforced only when the
// current EGL context explicitly requested a core profile (core bit in
// EGL_CONTEXT_OPENGL_PROFILE_MASK, or a >=3.1 version request without the compatibility
// bit) and MOBILEGL_RELAXED_SEMANTICS is off; no current context, legacy version
// requests, and the compatibility bit all relax.
Bool IsRelaxedSemanticsActive();
} // namespace MG_State } // namespace MG_State
} // namespace MobileGL } // namespace MobileGL
+1 -10
View File
@@ -7,7 +7,6 @@
// End of Source File Header // End of Source File Header
#include "Error.h" #include "Error.h"
#include <algorithm>
#include <MG_Util/Converters/GLToStr/GLEnumConverter.h> #include <MG_Util/Converters/GLToStr/GLEnumConverter.h>
#include <MG_Util/Converters/MGToGL/ErrorCodeConverter.h> #include <MG_Util/Converters/MGToGL/ErrorCodeConverter.h>
@@ -20,15 +19,7 @@ namespace MobileGL::MG_State::GLState {
MGLOG_E("Recording OpenGL error (%s):\n%s", MGLOG_E("Recording OpenGL error (%s):\n%s",
MG_Util::ConvertGLEnumToString(MG_Util::ConvertErrorCodeToGLEnum(code)).c_str(), MG_Util::ConvertGLEnumToString(MG_Util::ConvertErrorCodeToGLEnum(code)).c_str(),
info->toString().c_str()); info->toString().c_str());
// GL error semantics are sticky flags, not a queue (GL 3.3 core §2.5): with multiple m_errors.push_back(MakeUnique<Error>(code, Move(info)));
// error flags, each is set only while currently unset — repeated errors of the same
// code are discarded until glGetError reads the flag. Unbounded accumulation leaked
// stale errors into later, unrelated glGetError checks (GL CTS deinit noise).
const Bool alreadyPending = std::any_of(m_errors.begin(), m_errors.end(),
[code](const auto& e) { return e->code == code; });
if (!alreadyPending) {
m_errors.push_back(MakeUnique<Error>(code, Move(info)));
}
} }
} }
@@ -8,7 +8,6 @@
#include "ProgramObject.h" #include "ProgramObject.h"
#include <atomic> #include <atomic>
#include <cstring>
#include <MG_Backend/BackendObjects.h> #include <MG_Backend/BackendObjects.h>
#include <MG_State/GLState/VertexArrayState/VertexArrayObject.h> #include <MG_State/GLState/VertexArrayState/VertexArrayObject.h>
#include <MG_Util/Converters/GLToStr/GLEnumConverter.h> #include <MG_Util/Converters/GLToStr/GLEnumConverter.h>
@@ -88,19 +87,6 @@ namespace {
} }
} }
// How many consecutive uniform locations a uniform occupies. Array uniforms (opaque
// or not) span one location per element so glUniform*v(count > 1) and
// glGetUniformLocation("arr[k]") can address elements individually; everything else
// spans a single location. TObjectReflection.size only carries the element count for
// non-block arrays, so prefer the TType, which is authoritative for both.
static MobileGL::Int GetUniformLocationSpan(const glslang::TObjectReflection& uniform) {
const glslang::TType* type = uniform.getType();
if (type != nullptr && type->isSizedArray()) {
return std::max(1, type->getOuterArraySize());
}
return std::max(1, uniform.size);
}
static bool ComputeShaderDeclaresLocalSize(const MobileGL::String& source) { static bool ComputeShaderDeclaresLocalSize(const MobileGL::String& source) {
bool inLineComment = false; bool inLineComment = false;
bool inBlockComment = false; bool inBlockComment = false;
@@ -360,18 +346,7 @@ namespace MobileGL::MG_State::GLState {
} }
MGLOG_D("ProgramObject %u: DoReflection - building reflection", m_externalIndex); MGLOG_D("ProgramObject %u: DoReflection - building reflection", m_externalIndex);
// GL-style reflection naming (GL CTS uniform_block relies on all four): if (!m_program->buildReflection()) {
// - BasicArraySuffix: an array uniform is reported as "arr[0]" per the GL spec.
// - StrictArraySuffix: named-block struct arrays expand per element ("s[0].a",
// "s[1].a", ...) following ARB_program_interface_query rules. Default-block
// (loose) uniforms already expand per element without this option.
// - AllBlockVariables: every member of an active named block is active even when
// no shader statement reads it (ES 3.0/GL 3.3 named-block semantics).
// - SharedStd140UBO: a DECLARED uniform block is active even when no member is
// ever read (reflected from the linker objects). PreprocessShaderSource coerces
// every block to std140, so this covers all of them.
if (!m_program->buildReflection(EShReflectionStrictArraySuffix | EShReflectionBasicArraySuffix |
EShReflectionAllBlockVariables | EShReflectionSharedStd140UBO)) {
m_linkStatus = false; m_linkStatus = false;
m_infoLog = "Build reflection failed."; m_infoLog = "Build reflection failed.";
MGLOG_E("ProgramObject %u: DoReflection - buildReflection() returned false", m_externalIndex); MGLOG_E("ProgramObject %u: DoReflection - buildReflection() returned false", m_externalIndex);
@@ -386,7 +361,8 @@ namespace MobileGL::MG_State::GLState {
for (int i = 0; i < m_activeUniformCount; i++) { for (int i = 0; i < m_activeUniformCount; i++) {
auto& uniform = m_program->getUniform(i); auto& uniform = m_program->getUniform(i);
auto location = uniform.layoutLocation(); auto location = uniform.layoutLocation();
const Int locationSpan = GetUniformLocationSpan(uniform); const Int locationSpan =
(uniform.getType() && uniform.getType()->isOpaque()) ? std::max(1, uniform.size) : 1;
requiredUniformLocations += locationSpan; requiredUniformLocations += locationSpan;
if (location != glslang::TQualifier::layoutLocationEnd) { if (location != glslang::TQualifier::layoutLocationEnd) {
m_maxUniformLocation = std::max(m_maxUniformLocation, location + locationSpan - 1); m_maxUniformLocation = std::max(m_maxUniformLocation, location + locationSpan - 1);
@@ -425,7 +401,8 @@ namespace MobileGL::MG_State::GLState {
m_externalIndex, uniform.name.c_str()); m_externalIndex, uniform.name.c_str());
continue; // will allocate unallocated uniforms later continue; // will allocate unallocated uniforms later
} }
const Int locationSpan = GetUniformLocationSpan(uniform); const Int locationSpan =
(uniform.getType() && uniform.getType()->isOpaque()) ? std::max(1, uniform.size) : 1;
for (Int element = 0; element < locationSpan; ++element) { for (Int element = 0; element < locationSpan; ++element) {
m_uniformIndexInTProgram[location + element] = i; m_uniformIndexInTProgram[location + element] = i;
} }
@@ -442,8 +419,8 @@ namespace MobileGL::MG_State::GLState {
}); });
for (auto index : unallocatedUniformIndex) { for (auto index : unallocatedUniformIndex) {
auto& uniform = m_program->getUniform(index); auto& uniform = m_program->getUniform(index);
const Int locationSpan = GetUniformLocationSpan(uniform); const Int locationSpan =
Bool placed = false; (uniform.getType() && uniform.getType()->isOpaque()) ? std::max(1, uniform.size) : 1;
for (; locNeedle <= m_maxUniformLocation; locNeedle++) { for (; locNeedle <= m_maxUniformLocation; locNeedle++) {
bool hasRoom = locNeedle + locationSpan - 1 <= m_maxUniformLocation; bool hasRoom = locNeedle + locationSpan - 1 <= m_maxUniformLocation;
for (Int element = 0; hasRoom && element < locationSpan; ++element) { for (Int element = 0; hasRoom && element < locationSpan; ++element) {
@@ -460,25 +437,8 @@ namespace MobileGL::MG_State::GLState {
"(index %d)", "(index %d)",
m_externalIndex, uniform.name.c_str(), locNeedle, locNeedle + locationSpan - 1, index); m_externalIndex, uniform.name.c_str(), locNeedle, locNeedle + locationSpan - 1, index);
locNeedle += locationSpan; locNeedle += locationSpan;
placed = true;
break; break;
} }
if (!placed) {
// Explicit-location uniforms can fragment the space so no contiguous
// span is left; grow the table instead of leaving the uniform without
// a location (which would make it unsettable via glUniform*).
const SizeT base = m_uniformIndexInTProgram.size();
m_uniformIndexInTProgram.resize(base + locationSpan, glslang::TQualifier::layoutLocationEnd);
m_uniformSamplerOrImageUnitIndex.resize(base + locationSpan, -1);
m_maxUniformLocation = static_cast<Uint>(base + locationSpan - 1);
for (Int element = 0; element < locationSpan; ++element) {
m_uniformIndexInTProgram[base + element] = index;
}
m_uniformLocations[uniform.name] = static_cast<Uint>(base);
MGLOG_D("ProgramObject %u: Reflection - grew location table to place uniform '%s' at %zu..%zu",
m_externalIndex, uniform.name.c_str(), base, base + locationSpan - 1);
locNeedle = base + locationSpan;
}
} }
for (int i = 0; i < m_activeUniformCount; i++) { for (int i = 0; i < m_activeUniformCount; i++) {
@@ -494,17 +454,10 @@ namespace MobileGL::MG_State::GLState {
continue; continue;
} }
// Reflection names an array "texs[0]" while the layout(binding = N) map from the IO const auto explicitBinding = m_explicitOpaqueUniformBindings.find(uniform.name);
// resolver is keyed by the declared name ("texs"); look up both spellings.
auto explicitBinding = m_explicitOpaqueUniformBindings.find(uniform.name);
if (explicitBinding == m_explicitOpaqueUniformBindings.end() && uniform.name.length() > 3 &&
uniform.name.compare(uniform.name.length() - 3, 3, "[0]") == 0) {
explicitBinding =
m_explicitOpaqueUniformBindings.find(uniform.name.substr(0, uniform.name.length() - 3));
}
const int initialUnit = const int initialUnit =
explicitBinding != m_explicitOpaqueUniformBindings.end() ? static_cast<int>(explicitBinding->second) : 0; explicitBinding != m_explicitOpaqueUniformBindings.end() ? static_cast<int>(explicitBinding->second) : 0;
const Int locationSpan = GetUniformLocationSpan(uniform); const Int locationSpan = std::max(1, uniform.size);
for (Int element = 0; element < locationSpan && for (Int element = 0; element < locationSpan &&
location + element < m_uniformSamplerOrImageUnitIndex.size(); ++element) { location + element < m_uniformSamplerOrImageUnitIndex.size(); ++element) {
m_uniformSamplerOrImageUnitIndex[location + element] = m_uniformSamplerOrImageUnitIndex[location + element] =
@@ -669,11 +622,8 @@ namespace MobileGL::MG_State::GLState {
m_uniformSizesInBytes.clear(); m_uniformSizesInBytes.clear();
m_uniformOffsets.clear(); m_uniformOffsets.clear();
m_globalUboScratch.clear(); m_globalUboScratch.clear();
// kInvalidUniformOffset marks locations that end up without global-UBO backing m_uniformOffsets.resize(m_maxUniformLocation + 1);
// (e.g. the optimizer eliminated every use of the uniform); the fallback pass m_uniformSizesInBytes.resize(m_maxUniformLocation + 1);
// below gives those locations tail storage so glUniform* always has a target.
m_uniformOffsets.resize(m_maxUniformLocation + 1, kInvalidUniformOffset);
m_uniformSizesInBytes.resize(m_maxUniformLocation + 1, 0);
for (SizeT i = 0; i < m_generatedSpirv.size(); i++) { for (SizeT i = 0; i < m_generatedSpirv.size(); i++) {
auto& spv = m_generatedSpirv[i]; auto& spv = m_generatedSpirv[i];
@@ -703,95 +653,31 @@ namespace MobileGL::MG_State::GLState {
m_globalUboScratch.resize(size); m_globalUboScratch.resize(size);
} }
for (const auto& [name, offset] : meta.plainUniformOffsetsInUBO) { for (const auto& [name, offset] : meta.plainUniformOffsetsInUBO) {
// SPIRV-Reflect leaf names never carry a "[0]" suffix; frontend if (m_uniformLocations.find(name) != m_uniformLocations.end()) {
// reflection keys arrays as "arr[0]" (GL naming), so retry with the m_uniformOffsets[m_uniformLocations[name]] = offset;
// suffix before declaring the uniform unbacked. MGLOG_D("ProgramObject %u: GenerateBinary - uniform '%s' offset=%u assigned to location %u",
auto locationIt = m_uniformLocations.find(name); m_externalIndex, name.c_str(), offset, m_uniformLocations[name]);
if (locationIt == m_uniformLocations.end()) { } else {
locationIt = m_uniformLocations.find(name + "[0]"); MGLOG_D("ProgramObject %u: GenerateBinary - uniform '%s' offset=%u but not found in "
} "m_uniformLocations",
if (locationIt == m_uniformLocations.end()) { m_externalIndex, name.c_str(), offset);
MGLOG_D("ProgramObject %u: GenerateBinary - uniform '%s' offset=%u but not found in " }
"m_uniformLocations", }
m_externalIndex, name.c_str(), offset); for (const auto& [name, size] : meta.plainUniformMemberSizesInBytes) {
continue; if (m_uniformLocations.find(name) != m_uniformLocations.end()) {
} m_uniformSizesInBytes[m_uniformLocations[name]] = size;
const Uint baseLocation = locationIt->second; MGLOG_D("ProgramObject %u: GenerateBinary - uniform '%s' size=%u assigned to location %u",
if (!IsValidUniformLocation(static_cast<Int>(baseLocation))) { m_externalIndex, name.c_str(), size, m_uniformLocations[name]);
continue; } else {
} MGLOG_D("ProgramObject %u: GenerateBinary - uniform '%s' size=%u but not found in "
"m_uniformLocations",
const Int uniformIndex = m_uniformIndexInTProgram[baseLocation]; m_externalIndex, name.c_str(), size);
const GLint arraySize = GetActiveUniformArraySize(uniformIndex); }
SizeT memberSize = 0;
const auto sizeIt = meta.plainUniformMemberSizesInBytes.find(name);
if (sizeIt != meta.plainUniformMemberSizesInBytes.end()) {
memberSize = sizeIt->second;
}
Uint arrayStride = 0;
const auto strideIt = meta.plainUniformArrayStridesInUBO.find(name);
if (strideIt != meta.plainUniformArrayStridesInUBO.end()) {
arrayStride = strideIt->second;
}
// Array uniforms span one location per element (see DoReflection);
// give each element its real byte offset inside the UBO.
const GLint elementCount = (arraySize > 1 && arrayStride == 0) ? 1 : std::max(arraySize, 1);
for (GLint element = 0; element < elementCount; ++element) {
const Uint location = baseLocation + static_cast<Uint>(element);
if (location > m_maxUniformLocation || m_uniformIndexInTProgram[location] != uniformIndex) {
break;
}
m_uniformOffsets[location] = offset + static_cast<Uint>(element) * arrayStride;
const SizeT consumed = static_cast<SizeT>(element) * arrayStride;
m_uniformSizesInBytes[location] = memberSize > consumed ? memberSize - consumed : 0;
}
MGLOG_D("ProgramObject %u: GenerateBinary - uniform '%s' offset=%u stride=%u size=%zu assigned "
"to locations %u..%u",
m_externalIndex, name.c_str(), offset, arrayStride, memberSize, baseLocation,
baseLocation + static_cast<Uint>(elementCount) - 1);
} }
MGLOG_D("ProgramObject %u: GenerateBinary - finished parsing module %zu metadata", MGLOG_D("ProgramObject %u: GenerateBinary - finished parsing module %zu metadata",
m_externalIndex, i); m_externalIndex, i);
} }
} }
// Fallback pass: a linked program's active non-opaque uniforms must accept
// glUniform*/glGetUniform* even when the optimized SPIR-V no longer contains
// them (AggressiveDCE can remove a dead loop together with the only loads of a
// uniform -- or the entire global UBO, leaving the scratch unallocated). Hand
// such locations CPU-side storage at the (16-byte aligned) tail of the shadow
// buffer; backends bind at least the SPIR-V-declared UBO range, and the GPU
// never reads these bytes, so this only keeps the GL-visible state coherent.
for (Uint location = 0; location <= m_maxUniformLocation; ++location) {
if (m_uniformOffsets[location] != kInvalidUniformOffset) continue;
if (!IsValidUniformLocation(static_cast<Int>(location))) continue;
const auto& uniform = m_program->getUniform(m_uniformIndexInTProgram[location]);
const glslang::TType* type = uniform.getType();
if (type != nullptr && type->isOpaque()) continue;
if (uniform.index >= 0 && uniform.index < m_program->getNumUniformBlocks() &&
std::strstr(m_program->getUniformBlock(uniform.index).name.c_str(),
MG_Util::ShaderTranspiler::GLOBAL_UBO_NAME) == nullptr) {
// Member of a named uniform block: not settable through glUniform*, so it
// needs no global-UBO shadow storage.
continue;
}
// std140-style slot: the matrix upload paths write column vectors at
// 16-byte strides, so a matrix slot must cover cols * 16 bytes.
SizeT slotSize = MG_Util::GetGLTypeSize(uniform.glDefineType);
if (type != nullptr && type->isMatrix()) {
slotSize = static_cast<SizeT>(type->getMatrixCols()) * 16u;
}
slotSize = (slotSize + 15u) & ~static_cast<SizeT>(15u);
const SizeT slotOffset = (m_globalUboScratch.size() + 15u) & ~static_cast<SizeT>(15u);
m_globalUboScratch.resize(slotOffset + slotSize, 0);
m_uniformOffsets[location] = static_cast<Uint>(slotOffset);
m_uniformSizesInBytes[location] = slotSize;
MGLOG_D("ProgramObject %u: GenerateBinary - uniform '%s' location %u has no UBO backing in the "
"generated SPIR-V (optimized out?); allocated %zu fallback bytes at scratch offset %zu",
m_externalIndex, uniform.name.c_str(), location, slotSize, slotOffset);
}
} }
void ProgramObject::WaitUntilGenerationCompleted() const { void ProgramObject::WaitUntilGenerationCompleted() const {
@@ -18,13 +18,6 @@ namespace MobileGL::MG_State::GLState {
public: public:
ProgramObject(Uint externalIndex) : m_externalIndex(externalIndex), m_lifetimeId(AllocateLifetimeId()) {} ProgramObject(Uint externalIndex) : m_externalIndex(externalIndex), m_lifetimeId(AllocateLifetimeId()) {}
bool ShaderIsAttached(const SharedPtr<ShaderObject>& shader); bool ShaderIsAttached(const SharedPtr<ShaderObject>& shader);
// GL-visible attachment: in the attach list and not pending detach (glDetachShader
// defers the actual removal to the next link).
Bool ShaderIsAttachedGLVisible(const SharedPtr<ShaderObject>& shader) const {
const auto matches = [&shader](const SharedPtr<ShaderObject>& s) { return s.get() == shader.get(); };
if (std::none_of(m_shaders.begin(), m_shaders.end(), matches)) return false;
return std::none_of(m_detachedShaders.begin(), m_detachedShaders.end(), matches);
}
bool AttachShader(const SharedPtr<ShaderObject>& shader); bool AttachShader(const SharedPtr<ShaderObject>& shader);
SizeT DetachShader(const SharedPtr<ShaderObject>& shader); SizeT DetachShader(const SharedPtr<ShaderObject>& shader);
SizeT RemoveShader(const SharedPtr<ShaderObject>& shader); SizeT RemoveShader(const SharedPtr<ShaderObject>& shader);
@@ -50,51 +43,8 @@ namespace MobileGL::MG_State::GLState {
Uint GetMaxUniformLocation() const { return m_maxUniformLocation; } Uint GetMaxUniformLocation() const { return m_maxUniformLocation; }
Int GetUniformLocation(const String& name) const { Int GetUniformLocation(const String& name) const {
const auto it = m_uniformLocations.find(name); const auto it = m_uniformLocations.find(name);
if (it != m_uniformLocations.end()) return (Int)it->second; if (it == m_uniformLocations.end()) return -1;
return (Int)it->second;
// Reflection stores GL-style names: an array uniform is keyed "arr[0]" (its base
// location). A bare "arr" query resolves to that entry; an "arr[k]" query resolves
// to base + k because DoReflection reserves one location per array element.
if (name.empty()) return -1;
if (name.back() != ']') {
const auto suffixedIt = m_uniformLocations.find(name + "[0]");
if (suffixedIt != m_uniformLocations.end()) return (Int)suffixedIt->second;
return -1;
}
if (name.length() < 4) return -1;
const SizeT bracket = name.rfind('[');
// Require at least one digit between the brackets.
if (bracket == String::npos || bracket + 1 >= name.length() - 1) return -1;
Uint element = 0;
for (SizeT i = bracket + 1; i < name.length() - 1; ++i) {
if (name[i] < '0' || name[i] > '9') return -1;
element = element * 10 + static_cast<Uint>(name[i] - '0');
if (element > 0x0FFFFFFFu) return -1;
}
auto baseIt = m_uniformLocations.find(name.substr(0, bracket) + "[0]");
if (baseIt == m_uniformLocations.end()) {
// Legacy key without the "[0]" suffix (defensive; reflection normally
// stores the suffixed form for arrays).
baseIt = m_uniformLocations.find(name.substr(0, bracket));
if (baseIt == m_uniformLocations.end()) return -1;
}
const Int base = (Int)baseIt->second;
if (!IsValidUniformLocation(base)) return -1;
const Int index = m_uniformIndexInTProgram[base];
// "[k]" only addresses arrays ("scalar[0]" is not a uniform name), and only
// in-range elements.
const glslang::TType* type = m_program->getUniform(index).getType();
if (type == nullptr || !type->isArray()) return -1;
if (static_cast<GLint>(element) >= GetActiveUniformArraySize(index)) return -1;
const Int location = base + (Int)element;
if (!UniformLocationsAliasSameUniform(base, location)) return -1;
return location;
}
// True when both locations are element slots of the same uniform variable.
Bool UniformLocationsAliasSameUniform(Int a, Int b) const {
if (!IsValidUniformLocation(a) || !IsValidUniformLocation(b)) return false;
return m_uniformIndexInTProgram[a] == m_uniformIndexInTProgram[b];
} }
Int GetActiveUniformIndex(const String& name) const { Int GetActiveUniformIndex(const String& name) const {
@@ -104,19 +54,6 @@ namespace MobileGL::MG_State::GLState {
return uniformIndex; return uniformIndex;
} }
// Reflection stores an array uniform under "arr[0]"; accept the bare "arr"
// spelling too. The reverse ("arr[0]" against a bare "arr" entry) is kept for
// robustness against non-suffixed reflection entries.
if (!name.empty() && name.back() != ']') {
const String suffixedName = name + "[0]";
const Int suffixedIndex = m_program->getUniformIndex(suffixedName.c_str());
if (suffixedIndex >= 0 && suffixedIndex < m_activeUniformCount &&
m_program->getUniform(suffixedIndex).name == suffixedName) {
return suffixedIndex;
}
return -1;
}
if (name.length() <= 3 || name.compare(name.length() - 3, 3, "[0]") != 0) return -1; if (name.length() <= 3 || name.compare(name.length() - 3, 3, "[0]") != 0) return -1;
const String baseName = name.substr(0, name.length() - 3); const String baseName = name.substr(0, name.length() - 3);
const Int baseIndex = m_program->getUniformIndex(baseName.c_str()); const Int baseIndex = m_program->getUniformIndex(baseName.c_str());
@@ -167,24 +104,11 @@ namespace MobileGL::MG_State::GLState {
} }
// GL_UNIFORM_ARRAY_STRIDE: byte stride of an array member in a named block; 0 for a non-array // GL_UNIFORM_ARRAY_STRIDE: byte stride of an array member in a named block; 0 for a non-array
// block member; -1 for a default-block uniform (glslang yields arrayStride==0 there, so gate // block member; -1 for a default-block uniform. glslang yields arrayStride==0 for the
// on block membership for the spec-mandated -1). The stride itself is derived from the type // default-block case, so gate on block membership to return the spec-mandated -1.
// instead of glslang's reflected arrayStride: for an array nested inside a struct member,
// glslang computes that field against the enclosing STRUCT's (unset) packing and reports a
// tight std430-like stride (ivec2 a[7] -> 8), even though its own member offsets and the
// generated SPIR-V lay the array out with std140 16-byte-rounded strides. MobileGL's UBO
// layout is always std140, where every array element stride rounds up to a vec4.
GLint GetActiveUniformArrayStride(Uint index) const { GLint GetActiveUniformArrayStride(Uint index) const {
const auto& uniform = m_program->getUniform(static_cast<Int>(index)); const auto& uniform = m_program->getUniform(static_cast<Int>(index));
if (uniform.index < 0) return -1; return (uniform.index < 0) ? -1 : uniform.arrayStride;
const glslang::TType* type = uniform.getType();
if (type == nullptr || !type->isArray()) return 0;
if (type->isMatrix()) {
const bool rowMajor = GetActiveUniformIsRowMajor(index) != 0;
const int vectors = rowMajor ? type->getMatrixRows() : type->getMatrixCols();
return GetActiveUniformMatrixStride(index) * vectors;
}
return 16; // scalars and vectors: std140 rounds the element stride up to a vec4
} }
// GL_UNIFORM_IS_ROW_MAJOR: 1 only for a row-major matrix in a named block, else 0. The // GL_UNIFORM_IS_ROW_MAJOR: 1 only for a row-major matrix in a named block, else 0. The
@@ -246,9 +170,6 @@ namespace MobileGL::MG_State::GLState {
auto& uniform = m_program->getUniform(static_cast<Int>(index)); auto& uniform = m_program->getUniform(static_cast<Int>(index));
return uniform.name; return uniform.name;
} }
// Sentinel for a uniform location without global-UBO backing storage (should not
// survive linking: GenerateBinary falls back to tail-allocated scratch storage).
static constexpr Uint kInvalidUniformOffset = ~0u;
Uint GetUniformOffset(Uint location) const { return m_uniformOffsets[location]; } Uint GetUniformOffset(Uint location) const { return m_uniformOffsets[location]; }
Uint GetUniformSizesInBytes(Uint location) const { return MG_Util::GetGLTypeSize(GetUniformType(location)); } Uint GetUniformSizesInBytes(Uint location) const { return MG_Util::GetGLTypeSize(GetUniformType(location)); }
@@ -371,11 +292,6 @@ namespace MobileGL::MG_State::GLState {
Uint GetUniformBlockIndex(const char* name) const { Uint GetUniformBlockIndex(const char* name) const {
auto it = m_uniformBlockIndexByName.find(name); auto it = m_uniformBlockIndexByName.find(name);
if (it != m_uniformBlockIndexByName.end()) return it->second; if (it != m_uniformBlockIndexByName.end()) return it->second;
// Instances of an arrayed block are reflected as "Block[0]".."Block[N-1]";
// a bare "Block" query resolves to the first instance per GL semantics.
const String suffixedName = String(name) + "[0]";
it = m_uniformBlockIndexByName.find(suffixedName);
if (it != m_uniformBlockIndexByName.end()) return it->second;
return 0xFFFFFFFFu; // GL_INVALID_INDEX return 0xFFFFFFFFu; // GL_INVALID_INDEX
} }
Bool IsActiveUniformBlock(Uint index) const { Bool IsActiveUniformBlock(Uint index) const {
@@ -384,11 +300,7 @@ namespace MobileGL::MG_State::GLState {
} }
Uint GetUBOSizeAt(Uint index) const { Uint GetUBOSizeAt(Uint index) const {
if (!IsActiveUniformBlock(index)) return 0; if (!IsActiveUniformBlock(index)) return 0;
// glslang reports the unpadded end offset of the last member, but a std140 block return m_program->getUniformBlock((Int)index).size;
// (like a std140 struct) occupies a vec4-rounded size, and that is what the
// backend compiles: ES drivers reject draws whose bound UBO range is smaller
// than the block (a block ending in ivec3 reported 12 while the driver needs 16).
return (m_program->getUniformBlock((Int)index).size + 15u) & ~15u;
} }
const String& GetUniformBlockName(Uint index) const { const String& GetUniformBlockName(Uint index) const {
@@ -396,30 +308,8 @@ namespace MobileGL::MG_State::GLState {
return ubo.name; return ubo.name;
} }
// Uniform entries that belong to an arrayed uniform block are reflected once, against
// the first instance ("Block[0]"); per GL semantics every other instance shares that
// member set. Maps any instance's block index to the index owning the member entries.
Uint GetUniformBlockMemberOwnerIndex(Uint index) const {
const String& name = GetUniformBlockName(index);
if (name.empty() || name.back() != ']') return index;
const SizeT bracket = name.rfind('[');
if (bracket == String::npos) return index;
const auto it = m_uniformBlockIndexByName.find(name.substr(0, bracket) + "[0]");
if (it != m_uniformBlockIndexByName.end()) return it->second;
return index;
}
// GL_UNIFORM_BLOCK_ACTIVE_UNIFORMS: derived from the same active-uniform scan that
// fills GL_UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES, so the two queries always agree
// (glslang's numMembers counts declared members, which diverges from the reflected
// entry list for struct arrays and arrayed block instances).
Int GetUniformBlockActiveUniformCount(Uint index) const { Int GetUniformBlockActiveUniformCount(Uint index) const {
const Int ownerIndex = static_cast<Int>(GetUniformBlockMemberOwnerIndex(index)); return m_program->getUniformBlock((Int)index).numMembers;
Int count = 0;
for (Uint uniformIndex = 0; uniformIndex < m_activeUniformCount; ++uniformIndex) {
if (GetActiveUniformBlockIndex(uniformIndex) == ownerIndex) ++count;
}
return count;
} }
Bool IsUniformBlockReferencedByStage(Uint index, EShLanguage stage) const { Bool IsUniformBlockReferencedByStage(Uint index, EShLanguage stage) const {
@@ -29,18 +29,9 @@ 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();
programObject.reset(); programObject.reset();
m_programIndexGenerator.Delete(program); 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);
}
}
} }
} }
@@ -75,35 +66,12 @@ namespace MobileGL::MG_State::GLState {
if (!CheckIndexAvail(shader, m_shaderObjects)) return; if (!CheckIndexAvail(shader, m_shaderObjects)) return;
auto& shaderObject = m_shaderObjects[shader]; auto& shaderObject = m_shaderObjects[shader];
if (shaderObject != nullptr) { if (shaderObject != nullptr) {
// glDeleteShader on an attached shader only FLAGS it; the name stays valid (and m_shaderObjects[shader]->MarkAsDeleted();
// glShaderSource/glCompileShader keep working on it) until the shader is detached m_shaderObjects[shader].reset();
// from every program. The GL CTS compiles shaders through exactly this m_shaderIndexGenerator.Delete(shader);
// create-attach-delete-source-compile sequence (uniform_block.common.name_matching).
shaderObject->MarkAsDeleted();
ReleaseShaderNameIfOrphaned(shader);
} }
} }
Bool ProgramState::ShaderHasGLVisibleAttachment(const SharedPtr<ShaderObject>& shaderObject) const {
for (const auto& programObject : m_programObjects) {
if (programObject != nullptr && programObject->ShaderIsAttachedGLVisible(shaderObject)) {
return true;
}
}
// A program deleted while current vacates its table slot but stays alive as the
// current program; its attachments still count.
return m_currentProgram != nullptr && m_currentProgram->ShaderIsAttachedGLVisible(shaderObject);
}
void ProgramState::ReleaseShaderNameIfOrphaned(Uint shader) {
if (!CheckIndexAvail(shader, m_shaderObjects)) return;
auto& shaderObject = m_shaderObjects[shader];
if (shaderObject == nullptr || !shaderObject->GetDeleteStatus()) return;
if (ShaderHasGLVisibleAttachment(shaderObject)) return;
shaderObject.reset();
m_shaderIndexGenerator.Delete(shader);
}
Bool ProgramState::ValidateShaderObject(Uint shader) const { Bool ProgramState::ValidateShaderObject(Uint shader) const {
return CheckIndexAvail(shader, m_shaderObjects) && m_shaderObjects[shader] != nullptr; return CheckIndexAvail(shader, m_shaderObjects) && m_shaderObjects[shader] != nullptr;
} }
@@ -26,16 +26,11 @@ namespace MobileGL::MG_State::GLState {
Uint CreateShader(ShaderStage stage); Uint CreateShader(ShaderStage stage);
const SharedPtr<ShaderObject>& GetShaderObject(Uint shader); const SharedPtr<ShaderObject>& GetShaderObject(Uint shader);
void MarkShaderObjectForDeletion(Uint shader); void MarkShaderObjectForDeletion(Uint shader);
// Frees a deletion-flagged shader's name once no program holds a GL-visible
// attachment to it (the deferred half of glDeleteShader-while-attached).
void ReleaseShaderNameIfOrphaned(Uint shader);
Bool ValidateShaderObject(Uint shader) const; Bool ValidateShaderObject(Uint shader) const;
const SharedPtr<ProgramObject>& GetCurrentProgram() const { return m_currentProgram; } const SharedPtr<ProgramObject>& GetCurrentProgram() const { return m_currentProgram; }
private: private:
Bool ShaderHasGLVisibleAttachment(const SharedPtr<ShaderObject>& shaderObject) const;
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) {
return idx < vec.size(); return idx < vec.size();
@@ -76,14 +76,6 @@ namespace MobileGL {
BGRInteger, BGRInteger,
RGBAInteger, RGBAInteger,
BGRAInteger, BGRAInteger,
// Desktop-GL single-channel client formats (table 3.3): the data holds one component that
// feeds the G, B or A channel; the remaining channels default to 0 (color) / 1 (alpha).
Green,
Blue,
Alpha,
GreenInteger,
BlueInteger,
AlphaInteger,
StencilIndex, StencilIndex,
DepthComponent, DepthComponent,
DepthStencil, DepthStencil,
@@ -7,7 +7,6 @@
// End of Source File Header // End of Source File Header
#include "TextureObject.h" #include "TextureObject.h"
#include "MG_State/GLState/Core.h"
#include "MG_Util/Types.h" #include "MG_Util/Types.h"
#include <MG_Util/Metrics/TextureMetrics.h> #include <MG_Util/Metrics/TextureMetrics.h>
@@ -53,20 +52,6 @@ namespace MobileGL {
void TextureObjectBase::SetInternalFormat(TextureInternalFormat format) { void TextureObjectBase::SetInternalFormat(TextureInternalFormat format) {
if (format == m_internalFormat) return; if (format == m_internalFormat) return;
// A default texture (name 0) changes IsUndefinedDefaultTexture on the
// Unknown<->defined transition, which changes per-draw sampled-set membership
// without any bind happening; bump the bind generation so cached sampled sets
// re-resolve instead of replaying the stale membership. The identity check
// excludes the other externalIndex-0 objects (proxy textures, default-FBO
// attachments) whose definedness never feeds sampled-set membership, so e.g.
// proxy probes cannot churn the cache.
if (m_externalIndex == 0 && pGLContext &&
(m_internalFormat == TextureInternalFormat::Unknown) !=
(format == TextureInternalFormat::Unknown) &&
pGLContext->GetDefaultTextureObject(GetTarget()).get() == this) {
pGLContext->BumpTextureBindGeneration();
}
m_internalFormat = format; m_internalFormat = format;
++m_textureParamsVersion; ++m_textureParamsVersion;
} }
@@ -154,17 +154,6 @@ namespace MobileGL::MG_State::GLState {
: nullptr; : nullptr;
} }
// The per-target default texture objects (name 0) sit permanently in every texture unit's
// binding slots, so "nothing useful bound" is no longer a null slot. While a default texture
// has never been given an image (its internal format is still Unknown) it can contribute
// nothing to sampling; backends treat such a binding exactly like the old empty slot and
// skip per-draw sync/bind work for it. Once an application defines an image on a default
// texture it loses this shortcut and is synced like any other texture.
inline Bool IsUndefinedDefaultTexture(const ITextureObject* texture) {
return texture != nullptr && texture->GetExternalIndex() == 0 &&
texture->GetFormat() == TextureInternalFormat::Unknown;
}
class TextureObjectWithOneMipmap : public TextureObjectMipmap { class TextureObjectWithOneMipmap : public TextureObjectMipmap {
public: public:
TextureObjectWithOneMipmap(TextureTarget target, Uint externalIndex) TextureObjectWithOneMipmap(TextureTarget target, Uint externalIndex)
@@ -18,51 +18,9 @@
#include "TextureObjectStubs.h" #include "TextureObjectStubs.h"
namespace MobileGL::MG_State::GLState { namespace MobileGL::MG_State::GLState {
static SharedPtr<ITextureObject> MakeTextureObjectForTarget(Uint index, TextureTarget target) {
switch (target) {
case TextureTarget::Texture1D:
return MakeShared<TextureObject1D>(index);
case TextureTarget::TextureCubeMap:
return MakeShared<TextureObject2DCube>(index);
case TextureTarget::Texture2D:
return MakeShared<TextureObject2D>(index);
case TextureTarget::Texture3D:
return MakeShared<TextureObject3D>(index);
case TextureTarget::TextureBuffer:
return MakeShared<TextureObjectBuffer>(index);
// These texture types are still stubbed:
case TextureTarget::TextureRectangle:
return MakeShared<TextureObjectRectangle>(index);
case TextureTarget::Texture2DMultisample:
return MakeShared<TextureObject2DMultisample>(index);
case TextureTarget::Texture1DArray:
return MakeShared<TextureObject1DArray>(index);
case TextureTarget::Texture2DArray:
return MakeShared<TextureObject2DArray>(index);
case TextureTarget::TextureCubeMapArray:
return MakeShared<TextureObjectCubeMapArray>(index);
case TextureTarget::Texture2DMultisampleArray:
return MakeShared<TextureObject2DMultisampleArray>(index);
default:
MOBILEGL_ASSERT(false, "Unimplemented texture type when creating texture object!: %d", (int)target);
return nullptr;
}
}
TextureState::TextureState() : m_indexGenerator(1024, 1) { TextureState::TextureState() : m_indexGenerator(1024, 1) {
// GL 3.3 core 3.8: each target owns one default texture object (name 0) per context,
// shared across all texture units, and it is the initial binding of every unit/target
// slot. It is created outside m_textureObjects so name-based paths (glIsTexture,
// GenTextures/DeleteTextures, by-name DSA lookups) never see it.
for (int i = 0; i < (int)TextureTarget::TextureTargetCount; ++i) {
m_defaultTextureObjects[i] = MakeTextureObjectForTarget(0, static_cast<TextureTarget>(i));
}
for (int i = 0; i < MAX_TEXTURE_IMAGE_UNITS; ++i) { for (int i = 0; i < MAX_TEXTURE_IMAGE_UNITS; ++i) {
m_textureUnits[i] = TextureUnit(); m_textureUnits[i] = TextureUnit();
for (auto& bindingSlot : m_textureUnits[i].GetAllBindingSlots()) {
bindingSlot.Bind(m_defaultTextureObjects[(int)bindingSlot.GetTarget()]);
}
} }
} }
@@ -75,12 +33,6 @@ namespace MobileGL::MG_State::GLState {
return nullTextureObject; return nullTextureObject;
} }
const SharedPtr<ITextureObject>& TextureState::GetDefaultTextureObject(TextureTarget target) const {
MOBILEGL_ASSERT(target > TextureTarget::Unknown && target < TextureTarget::TextureTargetCount,
"GetDefaultTextureObject: invalid texture target %d", (int)target);
return m_defaultTextureObjects[(int)target];
}
void TextureState::GenerateNames(Uint number, Vector<Uint>& textures) { void TextureState::GenerateNames(Uint number, Vector<Uint>& textures) {
textures.resize(number); textures.resize(number);
m_indexGenerator.Generate(number, textures.data()); m_indexGenerator.Generate(number, textures.data());
@@ -88,15 +40,52 @@ namespace MobileGL::MG_State::GLState {
const SharedPtr<ITextureObject>& TextureState::CreateTextureObject(Uint index, TextureTarget target) { const SharedPtr<ITextureObject>& TextureState::CreateTextureObject(Uint index, TextureTarget target) {
auto& textureObject = m_textureObjects[index]; auto& textureObject = m_textureObjects[index];
textureObject = MakeTextureObjectForTarget(index, target); switch (target) {
if (!textureObject) { case TextureTarget::Texture1D:
textureObject = MakeShared<TextureObject1D>(index);
break;
case TextureTarget::TextureCubeMap:
textureObject = MakeShared<TextureObject2DCube>(index);
break;
case TextureTarget::Texture2D:
textureObject = MakeShared<TextureObject2D>(index);
break;
case TextureTarget::Texture3D:
textureObject = MakeShared<TextureObject3D>(index);
break;
case TextureTarget::TextureBuffer:
textureObject = MakeShared<TextureObjectBuffer>(index);
break;
// These texture types are still stubbed:
case TextureTarget::TextureRectangle:
textureObject = MakeShared<TextureObjectRectangle>(index);
break;
case TextureTarget::Texture2DMultisample:
textureObject = MakeShared<TextureObject2DMultisample>(index);
break;
case TextureTarget::Texture1DArray:
textureObject = MakeShared<TextureObject1DArray>(index);
break;
case TextureTarget::Texture2DArray:
textureObject = MakeShared<TextureObject2DArray>(index);
break;
case TextureTarget::TextureCubeMapArray:
textureObject = MakeShared<TextureObjectCubeMapArray>(index);
break;
case TextureTarget::Texture2DMultisampleArray:
textureObject = MakeShared<TextureObject2DMultisampleArray>(index);
break;
default:
MOBILEGL_ASSERT(false, "Unimplemented texture type when creating texture object!: %d", (int)target);
static SharedPtr<ITextureObject> nullTextureObject = nullptr; static SharedPtr<ITextureObject> nullTextureObject = nullptr;
return nullTextureObject; return nullTextureObject;
} }
return textureObject; return textureObject;
} }
void TextureState::MarkTextureObjectForDeletion(Uint index, Bool keepUnboundReservation) { void TextureState::MarkTextureObjectForDeletion(Uint index) {
if (m_indexGenerator.IsValid(index)) { if (m_indexGenerator.IsValid(index)) {
auto it = m_textureObjects.find(index); auto it = m_textureObjects.find(index);
if (it != m_textureObjects.end()) { if (it != m_textureObjects.end()) {
@@ -105,9 +94,7 @@ namespace MobileGL::MG_State::GLState {
auto& bindingSlots = m_textureUnits[unit].GetAllBindingSlots(); auto& bindingSlots = m_textureUnits[unit].GetAllBindingSlots();
for (auto& bindingSlot : bindingSlots) { for (auto& bindingSlot : bindingSlots) {
if (bindingSlot.GetBoundObject() == it->second) { if (bindingSlot.GetBoundObject() == it->second) {
// GL 3.3 core 3.8.1: deleting a bound texture rebinds zero, i.e. the bindingSlot.Bind(nullptr);
// target's default texture object, on every unit it was bound to.
bindingSlot.Bind(m_defaultTextureObjects[(int)bindingSlot.GetTarget()]);
} }
} }
} }
@@ -123,15 +110,7 @@ namespace MobileGL::MG_State::GLState {
BumpTextureBindGeneration(); BumpTextureBindGeneration();
m_textureObjects.erase(index); m_textureObjects.erase(index);
m_indexGenerator.Delete(index); m_indexGenerator.Delete(index);
} else if (!keepUnboundReservation) {
// GL 3.3 core 3.8.1 makes a deleted name unused again even when GenTextures only
// reserved it and no bind ever instantiated an object (so a later bind of it must
// fail), and the reservation has to return to the free list.
m_indexGenerator.Delete(index);
} }
// Relaxed semantics: legacy apps may delete a generated name before its first
// bind, then bind and populate that same name. Keep such a reservation alive there;
// a real texture object reaching deletion still releases its index above.
} }
} }
@@ -49,18 +49,12 @@ namespace MobileGL::MG_State::GLState {
void GenerateNames(Uint number, Vector<Uint>& textures); void GenerateNames(Uint number, Vector<Uint>& textures);
const SharedPtr<ITextureObject>& CreateTextureObject(Uint index, TextureTarget target); const SharedPtr<ITextureObject>& CreateTextureObject(Uint index, TextureTarget target);
const SharedPtr<ITextureObject>& GetTextureObject(Uint index); const SharedPtr<ITextureObject>& GetTextureObject(Uint index);
// The context's default texture object (name 0) for `target`. GL 3.3 core 3.8: texture
// zero names a real, per-target texture object shared by every texture unit; binding 0
// binds it, and image/parameter calls on it must work like on any texture. It is not a
// GenTextures name: it lives outside m_textureObjects (so glIsTexture(0) stays GL_FALSE
// and by-name lookups keep failing for 0) and can never be deleted.
const SharedPtr<ITextureObject>& GetDefaultTextureObject(TextureTarget target) const;
TextureUnit& GetUnitObject(Int unit); TextureUnit& GetUnitObject(Int unit);
ImageTextureBinding& GetImageTextureBinding(Int unit); ImageTextureBinding& GetImageTextureBinding(Int unit);
const ImageTextureBinding& GetImageTextureBinding(Int unit) const; const ImageTextureBinding& GetImageTextureBinding(Int unit) const;
Int GetActiveTextureUnit() const; Int GetActiveTextureUnit() const;
void SetActiveTextureUnit(Int unit); void SetActiveTextureUnit(Int unit);
void MarkTextureObjectForDeletion(Uint index, Bool keepUnboundReservation); void MarkTextureObjectForDeletion(Uint index);
Bool ValidateName(Uint index) const; Bool ValidateName(Uint index) const;
Bool ValidateTextureObject(Uint index) const; Bool ValidateTextureObject(Uint index) const;
@@ -89,8 +83,5 @@ namespace MobileGL::MG_State::GLState {
Array<ImageTextureBinding, MAX_TEXTURE_IMAGE_UNITS> m_imageTextureBindings; Array<ImageTextureBinding, MAX_TEXTURE_IMAGE_UNITS> m_imageTextureBindings;
IndexGenerator<Uint> m_indexGenerator; IndexGenerator<Uint> m_indexGenerator;
UnorderedMap<GLuint, SharedPtr<ITextureObject>> m_textureObjects; UnorderedMap<GLuint, SharedPtr<ITextureObject>> m_textureObjects;
// One default texture object (external name 0) per target, created with the context and
// immortal for its lifetime; the initial binding of every unit/target slot.
Array<SharedPtr<ITextureObject>, (int)TextureTarget::TextureTargetCount> m_defaultTextureObjects;
}; };
} // namespace MobileGL::MG_State::GLState } // namespace MobileGL::MG_State::GLState
@@ -40,16 +40,10 @@ if (APPLE)
target_link_libraries(DirectVulkanSanityTest PRIVATE objc) target_link_libraries(DirectVulkanSanityTest PRIVATE objc)
target_link_libraries(DirectVulkanSanityTest PRIVATE ${QUARTZCORE_FRAMEWORK}) target_link_libraries(DirectVulkanSanityTest PRIVATE ${QUARTZCORE_FRAMEWORK})
endif() endif()
if (MSVC)
# The GLES headers declare gl* as dllimport on Windows, so objects like GetProcAddress.cpp
# reference __imp_gl*. Those resolve against the in-library GL entry-point definitions only
# if their objects are part of the link; pull the whole static library like the DLL does.
target_link_options(DirectVulkanSanityTest PRIVATE /WHOLEARCHIVE:MobileGL_s)
endif()
target_compile_definitions(DirectVulkanSanityTest PRIVATE -DNOMINMAX) target_compile_definitions(DirectVulkanSanityTest PRIVATE -DNOMINMAX)
include(GoogleTest) include(GoogleTest)
gtest_discover_tests(DirectVulkanSanityTest DISCOVERY_TIMEOUT 30 PROPERTIES LABELS integration) gtest_discover_tests(DirectVulkanSanityTest DISCOVERY_TIMEOUT 30)
add_executable( add_executable(
DirectVulkanTestExec DirectVulkanTestExec
@@ -12,10 +12,6 @@
#include <string> #include <string>
#include <vector> #include <vector>
#include <algorithm>
#include <MG_Backend/DirectGLES/BackendObject_DirectGLES.h>
#include <MG_Backend/DirectVulkan/BackendObject_DirectVulkan.h>
#include <MG_Util/BackendLoaders/OpenGL/Loader.h> #include <MG_Util/BackendLoaders/OpenGL/Loader.h>
// ProbeIndirectInstanceIdIncludesBaseInstance is driven against a fake GLES driver: // ProbeIndirectInstanceIdIncludesBaseInstance is driven against a fake GLES driver:
@@ -27,13 +23,6 @@ namespace {
struct FakeDriverState { struct FakeDriverState {
// Behavior knobs, configured per test before running the probe. // Behavior knobs, configured per test before running the probe.
GLint maxVertexSsboBlocks = 4; GLint maxVertexSsboBlocks = 4;
GLint glesMajorVersion = 3;
GLint glesMinorVersion = 1;
GLint maxVertexImageUniforms = 2;
GLint maxGeometryImageUniforms = 3;
GLint maxFragmentImageUniforms = 4;
GLint maxComputeImageUniforms = 5;
bool maxGeometryImageUniformsQueried = false;
// Emulates ANGLE-on-Vulkan: the draw reads the indirect command's // Emulates ANGLE-on-Vulkan: the draw reads the indirect command's
// baseInstance word and exposes it through gl_InstanceID. // baseInstance word and exposes it through gl_InstanceID.
bool drawLeaksBaseInstanceWord = false; bool drawLeaksBaseInstanceWord = false;
@@ -42,11 +31,6 @@ namespace {
GLenum pendingError = GL_NO_ERROR; GLenum pendingError = GL_NO_ERROR;
std::vector<std::string> extensions; std::vector<std::string> extensions;
// GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT the fake reports, and whether it was ever asked:
// querying it on a driver without the extension would raise GL_INVALID_ENUM.
GLfloat maxTextureMaxAnisotropy = 16.0f;
bool maxTextureMaxAnisotropyQueried = false;
GLuint nextBufferId = 1; GLuint nextBufferId = 1;
GLuint nextShaderId = 1; GLuint nextShaderId = 1;
GLuint nextProgramId = 1; GLuint nextProgramId = 1;
@@ -95,26 +79,13 @@ namespace {
case GL_MAX_VERTEX_SHADER_STORAGE_BLOCKS: case GL_MAX_VERTEX_SHADER_STORAGE_BLOCKS:
*data = g_fake.maxVertexSsboBlocks; *data = g_fake.maxVertexSsboBlocks;
break; break;
case GL_MAX_VERTEX_IMAGE_UNIFORMS:
*data = g_fake.maxVertexImageUniforms;
break;
case GL_MAX_GEOMETRY_IMAGE_UNIFORMS:
g_fake.maxGeometryImageUniformsQueried = true;
*data = g_fake.maxGeometryImageUniforms;
break;
case GL_MAX_FRAGMENT_IMAGE_UNIFORMS:
*data = g_fake.maxFragmentImageUniforms;
break;
case GL_MAX_COMPUTE_IMAGE_UNIFORMS:
*data = g_fake.maxComputeImageUniforms;
break;
// FillInGLESCapabilities reads the context version before running the // FillInGLESCapabilities reads the context version before running the
// baseInstance probe, which requires ES >= 3.1. // baseInstance probe, which requires ES >= 3.1.
case GL_MAJOR_VERSION: case GL_MAJOR_VERSION:
*data = g_fake.glesMajorVersion; *data = 3;
break; break;
case GL_MINOR_VERSION: case GL_MINOR_VERSION:
*data = g_fake.glesMinorVersion; *data = 1;
break; break;
case GL_NUM_EXTENSIONS: case GL_NUM_EXTENSIONS:
*data = static_cast<GLint>(g_fake.extensions.size()); *data = static_cast<GLint>(g_fake.extensions.size());
@@ -151,10 +122,6 @@ namespace {
}; };
funcs.glGetFloatv = [](GLenum pname, GLfloat* data) { funcs.glGetFloatv = [](GLenum pname, GLfloat* data) {
switch (pname) { switch (pname) {
case GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT:
g_fake.maxTextureMaxAnisotropyQueried = true;
data[0] = g_fake.maxTextureMaxAnisotropy;
break;
// Two-component range queries. // Two-component range queries.
case GL_ALIASED_LINE_WIDTH_RANGE: case GL_ALIASED_LINE_WIDTH_RANGE:
case GL_SMOOTH_LINE_WIDTH_RANGE: case GL_SMOOTH_LINE_WIDTH_RANGE:
@@ -437,76 +404,6 @@ TEST(IndirectInstanceIdProbe, FillInCapabilitiesWiresProbeResult) {
ExpectProbeReleasedAllObjects(); ExpectProbeReleasedAllObjects();
} }
TEST(ImageUniformCapabilities, QueriesRealPerStageLimitsAndConservativelyGatesGeometry) {
const auto funcs = MakeFakeGLESFunctions();
ResetFakeDriver();
g_fake.maxVertexSsboBlocks = 0;
MobileGL::MG_External::GLESCapabilities es31Caps;
ASSERT_TRUE(MobileGL::MG_Util::BackendLoader::FillInGLESCapabilities(es31Caps, funcs));
EXPECT_EQ(es31Caps.MaxVertexImageUniforms, g_fake.maxVertexImageUniforms);
EXPECT_EQ(es31Caps.MaxGeometryImageUniforms, 0);
EXPECT_EQ(es31Caps.MaxFragmentImageUniforms, g_fake.maxFragmentImageUniforms);
EXPECT_EQ(es31Caps.MaxComputeImageUniforms, g_fake.maxComputeImageUniforms);
EXPECT_FALSE(g_fake.maxGeometryImageUniformsQueried);
ResetFakeDriver();
g_fake.maxVertexSsboBlocks = 0;
g_fake.glesMinorVersion = 2;
MobileGL::MG_External::GLESCapabilities es32Caps;
ASSERT_TRUE(MobileGL::MG_Util::BackendLoader::FillInGLESCapabilities(es32Caps, funcs));
EXPECT_EQ(es32Caps.MaxVertexImageUniforms, g_fake.maxVertexImageUniforms);
EXPECT_EQ(es32Caps.MaxGeometryImageUniforms, g_fake.maxGeometryImageUniforms);
EXPECT_EQ(es32Caps.MaxFragmentImageUniforms, g_fake.maxFragmentImageUniforms);
EXPECT_EQ(es32Caps.MaxComputeImageUniforms, g_fake.maxComputeImageUniforms);
EXPECT_TRUE(g_fake.maxGeometryImageUniformsQueried);
}
// The extension string is what apps gate on (LWJGL builds GLCapabilities from it), so advertising
// it on a driver that cannot filter anisotropically would leave them silently on trilinear.
TEST(TextureAnisotropyCapabilities, ExtensionIsAdvertisedOnlyWhenTheHostDriverSupportsIt) {
const auto contains = [](const MobileGL::Vector<MobileGL::GLExtension>& extensions,
MobileGL::GLExtension wanted) {
return std::find(extensions.begin(), extensions.end(), wanted) != extensions.end();
};
const auto without = MobileGL::MG_Backend::DirectGLES::BuildAdvertisedExtensions(false, false);
EXPECT_FALSE(contains(without, MobileGL::E_GL_EXT_texture_filter_anisotropic));
EXPECT_FALSE(contains(without, MobileGL::E_GL_ARB_texture_filter_anisotropic));
const auto with = MobileGL::MG_Backend::DirectGLES::BuildAdvertisedExtensions(false, true);
EXPECT_TRUE(contains(with, MobileGL::E_GL_EXT_texture_filter_anisotropic));
EXPECT_TRUE(contains(with, MobileGL::E_GL_ARB_texture_filter_anisotropic));
// Same rule on the Vulkan backend, where the gate is the samplerAnisotropy device feature.
const auto vkWithout = MobileGL::MG_Backend::DirectVulkan::BuildAdvertisedExtensions(false, false, false);
EXPECT_FALSE(contains(vkWithout, MobileGL::E_GL_EXT_texture_filter_anisotropic));
const auto vkWith = MobileGL::MG_Backend::DirectVulkan::BuildAdvertisedExtensions(false, false, true);
EXPECT_TRUE(contains(vkWith, MobileGL::E_GL_EXT_texture_filter_anisotropic));
EXPECT_TRUE(contains(vkWith, MobileGL::E_GL_ARB_texture_filter_anisotropic));
}
TEST(TextureAnisotropyCapabilities, MaxAnisotropyIsQueriedOnlyWhenTheExtensionIsPresent) {
ResetFakeDriver();
g_fake.maxVertexSsboBlocks = 0;
const auto funcs = MakeFakeGLESFunctions();
MobileGL::MG_External::GLESCapabilities absentCaps;
ASSERT_TRUE(MobileGL::MG_Util::BackendLoader::FillInGLESCapabilities(absentCaps, funcs));
// Never probed (it would be GL_INVALID_ENUM), and reported as "no anisotropy".
EXPECT_FALSE(g_fake.maxTextureMaxAnisotropyQueried);
EXPECT_FLOAT_EQ(absentCaps.MaxTextureMaxAnisotropy, 1.0f);
ResetFakeDriver();
g_fake.maxVertexSsboBlocks = 0;
g_fake.maxTextureMaxAnisotropy = 16.0f;
g_fake.extensions.emplace_back("GL_EXT_texture_filter_anisotropic");
MobileGL::MG_External::GLESCapabilities presentCaps;
ASSERT_TRUE(MobileGL::MG_Util::BackendLoader::FillInGLESCapabilities(presentCaps, funcs));
EXPECT_TRUE(g_fake.maxTextureMaxAnisotropyQueried);
EXPECT_FLOAT_EQ(presentCaps.MaxTextureMaxAnisotropy, 16.0f);
}
TEST(TextureAnisotropyCapabilities, ExtensionPresenceIsDetectedExactly) { TEST(TextureAnisotropyCapabilities, ExtensionPresenceIsDetectedExactly) {
ResetFakeDriver(); ResetFakeDriver();
g_fake.maxVertexSsboBlocks = 0; g_fake.maxVertexSsboBlocks = 0;
@@ -17,4 +17,4 @@ target_link_libraries(
) )
include(GoogleTest) include(GoogleTest)
gtest_discover_tests(BackendLoaderTest DISCOVERY_TIMEOUT 30 PROPERTIES LABELS unit) gtest_discover_tests(BackendLoaderTest DISCOVERY_TIMEOUT 30)
+2 -73
View File
@@ -8,8 +8,6 @@
#include <gtest/gtest.h> #include <gtest/gtest.h>
#include <limits>
#include "Includes.h" #include "Includes.h"
#include "Init.h" #include "Init.h"
#include <Config.h> #include <Config.h>
@@ -22,31 +20,9 @@ using namespace MobileGL;
class BufferTest : public ::testing::Test { class BufferTest : public ::testing::Test {
protected: protected:
// GL error flags are sticky per error code and the context outlives an individual test in this void SetUp() override { MobileGL::Initialize(); }
// binary, so drain whatever an earlier test left pending - otherwise an error-code assertion
// here reads someone else's error. Bounded: one flag per code, so this cannot hang the suite.
static void DrainPendingGlErrors() {
for (Int drained = 0; drained < 16 && MG_Impl::GLImpl::GetError() != GL_NO_ERROR; ++drained) {
}
}
// The call under test must raise exactly the expected error and nothing more: a second pending void TearDown() override {}
// error means one entry point queued several, which GetError() would hand out at an unrelated
// call site later on.
static void ExpectSingleGlError(GLenum expected) {
EXPECT_EQ(MG_Impl::GLImpl::GetError(), expected);
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR) << "the call recorded more than one error";
}
void SetUp() override {
MobileGL::Initialize();
DrainPendingGlErrors();
}
void TearDown() override {
// Attribute a leaked error to the test that caused it instead of to whoever runs next.
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR) << "test left an unconsumed GL error behind";
}
}; };
TEST_F(BufferTest, Binding) { TEST_F(BufferTest, Binding) {
@@ -129,53 +105,6 @@ TEST_F(BufferTest, GenerateManyNames_NoPrematureCreation) {
} }
} }
// GL 3.3 core 2.9 name lifecycle. The same three rules are asserted per object family (see the
// texture/vertex-array/framebuffer/renderbuffer suites): a deleted or never-generated name is
// INVALID_OPERATION to bind, deleting one is silent, and a generated-but-never-bound reservation
// is still released so the name gets recycled.
TEST_F(BufferTest, DeleteOfUnknownOrAlreadyDeletedBufferNameIsSilent) {
GLuint buffer = 0;
MG_Impl::GLImpl::GenBuffers(1, &buffer);
ASSERT_NE(buffer, 0u);
ASSERT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
MG_Impl::GLImpl::DeleteBuffers(1, &buffer);
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
// Double delete, name 0 and a never-generated name must all be ignored without an error.
MG_Impl::GLImpl::DeleteBuffers(1, &buffer);
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
const GLuint unknownNames[] = {0u, std::numeric_limits<GLuint>::max()};
MG_Impl::GLImpl::DeleteBuffers(2, unknownNames);
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
}
TEST_F(BufferTest, DeleteGeneratedButUnboundBufferNameReleasesReservationAndBindFails) {
GLuint buffer = 0;
MG_Impl::GLImpl::GenBuffers(1, &buffer);
ASSERT_NE(buffer, 0u);
ASSERT_TRUE(MG_State::pGLContext->ValidateBufferName(buffer));
MG_Impl::GLImpl::DeleteBuffers(1, &buffer);
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
EXPECT_FALSE(MG_State::pGLContext->ValidateBufferName(buffer));
MG_Impl::GLImpl::BindBuffer(GL_ARRAY_BUFFER, buffer);
ExpectSingleGlError(GL_INVALID_OPERATION);
GLuint recycled = 0;
MG_Impl::GLImpl::GenBuffers(1, &recycled);
EXPECT_EQ(recycled, buffer);
}
TEST_F(BufferTest, BindNeverGeneratedBufferNameIsInvalidOperation) {
// Not a small literal: other tests in this binary share the context and generate names in
// bulk, so a low number may well be a legitimately reserved name here.
MG_Impl::GLImpl::BindBuffer(GL_ARRAY_BUFFER, std::numeric_limits<GLuint>::max());
ExpectSingleGlError(GL_INVALID_OPERATION);
}
TEST_F(BufferTest, AcquireMemory) { TEST_F(BufferTest, AcquireMemory) {
auto& slot = MobileGL::MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::Uniform); auto& slot = MobileGL::MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::Uniform);
Vector<Uint> bufferNames; Vector<Uint> bufferNames;
+1 -1
View File
@@ -24,4 +24,4 @@ if (MSVC)
endif() endif()
include(GoogleTest) include(GoogleTest)
gtest_discover_tests(BufferTest DISCOVERY_TIMEOUT 30 PROPERTIES LABELS unit) gtest_discover_tests(BufferTest DISCOVERY_TIMEOUT 30)
+1 -1
View File
@@ -62,7 +62,7 @@ set(LINK_LIBRARIES
) )
include(GoogleTest) include(GoogleTest)
gtest_discover_tests(SanityTest DISCOVERY_TIMEOUT 30 PROPERTIES LABELS unit) gtest_discover_tests(SanityTest DISCOVERY_TIMEOUT 30)
add_subdirectory(BackendLoader) add_subdirectory(BackendLoader)
add_subdirectory(Buffer) add_subdirectory(Buffer)
+1 -1
View File
@@ -17,4 +17,4 @@ target_link_libraries(
) )
include(GoogleTest) include(GoogleTest)
gtest_discover_tests(EGLStateTest DISCOVERY_TIMEOUT 30 PROPERTIES LABELS unit) gtest_discover_tests(EGLStateTest DISCOVERY_TIMEOUT 30)
@@ -102,33 +102,3 @@ TEST(EGLStateMakeCurrent, SameThreadRepeatedAttachReleaseDoesNotLeaveStaleOwner)
EXPECT_TRUE(fixture->State.MakeCurrent(EGL_NO_DISPLAY, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)); EXPECT_TRUE(fixture->State.MakeCurrent(EGL_NO_DISPLAY, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
EXPECT_EQ(fixture->State.ConsumeError(), EGL_SUCCESS); EXPECT_EQ(fixture->State.ConsumeError(), EGL_SUCCESS);
} }
// The compatibility-profile accessor is affirmative-only (it backs GL_CONTEXT_PROFILE_MASK
// reporting): attrib-less contexts (profile mask 0) and released threads both answer "not
// compat" and therefore read as core-profile contexts.
TEST(EGLStateProfile, CompatibilityProfileRequiresExplicitCompatBit) {
auto fixture = CreateFixture();
EXPECT_FALSE(fixture->State.IsCurrentContextOpenGLCompatibilityProfile());
EXPECT_TRUE(fixture->State.MakeCurrent(fixture->Display, fixture->Surface, fixture->Surface, fixture->Context));
EXPECT_FALSE(fixture->State.IsCurrentContextOpenGLCompatibilityProfile());
const EGLint compatAttribs[] = {EGL_CONTEXT_MAJOR_VERSION,
3,
EGL_CONTEXT_MINOR_VERSION,
3,
EGL_CONTEXT_OPENGL_PROFILE_MASK,
EGL_CONTEXT_OPENGL_COMPATIBILITY_PROFILE_BIT,
EGL_NONE};
const auto compatContext =
fixture->State.CreateContext(fixture->Display, fixture->Config, EGL_NO_CONTEXT, compatAttribs);
ASSERT_NE(compatContext, EGL_NO_CONTEXT);
EXPECT_TRUE(fixture->State.MakeCurrent(fixture->Display, fixture->Surface, fixture->Surface, compatContext));
EXPECT_TRUE(fixture->State.IsCurrentContextOpenGLCompatibilityProfile());
EXPECT_FALSE(fixture->State.IsCurrentContextOpenGLCoreProfile());
EXPECT_TRUE(fixture->State.MakeCurrent(EGL_NO_DISPLAY, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
EXPECT_FALSE(fixture->State.IsCurrentContextOpenGLCompatibilityProfile());
EXPECT_EQ(fixture->State.ConsumeError(), EGL_SUCCESS);
}
+1 -1
View File
@@ -17,4 +17,4 @@ target_link_libraries(
) )
include(GoogleTest) include(GoogleTest)
gtest_discover_tests(FramebufferTest DISCOVERY_TIMEOUT 30 PROPERTIES LABELS unit) gtest_discover_tests(FramebufferTest DISCOVERY_TIMEOUT 30)
@@ -8,12 +8,9 @@
#include <gtest/gtest.h> #include <gtest/gtest.h>
#include <limits>
#include "Includes.h" #include "Includes.h"
#include "Init.h" #include "Init.h"
#include <MG_Backend/BackendObjects.h> #include <MG_Backend/BackendObjects.h>
#include <MG_Backend/DirectGLES/Utils.h>
#include <MG_Impl/GLImpl/Buffer/GL_Buffer.h> #include <MG_Impl/GLImpl/Buffer/GL_Buffer.h>
#include <MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.h> #include <MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.h>
#include <MG_Impl/GLImpl/Getter/GL_Getter.h> #include <MG_Impl/GLImpl/Getter/GL_Getter.h>
@@ -35,8 +32,6 @@ namespace {
Int g_clearNamedFramebufferfvCallCount = 0; Int g_clearNamedFramebufferfvCallCount = 0;
Int g_clearNamedFramebufferfiCallCount = 0; Int g_clearNamedFramebufferfiCallCount = 0;
Int g_readPixelsCallCount = 0; Int g_readPixelsCallCount = 0;
GLenum g_lastReadPixelsFormat = GL_NONE;
GLenum g_lastReadPixelsType = GL_NONE;
void RecordBlitNamedFramebuffer(const SharedPtr<MG_State::GLState::FramebufferObject>& readFramebuffer, void RecordBlitNamedFramebuffer(const SharedPtr<MG_State::GLState::FramebufferObject>& readFramebuffer,
const SharedPtr<MG_State::GLState::FramebufferObject>& drawFramebuffer, const SharedPtr<MG_State::GLState::FramebufferObject>& drawFramebuffer,
@@ -71,39 +66,15 @@ namespace {
++g_clearNamedFramebufferfiCallCount; ++g_clearNamedFramebufferfiCallCount;
} }
void RecordReadPixels(GLint, GLint, GLsizei, GLsizei, GLenum format, GLenum type, void*) { void RecordReadPixels(GLint, GLint, GLsizei, GLsizei, GLenum, GLenum, void*) {
++g_readPixelsCallCount; ++g_readPixelsCallCount;
g_lastReadPixelsFormat = format;
g_lastReadPixelsType = type;
} }
} // namespace } // namespace
class FramebufferTest : public ::testing::Test { class FramebufferTest : public ::testing::Test {
protected: protected:
// GL error flags are sticky per error code and the context outlives an individual test in this
// binary, so drain whatever an earlier test left pending - otherwise an error-code assertion
// here reads someone else's error. Bounded: one flag per code, so this cannot hang the suite.
static void DrainPendingGlErrors() {
for (Int drained = 0; drained < 16 && MG_Impl::GLImpl::GetError() != GL_NO_ERROR; ++drained) {
}
}
// The call under test must raise exactly the expected error and nothing more: a second pending
// error means one entry point queued several, which GetError() would hand out at an unrelated
// call site later on.
static void ExpectSingleGlError(GLenum expected) {
EXPECT_EQ(MG_Impl::GLImpl::GetError(), expected);
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR) << "the call recorded more than one error";
}
void TearDown() override {
// Attribute a leaked error to the test that caused it instead of to whoever runs next.
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR) << "test left an unconsumed GL error behind";
}
void SetUp() override { void SetUp() override {
MobileGL::Initialize(); MobileGL::Initialize();
DrainPendingGlErrors();
const auto defaultFramebuffer = MG_State::pGLContext->GetFramebufferObject(0); const auto defaultFramebuffer = MG_State::pGLContext->GetFramebufferObject(0);
ASSERT_NE(defaultFramebuffer, nullptr); ASSERT_NE(defaultFramebuffer, nullptr);
MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Draw).Bind(defaultFramebuffer); MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Draw).Bind(defaultFramebuffer);
@@ -126,8 +97,6 @@ protected:
g_clearNamedFramebufferfvCallCount = 0; g_clearNamedFramebufferfvCallCount = 0;
g_clearNamedFramebufferfiCallCount = 0; g_clearNamedFramebufferfiCallCount = 0;
g_readPixelsCallCount = 0; g_readPixelsCallCount = 0;
g_lastReadPixelsFormat = GL_NONE;
g_lastReadPixelsType = GL_NONE;
MG_Backend::gBackendFunctionsTable.GL.BlitNamedFramebuffer = nullptr; MG_Backend::gBackendFunctionsTable.GL.BlitNamedFramebuffer = nullptr;
MG_Backend::gBackendFunctionsTable.GL.ClearNamedFramebufferfv = nullptr; MG_Backend::gBackendFunctionsTable.GL.ClearNamedFramebufferfv = nullptr;
MG_Backend::gBackendFunctionsTable.GL.ClearNamedFramebufferfi = nullptr; MG_Backend::gBackendFunctionsTable.GL.ClearNamedFramebufferfi = nullptr;
@@ -146,83 +115,6 @@ TEST_F(FramebufferTest, CreateFramebuffersCreatesObjectsImmediately) {
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR); EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
} }
// GL 3.3 core 4.4.1/4.4.2 name lifecycle - mirrors the rules asserted for the other object
// families: deleting an unknown name is silent, a released reservation is recycled, and binding
// a dead name is INVALID_OPERATION.
TEST_F(FramebufferTest, DeleteOfUnknownOrAlreadyDeletedFramebufferNameIsSilent) {
GLuint framebuffer = 0;
MG_Impl::GLImpl::GenFramebuffers(1, &framebuffer);
ASSERT_NE(framebuffer, 0u);
ASSERT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
MG_Impl::GLImpl::DeleteFramebuffers(1, &framebuffer);
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
MG_Impl::GLImpl::DeleteFramebuffers(1, &framebuffer);
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
// Not a small literal: other tests in this binary share the context and generate names in
// bulk, so a low number may well be a legitimately reserved name here.
const GLuint unknownNames[] = {0u, std::numeric_limits<GLuint>::max()};
MG_Impl::GLImpl::DeleteFramebuffers(2, unknownNames);
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
}
TEST_F(FramebufferTest, DeleteGeneratedButUnboundFramebufferNameReleasesReservationAndBindFails) {
GLuint framebuffer = 0;
MG_Impl::GLImpl::GenFramebuffers(1, &framebuffer);
ASSERT_NE(framebuffer, 0u);
ASSERT_TRUE(MG_State::pGLContext->ValidateFramebufferName(framebuffer));
MG_Impl::GLImpl::DeleteFramebuffers(1, &framebuffer);
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
EXPECT_FALSE(MG_State::pGLContext->ValidateFramebufferName(framebuffer));
MG_Impl::GLImpl::BindFramebuffer(GL_FRAMEBUFFER, framebuffer);
ExpectSingleGlError(GL_INVALID_OPERATION);
GLuint recycled = 0;
MG_Impl::GLImpl::GenFramebuffers(1, &recycled);
EXPECT_EQ(recycled, framebuffer);
}
TEST_F(FramebufferTest, DeleteOfUnknownOrAlreadyDeletedRenderbufferNameIsSilent) {
GLuint renderbuffer = 0;
MG_Impl::GLImpl::GenRenderbuffers(1, &renderbuffer);
ASSERT_NE(renderbuffer, 0u);
ASSERT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
MG_Impl::GLImpl::DeleteRenderbuffers(1, &renderbuffer);
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
MG_Impl::GLImpl::DeleteRenderbuffers(1, &renderbuffer);
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
// Not a small literal: other tests in this binary share the context and generate names in
// bulk, so a low number may well be a legitimately reserved name here.
const GLuint unknownNames[] = {0u, std::numeric_limits<GLuint>::max()};
MG_Impl::GLImpl::DeleteRenderbuffers(2, unknownNames);
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
}
TEST_F(FramebufferTest, DeleteGeneratedButUnboundRenderbufferNameReleasesReservationAndBindFails) {
GLuint renderbuffer = 0;
MG_Impl::GLImpl::GenRenderbuffers(1, &renderbuffer);
ASSERT_NE(renderbuffer, 0u);
ASSERT_TRUE(MG_State::pGLContext->ValidateRenderbufferName(renderbuffer));
MG_Impl::GLImpl::DeleteRenderbuffers(1, &renderbuffer);
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
EXPECT_FALSE(MG_State::pGLContext->ValidateRenderbufferName(renderbuffer));
MG_Impl::GLImpl::BindRenderbuffer(GL_RENDERBUFFER, renderbuffer);
ExpectSingleGlError(GL_INVALID_OPERATION);
GLuint recycled = 0;
MG_Impl::GLImpl::GenRenderbuffers(1, &recycled);
EXPECT_EQ(recycled, renderbuffer);
}
TEST_F(FramebufferTest, DefaultFramebufferIdentityTracksFramebufferNameZero) { TEST_F(FramebufferTest, DefaultFramebufferIdentityTracksFramebufferNameZero) {
const auto defaultFramebuffer = MG_State::pGLContext->GetFramebufferObject(0); const auto defaultFramebuffer = MG_State::pGLContext->GetFramebufferObject(0);
ASSERT_NE(defaultFramebuffer, nullptr); ASSERT_NE(defaultFramebuffer, nullptr);
@@ -329,77 +221,6 @@ TEST_F(FramebufferTest, ReadPixelsAllowsPersistentMappedPixelPackBuffer) {
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR); EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
} }
TEST_F(FramebufferTest, ReadPixelsRejectsMismatchedPackedTypeFormatPairs) {
GLuint framebuffer = 0;
GLuint texture = 0;
MG_Impl::GLImpl::CreateFramebuffers(1, &framebuffer);
MG_Impl::GLImpl::CreateTextures(GL_TEXTURE_2D, 1, &texture);
MG_Impl::GLImpl::TextureStorage2D(texture, 1, GL_RGBA8, 4, 4);
MG_Impl::GLImpl::NamedFramebufferTexture(framebuffer, GL_COLOR_ATTACHMENT0, texture, 0);
MG_Impl::GLImpl::BindFramebuffer(GL_READ_FRAMEBUFFER, framebuffer);
MG_Backend::gBackendFunctionsTable.GL.ReadPixels = RecordReadPixels;
Uint8 pixelStorage[4 * 4 * 4] = {};
// Packed RGB type with a non-RGB format must never reach the backend (GL CTS packed_pixels
// reads GL_RED with GL_UNSIGNED_SHORT_5_6_5 and expects an error).
MG_Impl::GLImpl::ReadPixels(0, 0, 4, 4, GL_RED, GL_UNSIGNED_SHORT_5_6_5, pixelStorage);
EXPECT_EQ(g_readPixelsCallCount, 0);
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_INVALID_OPERATION);
// Packed RGBA type with a non-RGBA/BGRA format is rejected as well.
MG_Impl::GLImpl::ReadPixels(0, 0, 4, 4, GL_RGB, GL_UNSIGNED_INT_8_8_8_8, pixelStorage);
EXPECT_EQ(g_readPixelsCallCount, 0);
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_INVALID_OPERATION);
// Packed depth-stencil type requires the DEPTH_STENCIL format.
MG_Impl::GLImpl::ReadPixels(0, 0, 4, 4, GL_RGBA, GL_UNSIGNED_INT_24_8, pixelStorage);
EXPECT_EQ(g_readPixelsCallCount, 0);
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_INVALID_OPERATION);
// A plain RGBA/UNSIGNED_BYTE readback keeps working.
MG_Impl::GLImpl::ReadPixels(0, 0, 4, 4, GL_RGBA, GL_UNSIGNED_BYTE, pixelStorage);
EXPECT_EQ(g_readPixelsCallCount, 1);
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
}
TEST_F(FramebufferTest, ReadPixelsForwardsSingleChannelDesktopClientFormats) {
GLuint framebuffer = 0;
GLuint texture = 0;
MG_Impl::GLImpl::CreateFramebuffers(1, &framebuffer);
MG_Impl::GLImpl::CreateTextures(GL_TEXTURE_2D, 1, &texture);
MG_Impl::GLImpl::TextureStorage2D(texture, 1, GL_RGBA8, 4, 4);
MG_Impl::GLImpl::NamedFramebufferTexture(framebuffer, GL_COLOR_ATTACHMENT0, texture, 0);
MG_Impl::GLImpl::BindFramebuffer(GL_READ_FRAMEBUFFER, framebuffer);
MG_Backend::gBackendFunctionsTable.GL.ReadPixels = RecordReadPixels;
Uint8 pixelStorage[4 * 4 * 4] = {};
// Desktop GL treats GL_GREEN/GL_BLUE/GL_ALPHA as valid ReadPixels client formats (GL CTS
// packed_pixels rgba8_format_green failed with GL_INVALID_ENUM before). The state layer must
// validate them and forward the raw enum to the backend, which extracts the source channel
// from a wide RGBA read.
const GLenum singleChannelFormats[] = {GL_GREEN, GL_BLUE, GL_ALPHA};
Int expectedCallCount = 0;
for (const GLenum format : singleChannelFormats) {
MG_Impl::GLImpl::ReadPixels(0, 0, 4, 4, format, GL_UNSIGNED_BYTE, pixelStorage);
EXPECT_EQ(g_readPixelsCallCount, ++expectedCallCount);
EXPECT_EQ(g_lastReadPixelsFormat, format);
EXPECT_EQ(g_lastReadPixelsType, static_cast<GLenum>(GL_UNSIGNED_BYTE));
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
}
// Packed-type pairing still applies: packed RGB/RGBA types never pair with single-channel formats.
MG_Impl::GLImpl::ReadPixels(0, 0, 4, 4, GL_GREEN, GL_UNSIGNED_SHORT_5_6_5, pixelStorage);
EXPECT_EQ(g_readPixelsCallCount, expectedCallCount);
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_INVALID_OPERATION);
// Integer client formats reject floating-point types.
MG_Impl::GLImpl::ReadPixels(0, 0, 4, 4, GL_GREEN_INTEGER, GL_FLOAT, pixelStorage);
EXPECT_EQ(g_readPixelsCallCount, expectedCallCount);
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_INVALID_OPERATION);
}
TEST_F(FramebufferTest, NamedRenderbufferStorageAndFramebufferAttachDoNotChangeBindings) { TEST_F(FramebufferTest, NamedRenderbufferStorageAndFramebufferAttachDoNotChangeBindings) {
GLuint framebuffer = 0; GLuint framebuffer = 0;
GLuint renderbuffer = 0; GLuint renderbuffer = 0;
@@ -598,263 +419,3 @@ TEST_F(FramebufferTest, BlitNamedFramebufferAllowsDefaultFramebufferZero) {
EXPECT_EQ(MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Read).GetBoundObject(), defaultRead); EXPECT_EQ(MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Read).GetBoundObject(), defaultRead);
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR); EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
} }
// ---- Packed-type readback encoding ------------------------------------------------------------------
// Oracle-independent guard for the DirectGLES client-format readback conversion: feeds known wide RGBA
// rows through ReadbackImpl::ConvertWideReadbackRow and asserts the exact packed words. Field positions
// were hand-computed from GL 3.3 table 3.6 and match the GL CTS packed_pixels comparison functions
// (glcPackedPixelsTests.cpp pack_UNSIGNED_*): non-REV types pack the first format component from the
// most significant bit, *_REV types from the least significant bit.
namespace {
namespace ReadbackImpl = MG_Backend::DirectGLES::ReadbackImpl;
// Converts a row of wide pixels (4 components of wideType each) into `format`/`type` words.
template <typename WordT, typename SrcT>
Vector<WordT> ConvertWideRowToPackedWords(const Vector<SrcT>& wide, GLenum wideType, GLenum format,
GLenum type) {
ReadbackImpl::ReadbackChannelMapping mapping{};
EXPECT_TRUE(ReadbackImpl::GetReadbackChannelMapping(format, mapping));
EXPECT_EQ(ReadbackImpl::GetReadbackDstPixelSize(mapping, type), sizeof(WordT));
const SizeT width = wide.size() / 4;
Vector<WordT> out(width, static_cast<WordT>(0));
ReadbackImpl::ConvertWideReadbackRow(reinterpret_cast<const Uint8*>(wide.data()),
reinterpret_cast<Uint8*>(out.data()), width, wideType, mapping,
type);
return out;
}
// Normalized encodes read the wide row as RGBA8 (values are v / 255).
template <typename WordT>
Vector<WordT> ConvertRGBA8Row(const Vector<Uint8>& rgba, GLenum format, GLenum type) {
return ConvertWideRowToPackedWords<WordT>(rgba, GL_UNSIGNED_BYTE, format, type);
}
// Wide RGBA8 pattern shared by the normalized-encode tests. Expected fields below are
// round(v / 255 * (2^bits - 1)), computed by hand per pixel.
// R G B A
const Vector<Uint8> kRGBA8Row{255, 0, 128, 64, // P0
10, 250, 33, 200, // P1
85, 170, 255, 0}; // P2
} // namespace
TEST(PackedReadbackEncodeTest, EncodesUnsignedShort565) {
// P0: R=31 G=0 B=round(128*31/255)=16 -> 31<<11 | 0<<5 | 16 = 0xF810
// P1: R=round(10*31/255)=1 G=round(250*63/255)=62 B=round(33*31/255)=4 -> 1<<11|62<<5|4 = 0x0FC4
// P2: R=round(85*31/255)=10 G=round(170*63/255)=42 B=31 -> 10<<11|42<<5|31 = 0x555F
const auto words = ConvertRGBA8Row<Uint16>(kRGBA8Row, GL_RGB, GL_UNSIGNED_SHORT_5_6_5);
EXPECT_EQ(words[0], 0xF810u);
EXPECT_EQ(words[1], 0x0FC4u);
EXPECT_EQ(words[2], 0x555Fu);
}
TEST(PackedReadbackEncodeTest, EncodesUnsignedShort565Rev) {
// REV packs R from the LSB: P2 -> 10 | 42<<5 | 31<<11 = 0xFD4A
const auto words = ConvertRGBA8Row<Uint16>(kRGBA8Row, GL_RGB, GL_UNSIGNED_SHORT_5_6_5_REV);
EXPECT_EQ(words[2], 0xFD4Au);
}
TEST(PackedReadbackEncodeTest, EncodesUnsignedShort4444) {
// P0: R=15 G=0 B=round(128*15/255)=8 A=round(64*15/255)=4 -> 0xF084
// P2: R=round(85*15/255)=5 G=round(170*15/255)=10 B=15 A=0 -> 0x5AF0
const auto words = ConvertRGBA8Row<Uint16>(kRGBA8Row, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4);
EXPECT_EQ(words[0], 0xF084u);
EXPECT_EQ(words[2], 0x5AF0u);
}
TEST(PackedReadbackEncodeTest, EncodesUnsignedShort4444Rev) {
// P0 fields R=15 G=0 B=8 A=4 packed from the LSB -> 15 | 0<<4 | 8<<8 | 4<<12 = 0x480F
const auto words = ConvertRGBA8Row<Uint16>(kRGBA8Row, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4_REV);
EXPECT_EQ(words[0], 0x480Fu);
}
TEST(PackedReadbackEncodeTest, EncodesUnsignedShort5551) {
// P0: R=31 G=0 B=16 A=round(64/255)=0 -> 31<<11 | 16<<1 = 0xF820
// P1: R=1 G=round(250*31/255)=30 B=4 A=round(200/255)=1 -> 1<<11|30<<6|4<<1|1 = 0x0F89
const auto words = ConvertRGBA8Row<Uint16>(kRGBA8Row, GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1);
EXPECT_EQ(words[0], 0xF820u);
EXPECT_EQ(words[1], 0x0F89u);
}
TEST(PackedReadbackEncodeTest, EncodesUnsignedShort1555Rev) {
// P1 fields R=1 G=30 B=4 A=1 packed from the LSB -> 1 | 30<<5 | 4<<10 | 1<<15 = 0x93C1
const auto words = ConvertRGBA8Row<Uint16>(kRGBA8Row, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV);
EXPECT_EQ(words[1], 0x93C1u);
}
TEST(PackedReadbackEncodeTest, EncodesUnsignedInt2101010Rev) {
// P0: R=1023 G=0 B=round(128*1023/255)=514 A=round(64*3/255)=1 -> 1023|514<<20|1<<30 = 0x602003FF
// P2: R=round(85*1023/255)=341 G=round(170*1023/255)=682 B=1023 A=0 -> 0x3FFAA955
const auto words = ConvertRGBA8Row<Uint32>(kRGBA8Row, GL_RGBA, GL_UNSIGNED_INT_2_10_10_10_REV);
EXPECT_EQ(words[0], 0x602003FFu);
EXPECT_EQ(words[2], 0x3FFAA955u);
}
TEST(PackedReadbackEncodeTest, EncodesUnsignedInt1010102) {
// P2 fields R=341 G=682 B=1023 A=0 packed from the MSB -> 341<<22 | 682<<12 | 1023<<2 = 0x556AAFFC
const auto words = ConvertRGBA8Row<Uint32>(kRGBA8Row, GL_RGBA, GL_UNSIGNED_INT_10_10_10_2);
EXPECT_EQ(words[2], 0x556AAFFCu);
}
TEST(PackedReadbackEncodeTest, EncodesUnsignedByte332) {
// P0: R=7 G=0 B=round(128*3/255)=2 -> 7<<5 | 2 = 0xE2
// P1: R=round(10*7/255)=0 G=round(250*7/255)=7 B=round(33*3/255)=0 -> 7<<2 = 0x1C
const auto words = ConvertRGBA8Row<Uint8>(kRGBA8Row, GL_RGB, GL_UNSIGNED_BYTE_3_3_2);
EXPECT_EQ(words[0], 0xE2u);
EXPECT_EQ(words[1], 0x1Cu);
}
TEST(PackedReadbackEncodeTest, EncodesUnsignedByte233Rev) {
// P0 fields R=7 G=0 B=2 packed from the LSB -> 7 | 0<<3 | 2<<6 = 0x87
const auto words = ConvertRGBA8Row<Uint8>(kRGBA8Row, GL_RGB, GL_UNSIGNED_BYTE_2_3_3_REV);
EXPECT_EQ(words[0], 0x87u);
}
TEST(PackedReadbackEncodeTest, Encodes8888KeepsLegacyByteOrder) {
// Regression for the previously supported types: P0 = (255, 0, 128, 64).
const auto msbFirst = ConvertRGBA8Row<Uint32>(kRGBA8Row, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8);
EXPECT_EQ(msbFirst[0], 0xFF008040u);
const auto lsbFirst = ConvertRGBA8Row<Uint32>(kRGBA8Row, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV);
EXPECT_EQ(lsbFirst[0], 0x408000FFu);
}
TEST(PackedReadbackEncodeTest, EncodesBGRAWithChannelMapping) {
// BGRA's first format component is Blue: P0 fields B=8 G=0 R=15 A=4 -> 8<<12 | 15<<4 | 4 = 0x80F4
const auto words = ConvertRGBA8Row<Uint16>(kRGBA8Row, GL_BGRA, GL_UNSIGNED_SHORT_4_4_4_4);
EXPECT_EQ(words[0], 0x80F4u);
}
TEST(PackedReadbackEncodeTest, EncodesIntegerRGBA2101010RevWithFieldClamp) {
// Integer sources clamp to each field's unsigned range (10/10/10/2 bits).
const Vector<Uint32> wide{1023u, 1024u, 5u, 4u};
const auto words =
ConvertWideRowToPackedWords<Uint32>(wide, GL_UNSIGNED_INT, GL_RGBA_INTEGER, GL_UNSIGNED_INT_2_10_10_10_REV);
EXPECT_EQ(words[0], 0xC05FFFFFu); // 1023 | 1023<<10 | 5<<20 | 3<<30
}
TEST(PackedReadbackEncodeTest, EncodesIntegerNegativeValuesClampToZero) {
const Vector<Int32> wide{-5, 2, 100000, 1};
const auto words =
ConvertWideRowToPackedWords<Uint32>(wide, GL_INT, GL_RGBA_INTEGER, GL_UNSIGNED_INT_2_10_10_10_REV);
EXPECT_EQ(words[0], 0x7FF00800u); // 0 | 2<<10 | 1023<<20 | 1<<30
}
TEST(PackedReadbackEncodeTest, EncodesIntegerRGB565) {
const Vector<Uint32> wide{31u, 64u, 2u, 0u};
const auto words =
ConvertWideRowToPackedWords<Uint16>(wide, GL_UNSIGNED_INT, GL_RGB_INTEGER, GL_UNSIGNED_SHORT_5_6_5);
EXPECT_EQ(words[0], 0xFFE2u); // 31<<11 | 63<<5 | 2 (G clamps 64 -> 63)
}
TEST(PackedReadbackEncodeTest, EncodesPackedFloat10F11F11FRev) {
// F11(1.0)=0x3C0 F11(0.5)=0x380 F10(0.25)=0x1A0 -> 0x3C0 | 0x380<<11 | 0x1A0<<22 = 0x681C03C0.
// Second pixel: values above 65024 clamp to the max finite F11 (0x7BF), negatives go to zero.
const Vector<Float> wide{1.0f, 0.5f, 0.25f, 1.0f, 100000.0f, -1.0f, 0.25f, 1.0f};
const auto words = ConvertWideRowToPackedWords<Uint32>(wide, GL_FLOAT, GL_RGB, GL_UNSIGNED_INT_10F_11F_11F_REV);
EXPECT_EQ(words[0], 0x681C03C0u);
EXPECT_EQ(words[1], 0x680007BFu);
}
TEST(PackedReadbackEncodeTest, EncodesSharedExponent5999Rev) {
// (1.0, 0.5, 0.25): shared exponent 16, fields 256/128/64 -> 256 | 128<<9 | 64<<18 | 16<<27
const Vector<Float> wide{1.0f, 0.5f, 0.25f, 1.0f};
const auto words = ConvertWideRowToPackedWords<Uint32>(wide, GL_FLOAT, GL_RGB, GL_UNSIGNED_INT_5_9_9_9_REV);
EXPECT_EQ(words[0], 0x81010100u);
}
TEST(PackedReadbackEncodeTest, RejectsMismatchedPackedFieldCounts) {
ReadbackImpl::ReadbackChannelMapping rgba{};
ASSERT_TRUE(ReadbackImpl::GetReadbackChannelMapping(GL_RGBA, rgba));
ReadbackImpl::ReadbackChannelMapping rgbInteger{};
ASSERT_TRUE(ReadbackImpl::GetReadbackChannelMapping(GL_RGB_INTEGER, rgbInteger));
// 3-field packed types never pair with 4-component formats and vice versa.
EXPECT_EQ(ReadbackImpl::GetReadbackDstPixelSize(rgba, GL_UNSIGNED_SHORT_5_6_5), 0u);
EXPECT_EQ(ReadbackImpl::GetReadbackDstPixelSize(rgbInteger, GL_UNSIGNED_SHORT_4_4_4_4), 0u);
// Packed-float RGB types never pair with integer formats.
EXPECT_EQ(ReadbackImpl::GetReadbackDstPixelSize(rgbInteger, GL_UNSIGNED_INT_5_9_9_9_REV), 0u);
EXPECT_EQ(ReadbackImpl::GetReadbackDstPixelSize(rgbInteger, GL_UNSIGNED_INT_10F_11F_11F_REV), 0u);
}
// ---- GL CTS packed_pixels readback root-cause regressions --------------------------------------
TEST_F(FramebufferTest, ReadPixelsRejectsIntegerFormatMismatchWithReadBuffer) {
GLuint framebuffer = 0;
GLuint texture = 0;
MG_Impl::GLImpl::CreateFramebuffers(1, &framebuffer);
MG_Impl::GLImpl::CreateTextures(GL_TEXTURE_2D, 1, &texture);
MG_Impl::GLImpl::TextureStorage2D(texture, 1, GL_RGBA8UI, 4, 4);
MG_Impl::GLImpl::NamedFramebufferTexture(framebuffer, GL_COLOR_ATTACHMENT0, texture, 0);
MG_Impl::GLImpl::BindFramebuffer(GL_READ_FRAMEBUFFER, framebuffer);
MG_Backend::gBackendFunctionsTable.GL.ReadPixels = RecordReadPixels;
Uint8 pixelStorage[4 * 4 * 4] = {};
// GL 3.3 section 4.3.1: normalized format on an integer read buffer -> GL_INVALID_OPERATION
// (GL CTS packed_pixels expects the error for every mismatched combination).
MG_Impl::GLImpl::ReadPixels(0, 0, 4, 4, GL_RGBA, GL_UNSIGNED_BYTE, pixelStorage);
EXPECT_EQ(g_readPixelsCallCount, 0);
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_INVALID_OPERATION);
// The matching integer readback stays valid.
MG_Impl::GLImpl::ReadPixels(0, 0, 4, 4, GL_RGBA_INTEGER, GL_UNSIGNED_INT, pixelStorage);
EXPECT_EQ(g_readPixelsCallCount, 1);
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
// And the inverse mismatch: integer format on a normalized attachment.
GLuint normalizedTexture = 0;
MG_Impl::GLImpl::CreateTextures(GL_TEXTURE_2D, 1, &normalizedTexture);
MG_Impl::GLImpl::TextureStorage2D(normalizedTexture, 1, GL_RGBA8, 4, 4);
MG_Impl::GLImpl::NamedFramebufferTexture(framebuffer, GL_COLOR_ATTACHMENT0, normalizedTexture, 0);
MG_Impl::GLImpl::ReadPixels(0, 0, 4, 4, GL_RGBA_INTEGER, GL_UNSIGNED_INT, pixelStorage);
EXPECT_EQ(g_readPixelsCallCount, 1);
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_INVALID_OPERATION);
}
TEST_F(FramebufferTest, BindRenderbufferZeroUnbindsWithoutError) {
// The GL CTS state reset calls glBindRenderbuffer(GL_RENDERBUFFER, 0) and expects no error;
// name 0 used to be reported as an invalid renderbuffer name.
MG_Impl::GLImpl::BindRenderbuffer(GL_RENDERBUFFER, 0);
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
}
TEST_F(FramebufferTest, FramebufferTexture3DAttachesSliceWithLayerTracking) {
// glFramebufferTexture3D with zoffset used to be rejected outright, leaving a sticky
// GL_INVALID_OPERATION behind (GL CTS packed_pixels varied_rectangle runs on GL_TEXTURE_3D).
GLuint framebuffer = 0;
GLuint texture = 0;
MG_Impl::GLImpl::CreateFramebuffers(1, &framebuffer);
MG_Impl::GLImpl::CreateTextures(GL_TEXTURE_3D, 1, &texture);
MG_Impl::GLImpl::TextureStorage3D(texture, 1, GL_RGBA8, 4, 4, 2);
MG_Impl::GLImpl::BindFramebuffer(GL_DRAW_FRAMEBUFFER, framebuffer);
MG_Impl::GLImpl::FramebufferTexture3D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_3D, texture, 0, 1);
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
const auto framebufferObject = MG_State::pGLContext->GetFramebufferObject(framebuffer);
ASSERT_NE(framebufferObject, nullptr);
const auto& attachment = framebufferObject->GetAttachment(FramebufferAttachmentType::Color0);
ASSERT_TRUE(attachment.IsTexture());
EXPECT_EQ(attachment.GetTextureLayer(), 1);
EXPECT_FALSE(attachment.IsLayered());
}
TEST_F(FramebufferTest, NonRenderableColorFormatsReportUnsupportedFramebuffer) {
// Without a probing backend the conservative list applies: RGB9_E5 is texture-only, so
// attaching it must not report GL_FRAMEBUFFER_COMPLETE (GL CTS packed_pixels rgb9_e5 expects
// read errors instead of silent unwritten readbacks).
GLuint framebuffer = 0;
GLuint texture = 0;
MG_Impl::GLImpl::CreateFramebuffers(1, &framebuffer);
MG_Impl::GLImpl::CreateTextures(GL_TEXTURE_2D, 1, &texture);
MG_Impl::GLImpl::TextureStorage2D(texture, 1, GL_RGB9_E5, 4, 4);
MG_Impl::GLImpl::NamedFramebufferTexture(framebuffer, GL_COLOR_ATTACHMENT0, texture, 0);
MG_Impl::GLImpl::BindFramebuffer(GL_READ_FRAMEBUFFER, framebuffer);
EXPECT_EQ(MG_Impl::GLImpl::CheckFramebufferStatus(GL_READ_FRAMEBUFFER),
static_cast<GLenum>(GL_FRAMEBUFFER_UNSUPPORTED));
Uint8 pixelStorage[4 * 4 * 4] = {};
MG_Impl::GLImpl::ReadPixels(0, 0, 4, 4, GL_RGBA, GL_UNSIGNED_BYTE, pixelStorage);
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_INVALID_FRAMEBUFFER_OPERATION);
}
+2 -2
View File
@@ -39,5 +39,5 @@ target_link_libraries(
) )
include(GoogleTest) include(GoogleTest)
gtest_discover_tests(ProgramUtilTest DISCOVERY_TIMEOUT 30 PROPERTIES LABELS unit) gtest_discover_tests(ProgramUtilTest DISCOVERY_TIMEOUT 30)
gtest_discover_tests(ProgramTest DISCOVERY_TIMEOUT 30 PROPERTIES LABELS unit) gtest_discover_tests(ProgramTest DISCOVERY_TIMEOUT 30)
-520
View File
@@ -7,7 +7,6 @@
// End of Source File Header // End of Source File Header
#include <gtest/gtest.h> #include <gtest/gtest.h>
#include <spirv_reflect.h>
#include <cstring> #include <cstring>
#include <vector> #include <vector>
@@ -1326,88 +1325,6 @@ TEST_F(ProgramTest, CompileAndLinkWithExplicitVertexIn) {
<< "\")"; << "\")";
} }
TEST_F(ProgramTest, InactiveExplicitVertexBindingsDoNotReserveLocations) {
const char* vertexSource = R"(#version 430 compatibility
in vec3 Position;
in vec2 UV0;
in vec3 vaPosition;
void main() {
gl_Position = vec4(vaPosition, 1.0);
}
)";
const char* fragmentSource = R"(#version 430 compatibility
out vec4 fragColor;
void main() {
fragColor = vec4(1.0);
}
)";
GLuint vertexShader = CreateShader(GL_VERTEX_SHADER);
ShaderSource(vertexShader, 1, &vertexSource, nullptr);
CompileShader(vertexShader);
GLint compileStatus = GL_FALSE;
GetShaderiv(vertexShader, GL_COMPILE_STATUS, &compileStatus);
ASSERT_EQ(compileStatus, GL_TRUE);
GLuint fragmentShader = CreateShader(GL_FRAGMENT_SHADER);
ShaderSource(fragmentShader, 1, &fragmentSource, nullptr);
CompileShader(fragmentShader);
GetShaderiv(fragmentShader, GL_COMPILE_STATUS, &compileStatus);
ASSERT_EQ(compileStatus, GL_TRUE);
GLuint program = CreateProgram();
AttachShader(program, vertexShader);
AttachShader(program, fragmentShader);
// Iris binds these canonical names before linking every program. Its compatibility
// transformer can inject both declarations even when the shader pack instead reads
// vaPosition. Inactive API bindings must not consume locations during the link.
BindAttribLocation(program, 0, "Position");
BindAttribLocation(program, 1, "UV0");
LinkProgram(program);
GLint linkStatus = GL_FALSE;
GetProgramiv(program, GL_LINK_STATUS, &linkStatus);
ASSERT_EQ(linkStatus, GL_TRUE);
EXPECT_EQ(GetAttribLocation(program, "Position"), -1);
EXPECT_EQ(GetAttribLocation(program, "UV0"), -1);
EXPECT_EQ(GetAttribLocation(program, "vaPosition"), 0);
auto programObject = MG_State::pGLContext->GetProgramObject(program);
ASSERT_NE(programObject, nullptr);
const Int vertexIndex = programObject->GetShaderIndexByStage(ShaderStage::Vertex);
ASSERT_GE(vertexIndex, 0);
const auto& spirvs = programObject->GetGeneratedSpirv();
ASSERT_LT(static_cast<SizeT>(vertexIndex), spirvs.size());
const auto& vertexSpirv = spirvs[vertexIndex];
spv_reflect::ShaderModule reflection(vertexSpirv.size() * sizeof(Uint), vertexSpirv.data());
ASSERT_EQ(reflection.GetResult(), SPV_REFLECT_RESULT_SUCCESS);
uint32_t inputCount = 0;
ASSERT_EQ(reflection.EnumerateInputVariables(&inputCount, nullptr), SPV_REFLECT_RESULT_SUCCESS);
Vector<SpvReflectInterfaceVariable*> inputs(inputCount);
ASSERT_EQ(reflection.EnumerateInputVariables(&inputCount, inputs.data()), SPV_REFLECT_RESULT_SUCCESS);
Uint32 userInputCount = 0;
Uint32 locationMask = 0;
for (const auto* input : inputs) {
if (input == nullptr || (input->decoration_flags & SPV_REFLECT_DECORATION_BUILT_IN) != 0) {
continue;
}
ASSERT_LT(input->location, 32u);
locationMask |= 1u << input->location;
++userInputCount;
}
EXPECT_EQ(userInputCount, 1u);
EXPECT_EQ(locationMask, 0x1u);
}
TEST_F(ProgramTest, CompileAndLinkWithExplicitFragmentOut) { TEST_F(ProgramTest, CompileAndLinkWithExplicitFragmentOut) {
char infoLog[1024] = ""; char infoLog[1024] = "";
@@ -1928,440 +1845,3 @@ TEST_F(ProgramTest, GetActiveUniformsivErrors) {
EXPECT_EQ(GetError(), GL_NO_ERROR); EXPECT_EQ(GetError(), GL_NO_ERROR);
EXPECT_EQ(params[0], -999); EXPECT_EQ(params[0], -999);
} }
namespace {
GLuint LinkVsFsProgram(const char* vsSource, const char* fsSource) {
char infoLog[4096] = "";
GLuint vs = CreateShader(GL_VERTEX_SHADER);
ShaderSource(vs, 1, &vsSource, nullptr);
CompileShader(vs);
GLint vsStatus = GL_FALSE;
GetShaderiv(vs, GL_COMPILE_STATUS, &vsStatus);
GetShaderInfoLog(vs, sizeof(infoLog), nullptr, infoLog);
EXPECT_EQ(vsStatus, GL_TRUE) << infoLog;
GLuint fs = CreateShader(GL_FRAGMENT_SHADER);
ShaderSource(fs, 1, &fsSource, nullptr);
CompileShader(fs);
GLint fsStatus = GL_FALSE;
GetShaderiv(fs, GL_COMPILE_STATUS, &fsStatus);
GetShaderInfoLog(fs, sizeof(infoLog), nullptr, infoLog);
EXPECT_EQ(fsStatus, GL_TRUE) << infoLog;
GLuint program = CreateProgram();
AttachShader(program, vs);
AttachShader(program, fs);
LinkProgram(program);
GLint linkStatus = GL_FALSE;
GetProgramiv(program, GL_LINK_STATUS, &linkStatus);
GetProgramInfoLog(program, sizeof(infoLog), nullptr, infoLog);
EXPECT_EQ(linkStatus, GL_TRUE) << infoLog;
return program;
}
const char* kPassthroughCoordsVs = R"(#version 330
in vec4 a_position;
in vec4 a_coords;
out vec4 coords_in;
void main() {
gl_Position = a_position;
coords_in = a_coords;
})";
} // namespace
// Repro for KHR-GL33.shaders.loops.do_while_dynamic_iterations.empty_body_* (and the
// only_continue / unconditional_break variants): the loop is dead code, so the SPIR-V
// optimizer eliminates it together with the only loads of `one` / `ui_one` -- and with
// them the entire global UBO. The uniforms stay active in link reflection, so
// glUniform1i on them must still have backing storage instead of memcpy-ing to null.
TEST_F(ProgramTest, DoWhileDeadLoopUniformsKeepBackingStorage) {
const char* loopBodies[] = {"", "continue;", "break;"};
for (const char* body : loopBodies) {
const String fsSource = String(R"(#version 330
uniform int ui_one;
uniform mediump int one;
in vec4 coords_in;
out vec4 o_color;
void main() {
vec4 res = coords_in;
mediump int i = 0;
do {)") + body + R"(} while (i++ < one*ui_one);
o_color = res;
})";
GLuint program = LinkVsFsProgram(kPassthroughCoordsVs, fsSource.c_str());
const GLint locOne = GetUniformLocation(program, "one");
const GLint locUiOne = GetUniformLocation(program, "ui_one");
ASSERT_GE(locOne, 0) << "body: '" << body << "'";
ASSERT_GE(locUiOne, 0) << "body: '" << body << "'";
UseProgram(program);
Uniform1i(locOne, 1); // crashed with a null MapUBO() before the fallback storage
Uniform1i(locUiOne, 2);
EXPECT_EQ(GetError(), GL_NO_ERROR) << "body: '" << body << "'";
GLint readback = -1;
GetUniformiv(program, locOne, &readback);
EXPECT_EQ(readback, 1) << "body: '" << body << "'";
readback = -1;
GetUniformiv(program, locUiOne, &readback);
EXPECT_EQ(readback, 2) << "body: '" << body << "'";
EXPECT_EQ(GetError(), GL_NO_ERROR) << "body: '" << body << "'";
}
}
// Repro for KHR-GL33.shaders.struct.uniform.*nested_struct_array_*: leaf uniforms of
// nested struct arrays need (a) one location per array element and (b) real byte
// offsets inside the global UBO. Before the fix every leaf had a single location and
// offset 0, so glUniform2fv(loc, 2, ...) tripped the size assert on the neighboring
// float uniform (and corrupted it in release builds).
TEST_F(ProgramTest, NestedStructArrayUniformElementWrites) {
// Struct shape from CTS glcShaderStructTests nested_struct_array (uniform case).
const char* fsSource = R"(#version 330
struct T {
mediump float a;
mediump vec2 b[2];
};
struct S {
mediump float a;
T b[3];
int c;
};
uniform S s[2];
in vec4 coords_in;
out vec4 o_color;
void main() {
mediump float r = (s[0].b[1].b[0].x + s[1].b[2].b[1].y) * s[0].b[0].a;
mediump float g = s[1].b[0].b[0].y * s[0].b[2].a * s[1].b[2].a;
mediump float b = (s[0].b[2].b[1].y + s[0].b[1].b[0].y + s[1].a) * s[0].b[1].a;
mediump float a = float(s[0].c) + s[1].b[2].a - s[1].b[1].a;
o_color = vec4(r, g, b, a);
})";
GLuint program = LinkVsFsProgram(kPassthroughCoordsVs, fsSource);
UseProgram(program);
const GLint locVecArray = GetUniformLocation(program, "s[0].b[1].b");
ASSERT_GE(locVecArray, 0);
// Element locations are consecutive and reachable via the "[k]" suffix.
EXPECT_EQ(GetUniformLocation(program, "s[0].b[1].b[0]"), locVecArray);
EXPECT_EQ(GetUniformLocation(program, "s[0].b[1].b[1]"), locVecArray + 1);
EXPECT_EQ(GetUniformLocation(program, "s[0].b[1].b[2]"), -1);
// Distinct scalar leaves must land at distinct UBO offsets (they all aliased
// offset 0 before the fix).
const char* scalarLeaves[] = {"s[0].b[0].a", "s[0].b[1].a", "s[0].b[2].a", "s[1].a", "s[1].b[1].a",
"s[1].b[2].a"};
const GLfloat scalarValues[] = {0.5f, 0.25f, 0.125f, 7.0f, 3.0f, 4.0f};
for (SizeT i = 0; i < std::size(scalarLeaves); ++i) {
const GLint loc = GetUniformLocation(program, scalarLeaves[i]);
ASSERT_GE(loc, 0) << scalarLeaves[i];
Uniform1f(loc, scalarValues[i]);
}
// CTS-style whole-array write: glUniform2fv with count = 2 on a vec2[2] leaf.
// Before the fix this asserted/corrupted the next uniform ("s[0].b[2].a").
const GLfloat vecData[4] = {1.0f, 2.0f, 3.0f, 4.0f};
Uniform2fv(locVecArray, 2, vecData);
EXPECT_EQ(GetError(), GL_NO_ERROR);
GLfloat vecReadback[2] = {};
GetUniformfv(program, locVecArray, vecReadback);
EXPECT_EQ(vecReadback[0], 1.0f);
EXPECT_EQ(vecReadback[1], 2.0f);
GetUniformfv(program, locVecArray + 1, vecReadback);
EXPECT_EQ(vecReadback[0], 3.0f);
EXPECT_EQ(vecReadback[1], 4.0f);
// All scalar leaves survived the array write intact.
for (SizeT i = 0; i < std::size(scalarLeaves); ++i) {
GLfloat readback = -1.0f;
GetUniformfv(program, GetUniformLocation(program, scalarLeaves[i]), &readback);
EXPECT_EQ(readback, scalarValues[i]) << scalarLeaves[i];
}
// std140: vec2 array elements inside the struct are 16 bytes apart, and the
// per-element offsets differ.
auto programObject = MG_State::pGLContext->GetProgramObject(program);
ASSERT_NE(programObject, nullptr);
const Uint offsetElement0 = programObject->GetUniformOffset(static_cast<Uint>(locVecArray));
const Uint offsetElement1 = programObject->GetUniformOffset(static_cast<Uint>(locVecArray + 1));
EXPECT_EQ(offsetElement1, offsetElement0 + 16u);
EXPECT_EQ(GetError(), GL_NO_ERROR);
}
// Plain top-level uniform arrays share the same per-element location machinery.
TEST_F(ProgramTest, PlainArrayUniformElementLocationsAndWrites) {
const char* fsSource = R"(#version 330
uniform float arr[4];
uniform float guard;
in vec4 coords_in;
out vec4 o_color;
void main() {
o_color = vec4(arr[0] + arr[1], arr[2] + arr[3], guard, 1.0);
})";
GLuint program = LinkVsFsProgram(kPassthroughCoordsVs, fsSource);
UseProgram(program);
const GLint locArr = GetUniformLocation(program, "arr");
ASSERT_GE(locArr, 0);
EXPECT_EQ(GetUniformLocation(program, "arr[0]"), locArr);
EXPECT_EQ(GetUniformLocation(program, "arr[2]"), locArr + 2);
EXPECT_EQ(GetUniformLocation(program, "arr[4]"), -1);
const GLint locGuard = GetUniformLocation(program, "guard");
ASSERT_GE(locGuard, 0);
EXPECT_EQ(GetUniformLocation(program, "guard[0]"), -1); // not an array
Uniform1f(locGuard, 9.0f);
const GLfloat values[4] = {1.0f, 2.0f, 3.0f, 4.0f};
Uniform1fv(locArr, 4, values);
for (int i = 0; i < 4; ++i) {
GLfloat readback = -1.0f;
GetUniformfv(program, locArr + i, &readback);
EXPECT_EQ(readback, values[i]) << "arr[" << i << "]";
}
// Overlong writes stop at the end of the array (GL 3.3 §2.11.4) instead of
// spilling into the next uniform.
const GLfloat tail[3] = {30.0f, 40.0f, 50.0f};
Uniform1fv(GetUniformLocation(program, "arr[2]"), 3, tail);
EXPECT_EQ(GetError(), GL_NO_ERROR);
GLfloat readback = -1.0f;
GetUniformfv(program, locArr + 2, &readback);
EXPECT_EQ(readback, 30.0f);
GetUniformfv(program, locArr + 3, &readback);
EXPECT_EQ(readback, 40.0f);
GetUniformfv(program, locGuard, &readback);
EXPECT_EQ(readback, 9.0f); // untouched by the overlong write
EXPECT_EQ(GetError(), GL_NO_ERROR);
}
// ---------------------------------------------------------------------------
// GL CTS KHR-GL33.shaders.uniform_block regression pack. MobileGL's SPIR-V
// pipeline lays every uniform block out as std140; the frontend implements the
// GL-visible consequences of that choice: packed/shared qualifiers compile (as
// std140), reflection uses GL naming ("arr[0]", per-element struct arrays),
// unused block members stay active, block sizes are vec4-padded, and array
// strides are std140 even for arrays nested inside struct members.
// ---------------------------------------------------------------------------
TEST_F(ProgramTest, UniformBlockPackedAndSharedLayoutsCompileAsStd140) {
const char* fsSource = R"(#version 330
layout(packed) uniform PackedBlock {
vec4 pv;
};
layout(shared, row_major) uniform SharedBlock {
float sf;
mat4 sm;
};
out vec4 o_color;
void main() {
o_color = pv + vec4(sf) + vec4(sm[0][0]);
})";
GLuint program = LinkVsFsProgram(kPassthroughCoordsVs, fsSource);
// The blocks land on the implementation's chosen layout: std140 offsets.
const GLuint pv = UniformIndexByName(program, "pv");
const GLuint sf = UniformIndexByName(program, "sf");
const GLuint sm = UniformIndexByName(program, "sm");
ASSERT_NE(pv, GL_INVALID_INDEX);
ASSERT_NE(sf, GL_INVALID_INDEX);
ASSERT_NE(sm, GL_INVALID_INDEX);
EXPECT_EQ(QueryUniformiv(program, pv, GL_UNIFORM_OFFSET), 0);
EXPECT_EQ(QueryUniformiv(program, sf, GL_UNIFORM_OFFSET), 0);
EXPECT_EQ(QueryUniformiv(program, sm, GL_UNIFORM_OFFSET), 16);
// The remaining qualifiers in the rewritten layout() list survive.
EXPECT_EQ(QueryUniformiv(program, sm, GL_UNIFORM_IS_ROW_MAJOR), 1);
EXPECT_EQ(GetError(), GL_NO_ERROR);
}
TEST_F(ProgramTest, UniformBlockReflectsUnusedMembersWithGLNamesAndPaddedSize) {
const char* fsSource = R"(#version 330
layout(std140) uniform Blk {
float used;
vec4 unusedArr[3];
ivec3 tail;
};
out vec4 o_color;
void main() {
o_color = vec4(used);
})";
GLuint program = LinkVsFsProgram(kPassthroughCoordsVs, fsSource);
const GLuint blockIndex = GetUniformBlockIndex(program, "Blk");
ASSERT_NE(blockIndex, GL_INVALID_INDEX);
// All three members are active (unusedArr and tail are never read), the array is
// reported under its GL name "unusedArr[0]", and both spellings resolve.
const GLuint used = UniformIndexByName(program, "used");
const GLuint unusedSuffixed = UniformIndexByName(program, "unusedArr[0]");
const GLuint unusedBare = UniformIndexByName(program, "unusedArr");
const GLuint tail = UniformIndexByName(program, "tail");
ASSERT_NE(used, GL_INVALID_INDEX);
ASSERT_NE(unusedSuffixed, GL_INVALID_INDEX);
ASSERT_NE(tail, GL_INVALID_INDEX);
EXPECT_EQ(unusedSuffixed, unusedBare);
char nameBuf[64] = "";
GLsizei nameLen = 0;
GLint arraySize = 0;
GLenum type = 0;
GetActiveUniform(program, unusedSuffixed, sizeof(nameBuf), &nameLen, &arraySize, &type, nameBuf);
EXPECT_STREQ(nameBuf, "unusedArr[0]");
EXPECT_EQ(arraySize, 3);
EXPECT_EQ(type, static_cast<GLenum>(GL_FLOAT_VEC4));
// std140 layout of the unused members.
EXPECT_EQ(QueryUniformiv(program, unusedSuffixed, GL_UNIFORM_OFFSET), 16);
EXPECT_EQ(QueryUniformiv(program, unusedSuffixed, GL_UNIFORM_ARRAY_STRIDE), 16);
EXPECT_EQ(QueryUniformiv(program, tail, GL_UNIFORM_OFFSET), 64);
// GL_UNIFORM_BLOCK_ACTIVE_UNIFORMS agrees with the INDICES list and counts all members.
GLint activeInBlock = 0;
GetActiveUniformBlockiv(program, blockIndex, GL_UNIFORM_BLOCK_ACTIVE_UNIFORMS, &activeInBlock);
ASSERT_EQ(activeInBlock, 3);
GLint indices[3] = {-1, -1, -1};
GetActiveUniformBlockiv(program, blockIndex, GL_UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES, indices);
for (GLint index : indices) {
EXPECT_TRUE(index == static_cast<GLint>(used) || index == static_cast<GLint>(unusedSuffixed) ||
index == static_cast<GLint>(tail));
}
// The block ends with an ivec3 at offset 64 (unpadded end 76); the backend compiles
// the std140 block at its vec4-padded size, and the reported size must cover it or
// buffers sized from this query are too small to draw with.
GLint dataSize = 0;
GetActiveUniformBlockiv(program, blockIndex, GL_UNIFORM_BLOCK_DATA_SIZE, &dataSize);
EXPECT_EQ(dataSize, 80);
EXPECT_EQ(GetError(), GL_NO_ERROR);
}
TEST_F(ProgramTest, UniformBlockStructArrayExpandsPerElementWithStd140Strides) {
const char* fsSource = R"(#version 330
struct S {
ivec2 v[2];
float f;
};
layout(std140) uniform Blk2 {
S s[2];
} inst;
out vec4 o_color;
void main() {
o_color = vec4(inst.s[0].f);
})";
GLuint program = LinkVsFsProgram(kPassthroughCoordsVs, fsSource);
// ARB_program_interface_query naming: one entry per struct array element, prefixed
// with the BLOCK name (not the instance name), basic arrays suffixed with "[0]".
const GLuint v0 = UniformIndexByName(program, "Blk2.s[0].v[0]");
const GLuint f0 = UniformIndexByName(program, "Blk2.s[0].f");
const GLuint v1 = UniformIndexByName(program, "Blk2.s[1].v[0]");
const GLuint f1 = UniformIndexByName(program, "Blk2.s[1].f");
ASSERT_NE(v0, GL_INVALID_INDEX);
ASSERT_NE(f0, GL_INVALID_INDEX);
ASSERT_NE(v1, GL_INVALID_INDEX);
ASSERT_NE(f1, GL_INVALID_INDEX);
// std140: ivec2 v[2] rounds each element up to a vec4 (stride 16, NOT the tight 8
// glslang reflects for arrays nested inside a struct member); struct size rounds to
// 48, giving s[1] members a 48-byte bias.
EXPECT_EQ(QueryUniformiv(program, v0, GL_UNIFORM_OFFSET), 0);
EXPECT_EQ(QueryUniformiv(program, v0, GL_UNIFORM_ARRAY_STRIDE), 16);
EXPECT_EQ(QueryUniformiv(program, v0, GL_UNIFORM_SIZE), 2);
EXPECT_EQ(QueryUniformiv(program, f0, GL_UNIFORM_OFFSET), 32);
EXPECT_EQ(QueryUniformiv(program, v1, GL_UNIFORM_OFFSET), 48);
EXPECT_EQ(QueryUniformiv(program, f1, GL_UNIFORM_OFFSET), 80);
GLint dataSize = 0;
const GLuint blockIndex = GetUniformBlockIndex(program, "Blk2");
ASSERT_NE(blockIndex, GL_INVALID_INDEX);
GetActiveUniformBlockiv(program, blockIndex, GL_UNIFORM_BLOCK_DATA_SIZE, &dataSize);
EXPECT_EQ(dataSize, 96);
EXPECT_EQ(GetError(), GL_NO_ERROR);
}
TEST_F(ProgramTest, UniformBlockInstanceArrayReportsPerInstanceBlocks) {
const char* fsSource = R"(#version 330
layout(std140) uniform ArrBlk {
vec4 av;
} insts[2];
out vec4 o_color;
void main() {
o_color = insts[0].av + insts[1].av;
})";
GLuint program = LinkVsFsProgram(kPassthroughCoordsVs, fsSource);
const GLuint inst0 = GetUniformBlockIndex(program, "ArrBlk[0]");
const GLuint inst1 = GetUniformBlockIndex(program, "ArrBlk[1]");
ASSERT_NE(inst0, GL_INVALID_INDEX);
ASSERT_NE(inst1, GL_INVALID_INDEX);
EXPECT_NE(inst0, inst1);
// A bare block name resolves to the first instance.
EXPECT_EQ(GetUniformBlockIndex(program, "ArrBlk"), inst0);
// Every instance of the array shares the single reflected member set.
GLint count0 = 0;
GLint count1 = 0;
GetActiveUniformBlockiv(program, inst0, GL_UNIFORM_BLOCK_ACTIVE_UNIFORMS, &count0);
GetActiveUniformBlockiv(program, inst1, GL_UNIFORM_BLOCK_ACTIVE_UNIFORMS, &count1);
EXPECT_EQ(count0, 1);
EXPECT_EQ(count1, 1);
GLint index0 = -1;
GLint index1 = -1;
GetActiveUniformBlockiv(program, inst0, GL_UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES, &index0);
GetActiveUniformBlockiv(program, inst1, GL_UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES, &index1);
EXPECT_EQ(index0, index1);
EXPECT_EQ(static_cast<GLuint>(index0), UniformIndexByName(program, "ArrBlk.av"));
EXPECT_EQ(GetError(), GL_NO_ERROR);
}
TEST_F(ProgramTest, DeleteShaderWhileAttachedKeepsNameUsableUntilDetach) {
// GL CTS compiles through exactly this sequence (create, attach, DELETE, source,
// compile): glDeleteShader on an attached shader only flags it, and the name must
// keep working until the last detach.
const char* vsSource = R"(#version 330
void main() { gl_Position = vec4(0.0); }
)";
const char* fsSource = R"(#version 330
out vec4 o_color;
void main() { o_color = vec4(1.0); }
)";
GLuint program = CreateProgram();
GLuint vs = CreateShader(GL_VERTEX_SHADER);
AttachShader(program, vs);
DeleteShader(vs);
EXPECT_EQ(IsShader(vs), GL_TRUE); // still alive: attached
ShaderSource(vs, 1, &vsSource, nullptr);
CompileShader(vs);
GLint status = GL_FALSE;
GetShaderiv(vs, GL_COMPILE_STATUS, &status);
EXPECT_EQ(status, GL_TRUE);
status = GL_FALSE;
GetShaderiv(vs, GL_DELETE_STATUS, &status);
EXPECT_EQ(status, GL_TRUE);
GLuint fs = CreateShader(GL_FRAGMENT_SHADER);
AttachShader(program, fs);
DeleteShader(fs);
ShaderSource(fs, 1, &fsSource, nullptr);
CompileShader(fs);
LinkProgram(program);
GLint linkStatus = GL_FALSE;
char infoLog[1024] = "";
GetProgramiv(program, GL_LINK_STATUS, &linkStatus);
GetProgramInfoLog(program, sizeof(infoLog), nullptr, infoLog);
EXPECT_EQ(linkStatus, GL_TRUE) << infoLog;
// The last GL-visible detach releases the flagged shader's name.
DetachShader(program, vs);
EXPECT_EQ(IsShader(vs), GL_FALSE);
// Deleting the program releases the other flagged shader.
DeleteProgram(program);
EXPECT_EQ(IsShader(fs), GL_FALSE);
EXPECT_EQ(GetError(), GL_NO_ERROR);
}
@@ -8,9 +8,7 @@
#include <gtest/gtest.h> #include <gtest/gtest.h>
#include <cstring>
#include <string> #include <string>
#include <utility>
#include "Includes.h" #include "Includes.h"
#include "Init.h" #include "Init.h"
@@ -94,120 +92,6 @@ TEST_F(ProgramUtilTest, RenameSamplerFunctionParameterInSpirvPass) {
EXPECT_EQ(exactSamplerNameCount, 1u); EXPECT_EQ(exactSamplerNameCount, 1u);
} }
TEST_F(ProgramUtilTest, UnformattedFloatStorageImagesKeepIntegerAtomicImagesTyped) {
using namespace MG_Util::ShaderTranspiler;
const String source = R"(#version 430 core
layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
layout(rgba16, binding = 0) uniform image2D floatImage;
layout(r32ui, binding = 1) uniform uimage2D atomicImage;
void main() {
ivec2 coordinate = ivec2(gl_GlobalInvocationID.xy);
imageStore(floatImage, coordinate, imageLoad(floatImage, coordinate));
imageAtomicAdd(atomicImage, coordinate, 1u);
}
)";
ShaderAttrib shaderAttrib{.shaderType = GL_COMPUTE_SHADER, .sourceStr = source};
auto shaderResult = ShaderCompiler::CompileShader(shaderAttrib);
ASSERT_TRUE(shaderResult) << shaderResult.error().log;
ProgramAttrib programAttrib{.shaders = {shaderResult.value()}};
auto programResult = ShaderCompiler::LinkProgram(programAttrib);
ASSERT_TRUE(programResult) << programResult.error().log;
ProgramBinaryAttrib binaryAttrib{.shaderTypes = {GL_COMPUTE_SHADER}, .program = *programResult.value()};
auto binaryResult = ShaderCompiler::GetSpirvBinaryFromProgram(binaryAttrib);
ASSERT_TRUE(binaryResult) << binaryResult.error().log;
ASSERT_EQ(binaryResult->size(), 1u);
const auto& inputBinary = binaryResult->front();
spvtools::SpirvTools tools(SPV_ENV_VULKAN_1_1);
String inputText;
ASSERT_TRUE(tools.Disassemble(inputBinary, &inputText));
EXPECT_NE(inputText.find("2D 0 0 0 2 Rgba16"), String::npos) << inputText;
EXPECT_NE(inputText.find("2D 0 0 0 2 R32ui"), String::npos) << inputText;
EXPECT_EQ(inputText.find("StorageImageReadWithoutFormat"), String::npos) << inputText;
EXPECT_EQ(inputText.find("StorageImageWriteWithoutFormat"), String::npos) << inputText;
Vector<Uint32> outputBinary;
ASSERT_TRUE(ShaderCompiler::UseUnformattedFloatStorageImagesForVulkan(inputBinary, outputBinary));
String outputText;
ASSERT_TRUE(tools.Disassemble(outputBinary, &outputText));
EXPECT_EQ(outputText.find("2D 0 0 0 2 Rgba16"), String::npos) << outputText;
EXPECT_NE(outputText.find("2D 0 0 0 2 Unknown"), String::npos) << outputText;
EXPECT_NE(outputText.find("2D 0 0 0 2 R32ui"), String::npos) << outputText;
const auto countOccurrences = [](const String& text, const String& needle) {
SizeT count = 0;
for (SizeT offset = 0; (offset = text.find(needle, offset)) != String::npos;
offset += needle.size()) {
++count;
}
return count;
};
EXPECT_EQ(countOccurrences(outputText, "OpCapability StorageImageReadWithoutFormat"), 1u)
<< outputText;
EXPECT_EQ(countOccurrences(outputText, "OpCapability StorageImageWriteWithoutFormat"), 1u)
<< outputText;
EXPECT_TRUE(tools.Validate(outputBinary));
Vector<Uint32> secondOutputBinary;
ASSERT_TRUE(ShaderCompiler::UseUnformattedFloatStorageImagesForVulkan(outputBinary, secondOutputBinary));
EXPECT_EQ(secondOutputBinary, outputBinary);
}
TEST_F(ProgramUtilTest, UnformattedFloatStorageImagesKeepFloatAtomicImageTypesTyped) {
using namespace MG_Util::ShaderTranspiler;
const String spirvText = R"(
OpCapability Shader
OpCapability StorageImageExtendedFormats
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main"
OpExecutionMode %main LocalSize 1 1 1
OpDecorate %target DescriptorSet 0
OpDecorate %target Binding 0
%void = OpTypeVoid
%float = OpTypeFloat 32
%int = OpTypeInt 32 1
%v2int = OpTypeVector %int 2
%image = OpTypeImage %float 2D 0 0 0 2 R32f
%imageUniformPtr = OpTypePointer UniformConstant %image
%imageTexelPtr = OpTypePointer Image %float
%mainType = OpTypeFunction %void
%zero = OpConstant %int 0
%coordinate = OpConstantComposite %v2int %zero %zero
%target = OpVariable %imageUniformPtr UniformConstant
%main = OpFunction %void None %mainType
%entry = OpLabel
%texelPtr = OpImageTexelPointer %imageTexelPtr %target %coordinate %zero
OpReturn
OpFunctionEnd
)";
spvtools::SpirvTools tools(SPV_ENV_VULKAN_1_1);
Vector<Uint32> inputBinary;
ASSERT_TRUE(tools.Assemble(spirvText, &inputBinary));
Vector<Uint32> outputBinary;
ASSERT_TRUE(ShaderCompiler::UseUnformattedFloatStorageImagesForVulkan(inputBinary, outputBinary));
String outputText;
ASSERT_TRUE(tools.Disassemble(outputBinary, &outputText));
EXPECT_NE(outputText.find("2D 0 0 0 2 R32f"), String::npos) << outputText;
EXPECT_EQ(outputText.find("StorageImageReadWithoutFormat"), String::npos) << outputText;
EXPECT_EQ(outputText.find("StorageImageWriteWithoutFormat"), String::npos) << outputText;
String validationDiagnostics;
tools.SetMessageConsumer([&validationDiagnostics](spv_message_level_t, const char*,
const spv_position_t&, const char* message) {
validationDiagnostics += message;
});
EXPECT_TRUE(tools.Validate(outputBinary)) << validationDiagnostics;
}
TEST_F(ProgramUtilTest, PreprocessLegacyVertexShaderModernizesGlmarkStyleSource) { TEST_F(ProgramUtilTest, PreprocessLegacyVertexShaderModernizesGlmarkStyleSource) {
using namespace MG_Util::ShaderTranspiler; using namespace MG_Util::ShaderTranspiler;
@@ -672,81 +556,6 @@ TEST_F(ProgramUtilTest, CompileSimpleVertexShader) {
} }
} }
// Legacy desktop sources are normalized to "#version 330 core", which is stricter than the 460 they
// used to be forced to. A shader declaring 330 while using 420-era syntax without the matching
// #extension line is accepted by real drivers, so CompileShader retries it at 460 instead of failing.
TEST_F(ProgramUtilTest, CompileShaderRetriesAt460WhenLegacyVersionRejects420Syntax) {
using namespace MG_Util::ShaderTranspiler;
String source = R"(#version 330
layout(binding = 0) uniform sampler2D InSampler;
in vec2 texCoord;
out vec4 fragColor;
void main() {
fragColor = texture(InSampler, texCoord);
})";
PreprocessShaderSource(ShaderStage::Fragment, source);
// The normal path still emits 330 - the retry must not become the default.
ASSERT_EQ(source.find("#version 330 core"), 0u);
ShaderAttrib attrib{.shaderType = GL_FRAGMENT_SHADER, .sourceStr = source};
auto res = ShaderCompiler::CompileShader(attrib);
if (!res) {
FAIL() << "errc: " << res.error().errc << "\nlog: " << res.error().log;
}
// Same source compiled for the OpenGL environment must take the retry too.
ShaderAttrib glAttrib{
.shaderType = GL_FRAGMENT_SHADER, .sourceStr = source, .flags = ShaderCompileBits::CompileForOpenGL};
auto glRes = ShaderCompiler::CompileShader(glAttrib);
if (!glRes) {
FAIL() << "errc: " << glRes.error().errc << "\nlog: " << glRes.error().log;
}
}
TEST_F(ProgramUtilTest, CompileShaderStillFailsWithOriginalDiagnosticsWhenRetryCannotHelp) {
using namespace MG_Util::ShaderTranspiler;
String source = R"(#version 330
in vec2 texCoord;
out vec4 fragColor;
void main() {
fragColor = thisFunctionDoesNotExist(texCoord);
})";
PreprocessShaderSource(ShaderStage::Fragment, source);
ShaderAttrib attrib{.shaderType = GL_FRAGMENT_SHADER, .sourceStr = source};
auto res = ShaderCompiler::CompileShader(attrib);
ASSERT_FALSE(res);
EXPECT_EQ(res.error().errc, -2);
EXPECT_NE(res.error().log.find("thisFunctionDoesNotExist"), String::npos) << res.error().log;
}
TEST_F(ProgramUtilTest, RetargetLegacyVersionDirectiveOnlyTouchesNormalizedDesktopCore) {
using namespace MG_Util::ShaderTranspiler;
String normalized = "#version 330 core\nvoid main() {}\n";
EXPECT_TRUE(RetargetLegacyVersionDirectiveTo460(normalized));
EXPECT_EQ(normalized.find("#version 460 core"), 0u);
// Already modern: nothing to retarget.
String modern = "#version 460 core\nvoid main() {}\n";
EXPECT_FALSE(RetargetLegacyVersionDirectiveTo460(modern));
EXPECT_EQ(modern.find("#version 460 core"), 0u);
// ES and compatibility sources keep what they declared.
String es = "#version 300 es\nvoid main() {}\n";
EXPECT_FALSE(RetargetLegacyVersionDirectiveTo460(es));
EXPECT_EQ(es.find("#version 300 es"), 0u);
String compat = "#version 330 compatibility\nvoid main() {}\n";
EXPECT_FALSE(RetargetLegacyVersionDirectiveTo460(compat));
EXPECT_EQ(compat.find("#version 330 compatibility"), 0u);
// A commented-out directive is not the real one.
String commented = "// #version 330 core\nvoid main() {}\n";
EXPECT_FALSE(RetargetLegacyVersionDirectiveTo460(commented));
EXPECT_EQ(commented.find("#version 460"), String::npos);
}
const char* fs = R"(#version 150 const char* fs = R"(#version 150
uniform sampler2D InSampler; uniform sampler2D InSampler;
@@ -1445,182 +1254,3 @@ void main() {
ASSERT_TRUE(ShaderCompiler::SanitizeAndOptimizeBinary(binRes->at(0), optimized)); ASSERT_TRUE(ShaderCompiler::SanitizeAndOptimizeBinary(binRes->at(0), optimized));
} }
TEST_F(ProgramUtilTest, PreprocessCoercesBlockPackingQualifiersToStd140) {
using namespace MG_Util::ShaderTranspiler;
// glslang rejects `packed`/`shared` outright when generating SPIR-V, and MobileGL's
// UBO layout is always std140 anyway; the preprocessor rewrites the qualifiers so the
// validation compile, reflection, and generated SPIR-V all agree on std140 (GL CTS
// KHR-GL33.shaders.uniform_block.*.packed/shared).
String source = R"(#version 330
layout(packed) uniform PackedBlock { vec4 pv; };
layout(shared, row_major) uniform SharedBlock { mat4 sm; };
layout ( shared ) uniform SpacedBlock { float sx; };
layout(std140) uniform KeptBlock { float kx; };
// A non-layout use of the identifier stays untouched (compute storage qualifier).
void main() {
gl_Position = pv + vec4(sm[0][0]) + vec4(sx) + vec4(kx);
})";
PreprocessShaderSource(ShaderStage::Vertex, source);
EXPECT_EQ(source.find("packed"), String::npos);
EXPECT_EQ(source.find("layout(shared"), String::npos);
EXPECT_NE(source.find("layout(std140) uniform PackedBlock"), String::npos);
EXPECT_NE(source.find("layout(std140, row_major) uniform SharedBlock"), String::npos);
EXPECT_NE(source.find("layout ( std140 ) uniform SpacedBlock"), String::npos);
EXPECT_NE(source.find("layout(std140) uniform KeptBlock"), String::npos);
ShaderAttrib attrib{.shaderType = GL_VERTEX_SHADER,
.sourceStr = source,
.flags = ShaderCompileBits::CompileForOpenGL};
auto res = ShaderCompiler::CompileShader(attrib);
if (!res) {
FAIL() << "errc: " << res.error().errc << "\nlog: " << res.error().log << "\nsource:\n" << source;
}
}
TEST_F(ProgramUtilTest, PreprocessLeavesComputeSharedStorageQualifierAlone) {
using namespace MG_Util::ShaderTranspiler;
// `shared` is only a packing qualifier inside layout(...); the compute-shader storage
// qualifier of the same spelling must survive.
String source = R"(#version 430
layout(local_size_x = 8) in;
shared float sharedScratch[8];
layout(shared) uniform Blk { float bx; };
void main() {
sharedScratch[gl_LocalInvocationIndex] = bx;
})";
PreprocessShaderSource(ShaderStage::Compute, source);
EXPECT_NE(source.find("shared float sharedScratch[8];"), String::npos);
EXPECT_NE(source.find("layout(std140) uniform Blk"), String::npos);
}
namespace {
String MakeLinearSubgroupPrefixScanShader() {
return R"(#version 460 core
#extension GL_KHR_shader_subgroup_arithmetic : enable
layout(local_size_x = 1024) in;
shared float prefixSumCache[64];
layout(std430, binding = 0) writeonly buffer OutputBuffer {
float outputValues[];
};
void main() {
float importance = 1.0f;
float prefixSum = subgroupInclusiveAdd(importance);
if (gl_SubgroupInvocationID == gl_SubgroupSize - 1u) prefixSumCache[gl_SubgroupID] = prefixSum;
barrier();
uint loopLength = uint(findMSB(gl_NumSubgroups));
loopLength += uint(gl_NumSubgroups - (1u << (loopLength - 1u)) > 0u);
for (uint i = 0; i < loopLength; i++) {
if ((gl_SubgroupID & (1u << i)) > 0u) {
prefixSum += prefixSumCache[(gl_SubgroupID >> i << i) - 1u];
if (gl_SubgroupInvocationID == gl_SubgroupSize - 1u) prefixSumCache[gl_SubgroupID] = prefixSum;
}
barrier();
}
if (gl_LocalInvocationID.x == uint(1024 - 1)) prefixSumCache[0] = prefixSum;
barrier();
float sum = prefixSumCache[0];
float warp = (prefixSum - importance) / sum - float(gl_LocalInvocationID.x + 1u) / float(1024);
outputValues[gl_GlobalInvocationID.x] = warp;
}
)";
}
} // namespace
TEST_F(ProgramUtilTest, RewriteLinearSubgroupPrefixScanUsesSharedMemoryAndProducesValidSpirv) {
using namespace MG_Util::ShaderTranspiler;
String source = MakeLinearSubgroupPrefixScanShader();
ASSERT_TRUE(RewriteLinearSubgroupPrefixScanForVulkan(ShaderStage::Compute, 64, source));
EXPECT_NE(source.find("shared float prefixSumCache[1024]"), String::npos) << source;
EXPECT_NE(source.find("mglVirtualSubgroupInvocation"), String::npos) << source;
EXPECT_NE(source.find("for (uint mglPrefixLane"), String::npos) << source;
EXPECT_EQ(source.find("subgroupInclusiveAdd"), String::npos) << source;
EXPECT_EQ(source.find("gl_Subgroup"), String::npos) << source;
const String onceRewritten = source;
EXPECT_FALSE(RewriteLinearSubgroupPrefixScanForVulkan(ShaderStage::Compute, 64, source));
EXPECT_EQ(source, onceRewritten);
ShaderAttrib shaderAttrib{.shaderType = GL_COMPUTE_SHADER, .sourceStr = source};
auto shaderResult = ShaderCompiler::CompileShader(shaderAttrib);
ASSERT_TRUE(shaderResult) << shaderResult.error().log << "\nsource:\n" << source;
ProgramAttrib programAttrib{.shaders = {shaderResult.value()}};
auto programResult = ShaderCompiler::LinkProgram(programAttrib);
ASSERT_TRUE(programResult) << programResult.error().log;
ProgramBinaryAttrib binaryAttrib{.shaderTypes = {GL_COMPUTE_SHADER}, .program = *programResult.value()};
auto binaryResult = ShaderCompiler::GetSpirvBinaryFromProgram(binaryAttrib);
ASSERT_TRUE(binaryResult) << binaryResult.error().log;
ASSERT_EQ(binaryResult->size(), 1u);
String validationDiagnostics;
spvtools::SpirvTools tools(SPV_ENV_VULKAN_1_1);
tools.SetMessageConsumer([&](spv_message_level_t, const char*, const spv_position_t&, const char* message) {
validationDiagnostics += message;
validationDiagnostics += '\n';
});
EXPECT_TRUE(tools.Validate(binaryResult->front())) << validationDiagnostics;
String spirvText;
ASSERT_TRUE(tools.Disassemble(binaryResult->front(), &spirvText));
EXPECT_EQ(spirvText.find("OpGroupNonUniform"), String::npos) << spirvText;
}
TEST_F(ProgramUtilTest, RewriteLinearSubgroupPrefixScanRejectsOtherStagesAndSubgroupWidths) {
using namespace MG_Util::ShaderTranspiler;
const String original = MakeLinearSubgroupPrefixScanShader();
for (const auto& [stage, subgroupSize] :
{std::pair{ShaderStage::Compute, Uint32{32}}, std::pair{ShaderStage::Fragment, Uint32{64}},
std::pair{ShaderStage::Compute, Uint32{96}}}) {
String source = original;
EXPECT_FALSE(RewriteLinearSubgroupPrefixScanForVulkan(stage, subgroupSize, source));
EXPECT_EQ(source, original);
}
}
TEST_F(ProgramUtilTest, RewriteLinearSubgroupPrefixScanRejectsPartialOrUnsafeTemplateMatches) {
using namespace MG_Util::ShaderTranspiler;
const auto expectUnchanged = [](String source) {
const String original = source;
EXPECT_FALSE(RewriteLinearSubgroupPrefixScanForVulkan(ShaderStage::Compute, 64, source));
EXPECT_EQ(source, original);
};
String wrongLocalSize = MakeLinearSubgroupPrefixScanShader();
wrongLocalSize.replace(wrongLocalSize.find("local_size_x = 1024"), std::strlen("local_size_x = 1024"),
"local_size_x = 512");
expectUnchanged(std::move(wrongLocalSize));
String cacheHasAnotherUse = MakeLinearSubgroupPrefixScanShader();
cacheHasAnotherUse.insert(cacheHasAnotherUse.find("float importance"), "prefixSumCache[0] = 0.0f;\n ");
expectUnchanged(std::move(cacheHasAnotherUse));
String extraSubgroupBuiltin = MakeLinearSubgroupPrefixScanShader();
extraSubgroupBuiltin.insert(extraSubgroupBuiltin.find("float importance"),
"uvec4 extraMask = gl_SubgroupEqMask;\n ");
expectUnchanged(std::move(extraSubgroupBuiltin));
String alteredBarrier = MakeLinearSubgroupPrefixScanShader();
alteredBarrier.replace(alteredBarrier.find("barrier();"), std::strlen("barrier();"), "memoryBarrierShared();");
expectUnchanged(std::move(alteredBarrier));
String nestedScan = MakeLinearSubgroupPrefixScanShader();
nestedScan.insert(nestedScan.find("float prefixSum ="), "if (importance > 0.0f) {\n ");
const SizeT consumerEnd = nestedScan.find(';', nestedScan.find("float warp ="));
ASSERT_NE(consumerEnd, String::npos);
nestedScan.insert(consumerEnd + 1, "\n }");
expectUnchanged(std::move(nestedScan));
}
+1 -1
View File
@@ -17,4 +17,4 @@ target_link_libraries(
) )
include(GoogleTest) include(GoogleTest)
gtest_discover_tests(QueryTest DISCOVERY_TIMEOUT 30 PROPERTIES LABELS unit) gtest_discover_tests(QueryTest DISCOVERY_TIMEOUT 30)
-454
View File
@@ -19,17 +19,13 @@
#include <MG_Backend/BackendObjects.h> #include <MG_Backend/BackendObjects.h>
#include <MG_Impl/GLImpl/Getter/GL_Getter.h> #include <MG_Impl/GLImpl/Getter/GL_Getter.h>
#include <MG_Impl/GLImpl/RenderState/GL_RenderState.h> #include <MG_Impl/GLImpl/RenderState/GL_RenderState.h>
#include <MG_Impl/GLImpl/Texture/GL_Texture.h>
#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_State/GLState/FramebufferState/FramebufferObject.h> #include <MG_State/GLState/FramebufferState/FramebufferObject.h>
#include <MG_State/GLState/TextureState/TextureState.h> #include <MG_State/GLState/TextureState/TextureState.h>
#include <MG_Backend/DirectVulkan/Renderer/ProgramFactory.h>
#include <MG_Backend/DirectVulkan/Renderer/UniformManager.h>
#include <MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.h> #include <MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.h>
#include <MG_Backend/DirectVulkan/Renderer/VkTextureManager.h> #include <MG_Backend/DirectVulkan/Renderer/VkTextureManager.h>
#include <MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h> #include <MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h>
#include <MG_Util/Math/HalfFloat.h>
#include <MG_Util/ShaderTranspiler/ShaderCompiler.h> #include <MG_Util/ShaderTranspiler/ShaderCompiler.h>
#include <MG_Util/ShaderTranspiler/ShaderSourceProcessor.h> #include <MG_Util/ShaderTranspiler/ShaderSourceProcessor.h>
#include <MG_Util/Debug/Log.h> #include <MG_Util/Debug/Log.h>
@@ -107,79 +103,6 @@ namespace {
private: private:
MobileGL::MG_External::GLESCapabilities m_snapshot; MobileGL::MG_External::GLESCapabilities m_snapshot;
}; };
struct TextureBindCall {
GLenum target;
GLuint texture;
};
MobileGL::Vector<TextureBindCall>* g_textureBindCalls = nullptr;
GLuint g_nextBackendTextureId = 73;
void RecordTextureBind(GLenum target, GLuint texture) {
if (g_textureBindCalls) {
g_textureBindCalls->push_back({target, texture});
}
}
void GenerateBackendTextures(GLsizei count, GLuint* textures) {
for (GLsizei i = 0; i < count; ++i) {
textures[i] = g_nextBackendTextureId++;
}
}
void DeleteBackendTextures(GLsizei, const GLuint*) {}
GLenum NoBackendError() {
return GL_NO_ERROR;
}
// Isolates the DirectGLES globals touched by the binding-cache regression test. The test
// installs only the native ES entry points needed to construct/bind a backend texture and
// restores the process-wide state even when a gtest assertion unwinds the test body.
struct ScopedDirectGLESTextureBindings {
ScopedDirectGLESTextureBindings():
previousContext(MobileGL::Move(MobileGL::MG_State::pGLContext)),
previousFunctions(MobileGL::MG_Backend::DirectGLES::g_GLESFuncs),
previousActiveUnit(MobileGL::MG_Backend::DirectGLES::TextureImpl::g_activeTextureUnit),
previousCache(MobileGL::MG_Backend::DirectGLES::TextureImpl::g_boundTexturesCache),
previousRegistry(MobileGL::MG_Backend::DirectGLES::TextureImpl::g_backendTextureObjects) {
MobileGL::MG_State::pGLContext = MobileGL::MakeUnique<MobileGL::MG_State::GLState::GLContext>();
MobileGL::MG_Backend::DirectGLES::TextureImpl::g_activeTextureUnit = 0;
MobileGL::MG_Backend::DirectGLES::TextureImpl::g_boundTexturesCache = {};
MobileGL::MG_Backend::DirectGLES::TextureImpl::g_backendTextureObjects = {};
MobileGL::MG_External::GLESFunctionsTable functions{};
functions.glBindTexture = RecordTextureBind;
functions.glDeleteTextures = DeleteBackendTextures;
functions.glGenTextures = GenerateBackendTextures;
functions.glGetError = NoBackendError;
MobileGL::MG_Backend::DirectGLES::SetGLESFuncsTable(functions);
g_textureBindCalls = &bindCalls;
}
~ScopedDirectGLESTextureBindings() {
g_textureBindCalls = nullptr;
MobileGL::MG_Backend::DirectGLES::TextureImpl::g_boundTexturesCache = {};
MobileGL::MG_Backend::DirectGLES::TextureImpl::g_backendTextureObjects = previousRegistry;
MobileGL::MG_Backend::DirectGLES::SetGLESFuncsTable(previousFunctions);
MobileGL::MG_Backend::DirectGLES::TextureImpl::g_activeTextureUnit = previousActiveUnit;
MobileGL::MG_Backend::DirectGLES::TextureImpl::g_boundTexturesCache = previousCache;
MobileGL::MG_State::pGLContext = MobileGL::Move(previousContext);
}
ScopedDirectGLESTextureBindings(const ScopedDirectGLESTextureBindings&) = delete;
ScopedDirectGLESTextureBindings& operator=(const ScopedDirectGLESTextureBindings&) = delete;
MobileGL::Vector<TextureBindCall> bindCalls;
private:
MobileGL::UniquePtr<MobileGL::MG_State::GLState::GLContext> previousContext;
MobileGL::MG_External::GLESFunctionsTable previousFunctions;
MobileGL::Uint previousActiveUnit;
decltype(MobileGL::MG_Backend::DirectGLES::TextureImpl::g_boundTexturesCache) previousCache;
decltype(MobileGL::MG_Backend::DirectGLES::TextureImpl::g_backendTextureObjects) previousRegistry;
};
} // namespace } // namespace
TEST(Sanity, BasicAssertions) { TEST(Sanity, BasicAssertions) {
@@ -225,52 +148,6 @@ TEST(DirectGLESSanity, AdvertisesVoxyRequiredRenderingExtensionsWithoutRaisingGL
extensions.end()); extensions.end());
} }
TEST(DirectGLESSanity, BindingZeroClearsPreviousNativeTextureBinding) {
using namespace MobileGL;
namespace DirectGLES = MG_Backend::DirectGLES;
ScopedDirectGLESTextureBindings state;
GLuint frontendTexture = 0;
MG_Impl::GLImpl::GenTextures(1, &frontendTexture);
ASSERT_NE(frontendTexture, 0u);
MG_Impl::GLImpl::BindTexture(GL_TEXTURE_2D, frontendTexture);
const auto& frontendTextureObject = MG_State::pGLContext->GetTextureUnitObject(0)
.GetBindingSlot(TextureTarget::Texture2D)
.GetBoundObject();
ASSERT_NE(frontendTextureObject, nullptr);
ASSERT_EQ(frontendTextureObject->GetExternalIndex(), frontendTexture);
auto& backendTexture = DirectGLES::TextureImpl::g_backendTextureObjects.GetOrCreate(frontendTextureObject);
backendTexture = MakeShared<DirectGLES::TextureImpl::BackendTextureObject>();
const GLuint backendTextureId = backendTexture->GetBackendTextureId();
DirectGLES::BindCurrentTextures();
ASSERT_EQ(state.bindCalls.size(), 1u);
EXPECT_EQ(state.bindCalls[0].target, GL_TEXTURE_2D);
EXPECT_EQ(state.bindCalls[0].texture, backendTextureId);
// The default 1D slot maps to the same native ES target as 2D. It must not clear and force a
// redundant rebind while the real 2D frontend object remains current.
DirectGLES::BindCurrentTextures();
EXPECT_EQ(state.bindCalls.size(), 1u);
MG_Impl::GLImpl::BindTexture(GL_TEXTURE_2D, 0);
ASSERT_TRUE(MG_State::GLState::IsUndefinedDefaultTexture(
MG_State::pGLContext->GetTextureUnitObject(0)
.GetBindingSlot(TextureTarget::Texture2D)
.GetBoundObject()
.get()));
DirectGLES::BindCurrentTextures();
ASSERT_EQ(state.bindCalls.size(), 2u);
EXPECT_EQ(state.bindCalls[1].target, GL_TEXTURE_2D);
EXPECT_EQ(state.bindCalls[1].texture, 0u);
EXPECT_EQ(DirectGLES::TextureImpl::g_boundTexturesCache[0][static_cast<SizeT>(TextureTarget::Texture2D)],
nullptr);
}
TEST(DirectGLESSanity, ProvidesNamedFramebufferBlitForDirectStateAccess) { TEST(DirectGLESSanity, ProvidesNamedFramebufferBlitForDirectStateAccess) {
MobileGL::MG_Backend::DirectGLES::BackendObject_DirectGLES backend; MobileGL::MG_Backend::DirectGLES::BackendObject_DirectGLES backend;
const auto& funcs = backend.GetBackendFunctions().GL; const auto& funcs = backend.GetBackendFunctions().GL;
@@ -463,58 +340,6 @@ TEST(DirectVulkanSanity, ClampsAdvertisedTextureAndDrawBufferLimitsToFrontendSta
EXPECT_EQ(lowParams.MaxColorAttachments, 6); EXPECT_EQ(lowParams.MaxColorAttachments, 6);
} }
TEST(DirectVulkanSanity, GatesPerStageImageUniformLimitsOnPhysicalDeviceFeatures) {
using namespace MobileGL;
MG_Backend::DirectVulkan::BackendObject_DirectVulkan backend;
MG_External::VulkanCapabilities caps;
caps.MaxImageUnits = 12;
caps.MaxCombinedImageUniforms = 10;
caps.MaxComputeImageUniforms = 9;
caps.SupportsVertexPipelineStoresAndAtomics = true;
caps.SupportsFragmentStoresAndAtomics = true;
caps.SupportsGeometryShader = false;
backend.ApplyVulkanCapabilitiesForTesting(caps);
const auto& withoutGeometry = backend.GetDynamicParameters();
EXPECT_EQ(withoutGeometry.MaxVertexImageUniforms, 10);
EXPECT_EQ(withoutGeometry.MaxGeometryImageUniforms, 0);
EXPECT_EQ(withoutGeometry.MaxFragmentImageUniforms, 10);
EXPECT_EQ(withoutGeometry.MaxComputeImageUniforms, 9);
caps.SupportsGeometryShader = true;
backend.ApplyVulkanCapabilitiesForTesting(caps);
EXPECT_EQ(backend.GetDynamicParameters().MaxGeometryImageUniforms, 10);
caps.SupportsVertexPipelineStoresAndAtomics = false;
caps.SupportsFragmentStoresAndAtomics = false;
backend.ApplyVulkanCapabilitiesForTesting(caps);
EXPECT_EQ(backend.GetDynamicParameters().MaxVertexImageUniforms, 0);
EXPECT_EQ(backend.GetDynamicParameters().MaxGeometryImageUniforms, 0);
EXPECT_EQ(backend.GetDynamicParameters().MaxFragmentImageUniforms, 0);
EXPECT_EQ(backend.GetDynamicParameters().MaxComputeImageUniforms, 9);
}
TEST(DirectGLESSanity, PreservesHostPerStageImageUniformLimits) {
using namespace MobileGL;
MG_Backend::DirectGLES::BackendObject_DirectGLES backend;
MG_External::GLESCapabilities caps;
caps.MaxImageUnits = 8;
caps.MaxCombinedImageUniforms = 16;
caps.MaxVertexImageUniforms = 2;
caps.MaxGeometryImageUniforms = 3;
caps.MaxFragmentImageUniforms = 4;
caps.MaxComputeImageUniforms = 5;
backend.ApplyGLESCapabilitiesForTesting(caps);
const auto& params = backend.GetDynamicParameters();
EXPECT_EQ(params.MaxVertexImageUniforms, 2);
EXPECT_EQ(params.MaxGeometryImageUniforms, 3);
EXPECT_EQ(params.MaxFragmentImageUniforms, 4);
EXPECT_EQ(params.MaxComputeImageUniforms, 5);
}
TEST(DirectVulkanSanity, AdvertisesSubgroupOnlyWhenVulkanReportsUsableSupport) { TEST(DirectVulkanSanity, AdvertisesSubgroupOnlyWhenVulkanReportsUsableSupport) {
using namespace MobileGL; using namespace MobileGL;
@@ -637,54 +462,6 @@ TEST(GetterSanity, ClampsMaxVertexAttribsToCurrentValueStorageCapacity) {
MG_State::pGLContext.reset(); MG_State::pGLContext.reset();
} }
TEST(GetterSanity, PerStageImageUniformQueriesMatchShaderCompilerLimits) {
using namespace MobileGL;
MG_Backend::DynamicBackendParameters params;
params.MaxImageUnits = 8;
params.MaxCombinedImageUniforms = 8;
params.MaxVertexImageUniforms = 1;
params.MaxGeometryImageUniforms = 2;
params.MaxFragmentImageUniforms = 3;
params.MaxComputeImageUniforms = 4;
MG_Backend::pActiveBackendObject = MakeUnique<DynamicParameterBackend>(params);
GLint reported = -1;
MG_Impl::GLImpl::GetIntegerv(GL_MAX_VERTEX_IMAGE_UNIFORMS, &reported);
EXPECT_EQ(reported, 1);
MG_Impl::GLImpl::GetIntegerv(GL_MAX_GEOMETRY_IMAGE_UNIFORMS, &reported);
EXPECT_EQ(reported, 2);
MG_Impl::GLImpl::GetIntegerv(GL_MAX_FRAGMENT_IMAGE_UNIFORMS, &reported);
EXPECT_EQ(reported, 3);
MG_Impl::GLImpl::GetIntegerv(GL_MAX_COMPUTE_IMAGE_UNIFORMS, &reported);
EXPECT_EQ(reported, 4);
const String vertexImageStore = R"(#version 430 core
layout(r32ui, binding = 0) uniform uimage2D targetImages[gl_MaxVertexImageUniforms];
void main() {
imageStore(targetImages[0], ivec2(0), uvec4(1));
gl_Position = vec4(0.0, 0.0, 0.0, 1.0);
}
)";
auto supported = MG_Util::ShaderTranspiler::ShaderCompiler::CompileShader({
.shaderType = GL_VERTEX_SHADER,
.sourceStr = vertexImageStore,
});
EXPECT_TRUE(supported) << (supported ? "" : supported.error().log);
params.MaxVertexImageUniforms = 0;
MG_Backend::pActiveBackendObject = MakeUnique<DynamicParameterBackend>(params);
MG_Impl::GLImpl::GetIntegerv(GL_MAX_VERTEX_IMAGE_UNIFORMS, &reported);
EXPECT_EQ(reported, 0);
auto unsupported = MG_Util::ShaderTranspiler::ShaderCompiler::CompileShader({
.shaderType = GL_VERTEX_SHADER,
.sourceStr = vertexImageStore,
});
EXPECT_FALSE(unsupported);
MG_Backend::pActiveBackendObject.reset();
}
TEST(GetterSanity, ReportsKhrSubgroupDynamicParameters) { TEST(GetterSanity, ReportsKhrSubgroupDynamicParameters) {
using namespace MobileGL; using namespace MobileGL;
@@ -732,68 +509,6 @@ TEST(DirectVulkanSanity, CommandMemoryBarrierMakesIndirectDrawCommandsVisible) {
EXPECT_EQ(storageOnlyBarrier.dstAccessMask & VK_ACCESS_INDIRECT_COMMAND_READ_BIT, 0u); EXPECT_EQ(storageOnlyBarrier.dstAccessMask & VK_ACCESS_INDIRECT_COMMAND_READ_BIT, 0u);
} }
TEST(DirectVulkanSanity, ReadbackUsesTheSourceFormatTexelSize) {
using MobileGL::MG_Backend::DirectVulkan::VulkanRenderer;
EXPECT_EQ(VulkanRenderer::GetReadbackTexelSize(VK_FORMAT_R8G8B8A8_UNORM), 4u);
EXPECT_EQ(VulkanRenderer::GetReadbackTexelSize(VK_FORMAT_R16G16B16A16_SFLOAT), 8u);
EXPECT_EQ(VulkanRenderer::GetReadbackTexelSize(VK_FORMAT_R32G32B32A32_SFLOAT), 16u);
}
TEST(DirectVulkanSanity, ReadbackConvertsRgba8AndRgba16fPixels) {
using MobileGL::MG_Backend::DirectVulkan::VulkanRenderer;
using MobileGL::MG_Util::EncodeFloatToHalfBits;
const MobileGL::Uint8 rgba8[] = {17, 34, 51, 68, 85, 102, 119, 136};
MobileGL::Uint8 rgba8Result[sizeof(rgba8)]{};
ASSERT_TRUE(VulkanRenderer::ConvertReadbackPixels(
rgba8, VK_FORMAT_R8G8B8A8_UNORM, 2, 1, GL_RGBA, GL_UNSIGNED_BYTE,
sizeof(rgba8Result), rgba8Result));
EXPECT_TRUE(std::equal(std::begin(rgba8), std::end(rgba8), std::begin(rgba8Result)));
const MobileGL::Uint8 bgra8[] = {51, 34, 17, 68};
MobileGL::Uint8 bgra8Result[4]{};
ASSERT_TRUE(VulkanRenderer::ConvertReadbackPixels(
bgra8, VK_FORMAT_B8G8R8A8_UNORM, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE,
sizeof(bgra8Result), bgra8Result));
const MobileGL::Uint8 expectedBgra8[] = {17, 34, 51, 68};
EXPECT_TRUE(std::equal(std::begin(expectedBgra8), std::end(expectedBgra8), std::begin(bgra8Result)));
const MobileGL::Uint16 rgba16f[] = {
EncodeFloatToHalfBits(-0.25f), EncodeFloatToHalfBits(0.5f), EncodeFloatToHalfBits(1.5f),
EncodeFloatToHalfBits(1.0f), EncodeFloatToHalfBits(0.25f), EncodeFloatToHalfBits(0.0f),
EncodeFloatToHalfBits(1.0f), EncodeFloatToHalfBits(0.5f),
EncodeFloatToHalfBits(0.75f), EncodeFloatToHalfBits(0.125f), EncodeFloatToHalfBits(-1.0f),
EncodeFloatToHalfBits(2.0f), EncodeFloatToHalfBits(1.0f), EncodeFloatToHalfBits(0.75f),
EncodeFloatToHalfBits(0.25f), EncodeFloatToHalfBits(0.0f),
};
constexpr MobileGL::SizeT kDestinationRowStride = 12;
MobileGL::Uint8 rgba16fResult[kDestinationRowStride * 2];
std::fill(std::begin(rgba16fResult), std::end(rgba16fResult), 0xCD);
ASSERT_TRUE(VulkanRenderer::ConvertReadbackPixels(
reinterpret_cast<const MobileGL::Uint8*>(rgba16f), VK_FORMAT_R16G16B16A16_SFLOAT,
2, 2, GL_RGBA, GL_UNSIGNED_BYTE, kDestinationRowStride, rgba16fResult));
const MobileGL::Uint8 expectedRgba16fRow0[] = {0, 128, 255, 255, 64, 0, 255, 128};
const MobileGL::Uint8 expectedRgba16fRow1[] = {191, 32, 0, 255, 255, 191, 64, 0};
EXPECT_TRUE(std::equal(std::begin(expectedRgba16fRow0), std::end(expectedRgba16fRow0),
std::begin(rgba16fResult)));
EXPECT_TRUE(std::equal(std::begin(expectedRgba16fRow1), std::end(expectedRgba16fRow1),
std::begin(rgba16fResult) + kDestinationRowStride));
EXPECT_TRUE(std::all_of(std::begin(rgba16fResult) + 8,
std::begin(rgba16fResult) + kDestinationRowStride,
[](MobileGL::Uint8 value) { return value == 0xCD; }));
MobileGL::Float rgba16fFloatResult[16]{};
ASSERT_TRUE(VulkanRenderer::ConvertReadbackPixels(
reinterpret_cast<const MobileGL::Uint8*>(rgba16f), VK_FORMAT_R16G16B16A16_SFLOAT,
2, 2, GL_RGBA, GL_FLOAT, sizeof(MobileGL::Float) * 8,
reinterpret_cast<MobileGL::Uint8*>(rgba16fFloatResult)));
EXPECT_FLOAT_EQ(rgba16fFloatResult[0], -0.25f);
EXPECT_FLOAT_EQ(rgba16fFloatResult[1], 0.5f);
EXPECT_FLOAT_EQ(rgba16fFloatResult[2], 1.5f);
EXPECT_FLOAT_EQ(rgba16fFloatResult[3], 1.0f);
}
TEST(DirectVulkanSanity, DrawIndexedIndirectCommandMatchesGlAndVulkanLayout) { TEST(DirectVulkanSanity, DrawIndexedIndirectCommandMatchesGlAndVulkanLayout) {
using namespace MobileGL::MG_Backend::DirectVulkan; using namespace MobileGL::MG_Backend::DirectVulkan;
@@ -840,175 +555,6 @@ TEST(DirectVulkanSanity, SampledDepthStencilViewUsesSingleDepthAspect) {
VK_IMAGE_ASPECT_DEPTH_BIT); VK_IMAGE_ASPECT_DEPTH_BIT);
} }
TEST(DirectVulkanSanity, SpirvStorageImageFormatsMapToVulkanFormats) {
using MobileGL::MG_Backend::DirectVulkan::ProgramFactory;
struct FormatCase {
SpvImageFormat spirv;
VkFormat vulkan;
};
const FormatCase cases[] = {
{SpvImageFormatUnknown, VK_FORMAT_UNDEFINED},
{SpvImageFormatRgba32f, VK_FORMAT_R32G32B32A32_SFLOAT},
{SpvImageFormatRgba16f, VK_FORMAT_R16G16B16A16_SFLOAT},
{SpvImageFormatR32f, VK_FORMAT_R32_SFLOAT},
{SpvImageFormatRgba8, VK_FORMAT_R8G8B8A8_UNORM},
{SpvImageFormatRgba8Snorm, VK_FORMAT_R8G8B8A8_SNORM},
{SpvImageFormatRg32f, VK_FORMAT_R32G32_SFLOAT},
{SpvImageFormatRg16f, VK_FORMAT_R16G16_SFLOAT},
{SpvImageFormatR11fG11fB10f, VK_FORMAT_B10G11R11_UFLOAT_PACK32},
{SpvImageFormatR16f, VK_FORMAT_R16_SFLOAT},
{SpvImageFormatRgba16, VK_FORMAT_R16G16B16A16_UNORM},
{SpvImageFormatRgb10A2, VK_FORMAT_A2R10G10B10_UNORM_PACK32},
{SpvImageFormatRg16, VK_FORMAT_R16G16_UNORM},
{SpvImageFormatRg8, VK_FORMAT_R8G8_UNORM},
{SpvImageFormatR16, VK_FORMAT_R16_UNORM},
{SpvImageFormatR8, VK_FORMAT_R8_UNORM},
{SpvImageFormatRgba16Snorm, VK_FORMAT_R16G16B16A16_SNORM},
{SpvImageFormatRg16Snorm, VK_FORMAT_R16G16_SNORM},
{SpvImageFormatRg8Snorm, VK_FORMAT_R8G8_SNORM},
{SpvImageFormatR16Snorm, VK_FORMAT_R16_SNORM},
{SpvImageFormatR8Snorm, VK_FORMAT_R8_SNORM},
{SpvImageFormatRgba32i, VK_FORMAT_R32G32B32A32_SINT},
{SpvImageFormatRgba16i, VK_FORMAT_R16G16B16A16_SINT},
{SpvImageFormatRgba8i, VK_FORMAT_R8G8B8A8_SINT},
{SpvImageFormatR32i, VK_FORMAT_R32_SINT},
{SpvImageFormatRg32i, VK_FORMAT_R32G32_SINT},
{SpvImageFormatRg16i, VK_FORMAT_R16G16_SINT},
{SpvImageFormatRg8i, VK_FORMAT_R8G8_SINT},
{SpvImageFormatR16i, VK_FORMAT_R16_SINT},
{SpvImageFormatR8i, VK_FORMAT_R8_SINT},
{SpvImageFormatRgba32ui, VK_FORMAT_R32G32B32A32_UINT},
{SpvImageFormatRgba16ui, VK_FORMAT_R16G16B16A16_UINT},
{SpvImageFormatRgba8ui, VK_FORMAT_R8G8B8A8_UINT},
{SpvImageFormatR32ui, VK_FORMAT_R32_UINT},
{SpvImageFormatRgb10a2ui, VK_FORMAT_A2R10G10B10_UINT_PACK32},
{SpvImageFormatRg32ui, VK_FORMAT_R32G32_UINT},
{SpvImageFormatRg16ui, VK_FORMAT_R16G16_UINT},
{SpvImageFormatRg8ui, VK_FORMAT_R8G8_UINT},
{SpvImageFormatR16ui, VK_FORMAT_R16_UINT},
{SpvImageFormatR8ui, VK_FORMAT_R8_UINT},
{SpvImageFormatR64ui, VK_FORMAT_R64_UINT},
{SpvImageFormatR64i, VK_FORMAT_R64_SINT},
};
for (const auto& testCase : cases) {
EXPECT_EQ(ProgramFactory::ConvertSpirvImageFormatToVkFormat(testCase.spirv), testCase.vulkan)
<< "SpvImageFormat=" << static_cast<int>(testCase.spirv);
}
}
TEST(DirectVulkanSanity, MutableStorageImageViewsUseVulkanCompatibilityClasses) {
using MobileGL::MG_Backend::DirectVulkan::VkTextureManager;
EXPECT_TRUE(VkTextureManager::AreStorageImageViewFormatsCompatible(
VK_FORMAT_R32_SFLOAT, VK_FORMAT_R32_UINT));
EXPECT_TRUE(VkTextureManager::AreStorageImageViewFormatsCompatible(
VK_FORMAT_R32_UINT, VK_FORMAT_R32_SINT));
EXPECT_TRUE(VkTextureManager::AreStorageImageViewFormatsCompatible(
VK_FORMAT_R16G16B16A16_UNORM, VK_FORMAT_R16G16B16A16_SFLOAT));
EXPECT_TRUE(VkTextureManager::AreStorageImageViewFormatsCompatible(
VK_FORMAT_R32_SFLOAT, VK_FORMAT_R8G8B8A8_UINT));
EXPECT_TRUE(VkTextureManager::AreStorageImageViewFormatsCompatible(
VK_FORMAT_R32_SFLOAT, VK_FORMAT_R32_SFLOAT));
EXPECT_FALSE(VkTextureManager::AreStorageImageViewFormatsCompatible(
VK_FORMAT_R32_SFLOAT, VK_FORMAT_R16G16B16A16_SFLOAT));
EXPECT_FALSE(VkTextureManager::AreStorageImageViewFormatsCompatible(
VK_FORMAT_R32_SFLOAT, VK_FORMAT_D32_SFLOAT));
}
TEST(DirectVulkanSanity, StorageImageViewFormatUsesBindingOnlyForFormatlessFloatPolicy) {
using MobileGL::MG_Backend::DirectVulkan::UniformManager;
EXPECT_EQ(UniformManager::ResolveStorageImageViewFormat(
VK_FORMAT_UNDEFINED, GL_RGBA16F, VK_FORMAT_R16G16B16A16_UNORM, true),
VK_FORMAT_R16G16B16A16_SFLOAT);
EXPECT_EQ(UniformManager::ResolveStorageImageViewFormat(
VK_FORMAT_UNDEFINED, GL_RGBA16, VK_FORMAT_R16G16B16A16_SFLOAT, true),
VK_FORMAT_R16G16B16A16_UNORM);
EXPECT_EQ(UniformManager::ResolveStorageImageViewFormat(
VK_FORMAT_R32_UINT, GL_RGBA16F, VK_FORMAT_R32_SFLOAT, false),
VK_FORMAT_R32_UINT);
EXPECT_EQ(UniformManager::ResolveStorageImageViewFormat(
VK_FORMAT_UNDEFINED, GL_RGBA16F, VK_FORMAT_R32_SFLOAT, false),
VK_FORMAT_R32_SFLOAT);
EXPECT_EQ(UniformManager::ResolveStorageImageViewFormat(
VK_FORMAT_UNDEFINED, GL_NONE, VK_FORMAT_R16G16B16A16_SFLOAT, true),
VK_FORMAT_UNDEFINED);
}
TEST(DirectVulkanSanity, ProgramObjectMovePreservesStorageImageFormatPolicy) {
using MobileGL::MG_Backend::DirectVulkan::ProgramFactory;
ProgramFactory::VkProgramObject source;
source.storageImageFormatByBinding = {VK_FORMAT_UNDEFINED, VK_FORMAT_R32_UINT};
source.storageImageUsesBindingFormatByBinding = {true, false};
ProgramFactory::VkProgramObject moved(std::move(source));
ASSERT_EQ(moved.storageImageFormatByBinding.size(), 2u);
ASSERT_EQ(moved.storageImageUsesBindingFormatByBinding.size(), 2u);
EXPECT_EQ(moved.storageImageFormatByBinding[0], VK_FORMAT_UNDEFINED);
EXPECT_EQ(moved.storageImageFormatByBinding[1], VK_FORMAT_R32_UINT);
EXPECT_TRUE(moved.storageImageUsesBindingFormatByBinding[0]);
EXPECT_FALSE(moved.storageImageUsesBindingFormatByBinding[1]);
ProgramFactory::VkProgramObject assigned;
assigned = std::move(moved);
ASSERT_EQ(assigned.storageImageFormatByBinding.size(), 2u);
ASSERT_EQ(assigned.storageImageUsesBindingFormatByBinding.size(), 2u);
EXPECT_TRUE(assigned.storageImageUsesBindingFormatByBinding[0]);
EXPECT_FALSE(assigned.storageImageUsesBindingFormatByBinding[1]);
}
TEST(DirectVulkanSanity, SamplerUniformTypesPreserveTheirNumericDomain) {
using namespace MobileGL::MG_Backend::DirectVulkan;
EXPECT_EQ(ProgramFactory::UniformTypeToSamplerNumericDomain(GL_SAMPLER_2D),
SamplerNumericDomain::Float);
EXPECT_EQ(ProgramFactory::UniformTypeToSamplerNumericDomain(GL_SAMPLER_CUBE_MAP_ARRAY_SHADOW),
SamplerNumericDomain::Float);
EXPECT_EQ(ProgramFactory::UniformTypeToSamplerNumericDomain(GL_INT_SAMPLER_2D_ARRAY),
SamplerNumericDomain::SignedInteger);
EXPECT_EQ(ProgramFactory::UniformTypeToSamplerNumericDomain(GL_UNSIGNED_INT_SAMPLER_2D),
SamplerNumericDomain::UnsignedInteger);
EXPECT_EQ(ProgramFactory::UniformTypeToSamplerNumericDomain(GL_IMAGE_2D),
SamplerNumericDomain::Unknown);
}
TEST(DirectVulkanSanity, SampledViewFormatMatchesSamplerNumericDomainWithoutChangingComponentLayout) {
using namespace MobileGL::MG_Backend::DirectVulkan;
EXPECT_EQ(VkTextureManager::ResolveSampledImageViewFormat(
VK_FORMAT_R32_SFLOAT, SamplerNumericDomain::UnsignedInteger),
VK_FORMAT_R32_UINT);
EXPECT_EQ(VkTextureManager::ResolveSampledImageViewFormat(
VK_FORMAT_R32_SFLOAT, SamplerNumericDomain::SignedInteger),
VK_FORMAT_R32_SINT);
EXPECT_EQ(VkTextureManager::ResolveSampledImageViewFormat(
VK_FORMAT_R32_UINT, SamplerNumericDomain::Float),
VK_FORMAT_R32_SFLOAT);
EXPECT_EQ(VkTextureManager::ResolveSampledImageViewFormat(
VK_FORMAT_R16G16B16A16_SFLOAT, SamplerNumericDomain::UnsignedInteger),
VK_FORMAT_R16G16B16A16_UINT);
EXPECT_EQ(VkTextureManager::ResolveSampledImageViewFormat(
VK_FORMAT_R8G8B8A8_UNORM, SamplerNumericDomain::UnsignedInteger),
VK_FORMAT_R8G8B8A8_UINT);
EXPECT_EQ(VkTextureManager::ResolveSampledImageViewFormat(
VK_FORMAT_R32_UINT, SamplerNumericDomain::UnsignedInteger),
VK_FORMAT_R32_UINT);
EXPECT_EQ(VkTextureManager::ResolveSampledImageViewFormat(
VK_FORMAT_B10G11R11_UFLOAT_PACK32, SamplerNumericDomain::UnsignedInteger),
VK_FORMAT_UNDEFINED);
EXPECT_EQ(VkTextureManager::ResolveSampledImageViewFormat(
VK_FORMAT_D32_SFLOAT, SamplerNumericDomain::UnsignedInteger),
VK_FORMAT_UNDEFINED);
EXPECT_TRUE(VkTextureManager::AreSampledImageViewFormatsCompatible(
VK_FORMAT_R32_SFLOAT, VK_FORMAT_R32_UINT));
EXPECT_FALSE(VkTextureManager::AreSampledImageViewFormatsCompatible(
VK_FORMAT_R32_SFLOAT, VK_FORMAT_R16G16B16A16_UINT));
}
TEST(RenderStateSanity, ProvokingVertexUpdatesStateAndValidatesEnum) { TEST(RenderStateSanity, ProvokingVertexUpdatesStateAndValidatesEnum) {
using namespace MobileGL; using namespace MobileGL;
+2 -2
View File
@@ -17,7 +17,7 @@ target_link_libraries(
) )
include(GoogleTest) include(GoogleTest)
gtest_discover_tests(TextureTest DISCOVERY_TIMEOUT 30 PROPERTIES LABELS unit) gtest_discover_tests(TextureTest DISCOVERY_TIMEOUT 30)
add_executable( add_executable(
VkClearManagerTest VkClearManagerTest
@@ -35,4 +35,4 @@ target_link_libraries(
${LINK_LIBRARIES} ${LINK_LIBRARIES}
) )
gtest_discover_tests(VkClearManagerTest DISCOVERY_TIMEOUT 30 PROPERTIES LABELS unit) gtest_discover_tests(VkClearManagerTest DISCOVERY_TIMEOUT 30)
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -17,4 +17,4 @@ target_link_libraries(
) )
include(GoogleTest) include(GoogleTest)
gtest_discover_tests(VertexArrayTest DISCOVERY_TIMEOUT 30 PROPERTIES LABELS unit) gtest_discover_tests(VertexArrayTest DISCOVERY_TIMEOUT 30)
@@ -8,8 +8,6 @@
#include <gtest/gtest.h> #include <gtest/gtest.h>
#include <limits>
#include "Includes.h" #include "Includes.h"
#include "Init.h" #include "Init.h"
@@ -37,31 +35,9 @@ protected:
return vbo; return vbo;
} }
// GL error flags are sticky per error code and the context outlives an individual test in this void SetUp() override { MobileGL::Initialize(); }
// binary, so drain whatever an earlier test left pending - otherwise an error-code assertion
// here reads someone else's error. Bounded: one flag per code, so this cannot hang the suite.
static void DrainPendingGlErrors() {
for (Int drained = 0; drained < 16 && MG_Impl::GLImpl::GetError() != GL_NO_ERROR; ++drained) {
}
}
// The call under test must raise exactly the expected error and nothing more: a second pending void TearDown() override {}
// error means one entry point queued several, which GetError() would hand out at an unrelated
// call site later on.
static void ExpectSingleGlError(GLenum expected) {
EXPECT_EQ(MG_Impl::GLImpl::GetError(), expected);
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR) << "the call recorded more than one error";
}
void SetUp() override {
MobileGL::Initialize();
DrainPendingGlErrors();
}
void TearDown() override {
// Attribute a leaked error to the test that caused it instead of to whoever runs next.
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR) << "test left an unconsumed GL error behind";
}
}; };
TEST_F(VertexArrayTest, GenerateAndBindVAO) { TEST_F(VertexArrayTest, GenerateAndBindVAO) {
@@ -81,46 +57,6 @@ TEST_F(VertexArrayTest, GenerateAndBindVAO) {
// Do not detect if it supports default VAO // Do not detect if it supports default VAO
} }
// GL 3.3 core 2.10 name lifecycle - the same three rules the other object families assert:
// deleting an unknown name is silent, a released reservation is recycled, and binding a dead
// name is INVALID_OPERATION.
TEST_F(VertexArrayTest, DeleteOfUnknownOrAlreadyDeletedVertexArrayNameIsSilent) {
GLuint vao = 0;
MG_Impl::GLImpl::GenVertexArrays(1, &vao);
ASSERT_NE(vao, 0u);
ASSERT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
MG_Impl::GLImpl::DeleteVertexArrays(1, &vao);
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
MG_Impl::GLImpl::DeleteVertexArrays(1, &vao);
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
// Not a small literal: other tests in this binary share the context and generate names in
// bulk, so a low number may well be a legitimately reserved name here.
const GLuint unknownNames[] = {0u, std::numeric_limits<GLuint>::max()};
MG_Impl::GLImpl::DeleteVertexArrays(2, unknownNames);
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
}
TEST_F(VertexArrayTest, DeleteGeneratedButUnboundVertexArrayNameReleasesReservationAndBindFails) {
GLuint vao = 0;
MG_Impl::GLImpl::GenVertexArrays(1, &vao);
ASSERT_NE(vao, 0u);
ASSERT_TRUE(MG_State::pGLContext->ValidateVertexArrayName(vao));
MG_Impl::GLImpl::DeleteVertexArrays(1, &vao);
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
EXPECT_FALSE(MG_State::pGLContext->ValidateVertexArrayName(vao));
MG_Impl::GLImpl::BindVertexArray(vao);
ExpectSingleGlError(GL_INVALID_OPERATION);
GLuint recycled = 0;
MG_Impl::GLImpl::GenVertexArrays(1, &recycled);
EXPECT_EQ(recycled, vao);
}
TEST_F(VertexArrayTest, VertexAttributeSetup) { TEST_F(VertexArrayTest, VertexAttributeSetup) {
Vector<Uint> vaoNames; Vector<Uint> vaoNames;
MobileGL::MG_State::pGLContext->GenVertexArrayNames(1, vaoNames); MobileGL::MG_State::pGLContext->GenVertexArrayNames(1, vaoNames);
@@ -328,9 +264,7 @@ TEST_F(VertexArrayTest, VertexBindingIndexIsBoundedByTheAdvertisedAttribLimit) {
const GLuint outOfRange = MG_Impl::GLImpl::VertexArrayImpl::GetMaxVertexAttribs(); const GLuint outOfRange = MG_Impl::GLImpl::VertexArrayImpl::GetMaxVertexAttribs();
MG_Impl::GLImpl::VertexAttribBinding(0, outOfRange); MG_Impl::GLImpl::VertexAttribBinding(0, outOfRange);
// Asserting the exact code (rather than just "some error") also consumes it, so the next test EXPECT_TRUE(MG_State::pGLContext->HasGLError());
// does not inherit it - GL error flags are sticky and this context is shared.
ExpectSingleGlError(GL_INVALID_VALUE);
} }
// The default attribute -> binding-point mapping is the identity. It used to be a 16-element literal // The default attribute -> binding-point mapping is the identity. It used to be a 16-element literal
@@ -1173,17 +1107,9 @@ TEST_F(GeneralVertexArrayTest, CurrentAttrib_PackedValidation) {
GetVertexAttribfv(1, GL_CURRENT_VERTEX_ATTRIB, out); GetVertexAttribfv(1, GL_CURRENT_VERTEX_ATTRIB, out);
EXPECT_FLOAT_EQ(out[0], 42.0f); // unchanged by the failed call EXPECT_FLOAT_EQ(out[0], 42.0f); // unchanged by the failed call
// GL 3.3 core 2.7: attribute 0's current value is writable like any other generic // Attribute 0 is rejected by MobileGL policy (GL_INVALID_OPERATION).
// attribute - no error. (An earlier MobileGL policy rejected index 0 with
// GL_INVALID_OPERATION, which broke GL CTS's per-case state reset: gluStateReset writes
// vertexAttrib4f(0, 0,0,0,1) for every attribute after every case.)
VertexAttribP4ui(0, GL_UNSIGNED_INT_2_10_10_10_REV, GL_FALSE, 1u); VertexAttribP4ui(0, GL_UNSIGNED_INT_2_10_10_10_REV, GL_FALSE, 1u);
EXPECT_EQ(GetError(), GL_NO_ERROR); EXPECT_EQ(GetError(), GL_INVALID_OPERATION);
GetVertexAttribfv(0, GL_CURRENT_VERTEX_ATTRIB, out);
EXPECT_EQ(GetError(), GL_NO_ERROR);
EXPECT_FLOAT_EQ(out[0], 1.0f); // x field of the packed word
VertexAttrib4f(0, 0.0f, 0.0f, 0.0f, 1.0f); // restore the initial current value
EXPECT_EQ(GetError(), GL_NO_ERROR);
// Out-of-range index -> GL_INVALID_VALUE. // Out-of-range index -> GL_INVALID_VALUE.
VertexAttribP4ui(VertexArrayImpl::GetMaxVertexAttribs(), GL_UNSIGNED_INT_2_10_10_10_REV, GL_FALSE, 1u); VertexAttribP4ui(VertexArrayImpl::GetMaxVertexAttribs(), GL_UNSIGNED_INT_2_10_10_10_REV, GL_FALSE, 1u);
@@ -865,9 +865,6 @@ namespace MobileGL::MG_Util::BackendLoader {
GLint maxUniformBlockSize = 16384; GLint maxUniformBlockSize = 16384;
GLint maxImageUnits = 8; GLint maxImageUnits = 8;
GLint maxCombinedImageUniforms = 8; GLint maxCombinedImageUniforms = 8;
GLint maxVertexImageUniforms = 0;
GLint maxGeometryImageUniforms = 0;
GLint maxFragmentImageUniforms = 8;
GLint maxComputeImageUniforms = 8; GLint maxComputeImageUniforms = 8;
GLint maxDrawBuffers = 8; GLint maxDrawBuffers = 8;
GLint maxColorAttachments = 8; GLint maxColorAttachments = 8;
@@ -907,29 +904,13 @@ namespace MobileGL::MG_Util::BackendLoader {
glesFuncs.glGetIntegerv(GL_MAX_UNIFORM_BLOCK_SIZE, &maxUniformBlockSize); glesFuncs.glGetIntegerv(GL_MAX_UNIFORM_BLOCK_SIZE, &maxUniformBlockSize);
glesFuncs.glGetIntegerv(GL_MAX_IMAGE_UNITS, &maxImageUnits); glesFuncs.glGetIntegerv(GL_MAX_IMAGE_UNITS, &maxImageUnits);
glesFuncs.glGetIntegerv(GL_MAX_COMBINED_IMAGE_UNIFORMS, &maxCombinedImageUniforms); glesFuncs.glGetIntegerv(GL_MAX_COMBINED_IMAGE_UNIFORMS, &maxCombinedImageUniforms);
glesFuncs.glGetIntegerv(GL_MAX_VERTEX_IMAGE_UNIFORMS, &maxVertexImageUniforms);
glesFuncs.glGetIntegerv(GL_MAX_FRAGMENT_IMAGE_UNIFORMS, &maxFragmentImageUniforms);
glesFuncs.glGetIntegerv(GL_MAX_COMPUTE_IMAGE_UNIFORMS, &maxComputeImageUniforms); glesFuncs.glGetIntegerv(GL_MAX_COMPUTE_IMAGE_UNIFORMS, &maxComputeImageUniforms);
// Geometry shaders and their image-uniform query are core only in ES 3.2. DirectGLES
// emits ESSL 3.10 on an ES 3.1 context, so reporting zero there is both legal and an
// accurate description of what the backend compiler can consume.
if (caps.GLESVersion.Major > 3 ||
(caps.GLESVersion.Major == 3 && caps.GLESVersion.Minor >= 2)) {
glesFuncs.glGetIntegerv(GL_MAX_GEOMETRY_IMAGE_UNIFORMS, &maxGeometryImageUniforms);
}
glesFuncs.glGetIntegerv(GL_MAX_DRAW_BUFFERS, &maxDrawBuffers); glesFuncs.glGetIntegerv(GL_MAX_DRAW_BUFFERS, &maxDrawBuffers);
glesFuncs.glGetIntegerv(GL_MAX_COLOR_ATTACHMENTS, &maxColorAttachments); glesFuncs.glGetIntegerv(GL_MAX_COLOR_ATTACHMENTS, &maxColorAttachments);
glesFuncs.glGetIntegerv(GL_MAX_CLIP_DISTANCES, &maxClipDistances); glesFuncs.glGetIntegerv(GL_MAX_CLIP_DISTANCES, &maxClipDistances);
glesFuncs.glGetIntegerv(GL_MAX_VIEWPORTS, &maxViewports); glesFuncs.glGetIntegerv(GL_MAX_VIEWPORTS, &maxViewports);
glesFuncs.glGetIntegerv(GL_MAX_VIEWPORT_DIMS, maxViewportDims); glesFuncs.glGetIntegerv(GL_MAX_VIEWPORT_DIMS, maxViewportDims);
glesFuncs.glGetIntegerv(GL_VIEWPORT_SUBPIXEL_BITS, &viewportSubpixelBits); glesFuncs.glGetIntegerv(GL_VIEWPORT_SUBPIXEL_BITS, &viewportSubpixelBits);
// Only legal to query once the extension has been seen in the loop above, hence not batched
// with the unconditional probes: on a driver without it this raises GL_INVALID_ENUM.
if (caps.SupportsTextureFilterAnisotropy) {
GLfloat maxTextureMaxAnisotropy = 1.0f;
glesFuncs.glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &maxTextureMaxAnisotropy);
caps.MaxTextureMaxAnisotropy = std::max(maxTextureMaxAnisotropy, 1.0f);
}
caps.AliasedLineWidthRangeMin = aliasedLineWidthRange[0]; caps.AliasedLineWidthRangeMin = aliasedLineWidthRange[0];
caps.AliasedLineWidthRangeMax = aliasedLineWidthRange[1]; caps.AliasedLineWidthRangeMax = aliasedLineWidthRange[1];
caps.SmoothLineWidthRangeMin = smoothLineWidthRange[0]; caps.SmoothLineWidthRangeMin = smoothLineWidthRange[0];
@@ -967,9 +948,6 @@ namespace MobileGL::MG_Util::BackendLoader {
caps.MaxUniformBlockSize = maxUniformBlockSize; caps.MaxUniformBlockSize = maxUniformBlockSize;
caps.MaxImageUnits = maxImageUnits; caps.MaxImageUnits = maxImageUnits;
caps.MaxCombinedImageUniforms = maxCombinedImageUniforms; caps.MaxCombinedImageUniforms = maxCombinedImageUniforms;
caps.MaxVertexImageUniforms = maxVertexImageUniforms;
caps.MaxGeometryImageUniforms = maxGeometryImageUniforms;
caps.MaxFragmentImageUniforms = maxFragmentImageUniforms;
caps.MaxComputeImageUniforms = maxComputeImageUniforms; caps.MaxComputeImageUniforms = maxComputeImageUniforms;
caps.MaxDrawBuffers = maxDrawBuffers; caps.MaxDrawBuffers = maxDrawBuffers;
caps.MaxColorAttachments = maxColorAttachments; caps.MaxColorAttachments = maxColorAttachments;
@@ -1015,9 +993,6 @@ namespace MobileGL::MG_Util::BackendLoader {
MGLOG_I(" GL_MAX_UNIFORM_BLOCK_SIZE: %d", caps.MaxUniformBlockSize); MGLOG_I(" GL_MAX_UNIFORM_BLOCK_SIZE: %d", caps.MaxUniformBlockSize);
MGLOG_I(" GL_MAX_IMAGE_UNITS: %d", caps.MaxImageUnits); MGLOG_I(" GL_MAX_IMAGE_UNITS: %d", caps.MaxImageUnits);
MGLOG_I(" GL_MAX_COMBINED_IMAGE_UNIFORMS: %d", caps.MaxCombinedImageUniforms); MGLOG_I(" GL_MAX_COMBINED_IMAGE_UNIFORMS: %d", caps.MaxCombinedImageUniforms);
MGLOG_I(" GL_MAX_VERTEX_IMAGE_UNIFORMS: %d", caps.MaxVertexImageUniforms);
MGLOG_I(" GL_MAX_GEOMETRY_IMAGE_UNIFORMS: %d", caps.MaxGeometryImageUniforms);
MGLOG_I(" GL_MAX_FRAGMENT_IMAGE_UNIFORMS: %d", caps.MaxFragmentImageUniforms);
MGLOG_I(" GL_MAX_COMPUTE_IMAGE_UNIFORMS: %d", caps.MaxComputeImageUniforms); MGLOG_I(" GL_MAX_COMPUTE_IMAGE_UNIFORMS: %d", caps.MaxComputeImageUniforms);
MGLOG_I(" GL_MAX_DRAW_BUFFERS: %d", caps.MaxDrawBuffers); MGLOG_I(" GL_MAX_DRAW_BUFFERS: %d", caps.MaxDrawBuffers);
MGLOG_I(" GL_MAX_COLOR_ATTACHMENTS: %d", caps.MaxColorAttachments); MGLOG_I(" GL_MAX_COLOR_ATTACHMENTS: %d", caps.MaxColorAttachments);
@@ -1034,9 +1034,6 @@ namespace MobileGL {
// GL_EXT_texture_filter_anisotropic is present, so sampler/texture // GL_EXT_texture_filter_anisotropic is present, so sampler/texture
// anisotropy may be forwarded without raising GL_INVALID_ENUM in GLES. // anisotropy may be forwarded without raising GL_INVALID_ENUM in GLES.
Bool SupportsTextureFilterAnisotropy = false; Bool SupportsTextureFilterAnisotropy = false;
// GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT of the host driver; only queried when the
// extension above is present, and left at 1.0 (no anisotropy) otherwise.
Float MaxTextureMaxAnisotropy = 1.0f;
Bool SupportsBaseInstance = false; Bool SupportsBaseInstance = false;
// GL_EXT_disjoint_timer_query is present in the extension string. // GL_EXT_disjoint_timer_query is present in the extension string.
Bool SupportsDisjointTimerQuery = false; Bool SupportsDisjointTimerQuery = false;
@@ -1102,9 +1099,6 @@ namespace MobileGL {
Int MaxUniformBlockSize = 16384; Int MaxUniformBlockSize = 16384;
Int MaxImageUnits = 8; Int MaxImageUnits = 8;
Int MaxCombinedImageUniforms = 8; Int MaxCombinedImageUniforms = 8;
Int MaxVertexImageUniforms = 0;
Int MaxGeometryImageUniforms = 0;
Int MaxFragmentImageUniforms = 8;
Int MaxComputeImageUniforms = 8; Int MaxComputeImageUniforms = 8;
Int MaxDrawBuffers = 8; Int MaxDrawBuffers = 8;
Int MaxColorAttachments = 8; Int MaxColorAttachments = 8;
@@ -124,7 +124,6 @@ namespace MobileGL::MG_Util::BackendLoader {
caps.UniformBufferOffsetAlignment = static_cast<int>(p.limits.minUniformBufferOffsetAlignment); caps.UniformBufferOffsetAlignment = static_cast<int>(p.limits.minUniformBufferOffsetAlignment);
caps.AliasedLineWidthRangeMin = p.limits.lineWidthRange[0]; caps.AliasedLineWidthRangeMin = p.limits.lineWidthRange[0];
caps.AliasedLineWidthRangeMax = p.limits.lineWidthRange[1]; caps.AliasedLineWidthRangeMax = p.limits.lineWidthRange[1];
caps.MaxSamplerAnisotropy = p.limits.maxSamplerAnisotropy;
caps.SmoothLineWidthRangeMin = p.limits.lineWidthRange[0]; caps.SmoothLineWidthRangeMin = p.limits.lineWidthRange[0];
caps.SmoothLineWidthRangeMax = p.limits.lineWidthRange[1]; caps.SmoothLineWidthRangeMax = p.limits.lineWidthRange[1];
caps.SmoothLineWidthGranularity = p.limits.lineWidthGranularity; caps.SmoothLineWidthGranularity = p.limits.lineWidthGranularity;
@@ -174,10 +173,6 @@ namespace MobileGL::MG_Util::BackendLoader {
VkPhysicalDeviceFeatures supportedFeatures{}; VkPhysicalDeviceFeatures supportedFeatures{};
vkGetPhysicalDeviceFeatures(physicalDevice, &supportedFeatures); vkGetPhysicalDeviceFeatures(physicalDevice, &supportedFeatures);
caps.SupportsWideLines = supportedFeatures.wideLines == VK_TRUE; caps.SupportsWideLines = supportedFeatures.wideLines == VK_TRUE;
caps.SupportsVertexPipelineStoresAndAtomics =
supportedFeatures.vertexPipelineStoresAndAtomics == VK_TRUE;
caps.SupportsFragmentStoresAndAtomics = supportedFeatures.fragmentStoresAndAtomics == VK_TRUE;
caps.SupportsGeometryShader = supportedFeatures.geometryShader == VK_TRUE;
caps.MaxShaderStorageBlockSize = static_cast<SizeT>(p.limits.maxStorageBufferRange); caps.MaxShaderStorageBlockSize = static_cast<SizeT>(p.limits.maxStorageBufferRange);
const Bool supportsShaderSubgroup = vk.vkGetPhysicalDeviceProperties2 && const Bool supportsShaderSubgroup = vk.vkGetPhysicalDeviceProperties2 &&
HasUsableShaderSubgroupSupport(subgroupProps); HasUsableShaderSubgroupSupport(subgroupProps);
@@ -213,7 +208,6 @@ namespace MobileGL::MG_Util::BackendLoader {
caps.UniformBufferOffsetAlignment = static_cast<int>(properties.limits.minUniformBufferOffsetAlignment); caps.UniformBufferOffsetAlignment = static_cast<int>(properties.limits.minUniformBufferOffsetAlignment);
caps.AliasedLineWidthRangeMin = properties.limits.lineWidthRange[0]; caps.AliasedLineWidthRangeMin = properties.limits.lineWidthRange[0];
caps.AliasedLineWidthRangeMax = properties.limits.lineWidthRange[1]; caps.AliasedLineWidthRangeMax = properties.limits.lineWidthRange[1];
caps.MaxSamplerAnisotropy = properties.limits.maxSamplerAnisotropy;
caps.SmoothLineWidthRangeMin = properties.limits.lineWidthRange[0]; caps.SmoothLineWidthRangeMin = properties.limits.lineWidthRange[0];
caps.SmoothLineWidthRangeMax = properties.limits.lineWidthRange[1]; caps.SmoothLineWidthRangeMax = properties.limits.lineWidthRange[1];
caps.SmoothLineWidthGranularity = properties.limits.lineWidthGranularity; caps.SmoothLineWidthGranularity = properties.limits.lineWidthGranularity;
@@ -260,11 +254,6 @@ namespace MobileGL::MG_Util::BackendLoader {
caps.ViewportBoundsRangeMax = properties.limits.viewportBoundsRange[1]; caps.ViewportBoundsRangeMax = properties.limits.viewportBoundsRange[1];
caps.ViewportSubpixelBits = static_cast<Int>(properties.limits.viewportSubPixelBits); caps.ViewportSubpixelBits = static_cast<Int>(properties.limits.viewportSubPixelBits);
caps.SupportsWideLines = false; caps.SupportsWideLines = false;
// This helper only receives properties, not VkPhysicalDeviceFeatures. Leave optional
// stage writes disabled rather than inferring them from descriptor limits alone.
caps.SupportsVertexPipelineStoresAndAtomics = false;
caps.SupportsFragmentStoresAndAtomics = false;
caps.SupportsGeometryShader = false;
caps.MaxShaderStorageBlockSize = static_cast<SizeT>(properties.limits.maxStorageBufferRange); caps.MaxShaderStorageBlockSize = static_cast<SizeT>(properties.limits.maxStorageBufferRange);
caps.SupportsShaderSubgroup = false; caps.SupportsShaderSubgroup = false;
caps.SubgroupSize = 0; caps.SubgroupSize = 0;
@@ -18,9 +18,6 @@ namespace MobileGL {
Int UniformBufferOffsetAlignment = 256; Int UniformBufferOffsetAlignment = 256;
Float AliasedLineWidthRangeMin = 1.0f; Float AliasedLineWidthRangeMin = 1.0f;
Float AliasedLineWidthRangeMax = 1.0f; Float AliasedLineWidthRangeMax = 1.0f;
// VkPhysicalDeviceLimits::maxSamplerAnisotropy. Whether it can be used at all depends on
// the samplerAnisotropy feature, which the renderer decides at device creation.
Float MaxSamplerAnisotropy = 1.0f;
Float SmoothLineWidthRangeMin = 1.0f; Float SmoothLineWidthRangeMin = 1.0f;
Float SmoothLineWidthRangeMax = 1.0f; Float SmoothLineWidthRangeMax = 1.0f;
Float SmoothLineWidthGranularity = 1.0f; Float SmoothLineWidthGranularity = 1.0f;
@@ -67,12 +64,6 @@ namespace MobileGL {
Float ViewportBoundsRangeMax = 0.0f; Float ViewportBoundsRangeMax = 0.0f;
Int ViewportSubpixelBits = 0; Int ViewportSubpixelBits = 0;
Bool SupportsWideLines = false; Bool SupportsWideLines = false;
// Storage-image descriptors are limited per stage by
// maxPerStageDescriptorStorageImages, but writes/atomics outside compute additionally
// require these core Vulkan features to be enabled on the logical device.
Bool SupportsVertexPipelineStoresAndAtomics = false;
Bool SupportsFragmentStoresAndAtomics = false;
Bool SupportsGeometryShader = false;
SizeT MaxShaderStorageBlockSize = 128 * 1024 * 1024; SizeT MaxShaderStorageBlockSize = 128 * 1024 * 1024;
Bool SupportsShaderSubgroup = false; Bool SupportsShaderSubgroup = false;
Uint32 SubgroupSize = 0; Uint32 SubgroupSize = 0;
@@ -58,17 +58,9 @@ namespace MobileGL {
TextureInputFormat ConvertGLEnumToTextureInputFormat(GLenum format) { TextureInputFormat ConvertGLEnumToTextureInputFormat(GLenum format) {
switch (format) { switch (format) {
// Legacy carve-out: GL_ALPHA stays mapped to Red so alpha-texture uploads keep landing in
// the R channel of the R8-backed storage (TexImage*_State pairs this with a 0,0,0,R
// swizzle). Readback of GL_ALPHA is corrected at the backend, which maps the raw enum to
// source channel 3 (DirectGLES GetReadbackChannelMapping).
case GL_ALPHA: case GL_ALPHA:
case GL_RED: case GL_RED:
return TextureInputFormat::Red; return TextureInputFormat::Red;
case GL_GREEN:
return TextureInputFormat::Green;
case GL_BLUE:
return TextureInputFormat::Blue;
case GL_RG: case GL_RG:
return TextureInputFormat::RG; return TextureInputFormat::RG;
case GL_RGB: case GL_RGB:
@@ -81,12 +73,6 @@ namespace MobileGL {
return TextureInputFormat::BGRA; return TextureInputFormat::BGRA;
case GL_RED_INTEGER: case GL_RED_INTEGER:
return TextureInputFormat::RInteger; return TextureInputFormat::RInteger;
case GL_GREEN_INTEGER:
return TextureInputFormat::GreenInteger;
case GL_BLUE_INTEGER:
return TextureInputFormat::BlueInteger;
case GL_ALPHA_INTEGER:
return TextureInputFormat::AlphaInteger;
case GL_RG_INTEGER: case GL_RG_INTEGER:
return TextureInputFormat::RGInteger; return TextureInputFormat::RGInteger;
case GL_RGB_INTEGER: case GL_RGB_INTEGER:
@@ -131,9 +117,6 @@ namespace MobileGL {
case GL_RGB4: case GL_RGB4:
return TextureInternalFormat::RGB4; return TextureInternalFormat::RGB4;
case GL_RGB5: case GL_RGB5:
// GL_RGB565 (GL 4.1 / ARB_ES2_compatibility, used directly by the GL CTS) is the
// ES-facing rendition of the legacy RGB5 resolution.
case GL_RGB565:
return TextureInternalFormat::RGB5; return TextureInternalFormat::RGB5;
case GL_RGB8: case GL_RGB8:
return TextureInternalFormat::RGB8; return TextureInternalFormat::RGB8;
@@ -311,8 +294,6 @@ namespace MobileGL {
return TexturePixelDataType::UnsignedInt8888; return TexturePixelDataType::UnsignedInt8888;
case GL_UNSIGNED_INT_8_8_8_8_REV: case GL_UNSIGNED_INT_8_8_8_8_REV:
return TexturePixelDataType::UnsignedInt8888Rev; return TexturePixelDataType::UnsignedInt8888Rev;
case GL_UNSIGNED_INT_10_10_10_2:
return TexturePixelDataType::UnsignedInt1010102;
case GL_UNSIGNED_INT_10F_11F_11F_REV: case GL_UNSIGNED_INT_10F_11F_11F_REV:
return TexturePixelDataType::UnsignedInt101111Rev; return TexturePixelDataType::UnsignedInt101111Rev;
case GL_UNSIGNED_INT_2_10_10_10_REV: case GL_UNSIGNED_INT_2_10_10_10_REV:
@@ -535,7 +535,6 @@ namespace MobileGL {
CASE(GL_RED_INTEGER) CASE(GL_RED_INTEGER)
CASE(GL_GREEN_INTEGER) CASE(GL_GREEN_INTEGER)
CASE(GL_BLUE_INTEGER) CASE(GL_BLUE_INTEGER)
CASE(GL_ALPHA_INTEGER)
CASE(GL_RGB_INTEGER) CASE(GL_RGB_INTEGER)
CASE(GL_RGBA_INTEGER) CASE(GL_RGBA_INTEGER)
CASE(GL_BGR_INTEGER) CASE(GL_BGR_INTEGER)
@@ -67,18 +67,6 @@ namespace MobileGL {
return GL_RGBA_INTEGER; return GL_RGBA_INTEGER;
case TextureInputFormat::BGRAInteger: case TextureInputFormat::BGRAInteger:
return GL_BGRA_INTEGER; return GL_BGRA_INTEGER;
case TextureInputFormat::Green:
return GL_GREEN;
case TextureInputFormat::Blue:
return GL_BLUE;
case TextureInputFormat::Alpha:
return GL_ALPHA;
case TextureInputFormat::GreenInteger:
return GL_GREEN_INTEGER;
case TextureInputFormat::BlueInteger:
return GL_BLUE_INTEGER;
case TextureInputFormat::AlphaInteger:
return GL_ALPHA_INTEGER;
case TextureInputFormat::StencilIndex: case TextureInputFormat::StencilIndex:
return GL_STENCIL_INDEX; return GL_STENCIL_INDEX;
case TextureInputFormat::DepthComponent: case TextureInputFormat::DepthComponent:
@@ -113,10 +101,7 @@ namespace MobileGL {
case TextureInternalFormat::RGB4: case TextureInternalFormat::RGB4:
return GL_RGB4; return GL_RGB4;
case TextureInternalFormat::RGB5: case TextureInternalFormat::RGB5:
// Emit the ES-compatible GL_RGB565 rendition: desktop GL_RGB5 is not a legal return GL_RGB5;
// sized internalformat on OpenGL ES backends, GL_RGB565 is (and GL 4.1+
// accepts it too via ARB_ES2_compatibility).
return GL_RGB565;
case TextureInternalFormat::RGB8: case TextureInternalFormat::RGB8:
return GL_RGB8; return GL_RGB8;
case TextureInternalFormat::RGB8Snorm: case TextureInternalFormat::RGB8Snorm:
@@ -67,18 +67,6 @@ namespace MobileGL {
return "RGBAInteger"; return "RGBAInteger";
case TextureInputFormat::BGRAInteger: case TextureInputFormat::BGRAInteger:
return "BGRAInteger"; return "BGRAInteger";
case TextureInputFormat::Green:
return "Green";
case TextureInputFormat::Blue:
return "Blue";
case TextureInputFormat::Alpha:
return "Alpha";
case TextureInputFormat::GreenInteger:
return "GreenInteger";
case TextureInputFormat::BlueInteger:
return "BlueInteger";
case TextureInputFormat::AlphaInteger:
return "AlphaInteger";
case TextureInputFormat::StencilIndex: case TextureInputFormat::StencilIndex:
return "StencilIndex"; return "StencilIndex";
case TextureInputFormat::DepthComponent: case TextureInputFormat::DepthComponent:
@@ -65,16 +65,6 @@ namespace MobileGL {
return VK_FORMAT_R8G8B8A8_UINT; return VK_FORMAT_R8G8B8A8_UINT;
case TextureInputFormat::BGRAInteger: case TextureInputFormat::BGRAInteger:
return VK_FORMAT_B8G8R8A8_UINT; return VK_FORMAT_B8G8R8A8_UINT;
// Single-channel desktop client formats: the client memory holds one component per pixel,
// matching the R8 layouts (the channel it feeds is a pixel-transfer concern, not a layout one).
case TextureInputFormat::Green:
case TextureInputFormat::Blue:
case TextureInputFormat::Alpha:
return VK_FORMAT_R8_UNORM;
case TextureInputFormat::GreenInteger:
case TextureInputFormat::BlueInteger:
case TextureInputFormat::AlphaInteger:
return VK_FORMAT_R8_UINT;
case TextureInputFormat::StencilIndex: case TextureInputFormat::StencilIndex:
return VK_FORMAT_S8_UINT; return VK_FORMAT_S8_UINT;
case TextureInputFormat::DepthComponent: case TextureInputFormat::DepthComponent:
-69
View File
@@ -1,69 +0,0 @@
// MobileGL - MobileGL/MG_Util/Math/HalfFloat.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_Util {
inline Float DecodeHalfBitsToFloat(Uint16 half) {
const Uint32 sign = static_cast<Uint32>(half & 0x8000u) << 16;
const Uint32 exponent = (half >> 10) & 0x1Fu;
const Uint32 mantissa = half & 0x3FFu;
Uint32 bits;
if (exponent == 0) {
if (mantissa == 0) {
bits = sign; // signed zero
} else {
// Subnormal half: renormalize into a float exponent.
Uint32 e = 127 - 15 + 1;
Uint32 m = mantissa;
while ((m & 0x400u) == 0) {
m <<= 1;
--e;
}
bits = sign | (e << 23) | ((m & 0x3FFu) << 13);
}
} else if (exponent == 31) {
bits = sign | 0x7F800000u | (mantissa << 13); // Inf / NaN
} else {
bits = sign | ((exponent + 112) << 23) | (mantissa << 13);
}
return std::bit_cast<Float>(bits);
}
inline Uint16 EncodeFloatToHalfBits(Float value) {
const Uint32 bits = std::bit_cast<Uint32>(value);
const auto sign = static_cast<Uint16>((bits >> 16) & 0x8000u);
const Uint32 exponent = (bits >> 23) & 0xFFu;
const Uint32 mantissa = bits & 0x7FFFFFu;
if (exponent == 0xFF) { // Inf / NaN
return static_cast<Uint16>(sign | 0x7C00u | (mantissa != 0 ? 0x200u : 0u));
}
const Int32 halfExponent = static_cast<Int32>(exponent) - 127 + 15;
if (halfExponent >= 31) {
return static_cast<Uint16>(sign | 0x7C00u); // overflow -> Inf
}
if (halfExponent <= 0) {
if (halfExponent < -10) {
return sign; // underflow -> signed zero
}
const Uint32 m = mantissa | 0x800000u;
const Uint32 shift = static_cast<Uint32>(14 - halfExponent);
Uint32 half = m >> shift;
if ((m >> (shift - 1)) & 1u) {
++half; // round to nearest
}
return static_cast<Uint16>(sign | half);
}
Uint32 half = (static_cast<Uint32>(halfExponent) << 10) | (mantissa >> 13);
if (mantissa & 0x1000u) {
++half; // round to nearest; a carry into the exponent is the correct result
}
return static_cast<Uint16>(sign | half);
}
} // namespace MobileGL::MG_Util
-116
View File
@@ -1,116 +0,0 @@
// MobileGL - MobileGL/MG_Util/Math/SmallFloat.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 <bit>
#include <cmath>
#include <limits>
namespace MobileGL::MG_Util {
// Encodes an unsigned small float with a 5-bit exponent (bias 15) and mantissaBits mantissa
// bits, per the EXT_packed_float conversion rules: negatives (including -Inf) go to zero,
// +Inf stays +Inf, NaN stays NaN, and finite values above the largest representable value
// clamp to it. The mantissa is truncated (rounding mode is implementation-defined).
inline Uint32 EncodeFloatToUnsignedSmallFloat(Float value, Int mantissaBits) {
const Uint32 bits = std::bit_cast<Uint32>(value);
const Bool negative = (bits & 0x80000000u) != 0;
const Uint32 exponent = (bits >> 23) & 0xFFu;
const Uint32 mantissa = bits & 0x7FFFFFu;
const Uint32 exponentMask = 0x1Fu << mantissaBits;
if (exponent == 0xFFu) {
if (mantissa != 0) {
return exponentMask | 1u; // NaN keeps NaN
}
return negative ? 0u : exponentMask; // -Inf -> 0, +Inf -> +Inf
}
if (negative) {
return 0u;
}
const Int32 smallExponent = static_cast<Int32>(exponent) - 127 + 15;
if (smallExponent >= 31) { // above the largest finite value -> clamp to it
return ((31u - 1u) << mantissaBits) | ((1u << mantissaBits) - 1u);
}
if (smallExponent <= 0) { // subnormal range: renormalize, flushing tiny values to zero
const Uint32 fullMantissa = mantissa | 0x800000u;
const Int32 shift = (23 - mantissaBits) + 1 - smallExponent;
return shift > 23 ? 0u : fullMantissa >> shift;
}
return (static_cast<Uint32>(smallExponent) << mantissaBits) |
(mantissa >> (23u - static_cast<Uint32>(mantissaBits)));
}
inline Uint32 EncodeFloatToUnsignedF11(Float value) { return EncodeFloatToUnsignedSmallFloat(value, 6); }
inline Uint32 EncodeFloatToUnsignedF10(Float value) { return EncodeFloatToUnsignedSmallFloat(value, 5); }
// Decodes an unsigned small float (5-bit exponent, bias 15, mantissaBits mantissa bits).
inline Float DecodeUnsignedSmallFloatToFloat(Uint32 field, Int mantissaBits) {
const Uint32 exponent = (field >> mantissaBits) & 0x1Fu;
const Uint32 mantissa = field & ((1u << mantissaBits) - 1u);
const Float mantissaScale = 1.0f / static_cast<Float>(1u << mantissaBits);
if (exponent == 0) {
return std::exp2(-14.0f) * static_cast<Float>(mantissa) * mantissaScale;
}
if (exponent == 31) {
return mantissa == 0 ? std::numeric_limits<Float>::infinity()
: std::numeric_limits<Float>::quiet_NaN();
}
return std::exp2(static_cast<Float>(exponent) - 15.0f) *
(1.0f + static_cast<Float>(mantissa) * mantissaScale);
}
inline Float DecodeUnsignedF11ToFloat(Uint32 field) { return DecodeUnsignedSmallFloatToFloat(field, 6); }
inline Float DecodeUnsignedF10ToFloat(Uint32 field) { return DecodeUnsignedSmallFloatToFloat(field, 5); }
// RGB9E5 shared-exponent encode, following the EXT_texture_shared_exponent spec algorithm
// (N = 9 mantissa bits, B = 15 exponent bias, Emax = 31).
inline Uint32 EncodeSharedExponentRGB9E5(const Float rgb[3]) {
constexpr Int kMantissaBits = 9;
constexpr Int kExponentBias = 15;
constexpr Float kSharedExpMax = 511.0f / 512.0f * 65536.0f; // (2^N-1)/2^N * 2^(Emax-B)
Float clamped[3];
for (Int i = 0; i < 3; ++i) {
const Float v = rgb[i];
clamped[i] = (std::isnan(v) || v < 0.0f) ? 0.0f : std::min(v, kSharedExpMax);
}
const Float maxComponent = std::max(clamped[0], std::max(clamped[1], clamped[2]));
Int sharedExponent = 0; // all-zero input keeps the all-zero word
if (maxComponent > 0.0f) {
sharedExponent = std::max(-kExponentBias - 1, static_cast<Int>(std::floor(std::log2(maxComponent)))) +
1 + kExponentBias;
const Float maxScaled = std::floor(
maxComponent / std::exp2(static_cast<Float>(sharedExponent - kExponentBias - kMantissaBits)) +
0.5f);
if (maxScaled >= 512.0f) { // rounded up to 2^N: bump the shared exponent instead
++sharedExponent;
}
}
const Float scale = std::exp2(static_cast<Float>(sharedExponent - kExponentBias - kMantissaBits));
Uint32 word = static_cast<Uint32>(sharedExponent) << 27;
for (Int i = 0; i < 3; ++i) {
const auto field = static_cast<Uint32>(std::floor(clamped[i] / scale + 0.5f));
word |= std::min(field, 511u) << (i * kMantissaBits);
}
return word;
}
// RGB9E5 shared-exponent decode.
inline void DecodeSharedExponentRGB9E5(Uint32 word, Float outRgb[3]) {
constexpr Int kMantissaBits = 9;
constexpr Int kExponentBias = 15;
const Int exponent = static_cast<Int>(word >> 27) - kExponentBias - kMantissaBits;
const Float scale = std::exp2(static_cast<Float>(exponent));
for (Int i = 0; i < 3; ++i) {
outRgb[i] = static_cast<Float>((word >> (i * kMantissaBits)) & 0x1FFu) * scale;
}
}
} // namespace MobileGL::MG_Util
+3 -13
View File
@@ -15,10 +15,10 @@ namespace MobileGL {
SizeT GetSizedInternalFormatSizeInBytes(TextureInternalFormat internal) { SizeT GetSizedInternalFormatSizeInBytes(TextureInternalFormat internal) {
switch (internal) { switch (internal) {
case TextureInternalFormat::R8: case TextureInternalFormat::R8:
case TextureInternalFormat::Red: // UNorm8 shadow layout
case TextureInternalFormat::R8Snorm: case TextureInternalFormat::R8Snorm:
case TextureInternalFormat::R8I: case TextureInternalFormat::R8I:
case TextureInternalFormat::R8UI: case TextureInternalFormat::R8UI:
case TextureInternalFormat::R3G3B2:
return 1; return 1;
case TextureInternalFormat::R16: case TextureInternalFormat::R16:
@@ -27,17 +27,14 @@ namespace MobileGL {
case TextureInternalFormat::R16UI: case TextureInternalFormat::R16UI:
case TextureInternalFormat::R16F: case TextureInternalFormat::R16F:
case TextureInternalFormat::RG8: case TextureInternalFormat::RG8:
case TextureInternalFormat::RG: // UNorm8x2 shadow layout
case TextureInternalFormat::RG8Snorm: case TextureInternalFormat::RG8Snorm:
case TextureInternalFormat::RG8I: case TextureInternalFormat::RG8I:
case TextureInternalFormat::RG8UI: case TextureInternalFormat::RG8UI:
case TextureInternalFormat::DepthComponent16: case TextureInternalFormat::DepthComponent16:
return 2; return 2;
case TextureInternalFormat::R3G3B2: // UNorm8x3 shadow layout
case TextureInternalFormat::RGB4: case TextureInternalFormat::RGB4:
case TextureInternalFormat::RGB5: case TextureInternalFormat::RGB5:
case TextureInternalFormat::RGB: // UNorm8x3 shadow layout
case TextureInternalFormat::RGB8: case TextureInternalFormat::RGB8:
case TextureInternalFormat::RGB8Snorm: case TextureInternalFormat::RGB8Snorm:
case TextureInternalFormat::SRGB8: case TextureInternalFormat::SRGB8:
@@ -46,10 +43,11 @@ namespace MobileGL {
case TextureInternalFormat::DepthComponent24: case TextureInternalFormat::DepthComponent24:
return 3; return 3;
case TextureInternalFormat::RGB10:
case TextureInternalFormat::RGB12:
case TextureInternalFormat::RGBA2: case TextureInternalFormat::RGBA2:
case TextureInternalFormat::RGBA4: case TextureInternalFormat::RGBA4:
case TextureInternalFormat::RGB5A1: case TextureInternalFormat::RGB5A1:
case TextureInternalFormat::RGBA: // UNorm8x4 shadow layout
case TextureInternalFormat::RGBA8: case TextureInternalFormat::RGBA8:
case TextureInternalFormat::RGBA8Snorm: case TextureInternalFormat::RGBA8Snorm:
case TextureInternalFormat::RGBA8I: case TextureInternalFormat::RGBA8I:
@@ -74,8 +72,6 @@ namespace MobileGL {
return 4; return 4;
case TextureInternalFormat::RGB16: case TextureInternalFormat::RGB16:
case TextureInternalFormat::RGB10: // UNorm16x3 shadow layout
case TextureInternalFormat::RGB12: // UNorm16x3 shadow layout
case TextureInternalFormat::RGB16Snorm: case TextureInternalFormat::RGB16Snorm:
case TextureInternalFormat::RGB16F: case TextureInternalFormat::RGB16F:
case TextureInternalFormat::RGB16I: case TextureInternalFormat::RGB16I:
@@ -117,12 +113,6 @@ namespace MobileGL {
switch (format) { switch (format) {
case TextureInputFormat::Red: case TextureInputFormat::Red:
case TextureInputFormat::RInteger: case TextureInputFormat::RInteger:
case TextureInputFormat::Green:
case TextureInputFormat::GreenInteger:
case TextureInputFormat::Blue:
case TextureInputFormat::BlueInteger:
case TextureInputFormat::Alpha:
case TextureInputFormat::AlphaInteger:
return 1; return 1;
case TextureInputFormat::RG: case TextureInputFormat::RG:
case TextureInputFormat::RGInteger: case TextureInputFormat::RGInteger:
+3 -5
View File
@@ -548,8 +548,8 @@ namespace MobileGL::MG_Util::SelfTest {
if (summary.capsValid) { if (summary.capsValid) {
backendApiVersionString = MG_Backend::DirectGLES::FormatBackendAPIVersionString( backendApiVersionString = MG_Backend::DirectGLES::FormatBackendAPIVersionString(
summary.caps.GLESRendererString, summary.caps.GLESVersion.Major, summary.caps.GLESVersion.Minor); summary.caps.GLESRendererString, summary.caps.GLESVersion.Major, summary.caps.GLESVersion.Minor);
advertisedExtensions = JoinAdvertisedExtensions(MG_Backend::DirectGLES::BuildAdvertisedExtensions( advertisedExtensions = JoinAdvertisedExtensions(
summary.caps.SupportsDisjointTimerQuery, summary.caps.SupportsTextureFilterAnisotropy)); MG_Backend::DirectGLES::BuildAdvertisedExtensions(summary.caps.SupportsDisjointTimerQuery));
} }
AppendMobileGLReportedRows(builder, MG_Backend::DirectGLES::GetRendererIdentity(), backendApiVersionString, AppendMobileGLReportedRows(builder, MG_Backend::DirectGLES::GetRendererIdentity(), backendApiVersionString,
advertisedExtensions); advertisedExtensions);
@@ -841,7 +841,6 @@ namespace MobileGL::MG_Util::SelfTest {
String driverVersionString; // raw hex, vendor-encoded (see RunVulkanDriverPost) String driverVersionString; // raw hex, vendor-encoded (see RunVulkanDriverPost)
Bool shaderSubgroupUsable = false; Bool shaderSubgroupUsable = false;
Bool timerQueriesSupported = false; Bool timerQueriesSupported = false;
Bool samplerAnisotropySupported = false;
}; };
} // namespace } // namespace
@@ -1108,7 +1107,6 @@ namespace MobileGL::MG_Util::SelfTest {
VkPhysicalDeviceFeatures features{}; VkPhysicalDeviceFeatures features{};
vkGetPhysicalDeviceFeaturesFn(physicalDevice, &features); vkGetPhysicalDeviceFeaturesFn(physicalDevice, &features);
summary.samplerAnisotropySupported = features.samplerAnisotropy == VK_TRUE;
if (features.multiDrawIndirect == VK_TRUE) { if (features.multiDrawIndirect == VK_TRUE) {
builder.Pass("multiDrawIndirect", "indirect multi-draw batches run as single native commands"); builder.Pass("multiDrawIndirect", "indirect multi-draw batches run as single native commands");
} else { } else {
@@ -1281,7 +1279,7 @@ namespace MobileGL::MG_Util::SelfTest {
backendApiVersionString = MG_Backend::DirectVulkan::FormatBackendAPIVersionString( backendApiVersionString = MG_Backend::DirectVulkan::FormatBackendAPIVersionString(
summary.deviceName, summary.apiVersionString, summary.driverVersionString); summary.deviceName, summary.apiVersionString, summary.driverVersionString);
advertisedExtensions = JoinAdvertisedExtensions(MG_Backend::DirectVulkan::BuildAdvertisedExtensions( advertisedExtensions = JoinAdvertisedExtensions(MG_Backend::DirectVulkan::BuildAdvertisedExtensions(
summary.shaderSubgroupUsable, summary.timerQueriesSupported, summary.samplerAnisotropySupported)); summary.shaderSubgroupUsable, summary.timerQueriesSupported));
} }
AppendMobileGLReportedRows(builder, MG_Backend::DirectVulkan::GetRendererIdentity(), backendApiVersionString, AppendMobileGLReportedRows(builder, MG_Backend::DirectVulkan::GetRendererIdentity(), backendApiVersionString,
advertisedExtensions); advertisedExtensions);
@@ -6,10 +6,6 @@
// SPDX-License-Identifier: LGPL-3.0-only // SPDX-License-Identifier: LGPL-3.0-only
// End of Source File Header // End of Source File Header
#define SPV_ENABLE_UTILITY_CODE
#include "glslang/SPIRV/spirv.hpp11"
#undef SPV_ENABLE_UTILITY_CODE
#include "ShaderCompiler.h" #include "ShaderCompiler.h"
#include "SpirvPasses/EliminateFloatEqualsZeroPass.h" #include "SpirvPasses/EliminateFloatEqualsZeroPass.h"
@@ -18,20 +14,17 @@
#include "SpirvPasses/DecomposeWorkgroupVec3Pass.h" #include "SpirvPasses/DecomposeWorkgroupVec3Pass.h"
#include "SpirvPasses/LowerDrawParametersPass.h" #include "SpirvPasses/LowerDrawParametersPass.h"
#include "SpirvPasses/RebaseInstanceIndexPass.h" #include "SpirvPasses/RebaseInstanceIndexPass.h"
#include "SpirvPasses/StripUboMemberRelaxedPrecisionPass.h"
#include "spirv-tools/libspirv.h" #include "spirv-tools/libspirv.h"
#include "spirv-tools/optimizer.hpp" #include "spirv-tools/optimizer.hpp"
#include "ShaderSourceProcessor.h"
#include <MG_Backend/BackendObjects.h>
#include <MG_Util/Converters/GLToStr/GLEnumConverter.h> #include <MG_Util/Converters/GLToStr/GLEnumConverter.h>
#include <MG_Util/Converters/GLToGlslang/ProgramEnumConverter.h> #include <MG_Util/Converters/GLToGlslang/ProgramEnumConverter.h>
namespace MobileGL { namespace MobileGL {
namespace MG_Util { namespace MG_Util {
namespace ShaderTranspiler { namespace ShaderTranspiler {
TBuiltInResource BuildTBuiltInResource() { TBuiltInResource& GetTBuiltInResourceInstance() {
TBuiltInResource Resources{}; static TBuiltInResource Resources{};
Resources.maxLights = 32; Resources.maxLights = 32;
Resources.maxClipPlanes = 6; Resources.maxClipPlanes = 6;
Resources.maxTextureUnits = 32; Resources.maxTextureUnits = 32;
@@ -126,22 +119,6 @@ namespace MobileGL {
Resources.maxTaskWorkGroupSizeZ_NV = 1; Resources.maxTaskWorkGroupSizeZ_NV = 1;
Resources.maxMeshViewCountNV = 4; Resources.maxMeshViewCountNV = 4;
// Resource checking must describe the same backend contract exposed through
// glGetIntegerv. Keeping this copy local also avoids racing on a process-global
// TBuiltInResource when Iris compiles shaders concurrently.
const MG_Backend::DynamicBackendParameters fallbackParameters{};
const auto& activeBackend = MG_Backend::pActiveBackendObject;
const auto& dynamicParameters =
activeBackend ? activeBackend->GetDynamicParameters() : fallbackParameters;
Resources.maxImageUnits = dynamicParameters.MaxImageUnits;
Resources.maxCombinedImageUnitsAndFragmentOutputs =
dynamicParameters.MaxImageUnits + dynamicParameters.MaxDrawBuffers;
Resources.maxVertexImageUniforms = dynamicParameters.MaxVertexImageUniforms;
Resources.maxGeometryImageUniforms = dynamicParameters.MaxGeometryImageUniforms;
Resources.maxFragmentImageUniforms = dynamicParameters.MaxFragmentImageUniforms;
Resources.maxComputeImageUniforms = dynamicParameters.MaxComputeImageUniforms;
Resources.maxCombinedImageUniforms = dynamicParameters.MaxCombinedImageUniforms;
Resources.limits.nonInductiveForLoops = true; Resources.limits.nonInductiveForLoops = true;
Resources.limits.whileLoops = true; Resources.limits.whileLoops = true;
Resources.limits.doWhileLoops = true; Resources.limits.doWhileLoops = true;
@@ -155,23 +132,27 @@ namespace MobileGL {
return Resources; return Resources;
} }
// One parse attempt. A glslang::TShader cannot be re-parsed, so a retry has to build a Result<SharedPtr<glslang::TShader>> ShaderCompiler::CompileShader(const ShaderAttrib& attrib) {
// fresh one with byte-identical setup - hence a single factored body rather than two auto shaderType = attrib.shaderType;
// copies that could drift apart. auto& sourceStr = attrib.sourceStr;
static Result<SharedPtr<glslang::TShader>> ParseShaderSource(EShLanguage lang, GLenum shaderType,
const String& source, auto lang = MG_Util::ConvertGLEnumToEShLanguage(shaderType);
Flags<ShaderCompileBits> flags) { if (lang == EShLanguage::EShLangCount) {
ResultInfo r;
r.log += "Error: [Preprocess] Unsupported shader type: " + ConvertGLEnumToString(shaderType);
r.errc = -1;
return std::unexpected(r);
}
SharedPtr<glslang::TShader> res; SharedPtr<glslang::TShader> res;
auto& tshader = res; auto& tshader = res;
tshader = MakeShared<glslang::TShader>(lang); tshader = MakeShared<glslang::TShader>(lang);
// setStrings gets no length array, so it relies on NUL termination: source must be an const char* src[] = {sourceStr.data()};
// owning buffer that outlives parse(), never a StringView's substring.
const char* src[] = {source.c_str()};
tshader->setStrings(src, 1); tshader->setStrings(src, 1);
tshader->setNanMinMaxClamp(true); tshader->setNanMinMaxClamp(true);
tshader->setInvertY(true); tshader->setInvertY(true);
tshader->setPreamble("#undef VULKAN\n"); tshader->setPreamble("#undef VULKAN\n");
if (flags & ShaderCompileBits::CompileForOpenGL) { if (attrib.flags & ShaderCompileBits::CompileForOpenGL) {
tshader->setEnvInput(glslang::EShSourceGlsl, lang, glslang::EShClientVulkan, 450); tshader->setEnvInput(glslang::EShSourceGlsl, lang, glslang::EShClientVulkan, 450);
tshader->setEnvClient(glslang::EShClientOpenGL, glslang::EShTargetOpenGL_450); tshader->setEnvClient(glslang::EShClientOpenGL, glslang::EShTargetOpenGL_450);
tshader->setEnvTarget(glslang::EShTargetSpv, glslang::EShTargetSpv_1_3); tshader->setEnvTarget(glslang::EShTargetSpv, glslang::EShTargetSpv_1_3);
@@ -187,8 +168,7 @@ namespace MobileGL {
tshader->setAutoMapLocations(true); tshader->setAutoMapLocations(true);
tshader->setAutoMapBindings(true); tshader->setAutoMapBindings(true);
tshader->setGlobalUniformBlockName(GLOBAL_UBO_NAME); tshader->setGlobalUniformBlockName(GLOBAL_UBO_NAME);
auto resources = BuildTBuiltInResource(); if (!tshader->parse(&GetTBuiltInResourceInstance(), 460, ECoreProfile,
if (!tshader->parse(&resources, 460, ECoreProfile,
/*forceDefaultVersionAndProfile: */ false, /*forceDefaultVersionAndProfile: */ false,
/*forwardCompatible: */ true, EShMsgDefault)) { /*forwardCompatible: */ true, EShMsgDefault)) {
ResultInfo r; ResultInfo r;
@@ -201,39 +181,6 @@ namespace MobileGL {
return res; return res;
} }
Result<SharedPtr<glslang::TShader>> ShaderCompiler::CompileShader(const ShaderAttrib& attrib) {
auto shaderType = attrib.shaderType;
auto lang = MG_Util::ConvertGLEnumToEShLanguage(shaderType);
if (lang == EShLanguage::EShLangCount) {
ResultInfo r;
r.log += "Error: [Preprocess] Unsupported shader type: " + ConvertGLEnumToString(shaderType);
r.errc = -1;
return std::unexpected(r);
}
const String source(attrib.sourceStr);
auto result = ParseShaderSource(lang, shaderType, source, attrib.flags);
if (result) return result;
// Legacy desktop sources are normalized to "#version 330 core", which parses under
// stricter rules than the 460 they used to be forced to: a shader declaring 330 while
// using e.g. layout(binding=...) without the matching #extension line compiles on real
// drivers but is rejected here. Retry once at 460 before reporting failure; a genuinely
// broken shader fails both attempts and keeps its original diagnostics.
String retrySource = source;
if (!MG_Util::ShaderTranspiler::RetargetLegacyVersionDirectiveTo460(retrySource)) {
return result;
}
auto retryResult = ParseShaderSource(lang, shaderType, retrySource, attrib.flags);
if (!retryResult) return result;
MGLOG_D("CompileShader: %s only parsed after retargeting its legacy #version to 460",
ConvertGLEnumToString(shaderType).c_str());
return retryResult;
}
Result<SharedPtr<glslang::TProgram>> ShaderCompiler::LinkProgram(const ProgramAttrib& attrib) { Result<SharedPtr<glslang::TProgram>> ShaderCompiler::LinkProgram(const ProgramAttrib& attrib) {
SharedPtr<glslang::TProgram> program = MakeShared<glslang::TProgram>(); SharedPtr<glslang::TProgram> program = MakeShared<glslang::TProgram>();
for (auto& s : attrib.shaders) { for (auto& s : attrib.shaders) {
@@ -319,19 +266,6 @@ namespace MobileGL {
return optimizer.Run(inputBinary.data(), inputBinary.size(), &outputBinary, options); return optimizer.Run(inputBinary.data(), inputBinary.size(), &outputBinary, options);
} }
bool ShaderCompiler::StripUboMemberRelaxedPrecisionForEssl(const Vector<Uint32>& inputBinary,
Vector<uint32_t>& outputBinary) {
using namespace spvtools;
OptimizerOptions options;
options.set_run_validator(false);
Optimizer optimizer(SPV_ENV_VULKAN_1_1);
optimizer.RegisterPass(
StripUboMemberRelaxedPrecisionPass::CreateStripUboMemberRelaxedPrecisionPass());
return optimizer.Run(inputBinary.data(), inputBinary.size(), &outputBinary, options);
}
bool ShaderCompiler::RebaseInstanceIndexForVulkan(const Vector<Uint32>& inputBinary, bool ShaderCompiler::RebaseInstanceIndexForVulkan(const Vector<Uint32>& inputBinary,
Vector<uint32_t>& outputBinary) { Vector<uint32_t>& outputBinary) {
using namespace spvtools; using namespace spvtools;
@@ -344,136 +278,6 @@ namespace MobileGL {
return optimizer.Run(inputBinary.data(), inputBinary.size(), &outputBinary, options); return optimizer.Run(inputBinary.data(), inputBinary.size(), &outputBinary, options);
} }
bool ShaderCompiler::UseUnformattedFloatStorageImagesForVulkan(
const Vector<Uint32>& inputBinary, Vector<uint32_t>& outputBinary) {
constexpr SizeT kSpirvHeaderWordCount = 5;
outputBinary.clear();
if (inputBinary.size() < kSpirvHeaderWordCount || inputBinary[0] != spv::MagicNumber) {
return false;
}
Vector<Uint32> floatTypeIds;
Vector<Uint32> resultTypeById(inputBinary[3], 0);
Vector<Uint32> pointerPointeeTypeById(inputBinary[3], 0);
Bool hasReadWithoutFormatCapability = false;
Bool hasWriteWithoutFormatCapability = false;
SizeT capabilityInsertOffset = kSpirvHeaderWordCount;
for (SizeT offset = kSpirvHeaderWordCount; offset < inputBinary.size();) {
const Uint32 instructionWord = inputBinary[offset];
const Uint32 wordCount = instructionWord >> 16u;
const auto opcode = static_cast<spv::Op>(instructionWord & 0xffffu);
if (wordCount == 0 || offset + wordCount > inputBinary.size()) {
return false;
}
if (opcode == spv::Op::OpCapability && wordCount >= 2) {
capabilityInsertOffset = offset + wordCount;
const auto capability = static_cast<spv::Capability>(inputBinary[offset + 1]);
hasReadWithoutFormatCapability |=
capability == spv::Capability::StorageImageReadWithoutFormat;
hasWriteWithoutFormatCapability |=
capability == spv::Capability::StorageImageWriteWithoutFormat;
} else if (opcode == spv::Op::OpTypeFloat && wordCount >= 3) {
floatTypeIds.push_back(inputBinary[offset + 1]);
} else if (opcode == spv::Op::OpTypePointer && wordCount >= 4) {
const Uint32 pointerTypeId = inputBinary[offset + 1];
if (pointerTypeId >= pointerPointeeTypeById.size()) {
return false;
}
pointerPointeeTypeById[pointerTypeId] = inputBinary[offset + 3];
}
bool hasResult = false;
bool hasResultType = false;
spv::HasResultAndType(opcode, &hasResult, &hasResultType);
if (hasResult && hasResultType && wordCount >= 3) {
const Uint32 resultTypeId = inputBinary[offset + 1];
const Uint32 resultId = inputBinary[offset + 2];
if (resultId >= resultTypeById.size()) {
return false;
}
resultTypeById[resultId] = resultTypeId;
}
offset += wordCount;
}
// OpImageTexelPointer is the bridge to image atomic instructions. Vulkan requires
// those image types to retain an atomic-compatible declared format, so exclude only
// the exact image types used by an atomic path rather than disabling formatless
// access for unrelated float images in the same module.
Vector<Uint32> atomicImageTypeIds;
for (SizeT offset = kSpirvHeaderWordCount; offset < inputBinary.size();) {
const Uint32 instructionWord = inputBinary[offset];
const Uint32 wordCount = instructionWord >> 16u;
const auto opcode = static_cast<spv::Op>(instructionWord & 0xffffu);
if (opcode == spv::Op::OpImageTexelPointer && wordCount >= 6) {
const Uint32 imageId = inputBinary[offset + 3];
if (imageId >= resultTypeById.size()) {
return false;
}
Uint32 imageTypeId = resultTypeById[imageId];
if (imageTypeId < pointerPointeeTypeById.size() &&
pointerPointeeTypeById[imageTypeId] != 0) {
imageTypeId = pointerPointeeTypeById[imageTypeId];
}
if (imageTypeId != 0 &&
std::find(atomicImageTypeIds.begin(), atomicImageTypeIds.end(), imageTypeId) ==
atomicImageTypeIds.end()) {
atomicImageTypeIds.push_back(imageTypeId);
}
}
offset += wordCount;
}
outputBinary = inputBinary;
Bool hasFloatStorageImage = false;
for (SizeT offset = kSpirvHeaderWordCount; offset < outputBinary.size();) {
const Uint32 instructionWord = outputBinary[offset];
const Uint32 wordCount = instructionWord >> 16u;
const auto opcode = static_cast<spv::Op>(instructionWord & 0xffffu);
// OpTypeImage operands are: result id, sampled type, dim, depth, arrayed,
// multisampled, sampled, image format, and an optional access qualifier.
if (opcode == spv::Op::OpTypeImage && wordCount >= 9) {
const Uint32 imageTypeId = outputBinary[offset + 1];
const Uint32 sampledTypeId = outputBinary[offset + 2];
const Uint32 sampled = outputBinary[offset + 7];
const Bool hasFloatSampledType =
std::find(floatTypeIds.begin(), floatTypeIds.end(), sampledTypeId) != floatTypeIds.end();
const Bool usedByAtomic =
std::find(atomicImageTypeIds.begin(), atomicImageTypeIds.end(), imageTypeId) !=
atomicImageTypeIds.end();
if (sampled == 2 && hasFloatSampledType && !usedByAtomic) {
outputBinary[offset + 8] = static_cast<Uint32>(spv::ImageFormat::Unknown);
hasFloatStorageImage = true;
}
}
offset += wordCount;
}
if (!hasFloatStorageImage) {
return true;
}
Vector<Uint32> addedCapabilities;
const Uint32 capabilityInstruction =
(2u << 16u) | static_cast<Uint32>(spv::Op::OpCapability);
if (!hasReadWithoutFormatCapability) {
addedCapabilities.push_back(capabilityInstruction);
addedCapabilities.push_back(
static_cast<Uint32>(spv::Capability::StorageImageReadWithoutFormat));
}
if (!hasWriteWithoutFormatCapability) {
addedCapabilities.push_back(capabilityInstruction);
addedCapabilities.push_back(
static_cast<Uint32>(spv::Capability::StorageImageWriteWithoutFormat));
}
outputBinary.insert(outputBinary.begin() + static_cast<std::ptrdiff_t>(capabilityInsertOffset),
addedCapabilities.begin(), addedCapabilities.end());
return true;
}
Result<String> ShaderCompiler::DecompileShader(SpvcSession& session) { Result<String> ShaderCompiler::DecompileShader(SpvcSession& session) {
spvc_compiler_options options; spvc_compiler_options options;
session.CreateOptions(&options); session.CreateOptions(&options);
@@ -27,26 +27,12 @@ namespace MobileGL {
// Only for backends without native draw-parameter support (DirectGLES). // Only for backends without native draw-parameter support (DirectGLES).
static bool LowerDrawParametersForEssl(const Vector<Uint32>& inputBinary, static bool LowerDrawParametersForEssl(const Vector<Uint32>& inputBinary,
Vector<uint32_t>& outputBinary); Vector<uint32_t>& outputBinary);
// Drops RelaxedPrecision member decorations from uniform-block structs so
// SPIRV-Cross prints the same (highp) member precision in every stage; ES
// drivers reject cross-stage uniform blocks whose member precisions differ.
// Only for the DirectGLES transpile path.
static bool StripUboMemberRelaxedPrecisionForEssl(const Vector<Uint32>& inputBinary,
Vector<uint32_t>& outputBinary);
// Rebases loads of the InstanceIndex builtin to (InstanceIndex - BaseInstance) so // Rebases loads of the InstanceIndex builtin to (InstanceIndex - BaseInstance) so
// shaders see GL's zero-based gl_InstanceID. Vertex shaders only; DirectVulkan // shaders see GL's zero-based gl_InstanceID. Vertex shaders only; DirectVulkan
// backend only (glslang's relaxed mode aliases gl_InstanceID to gl_InstanceIndex, // backend only (glslang's relaxed mode aliases gl_InstanceID to gl_InstanceIndex,
// which wrongly includes baseInstance). // which wrongly includes baseInstance).
static bool RebaseInstanceIndexForVulkan(const Vector<Uint32>& inputBinary, static bool RebaseInstanceIndexForVulkan(const Vector<Uint32>& inputBinary,
Vector<uint32_t>& outputBinary); Vector<uint32_t>& outputBinary);
// Replaces the declared format of float storage images with Unknown and adds the
// matching SPIR-V capabilities. DirectVulkan uses this only when both Vulkan
// shaderStorageImage*WithoutFormat features are enabled, allowing the
// glBindImageTexture format to select the descriptor view at runtime. Integer
// storage images deliberately keep their declared format for GL-compatible bit
// reinterpretation paths (for example, R32F storage accessed as r32ui).
static bool UseUnformattedFloatStorageImagesForVulkan(
const Vector<Uint32>& inputBinary, Vector<uint32_t>& outputBinary);
static Result<String> DecompileShader(SpvcSession& session); static Result<String> DecompileShader(SpvcSession& session);
}; };
} // namespace ShaderTranspiler } // namespace ShaderTranspiler

Some files were not shown because too many files have changed in this diff Show More