mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-07 19:58:32 +09:00
Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
70cf4ad7ba | ||
|
|
9dc9572849 | ||
|
|
0343b7fd4e | ||
|
|
9a4bc2e3b4 |
@@ -1,27 +0,0 @@
|
||||
BasedOnStyle: Microsoft
|
||||
IndentWidth: 4
|
||||
UseTab: Never
|
||||
AccessModifierOffset: -4
|
||||
PointerAlignment: Left
|
||||
BreakBeforeBraces: Custom
|
||||
BraceWrapping:
|
||||
AfterFunction: false
|
||||
AfterClass: false
|
||||
AfterControlStatement: Never
|
||||
AfterEnum: false
|
||||
AfterNamespace: false
|
||||
AfterStruct: false
|
||||
AfterUnion: false
|
||||
BeforeElse: false
|
||||
SplitEmptyFunction: false
|
||||
SplitEmptyRecord: false
|
||||
|
||||
AllowShortBlocksOnASingleLine: Empty
|
||||
AllowShortIfStatementsOnASingleLine: WithoutElse
|
||||
AlwaysBreakTemplateDeclarations: Yes
|
||||
NamespaceIndentation: All
|
||||
AllowShortFunctionsOnASingleLine: Inline
|
||||
SortIncludes: false
|
||||
FixNamespaceComments: true
|
||||
ShortNamespaceLines: 0
|
||||
CompactNamespaces: false
|
||||
-142
@@ -1,142 +0,0 @@
|
||||
#Generated from CLion Inspection settings
|
||||
---
|
||||
Checks: '-*,
|
||||
bugprone-argument-comment,
|
||||
bugprone-assert-side-effect,
|
||||
bugprone-bad-signal-to-kill-thread,
|
||||
bugprone-branch-clone,
|
||||
bugprone-copy-constructor-init,
|
||||
bugprone-dangling-handle,
|
||||
bugprone-dynamic-static-initializers,
|
||||
bugprone-fold-init-type,
|
||||
bugprone-forward-declaration-namespace,
|
||||
bugprone-forwarding-reference-overload,
|
||||
bugprone-inaccurate-erase,
|
||||
bugprone-incorrect-roundings,
|
||||
bugprone-integer-division,
|
||||
bugprone-macro-parentheses,
|
||||
bugprone-macro-repeated-side-effects,
|
||||
bugprone-misplaced-operator-in-strlen-in-alloc,
|
||||
bugprone-misplaced-pointer-arithmetic-in-alloc,
|
||||
bugprone-misplaced-widening-cast,
|
||||
bugprone-move-forwarding-reference,
|
||||
bugprone-multiple-statement-macro,
|
||||
bugprone-no-escape,
|
||||
bugprone-parent-virtual-call,
|
||||
bugprone-posix-return,
|
||||
bugprone-reserved-identifier,
|
||||
bugprone-sizeof-container,
|
||||
bugprone-sizeof-expression,
|
||||
bugprone-spuriously-wake-up-functions,
|
||||
bugprone-string-constructor,
|
||||
bugprone-string-integer-assignment,
|
||||
bugprone-string-literal-with-embedded-nul,
|
||||
bugprone-suspicious-enum-usage,
|
||||
bugprone-suspicious-include,
|
||||
bugprone-suspicious-memset-usage,
|
||||
bugprone-suspicious-missing-comma,
|
||||
bugprone-suspicious-semicolon,
|
||||
bugprone-suspicious-string-compare,
|
||||
bugprone-suspicious-memory-comparison,
|
||||
bugprone-suspicious-realloc-usage,
|
||||
bugprone-swapped-arguments,
|
||||
bugprone-terminating-continue,
|
||||
bugprone-throw-keyword-missing,
|
||||
bugprone-too-small-loop-variable,
|
||||
bugprone-undefined-memory-manipulation,
|
||||
bugprone-undelegated-constructor,
|
||||
bugprone-unhandled-self-assignment,
|
||||
bugprone-unused-raii,
|
||||
bugprone-unused-return-value,
|
||||
bugprone-use-after-move,
|
||||
bugprone-virtual-near-miss,
|
||||
cert-dcl21-cpp,
|
||||
cert-dcl58-cpp,
|
||||
cert-err34-c,
|
||||
cert-err52-cpp,
|
||||
cert-err60-cpp,
|
||||
cert-flp30-c,
|
||||
cert-msc50-cpp,
|
||||
cert-msc51-cpp,
|
||||
cert-str34-c,
|
||||
cppcoreguidelines-interfaces-global-init,
|
||||
cppcoreguidelines-narrowing-conversions,
|
||||
cppcoreguidelines-pro-type-member-init,
|
||||
cppcoreguidelines-slicing,
|
||||
google-default-arguments,
|
||||
google-runtime-operator,
|
||||
hicpp-exception-baseclass,
|
||||
hicpp-multiway-paths-covered,
|
||||
misc-misplaced-const,
|
||||
misc-new-delete-overloads,
|
||||
misc-non-copyable-objects,
|
||||
misc-throw-by-value-catch-by-reference,
|
||||
misc-unconventional-assign-operator,
|
||||
misc-uniqueptr-reset-release,
|
||||
modernize-avoid-bind,
|
||||
modernize-concat-nested-namespaces,
|
||||
modernize-deprecated-headers,
|
||||
modernize-deprecated-ios-base-aliases,
|
||||
modernize-loop-convert,
|
||||
modernize-make-shared,
|
||||
modernize-make-unique,
|
||||
modernize-pass-by-value,
|
||||
modernize-raw-string-literal,
|
||||
modernize-redundant-void-arg,
|
||||
modernize-replace-auto-ptr,
|
||||
modernize-replace-disallow-copy-and-assign-macro,
|
||||
modernize-replace-random-shuffle,
|
||||
modernize-return-braced-init-list,
|
||||
modernize-shrink-to-fit,
|
||||
modernize-unary-static-assert,
|
||||
modernize-use-auto,
|
||||
modernize-use-bool-literals,
|
||||
modernize-use-emplace,
|
||||
modernize-use-equals-default,
|
||||
modernize-use-equals-delete,
|
||||
modernize-use-noexcept,
|
||||
modernize-use-nullptr,
|
||||
modernize-use-override,
|
||||
modernize-use-transparent-functors,
|
||||
modernize-use-uncaught-exceptions,
|
||||
mpi-buffer-deref,
|
||||
mpi-type-mismatch,
|
||||
openmp-use-default-none,
|
||||
performance-faster-string-find,
|
||||
performance-for-range-copy,
|
||||
performance-implicit-conversion-in-loop,
|
||||
performance-inefficient-algorithm,
|
||||
performance-inefficient-string-concatenation,
|
||||
performance-inefficient-vector-operation,
|
||||
performance-move-const-arg,
|
||||
performance-move-constructor-init,
|
||||
performance-no-automatic-move,
|
||||
performance-noexcept-move-constructor,
|
||||
performance-trivially-destructible,
|
||||
performance-type-promotion-in-math-fn,
|
||||
performance-unnecessary-copy-initialization,
|
||||
performance-unnecessary-value-param,
|
||||
portability-simd-intrinsics,
|
||||
readability-avoid-const-params-in-decls,
|
||||
readability-const-return-type,
|
||||
readability-container-size-empty,
|
||||
readability-convert-member-functions-to-static,
|
||||
readability-delete-null-pointer,
|
||||
readability-deleted-default,
|
||||
readability-inconsistent-declaration-parameter-name,
|
||||
readability-make-member-function-const,
|
||||
readability-misleading-indentation,
|
||||
readability-misplaced-array-index,
|
||||
readability-non-const-parameter,
|
||||
readability-redundant-control-flow,
|
||||
readability-redundant-declaration,
|
||||
readability-redundant-function-ptr-dereference,
|
||||
readability-redundant-smartptr-get,
|
||||
readability-redundant-string-cstr,
|
||||
readability-redundant-string-init,
|
||||
readability-simplify-subscript-expr,
|
||||
readability-static-accessed-through-instance,
|
||||
readability-static-definition-in-anonymous-namespace,
|
||||
readability-string-compare,
|
||||
readability-uniqueptr-delete-release,
|
||||
readability-use-anyofallof'
|
||||
@@ -1,4 +0,0 @@
|
||||
tools/trace_replay/fixtures/*.tgz filter=lfs diff=lfs merge=lfs -text
|
||||
tools/trace_replay/fixtures/*.png filter=lfs diff=lfs merge=lfs -text
|
||||
tools/trace_replay/fixtures/openra.tgz -filter -diff -merge -text
|
||||
tools/trace_replay/fixtures/openra.0000031249.png -filter -diff -merge -text
|
||||
@@ -1,208 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
if [ "$#" -lt 1 ] || [ "$#" -gt 2 ]; then
|
||||
echo "usage: $0 <trace-case> [fixture-dir]" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
case_name="$1"
|
||||
fixture_dir="${2:-tools/trace_replay/fixtures}"
|
||||
python_bin="${PYTHON:-python3}"
|
||||
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
|
||||
python_bin=python
|
||||
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 \
|
||||
--format fixture-files \
|
||||
--case "${case_name}" \
|
||||
--fixture-root "${fixture_dir}")"
|
||||
mapfile -t files <<< "${fixture_list}"
|
||||
|
||||
include="$(IFS=,; echo "${files[*]}")"
|
||||
if [ "${case_name}" = "OpenRA" ]; then
|
||||
echo "Fixture files for ${case_name} are stored in Git: ${include}"
|
||||
for file in "${files[@]}"; do
|
||||
test -s "${file}"
|
||||
if head -n 1 "${file}" | grep -q "version https://git-lfs.github.com/spec/v1"; then
|
||||
echo "fixture should not be stored as an LFS pointer: ${file}" >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
exit 0
|
||||
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() {
|
||||
mkdir -p "${fixture_dir}"
|
||||
for file in "${files[@]}"; do
|
||||
local name
|
||||
local url
|
||||
name="$(basename "${file}")"
|
||||
url="${mirror_base%/}/${name}"
|
||||
echo "Fetching trace fixture from mirror: ${url}"
|
||||
if ! fetch_file_from_mirror "${file}" "${url}"; then
|
||||
return 1
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
if fetch_from_mirror; then
|
||||
echo "Fetched trace fixture files for ${case_name} from mirror: ${include}"
|
||||
else
|
||||
echo "Mirror fetch failed for ${case_name}; falling back to Git LFS: ${include}"
|
||||
git lfs install --local
|
||||
git lfs pull --include="${include}" --exclude=""
|
||||
fi
|
||||
|
||||
for file in "${files[@]}"; do
|
||||
metadata="$(get_lfs_metadata "${file}")"
|
||||
read -r expected_oid expected_size <<< "${metadata}"
|
||||
verify_fixture_file "${file}" "${file}" "${expected_oid}" "${expected_size}"
|
||||
done
|
||||
@@ -1,75 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
if [[ $# -ne 3 ]]; then
|
||||
echo "Usage: $0 <aapt2> <plugin-apk> <trace-apk>" >&2
|
||||
exit 64
|
||||
fi
|
||||
|
||||
aapt2=$1
|
||||
plugin_apk=$2
|
||||
trace_apk=$3
|
||||
|
||||
require() {
|
||||
local needle=$1
|
||||
local content=$2
|
||||
local description=$3
|
||||
if ! grep -Fq -- "$needle" <<<"$content"; then
|
||||
echo "::error::Missing ${description}: ${needle}" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
for apk in "$plugin_apk" "$trace_apk"; do
|
||||
[[ -f "$apk" ]] || { echo "::error::APK not found: $apk" >&2; exit 1; }
|
||||
done
|
||||
|
||||
plugin_manifest=$("$aapt2" dump xmltree --file AndroidManifest.xml "$plugin_apk")
|
||||
plugin_resources=$("$aapt2" dump resources "$plugin_apk")
|
||||
plugin_resource_text=$(tr -d '"' <<<"$plugin_resources")
|
||||
trace_manifest=$("$aapt2" dump xmltree --file AndroidManifest.xml "$trace_apk")
|
||||
plugin_contents=$(unzip -Z1 "$plugin_apk")
|
||||
|
||||
require 'top.mobilegl.plugin' "$plugin_manifest" 'plugin package name'
|
||||
require 'MobileGL' "$plugin_manifest" 'plugin label'
|
||||
require 'fclPlugin' "$plugin_manifest" 'legacy plugin marker'
|
||||
require 'fclPlugin_V2' "$plugin_manifest" 'V2 plugin marker'
|
||||
require 'LIBGL_ES=3:POJAV_RENDERER=opengles3:MOBILEGL_BACKEND_TYPE=DirectGLES' "$plugin_manifest" 'V1 DirectGLES fallback'
|
||||
require 'string/config' "$plugin_resources" 'V2 renderer configuration resource'
|
||||
require '{displayName:MobileGL,rendererId:opengles3' "$plugin_resource_text" 'V2 MobileGL entry and renderer ID'
|
||||
require 'rendererGLPath:**|libMobileGL.so' "$plugin_resource_text" 'V2 GL library path'
|
||||
require 'rendererEGLPath:**|libMobileGL.so' "$plugin_resource_text" 'V2 EGL library path'
|
||||
require 'key:LIBGL_ES,value:3' "$plugin_resource_text" 'V2 fixed LIBGL_ES variable'
|
||||
require 'key:MOBILEGL_BACKEND_TYPE' "$plugin_resource_text" 'V2 backend variable'
|
||||
require 'defaultValue:DirectGLES' "$plugin_resource_text" 'V2 DirectGLES default'
|
||||
require 'DirectVulkan' "$plugin_resource_text" 'V2 DirectVulkan option'
|
||||
require 'key:MOBILEGL_DISABLE_TIMERQUERY' "$plugin_resource_text" 'V2 timer-query toggle'
|
||||
require 'key:MOBILEGL_DISABLE_SUBGROUP' "$plugin_resource_text" 'V2 Vulkan subgroup toggle'
|
||||
require 'key:MOBILEGL_MAGMA_R11G11B10F_FALLBACK' "$plugin_resource_text" 'V2 Magma format fallback toggle'
|
||||
require 'key:MOBILEGL_MAGMA_FRAMESINFLIGHT' "$plugin_resource_text" 'V2 Magma frames-in-flight setting'
|
||||
require 'key:MOBILEGL_AVOID_SAMPLER_MIPMAP_MIN_FILTER' "$plugin_resource_text" 'V2 sampler workaround toggle'
|
||||
require 'key:MOBILEGL_COHERENT_AS_FLUSH' "$plugin_resource_text" 'V2 coherent-as-flush toggle'
|
||||
require 'key:MOBILEGL_USE_ANGLE' "$plugin_resource_text" 'V2 ANGLE toggle'
|
||||
|
||||
if [[ $(grep -Fc 'fclPlugin_V2' <<<"$plugin_manifest") -ne 1 ]]; then
|
||||
echo '::error::Plugin manifest must expose exactly one V2 descriptor' >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! grep -Eq '^lib/[^/]+/libMobileGL\.so$' <<<"$plugin_contents"; then
|
||||
echo '::error::Plugin APK does not contain libMobileGL.so' >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
require 'top.mobilegl.plugin.trace' "$trace_manifest" 'trace package name'
|
||||
require 'top.mobilegl.plugin.TRACE_REPLAY' "$trace_manifest" 'trace replay action'
|
||||
if grep -Fq 'fclPlugin' <<<"$trace_manifest"; then
|
||||
echo '::error::Trace APK must not advertise renderer-plugin metadata' >&2
|
||||
exit 1
|
||||
fi
|
||||
if grep -Fq 'android.intent.action.MAIN' <<<"$trace_manifest"; then
|
||||
echo '::error::Trace APK must not expose a launcher activity' >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo 'Validated unified MobileGL plugin APK and isolated trace APK.'
|
||||
@@ -1,560 +0,0 @@
|
||||
name: MobileGL APK
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- dev
|
||||
- Feat/Backend-Direct-GLES
|
||||
- Feat/Backend-Direct-Vulkan
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
CCACHE_BASEDIR: ${{ github.workspace }}
|
||||
CCACHE_COMPRESS: "true"
|
||||
CCACHE_DIR: ${{ github.workspace }}/.ccache
|
||||
CCACHE_MAXSIZE: 4G
|
||||
CCACHE_NOHASHDIR: "true"
|
||||
MOBILEGL_CMAKE_COMPILER_LAUNCHER: ccache
|
||||
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Set artifact metadata
|
||||
run: |
|
||||
echo "date_today=$(date +'%Y-%m-%d')" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Set up JDK
|
||||
uses: actions/setup-java@v5
|
||||
with:
|
||||
distribution: zulu
|
||||
java-version: '17'
|
||||
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@v6
|
||||
with:
|
||||
gradle-version: 8.10.2
|
||||
|
||||
- name: Restore ccache
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: .ccache
|
||||
key: ${{ runner.os }}-apk-${{ github.job }}-ccache-${{ github.ref_name }}-${{ github.run_id }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-apk-${{ github.job }}-ccache-${{ github.ref_name }}-
|
||||
${{ runner.os }}-apk-${{ github.job }}-ccache-
|
||||
|
||||
- name: Install ccache
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y ccache
|
||||
ccache --version
|
||||
|
||||
- name: Setup Android SDK
|
||||
uses: android-actions/setup-android@v4
|
||||
with:
|
||||
accept-android-sdk-licenses: false
|
||||
|
||||
- name: Accept Android SDK licenses
|
||||
run: yes | sdkmanager --licenses >/dev/null
|
||||
|
||||
- name: Install Android NDK
|
||||
run: |
|
||||
sdkmanager "ndk;27.3.13750724"
|
||||
echo "ndk.dir=$ANDROID_HOME/ndk/27.3.13750724" >> android-plugin/local.properties
|
||||
|
||||
- name: Update glslang external sources
|
||||
working-directory: 3rdparty/glslang
|
||||
run: python update_glslang_sources.py
|
||||
|
||||
- name: Build plugin APK
|
||||
run: gradle --no-daemon -p android-plugin :app:assemblePluginRelease -Pmobilegl.apkSuffix="${GITHUB_SHA}" -Pmobilegl.logLevel=MOBILEGL_LOG_LEVEL_INFO --parallel --max-workers "$(nproc)"
|
||||
env:
|
||||
SIGNING_STORE_PASSWORD: ${{ secrets.SIGNING_STORE_PASSWORD }}
|
||||
SIGNING_KEY_ALIAS: ${{ secrets.SIGNING_KEY_ALIAS }}
|
||||
SIGNING_KEY_PASSWORD: ${{ secrets.SIGNING_KEY_PASSWORD }}
|
||||
|
||||
- name: Download ANGLE x86_64 libraries
|
||||
run: |
|
||||
angle_dir="android-plugin/app/src/trace/jniLibs/x86_64"
|
||||
rm -rf "${angle_dir}"
|
||||
mkdir -p "${angle_dir}"
|
||||
|
||||
package_angle_variant() {
|
||||
variant="$1"
|
||||
commit="$2"
|
||||
egl_sha="$3"
|
||||
gles_sha="$4"
|
||||
source_dir="${RUNNER_TEMP}/mobilegl-angle-${variant}"
|
||||
base="https://raw.githubusercontent.com/FCL-Team/FoldCraftLauncher/${commit}/FCLauncher/src/main/jniLibs/x86_64"
|
||||
mkdir -p "${source_dir}"
|
||||
curl -L --fail --retry 3 -o "${source_dir}/libEGL_angle.so" "${base}/libEGL_angle.so"
|
||||
curl -L --fail --retry 3 -o "${source_dir}/libGLESv2_angle.so" "${base}/libGLESv2_angle.so"
|
||||
echo "${egl_sha} ${source_dir}/libEGL_angle.so" | sha256sum -c -
|
||||
echo "${gles_sha} ${source_dir}/libGLESv2_angle.so" | sha256sum -c -
|
||||
for library in libEGL_angle libGLESv2_angle; do
|
||||
filename="${library}_${variant}.so"
|
||||
cp "${source_dir}/${library}.so" "${angle_dir}/${filename}"
|
||||
done
|
||||
}
|
||||
|
||||
package_angle_variant \
|
||||
ec889e6ea831 \
|
||||
f2a3d510dffd8f6540a52e1a7d0c5787d151075b \
|
||||
c41828768d089899fa058ec0bee711a91be88347f29bdb935223da6be1149c40 \
|
||||
e4f820d99f94365c66df868c7740fef142fe5c0cd7c941790a9e30638857ca4d
|
||||
package_angle_variant \
|
||||
90a62123d794 \
|
||||
bdcc96ac11c79001018ae4375eb73cb54a9f682f \
|
||||
d0f4298ccc770cc801fc52e21733521646161e8a4adb3bd0052d9a1b57ee0ca8 \
|
||||
66fdc867e552192d553d59095ea2e3cef4829de65c356f1fd826027b1905972e
|
||||
|
||||
- name: Build retrace APK
|
||||
run: gradle --no-daemon -p android-plugin :app:assembleTraceRelease -Pmobilegl.apkSuffix="${GITHUB_SHA}" -Pmobilegl.abis=all -Pmobilegl.debuggableRelease=true -Pmobilegl.logLevel=MOBILEGL_LOG_LEVEL_INFO --parallel --max-workers "$(nproc)"
|
||||
env:
|
||||
SIGNING_STORE_PASSWORD: ${{ secrets.SIGNING_STORE_PASSWORD }}
|
||||
SIGNING_KEY_ALIAS: ${{ secrets.SIGNING_KEY_ALIAS }}
|
||||
SIGNING_KEY_PASSWORD: ${{ secrets.SIGNING_KEY_PASSWORD }}
|
||||
|
||||
- name: Show ccache stats
|
||||
if: always()
|
||||
run: ccache --show-stats
|
||||
|
||||
- name: Verify APK metadata and packaging
|
||||
run: |
|
||||
AAPT2="$(find "$ANDROID_HOME/build-tools" -name aapt2 -type f | sort -V | tail -n 1)"
|
||||
plugin_apk="android-plugin/app/build/outputs/apk/plugin/release/MobileGL-plugin-release-${GITHUB_SHA}.apk"
|
||||
trace_apk="android-plugin/app/build/outputs/apk/trace/release/MobileGL-plugin-trace-release-${GITHUB_SHA}.apk"
|
||||
test -f "${plugin_apk}"
|
||||
test -f "${trace_apk}"
|
||||
bash .github/scripts/validate-plugin-apks.sh "$AAPT2" "$plugin_apk" "$trace_apk"
|
||||
|
||||
- name: Verify signed APKs
|
||||
run: |
|
||||
APKSIGNER="$(find "$ANDROID_HOME/build-tools" -name apksigner -type f | sort -V | tail -n 1)"
|
||||
mapfile -t APKS < <(printf '%s\n' \
|
||||
"android-plugin/app/build/outputs/apk/plugin/release/MobileGL-plugin-release-${GITHUB_SHA}.apk" \
|
||||
"android-plugin/app/build/outputs/apk/trace/release/MobileGL-plugin-trace-release-${GITHUB_SHA}.apk")
|
||||
for APK in "${APKS[@]}"; do
|
||||
if [[ ! -f "$APK" ]]; then
|
||||
echo "::error::Expected release APK was not produced: $APK"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
for APK in "${APKS[@]}"; do
|
||||
if [[ "$APK" == *-unsigned.apk ]]; then
|
||||
echo "::error::Unsigned release APK produced: $APK"
|
||||
exit 1
|
||||
fi
|
||||
"$APKSIGNER" verify --verbose "$APK"
|
||||
done
|
||||
|
||||
- name: Upload plugin APK
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: MobileGL-plugin-${{ env.date_today }}-${{ github.sha }}
|
||||
path: android-plugin/app/build/outputs/apk/plugin/release/MobileGL-plugin-release-${{ github.sha }}.apk
|
||||
archive: false
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Upload retrace APK
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: MobileGL-retrace-apk-${{ env.date_today }}-${{ github.sha }}
|
||||
path: android-plugin/app/build/outputs/apk/trace/release/MobileGL-plugin-trace-release-${{ github.sha }}.apk
|
||||
archive: false
|
||||
if-no-files-found: error
|
||||
|
||||
trace-cases:
|
||||
name: trace case matrix
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
outputs:
|
||||
android: ${{ steps.trace-cases.outputs.android }}
|
||||
names: ${{ steps.trace-cases.outputs.names }}
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Load trace cases
|
||||
id: trace-cases
|
||||
run: |
|
||||
echo "android=$(python3 tools/trace_replay/trace_cases.py --ci --format github-apk)" >> "$GITHUB_OUTPUT"
|
||||
echo "names=$(python3 tools/trace_replay/trace_cases.py --ci --format names)" >> "$GITHUB_OUTPUT"
|
||||
|
||||
trace-fixtures:
|
||||
name: trace fixture (${{ matrix.case }})
|
||||
runs-on: ubuntu-latest
|
||||
needs: trace-cases
|
||||
strategy:
|
||||
fail-fast: false
|
||||
max-parallel: 4
|
||||
matrix:
|
||||
case: ${{ fromJSON(needs.trace-cases.outputs.names) }}
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Fetch trace fixture
|
||||
run: bash .github/scripts/fetch-trace-fixture-lfs.sh '${{ matrix.case }}'
|
||||
|
||||
- name: Stage trace fixture
|
||||
run: |
|
||||
safe_case="$(printf '%s' '${{ matrix.case }}' | sed 's/[^A-Za-z0-9._-]/_/g')"
|
||||
stage_dir="trace-fixtures/${safe_case}"
|
||||
mkdir -p "${stage_dir}"
|
||||
python3 tools/trace_replay/trace_cases.py --format fixture-files --case '${{ matrix.case }}' |
|
||||
while IFS= read -r file; do
|
||||
cp "${file}" "${stage_dir}/"
|
||||
done
|
||||
|
||||
- name: Upload trace fixture
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: MobileGL-trace-fixture-${{ matrix.case }}
|
||||
path: trace-fixtures/**
|
||||
if-no-files-found: error
|
||||
|
||||
android-avd:
|
||||
name: android avd image
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
AVD_NAME: mobilegl-ci
|
||||
ANDROID_AVD_HOME: ${{ github.workspace }}/.android/avd
|
||||
ANDROID_HOME: ${{ github.workspace }}/.android/sdk
|
||||
ANDROID_SDK_ROOT: ${{ github.workspace }}/.android/sdk
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Setup Android SDK
|
||||
uses: android-actions/setup-android@v4
|
||||
with:
|
||||
accept-android-sdk-licenses: false
|
||||
|
||||
- name: Accept Android SDK licenses
|
||||
run: yes | sdkmanager --licenses >/dev/null
|
||||
|
||||
- name: Restore Android AVD cache
|
||||
id: android-avd-cache
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: |
|
||||
${{ env.ANDROID_AVD_HOME }}
|
||||
${{ env.ANDROID_SDK_ROOT }}/emulator
|
||||
${{ env.ANDROID_SDK_ROOT }}/platform-tools
|
||||
${{ env.ANDROID_SDK_ROOT }}/platforms/android-35
|
||||
${{ env.ANDROID_SDK_ROOT }}/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') }}
|
||||
|
||||
- name: Create AVD
|
||||
if: steps.android-avd-cache.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
sh android-plugin/run-avd-ci.sh create \
|
||||
--api-level 35 \
|
||||
--target google_apis \
|
||||
--arch x86_64 \
|
||||
--profile pixel_6 \
|
||||
--avd-name "${AVD_NAME}"
|
||||
|
||||
retrace:
|
||||
name: retrace (${{ matrix.backend.name }}, ${{ matrix.case.name }})
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- build
|
||||
- android-avd
|
||||
- trace-cases
|
||||
- trace-fixtures
|
||||
if: ${{ always() && needs.build.result == 'success' && needs.android-avd.result == 'success' && needs.trace-cases.result == 'success' }}
|
||||
timeout-minutes: 75
|
||||
env:
|
||||
AVD_NAME: mobilegl-ci
|
||||
ANDROID_AVD_HOME: ${{ github.workspace }}/.android/avd
|
||||
ANDROID_HOME: ${{ github.workspace }}/.android/sdk
|
||||
ANDROID_SDK_ROOT: ${{ github.workspace }}/.android/sdk
|
||||
strategy:
|
||||
fail-fast: false
|
||||
max-parallel: 4
|
||||
matrix:
|
||||
backend:
|
||||
- name: DirectGLES
|
||||
gpu: software
|
||||
- name: DirectVulkan
|
||||
gpu: lavapipe
|
||||
case: ${{ fromJSON(needs.trace-cases.outputs.android) }}
|
||||
steps:
|
||||
- name: Set Swap Space
|
||||
uses: pierotofy/set-swap-space@v1.0
|
||||
with:
|
||||
swap-size-gb: 8
|
||||
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Download trace fixture
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: MobileGL-trace-fixture-${{ matrix.case.name }}
|
||||
path: trace-fixture-download
|
||||
|
||||
- name: Install trace fixture
|
||||
run: |
|
||||
mkdir -p tools/trace_replay/fixtures
|
||||
find trace-fixture-download -type f -exec cp {} tools/trace_replay/fixtures/ \;
|
||||
|
||||
- name: Set artifact metadata
|
||||
run: |
|
||||
echo "date_today=$(date +'%Y-%m-%d')" >> "$GITHUB_ENV"
|
||||
echo "EMULATOR_LOG=${RUNNER_TEMP}/mobilegl-emulator.log" >> "$GITHUB_ENV"
|
||||
echo "EMULATOR_PID_FILE=${RUNNER_TEMP}/mobilegl-emulator.pid" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Setup Android SDK
|
||||
uses: android-actions/setup-android@v4
|
||||
with:
|
||||
accept-android-sdk-licenses: false
|
||||
|
||||
- name: Accept Android SDK licenses
|
||||
run: yes | sdkmanager --licenses >/dev/null
|
||||
|
||||
- name: Restore Android AVD cache
|
||||
id: android-avd-cache
|
||||
uses: actions/cache/restore@v5
|
||||
with:
|
||||
path: |
|
||||
${{ env.ANDROID_AVD_HOME }}
|
||||
${{ env.ANDROID_SDK_ROOT }}/emulator
|
||||
${{ env.ANDROID_SDK_ROOT }}/platform-tools
|
||||
${{ env.ANDROID_SDK_ROOT }}/platforms/android-35
|
||||
${{ env.ANDROID_SDK_ROOT }}/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') }}
|
||||
|
||||
- name: Download retrace APK
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: MobileGL-plugin-trace-release-${{ github.sha }}.apk
|
||||
path: android-retrace-apks
|
||||
|
||||
- name: Enable KVM
|
||||
run: |
|
||||
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
|
||||
sudo udevadm control --reload-rules
|
||||
sudo udevadm trigger --name-match=kvm
|
||||
|
||||
- name: Create AVD
|
||||
if: steps.android-avd-cache.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
sh android-plugin/run-avd-ci.sh create \
|
||||
--api-level 35 \
|
||||
--target google_apis \
|
||||
--arch x86_64 \
|
||||
--profile pixel_6 \
|
||||
--avd-name "${AVD_NAME}"
|
||||
|
||||
- name: Launch Emulator
|
||||
run: |
|
||||
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
|
||||
|
||||
- name: Retrace and validate
|
||||
env:
|
||||
MOBILEGL_USE_ANGLE: ${{ matrix.backend.name == 'DirectGLES' && '1' || '0' }}
|
||||
MOBILEGL_TRACE_ANGLE_VARIANT: ${{ matrix.case.name == 'minecraft-1.21.4-fabric-iris-bliss-in-world' && '90a62123d794' || 'ec889e6ea831' }}
|
||||
MOBILEGL_MAGMA_R11G11B10F_FALLBACK: ${{ matrix.backend.name == 'DirectVulkan' && '1' || '0' }}
|
||||
run: |
|
||||
apk_file="android-retrace-apks/MobileGL-plugin-trace-release-${GITHUB_SHA}.apk"
|
||||
test -f "${apk_file}"
|
||||
extra_retrace_args=()
|
||||
# Bliss needs the newer signed ANGLE variant plus sampler mipmap
|
||||
# min-filter downgrading on ANGLE llvmpipe.
|
||||
if [ "${{ matrix.backend.name }}" = "DirectGLES" ] && [ "${{ matrix.case.name }}" = "minecraft-1.21.4-fabric-iris-bliss-in-world" ]; then
|
||||
extra_retrace_args+=(--avoid-angle-llvmpipe-sampler-mipmap-min-filter)
|
||||
fi
|
||||
if [ "${{ matrix.case.coherent_as_flush || false }}" = "true" ]; then
|
||||
extra_retrace_args+=(--coherent-as-flush)
|
||||
fi
|
||||
|
||||
run_retrace() {
|
||||
timeout "$(( ${{ matrix.case.timeout_seconds }} + 300 ))" sh android-plugin/trace-replay-ci.sh \
|
||||
--apk-file "${apk_file}" \
|
||||
--package top.mobilegl.plugin.trace \
|
||||
--backend "${{ matrix.backend.name }}" \
|
||||
--result-root android-retrace-result \
|
||||
--fixture-root android-retrace-fixture \
|
||||
--case "${{ matrix.case.name }}" \
|
||||
--trace-archive "${{ matrix.case.trace_archive }}" \
|
||||
--trace-file "${{ matrix.case.trace_file }}" \
|
||||
--golden "${{ matrix.case.golden }}" \
|
||||
--alternate-golden "${{ matrix.case.alternate_golden || '' }}" \
|
||||
--target-call "${{ matrix.case.target_call }}" \
|
||||
--width "${{ matrix.case.width }}" \
|
||||
--height "${{ matrix.case.height }}" \
|
||||
--ssim-threshold "${{ matrix.case.ssim_threshold || '0.99' }}" \
|
||||
--crop-x "${{ matrix.case.crop_x }}" \
|
||||
--crop-y "${{ matrix.case.crop_y }}" \
|
||||
--crop-width "${{ matrix.case.crop_width }}" \
|
||||
--crop-height "${{ matrix.case.crop_height }}" \
|
||||
--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
|
||||
if: always()
|
||||
run: |
|
||||
safe_case="$(printf '%s' '${{ matrix.case.name }}' | sed 's/[^A-Za-z0-9._-]/_/g')"
|
||||
result_dir="android-retrace-result/${safe_case}-${{ matrix.backend.name }}"
|
||||
mkdir -p "${result_dir}"
|
||||
if [ -s "${{ matrix.case.golden }}" ]; then
|
||||
cp "${{ matrix.case.golden }}" "${result_dir}/${safe_case}-${{ matrix.backend.name }}-golden.png"
|
||||
fi
|
||||
if [ -n "${{ matrix.case.alternate_golden || '' }}" ] && [ -s "${{ matrix.case.alternate_golden || '' }}" ]; then
|
||||
cp "${{ matrix.case.alternate_golden || '' }}" "${result_dir}/${safe_case}-${{ matrix.backend.name }}-alternate-golden.png"
|
||||
fi
|
||||
|
||||
- name: Collect emulator diagnostics
|
||||
if: always()
|
||||
run: |
|
||||
mkdir -p android-retrace-result/diagnostics
|
||||
adb devices -l > android-retrace-result/diagnostics/adb-devices.txt || true
|
||||
timeout 30 adb logcat -d -t 1000 > android-retrace-result/diagnostics/logcat.txt || true
|
||||
if [ -f "${EMULATOR_LOG}" ]; then
|
||||
cp "${EMULATOR_LOG}" android-retrace-result/diagnostics/emulator.log
|
||||
fi
|
||||
|
||||
- name: Stop Emulator
|
||||
if: always()
|
||||
run: |
|
||||
sh android-plugin/run-avd-ci.sh stop \
|
||||
--avd-name "${AVD_NAME}" \
|
||||
--emulator-log "${EMULATOR_LOG}" \
|
||||
--pid-file "${EMULATOR_PID_FILE}"
|
||||
|
||||
- name: Upload Android retrace result
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: MobileGL-android-retrace-result-${{ env.date_today }}-${{ github.sha }}-${{ matrix.backend.name }}-${{ matrix.case.name }}
|
||||
path: android-retrace-result/**
|
||||
if-no-files-found: warn
|
||||
|
||||
retrace-summary:
|
||||
name: retrace summary
|
||||
runs-on: ubuntu-latest
|
||||
needs: retrace
|
||||
if: always()
|
||||
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Set artifact metadata
|
||||
run: |
|
||||
echo "date_today=$(date +'%Y-%m-%d')" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: '22'
|
||||
|
||||
- name: Download Android retrace results
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
pattern: MobileGL-android-retrace-result-*
|
||||
path: retrace-artifacts
|
||||
|
||||
- name: Render retrace summary
|
||||
run: |
|
||||
node tools/trace_replay/render_retrace_summary.mjs \
|
||||
--input retrace-artifacts \
|
||||
--output-dir android-retrace-summary \
|
||||
--title "MobileGL Android retrace overview" \
|
||||
--group-label "Android Emulator" \
|
||||
--html mobilegl-android-retrace-overview.html
|
||||
|
||||
- name: Upload Android retrace summary
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
path: android-retrace-summary/mobilegl-android-retrace-overview.html
|
||||
archive: false
|
||||
if-no-files-found: error
|
||||
|
||||
remove-artifact-clutter:
|
||||
name: remove artifact clutter
|
||||
runs-on: ubuntu-latest
|
||||
needs: retrace-summary
|
||||
if: always()
|
||||
permissions:
|
||||
actions: write
|
||||
steps:
|
||||
- name: Delete intermediate Android retrace artifacts
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
declare -A failed_cases=()
|
||||
while IFS= read -r job_name; do
|
||||
case_name="${job_name#retrace (*, }"
|
||||
case_name="${case_name%)}"
|
||||
failed_cases["${case_name}"]=1
|
||||
done < <(
|
||||
gh api --paginate "repos/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}/jobs?per_page=100" \
|
||||
--jq '.jobs[] | select(.name | startswith("retrace (")) | select(.conclusion == "failure" or .conclusion == "cancelled" or .conclusion == "timed_out" or .conclusion == "action_required") | .name'
|
||||
)
|
||||
|
||||
if ((${#failed_cases[@]})); then
|
||||
echo "Retaining fixtures for failed retrace case(s):"
|
||||
printf ' %s\n' "${!failed_cases[@]}"
|
||||
else
|
||||
echo "All retrace jobs succeeded; no fixtures need to be retained."
|
||||
fi
|
||||
|
||||
deleted=0
|
||||
retained=0
|
||||
while IFS=$'\t' read -r artifact_id artifact_name; do
|
||||
if [[ "${artifact_name}" == MobileGL-trace-fixture-* ]]; then
|
||||
case_name="${artifact_name#MobileGL-trace-fixture-}"
|
||||
if [[ -v "failed_cases[${case_name}]" ]]; then
|
||||
echo "Retaining ${artifact_name} (${artifact_id}) for failed retrace."
|
||||
((retained += 1))
|
||||
continue
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "Deleting ${artifact_name} (${artifact_id})"
|
||||
gh api --method DELETE "repos/${GITHUB_REPOSITORY}/actions/artifacts/${artifact_id}"
|
||||
((deleted += 1))
|
||||
done < <(
|
||||
gh api --paginate "repos/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}/artifacts?per_page=100" \
|
||||
--jq '.artifacts[] | select(.name | startswith("MobileGL-trace-fixture-") or startswith("MobileGL-android-retrace-result-") or startswith("trace-fixture-") or startswith("retrace-result-")) | [.id, .name] | @tsv'
|
||||
)
|
||||
|
||||
echo "Deleted ${deleted} intermediate Android artifact(s); retained ${retained} failed-retrace fixture(s)."
|
||||
@@ -1,560 +0,0 @@
|
||||
name: Test
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- dev
|
||||
- Feat/Backend-Direct-GLES
|
||||
- Feat/Backend-Direct-Vulkan
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build-linux:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
BUILD_DIR: build-linux
|
||||
CCACHE_BASEDIR: ${{ github.workspace }}
|
||||
CCACHE_COMPRESS: "true"
|
||||
CCACHE_DIR: ${{ github.workspace }}/.ccache
|
||||
CCACHE_MAXSIZE: 4G
|
||||
CCACHE_NOHASHDIR: "true"
|
||||
|
||||
steps:
|
||||
- name: Set Swap Space
|
||||
uses: pierotofy/set-swap-space@v1.0
|
||||
with:
|
||||
swap-size-gb: 32
|
||||
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Get CMake
|
||||
uses: lukka/get-cmake@v4.3.3
|
||||
|
||||
- name: Restore ccache
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: .ccache
|
||||
key: ${{ runner.os }}-test-${{ github.job }}-ccache-${{ github.ref_name }}-${{ github.run_id }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-test-${{ github.job }}-ccache-${{ github.ref_name }}-
|
||||
${{ runner.os }}-test-${{ github.job }}-ccache-
|
||||
|
||||
- name: Prepare Vulkan SDK
|
||||
uses: humbletim/setup-vulkan-sdk@v1.2.1
|
||||
with:
|
||||
vulkan-query-version: 1.4.304.1
|
||||
vulkan-components: Vulkan-Headers, Vulkan-Loader
|
||||
vulkan-use-cache: true
|
||||
|
||||
- name: Update glslang external sources
|
||||
working-directory: 3rdparty/glslang
|
||||
run: python update_glslang_sources.py
|
||||
|
||||
- name: Install build dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y ccache clang-20 clang++-20 lld-20 libc++-20-dev libc++abi-20-dev libvulkan-dev libegl1-mesa-dev libgles2-mesa-dev libgl1-mesa-dri mesa-vulkan-drivers ninja-build
|
||||
|
||||
- name: Show installed toolchain
|
||||
run: |
|
||||
ccache --version
|
||||
clang-20 --version
|
||||
clang++-20 --version
|
||||
ld.lld-20 --version || ld.lld --version || true
|
||||
dpkg -l 'libc++*' 'libegl*' 'libgles*' 'mesa*' 'vulkan*' || true
|
||||
|
||||
- name: Configure CMake
|
||||
run: |
|
||||
if [ "${{ secrets.ACTIONS_STEP_DEBUG }}" = "true" ]; then
|
||||
BUILD_TYPE=Debug
|
||||
else
|
||||
BUILD_TYPE=Release
|
||||
fi
|
||||
|
||||
cmake -S . -B "${BUILD_DIR}" -G Ninja \
|
||||
-DCMAKE_C_COMPILER=clang-20 \
|
||||
-DCMAKE_CXX_COMPILER=clang++-20 \
|
||||
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
|
||||
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
|
||||
-DCMAKE_BUILD_TYPE="${BUILD_TYPE}" \
|
||||
-DMOBILEGL_LOG_ACTIVE_LEVEL=MOBILEGL_LOG_LEVEL_INFO \
|
||||
-DMOBILEGL_BUILD_TEST=ON \
|
||||
-DMOBILEGL_BUILD_BENCHMARK=ON \
|
||||
-DMOBILEGL_BUILD_TRACE_REPLAY=OFF \
|
||||
-DBENCHMARK_DOWNLOAD_DEPENDENCIES=ON \
|
||||
-DBENCHMARK_ENABLE_TESTING=OFF \
|
||||
-DCMAKE_POLICY_VERSION_MINIMUM=3.5
|
||||
|
||||
- name: Build
|
||||
run: cmake --build "${BUILD_DIR}" --parallel "$(nproc)"
|
||||
|
||||
- name: Show ccache stats
|
||||
if: always()
|
||||
run: ccache --show-stats
|
||||
|
||||
- name: Package Linux runtime
|
||||
run: |
|
||||
mkdir -p ci-artifacts
|
||||
mapfile -t SHARED_LIBS < <(find "${BUILD_DIR}" -type f \( -name '*.so' -o -name '*.so.*' \) -print | sort)
|
||||
tar \
|
||||
--exclude='*/CMakeFiles' \
|
||||
--exclude='*.o' \
|
||||
--exclude='*.a' \
|
||||
--exclude='*.ninja*' \
|
||||
--exclude='build.ninja' \
|
||||
--exclude='cmake_install.cmake' \
|
||||
-czf ci-artifacts/mobilegl-linux-runtime.tgz \
|
||||
"${BUILD_DIR}/CTestTestfile.cmake" \
|
||||
"${BUILD_DIR}/MobileGL/MG_Test" \
|
||||
"${BUILD_DIR}/MobileGL/MG_Benchmark" \
|
||||
"${SHARED_LIBS[@]}"
|
||||
|
||||
- name: Upload Linux runtime
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: mobilegl-linux-runtime
|
||||
path: ci-artifacts/mobilegl-linux-runtime.tgz
|
||||
if-no-files-found: error
|
||||
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build-linux
|
||||
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Get CMake
|
||||
uses: lukka/get-cmake@v4.3.3
|
||||
|
||||
- name: Install runtime dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libvulkan1 libegl1 libgles2 libgl1-mesa-dri mesa-vulkan-drivers
|
||||
|
||||
- name: Download Linux runtime
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: mobilegl-linux-runtime
|
||||
path: .
|
||||
|
||||
- name: Unpack Linux runtime
|
||||
run: tar -xzf mobilegl-linux-runtime.tgz
|
||||
|
||||
- name: Normalize CTest command paths
|
||||
run: |
|
||||
python - <<'PY'
|
||||
from pathlib import Path
|
||||
import re
|
||||
|
||||
for path in Path('build-linux').rglob('CTestTestfile.cmake'):
|
||||
text = path.read_text()
|
||||
text = re.sub(r'"[^"]*/cmake-[^"]*/bin/cmake"', '"cmake"', text)
|
||||
path.write_text(text)
|
||||
PY
|
||||
|
||||
- name: Test
|
||||
working-directory: build-linux
|
||||
run: |
|
||||
if [ "${{ secrets.ACTIONS_STEP_DEBUG }}" = "true" ]; then
|
||||
ctest -V -L unit --no-tests=error
|
||||
else
|
||||
ctest --output-on-failure -L unit --no-tests=error
|
||||
fi
|
||||
|
||||
benchmark:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build-linux
|
||||
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Get CMake
|
||||
uses: lukka/get-cmake@v4.3.3
|
||||
|
||||
- name: Install runtime dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libvulkan1 libegl1 libgles2 libgl1-mesa-dri mesa-vulkan-drivers
|
||||
|
||||
- name: Download Linux runtime
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: mobilegl-linux-runtime
|
||||
path: .
|
||||
|
||||
- name: Unpack Linux runtime
|
||||
run: tar -xzf mobilegl-linux-runtime.tgz
|
||||
|
||||
- name: Normalize CTest command paths
|
||||
run: |
|
||||
python - <<'PY'
|
||||
from pathlib import Path
|
||||
import re
|
||||
|
||||
for path in Path('build-linux').rglob('CTestTestfile.cmake'):
|
||||
text = path.read_text()
|
||||
text = re.sub(r'"[^"]*/cmake-[^"]*/bin/cmake"', '"cmake"', text)
|
||||
path.write_text(text)
|
||||
PY
|
||||
|
||||
- name: Benchmark
|
||||
working-directory: build-linux
|
||||
run: ctest -V -C Release -L benchmark --no-tests=error
|
||||
|
||||
build-retrace:
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- build-linux
|
||||
- test
|
||||
- benchmark
|
||||
env:
|
||||
BUILD_DIR: build-retrace
|
||||
CCACHE_BASEDIR: ${{ github.workspace }}
|
||||
CCACHE_COMPRESS: "true"
|
||||
CCACHE_DIR: ${{ github.workspace }}/.ccache
|
||||
CCACHE_MAXSIZE: 4G
|
||||
CCACHE_NOHASHDIR: "true"
|
||||
MOBILEGL_LIBRARY: ${{ github.workspace }}/build-linux/libMobileGL.so
|
||||
|
||||
steps:
|
||||
- name: Set Swap Space
|
||||
uses: pierotofy/set-swap-space@v1.0
|
||||
with:
|
||||
swap-size-gb: 32
|
||||
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Get CMake
|
||||
uses: lukka/get-cmake@v4.3.3
|
||||
|
||||
- name: Restore ccache
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: .ccache
|
||||
key: ${{ runner.os }}-test-${{ github.job }}-ccache-${{ github.ref_name }}-${{ github.run_id }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-test-${{ github.job }}-ccache-${{ github.ref_name }}-
|
||||
${{ runner.os }}-test-${{ github.job }}-ccache-
|
||||
|
||||
- name: Prepare Vulkan SDK
|
||||
uses: humbletim/setup-vulkan-sdk@v1.2.1
|
||||
with:
|
||||
vulkan-query-version: 1.4.304.1
|
||||
vulkan-components: Vulkan-Headers, Vulkan-Loader
|
||||
vulkan-use-cache: true
|
||||
|
||||
- name: Update glslang external sources
|
||||
working-directory: 3rdparty/glslang
|
||||
run: python update_glslang_sources.py
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y ccache clang-20 clang++-20 lld-20 libc++-20-dev libc++abi-20-dev libvulkan-dev libegl1-mesa-dev libgles2-mesa-dev libgl1-mesa-dri mesa-vulkan-drivers ninja-build
|
||||
|
||||
- name: Show installed toolchain
|
||||
run: |
|
||||
ccache --version
|
||||
clang-20 --version
|
||||
clang++-20 --version
|
||||
ld.lld-20 --version || ld.lld --version || true
|
||||
dpkg -l 'libc++*' 'libegl*' 'libgles*' 'mesa*' 'vulkan*' || true
|
||||
|
||||
- name: Download Linux runtime
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: mobilegl-linux-runtime
|
||||
path: .
|
||||
|
||||
- name: Unpack Linux runtime
|
||||
run: |
|
||||
tar -xzf mobilegl-linux-runtime.tgz
|
||||
test -f "${MOBILEGL_LIBRARY}"
|
||||
|
||||
- name: Configure CMake
|
||||
run: |
|
||||
if [ "${{ secrets.ACTIONS_STEP_DEBUG }}" = "true" ]; then
|
||||
BUILD_TYPE=Debug
|
||||
else
|
||||
BUILD_TYPE=Release
|
||||
fi
|
||||
|
||||
cmake -S . -B "${BUILD_DIR}" -G Ninja \
|
||||
-DCMAKE_C_COMPILER=clang-20 \
|
||||
-DCMAKE_CXX_COMPILER=clang++-20 \
|
||||
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
|
||||
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
|
||||
-DCMAKE_BUILD_TYPE="${BUILD_TYPE}" \
|
||||
-DMOBILEGL_LOG_ACTIVE_LEVEL=MOBILEGL_LOG_LEVEL_INFO \
|
||||
-DMOBILEGL_BUILD_TEST=OFF \
|
||||
-DMOBILEGL_BUILD_BENCHMARK=OFF \
|
||||
-DMOBILEGL_BUILD_TRACE_REPLAY=ON \
|
||||
-DMOBILEGL_TRACE_REPLAY_MOBILEGL_LIBRARY="${MOBILEGL_LIBRARY}" \
|
||||
-DCMAKE_POLICY_VERSION_MINIMUM=3.5
|
||||
|
||||
- name: Build trace replay
|
||||
run: cmake --build "${BUILD_DIR}" --target mobilegl_trace_replay --parallel "$(nproc)"
|
||||
|
||||
- name: Show ccache stats
|
||||
if: always()
|
||||
run: ccache --show-stats
|
||||
|
||||
- name: Normalize CTest command paths
|
||||
run: |
|
||||
python - <<'PY'
|
||||
from pathlib import Path
|
||||
import re
|
||||
|
||||
for path in Path('build-retrace').rglob('CTestTestfile.cmake'):
|
||||
text = path.read_text()
|
||||
text = re.sub(r'"[^"]*/cmake-[^"]*/bin/cmake"', '"cmake"', text)
|
||||
path.write_text(text)
|
||||
PY
|
||||
|
||||
- name: Package trace replay
|
||||
run: |
|
||||
mkdir -p ci-artifacts
|
||||
tar -czf ci-artifacts/mobilegl-trace-replay.tgz \
|
||||
build-retrace/tools/trace_replay/mobilegl_trace_replay \
|
||||
build-retrace/tools/trace_replay/CTestTestfile.cmake
|
||||
|
||||
- name: Upload trace replay
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: mobilegl-trace-replay
|
||||
path: ci-artifacts/mobilegl-trace-replay.tgz
|
||||
if-no-files-found: error
|
||||
|
||||
trace-cases:
|
||||
name: trace case matrix
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- test
|
||||
- benchmark
|
||||
outputs:
|
||||
names: ${{ steps.trace-cases.outputs.names }}
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Load trace cases
|
||||
id: trace-cases
|
||||
run: echo "names=$(python3 tools/trace_replay/trace_cases.py --ci --format names)" >> "$GITHUB_OUTPUT"
|
||||
|
||||
trace-fixtures:
|
||||
name: trace fixture (${{ matrix.case }})
|
||||
runs-on: ubuntu-latest
|
||||
needs: trace-cases
|
||||
strategy:
|
||||
fail-fast: false
|
||||
max-parallel: 4
|
||||
matrix:
|
||||
case: ${{ fromJSON(needs.trace-cases.outputs.names) }}
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Fetch trace fixture
|
||||
run: bash .github/scripts/fetch-trace-fixture-lfs.sh '${{ matrix.case }}'
|
||||
|
||||
- name: Stage trace fixture
|
||||
run: |
|
||||
safe_case="$(printf '%s' '${{ matrix.case }}' | sed 's/[^A-Za-z0-9._-]/_/g')"
|
||||
stage_dir="trace-fixtures/${safe_case}"
|
||||
mkdir -p "${stage_dir}"
|
||||
python3 tools/trace_replay/trace_cases.py --format fixture-files --case '${{ matrix.case }}' |
|
||||
while IFS= read -r file; do
|
||||
cp "${file}" "${stage_dir}/"
|
||||
done
|
||||
|
||||
- name: Upload trace fixture
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: trace-fixture-${{ matrix.case }}
|
||||
path: trace-fixtures/**
|
||||
if-no-files-found: error
|
||||
|
||||
retrace:
|
||||
name: retrace (${{ matrix.backend }}, ${{ matrix.case }})
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- build-linux
|
||||
- build-retrace
|
||||
- trace-cases
|
||||
- trace-fixtures
|
||||
if: ${{ always() && needs.build-linux.result == 'success' && needs.build-retrace.result == 'success' && needs.trace-cases.result == 'success' }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
max-parallel: 4
|
||||
matrix:
|
||||
backend:
|
||||
- DirectGLES
|
||||
- DirectVulkan
|
||||
case: ${{ fromJSON(needs.trace-cases.outputs.names) }}
|
||||
|
||||
steps:
|
||||
- name: Set Swap Space
|
||||
uses: pierotofy/set-swap-space@v1.0
|
||||
with:
|
||||
swap-size-gb: 16
|
||||
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Download trace fixture
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: trace-fixture-${{ matrix.case }}
|
||||
path: trace-fixture-download
|
||||
|
||||
- name: Install trace fixture
|
||||
run: |
|
||||
mkdir -p tools/trace_replay/fixtures
|
||||
find trace-fixture-download -type f -exec cp {} tools/trace_replay/fixtures/ \;
|
||||
|
||||
- name: Get CMake
|
||||
uses: lukka/get-cmake@v4.3.3
|
||||
|
||||
- name: Install runtime dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libvulkan1 libegl1-mesa-dev libgles2-mesa-dev libgl1-mesa-dri mesa-vulkan-drivers
|
||||
test -e /usr/lib/x86_64-linux-gnu/libEGL.so
|
||||
test -e /usr/lib/x86_64-linux-gnu/libGLESv2.so
|
||||
|
||||
- name: Download Linux runtime
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: mobilegl-linux-runtime
|
||||
path: .
|
||||
|
||||
- name: Download trace replay
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: mobilegl-trace-replay
|
||||
path: .
|
||||
|
||||
- name: Unpack retrace runtime
|
||||
run: |
|
||||
tar -xzf mobilegl-linux-runtime.tgz
|
||||
tar -xzf mobilegl-trace-replay.tgz
|
||||
test -f build-linux/libMobileGL.so
|
||||
test -f build-retrace/tools/trace_replay/mobilegl_trace_replay
|
||||
|
||||
- name: Retrace and validate
|
||||
working-directory: build-retrace/tools/trace_replay
|
||||
run: |
|
||||
if [ '${{ matrix.backend }}' = 'DirectVulkan' ]; then
|
||||
export MOBILEGL_MAGMA_R11G11B10F_FALLBACK=1
|
||||
fi
|
||||
ctest -V --no-tests=error -R '^MobileGLTraceReplay\.${{ matrix.case }}\.${{ matrix.backend }}$'
|
||||
|
||||
- name: Upload actual image
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: retrace-result-${{ matrix.backend }}-${{ matrix.case }}
|
||||
path: |
|
||||
build-retrace/tools/trace_replay/${{ matrix.case }}/actual-images/**
|
||||
build-retrace/tools/trace_replay/${{ matrix.case }}/${{ matrix.backend }}/output/**
|
||||
if-no-files-found: warn
|
||||
|
||||
retrace-summary:
|
||||
name: retrace summary
|
||||
runs-on: ubuntu-latest
|
||||
needs: retrace
|
||||
if: ${{ always() && needs.retrace.result != 'skipped' }}
|
||||
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Set artifact metadata
|
||||
run: |
|
||||
echo "date_today=$(date +'%Y-%m-%d')" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: '22'
|
||||
|
||||
- name: Download retrace results
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
pattern: retrace-result-*
|
||||
path: retrace-artifacts
|
||||
|
||||
- name: Render retrace summary
|
||||
run: |
|
||||
node tools/trace_replay/render_retrace_summary.mjs \
|
||||
--input retrace-artifacts \
|
||||
--output-dir retrace-summary \
|
||||
--title "MobileGL Linux retrace overview" \
|
||||
--group-label "Linux" \
|
||||
--html mobilegl-linux-retrace-overview.html
|
||||
|
||||
- name: Upload retrace summary
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
path: retrace-summary/mobilegl-linux-retrace-overview.html
|
||||
archive: false
|
||||
if-no-files-found: error
|
||||
|
||||
remove-artifact-clutter:
|
||||
name: remove artifact clutter
|
||||
runs-on: ubuntu-latest
|
||||
needs: retrace-summary
|
||||
if: always()
|
||||
permissions:
|
||||
actions: write
|
||||
steps:
|
||||
- name: Delete intermediate Linux retrace artifacts
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
declare -A failed_cases=()
|
||||
while IFS= read -r job_name; do
|
||||
case_name="${job_name#retrace (*, }"
|
||||
case_name="${case_name%)}"
|
||||
failed_cases["${case_name}"]=1
|
||||
done < <(
|
||||
gh api --paginate "repos/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}/jobs?per_page=100" \
|
||||
--jq '.jobs[] | select(.name | startswith("retrace (")) | select(.conclusion == "failure" or .conclusion == "cancelled" or .conclusion == "timed_out" or .conclusion == "action_required") | .name'
|
||||
)
|
||||
|
||||
if ((${#failed_cases[@]})); then
|
||||
echo "Retaining fixtures for failed retrace case(s):"
|
||||
printf ' %s\n' "${!failed_cases[@]}"
|
||||
else
|
||||
echo "All retrace jobs succeeded; no fixtures need to be retained."
|
||||
fi
|
||||
|
||||
deleted=0
|
||||
retained=0
|
||||
while IFS=$'\t' read -r artifact_id artifact_name; do
|
||||
if [[ "${artifact_name}" == trace-fixture-* ]]; then
|
||||
case_name="${artifact_name#trace-fixture-}"
|
||||
if [[ -v "failed_cases[${case_name}]" ]]; then
|
||||
echo "Retaining ${artifact_name} (${artifact_id}) for failed retrace."
|
||||
((retained += 1))
|
||||
continue
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "Deleting ${artifact_name} (${artifact_id})"
|
||||
gh api --method DELETE "repos/${GITHUB_REPOSITORY}/actions/artifacts/${artifact_id}"
|
||||
((deleted += 1))
|
||||
done < <(
|
||||
gh api --paginate "repos/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}/artifacts?per_page=100" \
|
||||
--jq '.artifacts[] | select(.name | startswith("trace-fixture-") or startswith("retrace-result-")) | [.id, .name] | @tsv'
|
||||
)
|
||||
|
||||
echo "Deleted ${deleted} intermediate Linux artifact(s); retained ${retained} failed-retrace fixture(s)."
|
||||
-27
@@ -1,27 +0,0 @@
|
||||
################################################################################
|
||||
# 此 .gitignore 文件已由 Microsoft(R) Visual Studio 自动创建。
|
||||
################################################################################
|
||||
|
||||
/build
|
||||
.cxx
|
||||
/.vs
|
||||
/out/build
|
||||
CMakeSettings.json
|
||||
MobileGL/MG_Test/out
|
||||
MobileGL/MG_Test/.vs
|
||||
MobileGLCodeManager.*
|
||||
MobileGLCodeManager
|
||||
.vscode
|
||||
.clangd
|
||||
MobileGL/MG_Test/build
|
||||
/build_*
|
||||
/cmake-build*
|
||||
.idea
|
||||
MobileGL/MG*/build*
|
||||
MobileGL/MG*/cmake-build*
|
||||
/android-plugin/.gradle
|
||||
/android-plugin/build
|
||||
/android-plugin/app/build
|
||||
/android-plugin/app/src/trace/jniLibs
|
||||
/android-plugin/local.properties
|
||||
tools/trace_replay/work/
|
||||
-33
@@ -1,33 +0,0 @@
|
||||
[submodule "3rdparty/DiligentCore"]
|
||||
path = 3rdparty/DiligentCore
|
||||
url = https://github.com/DiligentGraphics/DiligentCore.git
|
||||
[submodule "3rdparty/glslang"]
|
||||
path = 3rdparty/glslang
|
||||
url = https://github.com/MobileGL-Dev/glslang.git
|
||||
[submodule "3rdparty/SPIRV-Cross"]
|
||||
path = 3rdparty/SPIRV-Cross
|
||||
url = https://github.com/KhronosGroup/SPIRV-Cross.git
|
||||
[submodule "include/FastSTL"]
|
||||
path = include/FastSTL
|
||||
url = https://github.com/MobileGL-Dev/FastSTL.git
|
||||
[submodule "3rdparty/tracy"]
|
||||
path = 3rdparty/tracy
|
||||
url = https://github.com/wolfpld/tracy.git
|
||||
[submodule "3rdparty/xxHash"]
|
||||
path = 3rdparty/xxHash
|
||||
url = https://github.com/Cyan4973/xxHash.git
|
||||
[submodule "3rdparty/VulkanMemoryAllocator"]
|
||||
path = 3rdparty/VulkanMemoryAllocator
|
||||
url = https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator.git
|
||||
[submodule "3rdparty/Vulkan-Utility-Libraries"]
|
||||
path = 3rdparty/Vulkan-Utility-Libraries
|
||||
url = https://github.com/KhronosGroup/Vulkan-Utility-Libraries.git
|
||||
[submodule "3rdparty/Vulkan-Headers"]
|
||||
path = 3rdparty/Vulkan-Headers
|
||||
url = https://github.com/KhronosGroup/Vulkan-Headers.git
|
||||
[submodule "3rdparty/SPIRV-Reflect"]
|
||||
path = 3rdparty/SPIRV-Reflect
|
||||
url = https://github.com/KhronosGroup/SPIRV-Reflect.git
|
||||
[submodule "3rdparty/apitrace"]
|
||||
path = 3rdparty/apitrace
|
||||
url = https://github.com/MobileGL-Dev/apitrace.git
|
||||
Vendored
-1
Submodule 3rdparty/DiligentCore deleted from f36e638805
Vendored
-1
Submodule 3rdparty/SPIRV-Cross deleted from 072444287f
Vendored
-1
Submodule 3rdparty/SPIRV-Reflect deleted from 10b4f09a24
Vendored
-1
Submodule 3rdparty/Vulkan-Headers deleted from ad9ce1235e
Vendored
-1
Submodule 3rdparty/Vulkan-Utility-Libraries deleted from 738ec97a3f
Vendored
-1
Submodule 3rdparty/VulkanMemoryAllocator deleted from e722e57c89
Vendored
-1
Submodule 3rdparty/apitrace deleted from 10935bb5e4
Vendored
-1
Submodule 3rdparty/glslang deleted from 26fe5ceb45
Vendored
-1
Submodule 3rdparty/tracy deleted from e6b9ea4609
Vendored
-1
Submodule 3rdparty/xxHash deleted from 1d7b2a9d21
+93
-471
@@ -2,498 +2,120 @@ cmake_minimum_required(VERSION 3.22.1)
|
||||
|
||||
project("MobileGL")
|
||||
|
||||
option(MOBILEGL_BUILD_TEST "Build MobileGL tests" ON )
|
||||
option(MOBILEGL_BUILD_BENCHMARK "Build MobileGL benchmarks" ON )
|
||||
option(MOBILEGL_FORCE_RELEASE_OPT "Enable Release optimization flags in Debug build" ON )
|
||||
option(MOBILEGL_ENABLE_TRACY "Enable tracy for profiling" OFF)
|
||||
option(MOBILEGL_BUILD_TRACE_REPLAY "Build desktop apitrace replay runner" OFF)
|
||||
option(MOBILEGL_TRACE_ANGLE_VARIANTS "Enable signed trace-APK ANGLE variant loading" OFF)
|
||||
option(MOBILEGL_IOS "Build MobileGL for iOS instead of macOS when APPLE is set" OFF)
|
||||
set(MOBILEGL_LOG_ACTIVE_LEVEL "MOBILEGL_LOG_LEVEL_INFO" CACHE STRING "MobileGL active log level macro")
|
||||
set(MOBILEGL_VULKAN_LIBRARY "" CACHE FILEPATH "Vulkan loader/MoltenVK library to link for iOS builds")
|
||||
|
||||
if (ANDROID)
|
||||
set(MOBILEGL_BUILD_TEST OFF CACHE BOOL "Build MobileGL tests" FORCE)
|
||||
set(MOBILEGL_BUILD_BENCHMARK OFF CACHE BOOL "Build MobileGL benchmarks" FORCE)
|
||||
endif()
|
||||
|
||||
if (NOT CMAKE_BUILD_TYPE STREQUAL "Debug" OR MOBILEGL_FORCE_RELEASE_OPT)
|
||||
# Check if ThinLTO or LTO is suppported
|
||||
include(CheckIPOSupported)
|
||||
include(CheckCCompilerFlag)
|
||||
include(CheckCXXCompilerFlag)
|
||||
|
||||
check_ipo_supported(RESULT LTOSupported OUTPUT LTOError)
|
||||
|
||||
check_c_compiler_flag("-flto" HAS_LTO_C)
|
||||
check_cxx_compiler_flag("-flto" HAS_LTO_CXX)
|
||||
|
||||
if (LTOSupported OR (HAS_LTO_C AND HAS_LTO_CXX))
|
||||
# Check ThinLTO
|
||||
check_c_compiler_flag("-flto=thin" HAS_THINLTO_C)
|
||||
check_cxx_compiler_flag("-flto=thin" HAS_THINLTO_CXX)
|
||||
if (HAS_THINLTO_C AND HAS_THINLTO_CXX)
|
||||
message(STATUS "ThinLTO supported, using -flto=thin")
|
||||
|
||||
add_compile_options(-flto=thin)
|
||||
add_link_options(-flto=thin)
|
||||
else()
|
||||
# ThinLTO is not supported
|
||||
message(STATUS "ThinLTO not available, fallback to CMAKE IPO")
|
||||
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE)
|
||||
endif()
|
||||
else()
|
||||
message(STATUS "IPO not supported: ${LTOError}")
|
||||
endif()
|
||||
|
||||
if (CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang" AND NOT MATCHES "AppleClang")
|
||||
add_compile_options(-O3 -ffunction-sections -fdata-sections)
|
||||
add_link_options(-Wl,--gc-sections)
|
||||
elseif (CMAKE_CXX_COMPILER_ID MATCHES "MSVC")
|
||||
# add_compile_options(/O2)
|
||||
else ()
|
||||
add_compile_options(-O2)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if (CMAKE_CXX_COMPILER_ID MATCHES "MSVC")
|
||||
add_compile_options(/Zc:preprocessor)
|
||||
add_compile_options(/Zc:__cplusplus)
|
||||
endif()
|
||||
|
||||
enable_language(CXX)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 23)
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -w -fvisibility=hidden -funwind-tables -g -D_THREAD_SAFE -fPIC -stdlib=libc++")
|
||||
|
||||
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -w -g -std=gnu99 -funwind-tables -O3 -fvisibility=hidden")
|
||||
|
||||
if (MSVC)
|
||||
set(CMAKE_CXX_FLAGS "/EHsc ${CMAKE_CXX_FLAGS}")
|
||||
endif()
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static-libstdc++")
|
||||
|
||||
# Generate MGGitHash.h
|
||||
execute_process(
|
||||
COMMAND git rev-parse HEAD
|
||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
|
||||
OUTPUT_VARIABLE GIT_COMMIT_HASH_FULL
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
)
|
||||
set(CMAKE_ANDROID_STL_TYPE c++_static)
|
||||
|
||||
string(SUBSTRING "${GIT_COMMIT_HASH_FULL}" 0 7 GIT_COMMIT_HASH_SHORT)
|
||||
#set(CMAKE_BUILD_TYPE Release)
|
||||
|
||||
configure_file(
|
||||
${CMAKE_SOURCE_DIR}/MobileGL/MG_Util/Miscellany/MGGitHash.h.in
|
||||
${CMAKE_BINARY_DIR}/generated/MGGitHash.h
|
||||
@ONLY
|
||||
)
|
||||
set(PROFILING OFF)
|
||||
|
||||
include_directories(${CMAKE_BINARY_DIR}/generated)
|
||||
|
||||
set(ENABLE_RTTI ON CACHE BOOL "Enables RTTI (will be took by glslang)" FORCE)
|
||||
|
||||
set(ENABLE_GLSLANG_BINARIES OFF CACHE BOOL "Enable glslangValidator/spirv-remap" FORCE)
|
||||
set(ENABLE_SPVREMAPPER OFF CACHE BOOL "Enable SPVRemapper" FORCE)
|
||||
set(ENABLE_OPT ON CACHE BOOL "Enable SPIRV-Tools opt usage in glslang" FORCE)
|
||||
set(BUILD_EXTERNAL ON CACHE BOOL "Build external deps in External/" FORCE)
|
||||
set(ENABLE_GLSLANG_INSTALL OFF CACHE BOOL "Install glslang targets" FORCE)
|
||||
|
||||
set(SPIRV_CROSS_C_API ON CACHE BOOL "Enable C API" FORCE)
|
||||
set(SPIRV_CROSS_ENABLE_GLSL ON CACHE BOOL "Enable GLSL backend" FORCE)
|
||||
set(SPIRV_CROSS_ENABLE_HLSL OFF CACHE BOOL "Disable HLSL backend" FORCE)
|
||||
set(SPIRV_CROSS_ENABLE_MSL OFF CACHE BOOL "Disable MSL backend" FORCE)
|
||||
set(SPIRV_CROSS_ENABLE_CPP OFF CACHE BOOL "Disable C++ API target" FORCE)
|
||||
set(SPIRV_CROSS_CLI OFF CACHE BOOL "Disable CLI binary" FORCE)
|
||||
set(SPIRV_CROSS_STATIC ON CACHE BOOL "Prefer static libs" FORCE)
|
||||
|
||||
set(SPIRV_REFLECT_EXECUTABLE OFF CACHE BOOL "Build spirv-reflect executable" FORCE)
|
||||
set(SPIRV_REFLECT_STATIC_LIB ON CACHE BOOL "Build a SPIRV-Reflect static library" FORCE)
|
||||
set(SPIRV_REFLECT_BUILD_TESTS OFF CACHE BOOL "Build the SPIRV-Reflect test suite" FORCE)
|
||||
set(SPIRV_REFLECT_ENABLE_ASSERTS OFF CACHE BOOL "Enable asserts for debugging" FORCE)
|
||||
set(SPIRV_REFLECT_ENABLE_ASAN OFF CACHE BOOL "Use address sanitization" FORCE)
|
||||
set(SPIRV_REFLECT_INSTALL OFF CACHE BOOL "Whether to install" FORCE)
|
||||
|
||||
# add_subdirectory(3rdparty/DiligentCore)
|
||||
add_subdirectory(3rdparty/glslang)
|
||||
add_subdirectory(3rdparty/SPIRV-Cross)
|
||||
add_subdirectory(3rdparty/VulkanMemoryAllocator)
|
||||
add_subdirectory(3rdparty/Vulkan-Headers)
|
||||
add_subdirectory(3rdparty/Vulkan-Utility-Libraries)
|
||||
add_subdirectory(3rdparty/SPIRV-Reflect)
|
||||
|
||||
set(XXHASH_BUILD_XXHSUM OFF)
|
||||
option(BUILD_SHARED_LIBS OFF)
|
||||
add_subdirectory(3rdparty/xxHash/build/cmake xxhash_build EXCLUDE_FROM_ALL)
|
||||
|
||||
set(TRACY_ENABLE ${MOBILEGL_ENABLE_TRACY} CACHE BOOL "Enable Tracy, this is an internal variable" FORCE)
|
||||
|
||||
if (TRACY_ENABLE)
|
||||
set(TRACY_ON_DEMAND ON CACHE BOOL "Enable profiling only connected" FORCE)
|
||||
set(TRACY_NO_EXIT OFF CACHE BOOL "Don't exit Tracy until connected" FORCE)
|
||||
set(TRACY_DELAYED_INIT ON CACHE BOOL "Don't init Tracy on library load" FORCE)
|
||||
set(TRACY_MANUAL_LIFETIME ON CACHE BOOL "Manually control Tracy lifetime" FORCE)
|
||||
set(TRACY_NO_CRASH_HANDLER ON CACHE BOOL "Disable crash handling" FORCE)
|
||||
|
||||
add_subdirectory(3rdparty/tracy)
|
||||
endif ()
|
||||
|
||||
set(SOURCE_FILES
|
||||
MobileGL/Init.cpp
|
||||
MobileGL/GlobalObjects.cpp
|
||||
MobileGL/ConfigLoader.cpp
|
||||
|
||||
MobileGL/MG_Util/Debug/Log.cpp
|
||||
|
||||
MobileGL/MG_Util/Math/VectorTypes.cpp
|
||||
MobileGL/MG_Util/Metrics/TextureMetrics.cpp
|
||||
|
||||
MobileGL/MG_Util/Metrics/BufferMetrics.cpp
|
||||
|
||||
MobileGL/MG_Util/Converters/GLToStr/GLEnumConverter.cpp
|
||||
MobileGL/MG_Util/Converters/EGLToStr/EGLEnumConverter.cpp
|
||||
MobileGL/MG_Util/Converters/MGToStr/DataTypeConverter.cpp
|
||||
MobileGL/MG_Util/Converters/MGToStr/RenderStateEnumConverter.cpp
|
||||
MobileGL/MG_Util/Converters/MGToStr/GLExtensionConverter.cpp
|
||||
MobileGL/MG_Util/Converters/MGToStr/BufferEnumConverter.cpp
|
||||
MobileGL/MG_Util/Converters/MGToStr/FramebufferEnumConverter.cpp
|
||||
MobileGL/MG_Util/Converters/MGToStr/TextureEnumConverter.cpp
|
||||
MobileGL/MG_Util/Converters/GLToGlslang/ProgramEnumConverter.cpp
|
||||
MobileGL/MG_Util/Converters/MGToGL/ErrorCodeConverter.cpp
|
||||
MobileGL/MG_Util/Converters/MGToGL/BufferEnumConverter.cpp
|
||||
MobileGL/MG_Util/Converters/MGToGL/FramebufferEnumConverter.cpp
|
||||
MobileGL/MG_Util/Converters/MGToGL/TextureEnumConverter.cpp
|
||||
MobileGL/MG_Util/Converters/MGToGL/DataTypeConverter.cpp
|
||||
MobileGL/MG_Util/Converters/MGToGL/RenderStateEnumConverter.cpp
|
||||
MobileGL/MG_Util/Converters/MGToGL/ProgramEnumConverter.cpp
|
||||
MobileGL/MG_Util/Converters/GLToMG/BufferEnumConverter.cpp
|
||||
MobileGL/MG_Util/Converters/GLToMG/FramebufferEnumConverter.cpp
|
||||
MobileGL/MG_Util/Converters/GLToMG/TextureEnumConverter.cpp
|
||||
MobileGL/MG_Util/Converters/GLToMG/DataTypeConverter.cpp
|
||||
MobileGL/MG_Util/Converters/GLToMG/RenderStateEnumConverter.cpp
|
||||
MobileGL/MG_Util/Converters/GLToMG/ProgramEnumConverter.cpp
|
||||
MobileGL/MG_Util/Converters/MGToMG/TextureEnumConverter.cpp
|
||||
MobileGL/MG_Util/Converters/MGToVk/RenderStateEnumConverter.cpp
|
||||
MobileGL/MG_Util/Converters/MGToVk/TextureEnumConverter.cpp
|
||||
|
||||
MobileGL/MG_Util/Classifiers/TextureEnumClassifier.cpp
|
||||
|
||||
MobileGL/MG_Util/ShaderTranspiler/ShaderCompiler.cpp
|
||||
MobileGL/MG_Util/ShaderTranspiler/SpvcSession.cpp
|
||||
MobileGL/MG_Util/ShaderTranspiler/ShaderSourceProcessor.cpp
|
||||
MobileGL/MG_Util/ShaderTranspiler/glslang/TMglGlslIoResolver.cpp
|
||||
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/FlattenInterfaceStructPass.cpp
|
||||
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/EliminateFloatEqualsZeroPass.cpp
|
||||
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/RenameSamplerFunctionParameterPass.cpp
|
||||
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/DecomposeWorkgroupVec3Pass.cpp
|
||||
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/LowerDrawParametersPass.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/Vulkan/Loader.cpp
|
||||
|
||||
MobileGL/MG_Util/SelfTest/DriverPost.cpp
|
||||
|
||||
MobileGL/MG_Util/Texture/PixelStoreProcessor.cpp
|
||||
MobileGL/MG_Util/Texture/TextureFormatProcessor.cpp
|
||||
|
||||
MobileGL/MG_Impl/GLXImpl/Exporting/Definitions.cpp
|
||||
MobileGL/MG_Impl/GLXImpl/LookUp/LookUp.cpp
|
||||
|
||||
MobileGL/MG_Impl/EGLImpl/Exporting/Definitions.cpp
|
||||
MobileGL/MG_Impl/EGLImpl/EGLImpl.cpp
|
||||
|
||||
# @INSERTION_POINT:SOURCE_FILE_GLIMPL@ #
|
||||
MobileGL/MG_Impl/GLImpl/Sampler/Validators.cpp
|
||||
MobileGL/MG_Impl/GLImpl/Sampler/GL_Sampler.cpp
|
||||
MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.cpp
|
||||
MobileGL/MG_Impl/GLImpl/RenderState/GL_RenderState.cpp
|
||||
MobileGL/MG_Impl/GLImpl/Framebuffer/Validators.cpp
|
||||
MobileGL/MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.cpp
|
||||
MobileGL/MG_Impl/GLImpl/Program/GL_Program.cpp
|
||||
MobileGL/MG_Impl/GLImpl/Texture/GL_Texture.cpp
|
||||
MobileGL/MG_Impl/GLImpl/Texture/Validators.cpp
|
||||
MobileGL/MG_Impl/GLImpl/Texture/ProxyTexture.cpp
|
||||
MobileGL/MG_Impl/GLImpl/VertexArray/GL_VertexArray.cpp
|
||||
MobileGL/MG_Impl/GLImpl/VertexArray/Validators.cpp
|
||||
MobileGL/MG_Impl/GLImpl/Buffer/GL_Buffer.cpp
|
||||
MobileGL/MG_Impl/GLImpl/Buffer/Validators.cpp
|
||||
MobileGL/MG_Impl/GLImpl/Exporting/Definitions.cpp
|
||||
MobileGL/MG_Impl/GLImpl/Getter/GL_Getter.cpp
|
||||
MobileGL/MG_Impl/GLImpl/Sync/GL_Sync.cpp
|
||||
MobileGL/MG_Impl/GLImpl/Query/GL_Query.cpp
|
||||
|
||||
MobileGL/MG_Impl/Init.cpp
|
||||
MobileGL/MG_Impl/GetProcAddress.cpp
|
||||
|
||||
MobileGL/MG_Backend/Init.cpp
|
||||
MobileGL/MG_Backend/BackendObject.cpp
|
||||
|
||||
MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp
|
||||
MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp
|
||||
MobileGL/MG_Backend/DirectGLES/Utils.cpp
|
||||
MobileGL/MG_Backend/DirectGLES/Managers.cpp
|
||||
|
||||
MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp
|
||||
MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.cpp
|
||||
MobileGL/MG_Backend/DirectVulkan/VmaImpl.cpp
|
||||
MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp
|
||||
MobileGL/MG_Backend/DirectVulkan/Renderer/SwapchainObject.cpp
|
||||
MobileGL/MG_Backend/DirectVulkan/Renderer/FrameContext.cpp
|
||||
MobileGL/MG_Backend/DirectVulkan/Renderer/PipelineFactory.cpp
|
||||
MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.cpp
|
||||
MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.cpp
|
||||
MobileGL/MG_Backend/DirectVulkan/Renderer/BufferArena.cpp
|
||||
MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.cpp
|
||||
MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateBuilder.cpp
|
||||
MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateFactory.cpp
|
||||
MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferObject.cpp
|
||||
MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.cpp
|
||||
MobileGL/MG_Backend/DirectVulkan/Renderer/VkTimerQueryManager.cpp
|
||||
MobileGL/MG_Backend/DirectVulkan/Renderer/VkSamplerManager.cpp
|
||||
MobileGL/MG_Backend/DirectVulkan/Renderer/VkClearManager.cpp
|
||||
MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.cpp
|
||||
|
||||
MobileGL/MG_State/GLState/Core.cpp
|
||||
MobileGL/MG_State/EGLState/Core.cpp
|
||||
MobileGL/MG_State/GLState/ErrorState/Error.cpp
|
||||
MobileGL/MG_State/GLState/BufferState/BufferState.cpp
|
||||
MobileGL/MG_State/GLState/BufferState/BufferObject.cpp
|
||||
MobileGL/MG_State/GLState/VertexArrayState/VertexArrayState.cpp
|
||||
MobileGL/MG_State/GLState/VertexArrayState/VertexArrayObject.cpp
|
||||
MobileGL/MG_State/GLState/TextureState/MipmapStorage.cpp
|
||||
MobileGL/MG_State/GLState/TextureState/TextureObject.cpp
|
||||
MobileGL/MG_State/GLState/TextureState/TextureObject1D.cpp
|
||||
MobileGL/MG_State/GLState/TextureState/TextureObject2D.cpp
|
||||
MobileGL/MG_State/GLState/TextureState/TextureObject2DCube.cpp
|
||||
MobileGL/MG_State/GLState/TextureState/TextureObject3D.cpp
|
||||
MobileGL/MG_State/GLState/TextureState/TextureObjectBuffer.cpp
|
||||
MobileGL/MG_State/GLState/TextureState/TextureUnit.cpp
|
||||
MobileGL/MG_State/GLState/TextureState/TextureState.cpp
|
||||
MobileGL/MG_State/GLState/ProgramState/ProgramObject.cpp
|
||||
MobileGL/MG_State/GLState/ProgramState/ShaderObject.cpp
|
||||
MobileGL/MG_State/GLState/ProgramState/ProgramState.cpp
|
||||
MobileGL/MG_State/GLState/RenderState/RenderState.cpp
|
||||
MobileGL/MG_State/GLState/FramebufferState/FramebufferObject.cpp
|
||||
MobileGL/MG_State/GLState/FramebufferState/FramebufferState.cpp
|
||||
MobileGL/MG_State/GLState/SamplerState/SamplerObject.cpp
|
||||
MobileGL/MG_State/GLState/SamplerState/SamplerState.cpp
|
||||
MobileGL/MG_State/GLState/RenderbufferState/RenderbufferObject.cpp
|
||||
MobileGL/MG_State/GLState/RenderbufferState/RenderbufferState.cpp
|
||||
)
|
||||
|
||||
if (APPLE AND NOT MOBILEGL_IOS)
|
||||
list(APPEND SOURCE_FILES
|
||||
MobileGL/MG_Impl/CGLImpl/CGLImpl.cpp
|
||||
MobileGL/MG_Impl/CGLImpl/Exporting/Definitions.cpp
|
||||
MobileGL/MG_Impl/DyldInterpose/DyldInterpose.cpp
|
||||
MobileGL/MG_Impl/NSOpenGLImpl/NSOpenGLImpl.cpp
|
||||
)
|
||||
endif()
|
||||
|
||||
if (ANDROID)
|
||||
list(APPEND SOURCE_FILES
|
||||
MobileGL/MG_Util/SelfTest/DriverPostJni.cpp
|
||||
)
|
||||
endif()
|
||||
|
||||
set(MOBILEGL_LINK_LIBRARIES
|
||||
glslang::glslang
|
||||
spirv-cross-c
|
||||
SPIRV-Tools-opt
|
||||
SPIRV-Tools
|
||||
xxHash::xxhash
|
||||
GPUOpen::VulkanMemoryAllocator
|
||||
Vulkan::UtilityHeaders
|
||||
spirv-reflect-static
|
||||
)
|
||||
|
||||
set(MOBILEGL_COMPILE_DEF
|
||||
-DVMA_STATIC_VULKAN_FUNCTIONS=0
|
||||
-DVMA_DYNAMIC_VULKAN_FUNCTIONS=1
|
||||
-DVMA_VULKAN_VERSION=1001000
|
||||
)
|
||||
|
||||
message(STATUS "MOBILEGL_COMPILE_DEF=${MOBILEGL_COMPILE_DEF}")
|
||||
|
||||
set(MOBILEGL_INCLUDE_DIR
|
||||
${CMAKE_SOURCE_DIR}/include
|
||||
${CMAKE_SOURCE_DIR}/MobileGL
|
||||
${spirv-tools_SOURCE_DIR}
|
||||
${spirv-tools_SOURCE_DIR}/include
|
||||
${spirv-tools_BINARY_DIR}
|
||||
${SPIRV-Headers_SOURCE_DIR}/include
|
||||
)
|
||||
find_library(GLSLANG_LIB glslang PATHS ${CMAKE_SOURCE_DIR}/libraries/arm64-v8a/)
|
||||
|
||||
add_library(${CMAKE_PROJECT_NAME} SHARED
|
||||
${SOURCE_FILES}
|
||||
MG/Init.cpp
|
||||
|
||||
# MG_GL/Implementations/GL
|
||||
MG/MG_GL/Implementations/GL/GLFuncsDefinitions/GLFuncsDefinitions.cpp
|
||||
|
||||
MG/MG_GL/Implementations/GL/Getter/GL_Getter.cpp
|
||||
MG/MG_GL/Implementations/GL/Getter/BackendInfo.cpp
|
||||
|
||||
MG/MG_GL/Implementations/GL/Framebuffer/GL_Framebuffer.cpp
|
||||
|
||||
MG/MG_GL/Implementations/GL/Common/GL_Common.cpp
|
||||
|
||||
MG/MG_GL/Implementations/GL/Buffer/GL_Buffer.cpp
|
||||
|
||||
MG/MG_GL/Implementations/GL/Drawing/GL_Drawing.cpp
|
||||
|
||||
MG/MG_GL/Implementations/GL/Program/GL_Program.cpp
|
||||
|
||||
MG/MG_GL/Implementations/GL/Texture/GL_Texture.cpp
|
||||
|
||||
MG/MG_GL/Implementations/GL/VertexArray/GL_VertexArray.cpp
|
||||
|
||||
# MG_GL/Implementations/GLX
|
||||
MG/MG_GL/Implementations/GLX/LookUp/LookUp.cpp
|
||||
|
||||
# MG_GL/Implementations/EGL
|
||||
MG/MG_GL/Implementations/EGL/VK/EGL_EMU.cpp
|
||||
|
||||
MG/MG_GL/Implementations/EGL/GLES/EGL_WRAP.cpp
|
||||
|
||||
# MG_GL/State
|
||||
MG/MG_GL/State/Core/GLState.cpp
|
||||
|
||||
MG/MG_GL/State/Common/CommonState.cpp
|
||||
|
||||
MG/MG_GL/State/Texture/TextureState.cpp
|
||||
|
||||
MG/MG_GL/State/VertexArray/VertexArrayState.cpp
|
||||
|
||||
MG/MG_GL/State/Buffer/BufferState.cpp
|
||||
|
||||
MG/MG_GL/State/Program/ShaderObject.cpp
|
||||
MG/MG_GL/State/Program/ProgramState.cpp
|
||||
|
||||
MG/MG_GL/State/Framebuffer/FramebufferState.cpp
|
||||
|
||||
# MG_GL/BackendLoader
|
||||
MG/MG_GL/BackendLoader/GLES/Loader.cpp
|
||||
|
||||
# MG_UTIL/Debug
|
||||
MG/MG_UTIL/Debug/Debug.cpp
|
||||
|
||||
# MG_UTIL/Program
|
||||
MG/MG_UTIL/Program/DebugTool.cpp
|
||||
MG/MG_UTIL/Program/GLSLTool.cpp
|
||||
|
||||
# MG_RHI/GLES
|
||||
MG/MG_RHI/GLES/Test/TestDrawing.cpp
|
||||
)
|
||||
|
||||
if (CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||
set_target_properties(${CMAKE_PROJECT_NAME} PROPERTIES
|
||||
C_VISIBILITY_PRESET default
|
||||
CXX_VISIBILITY_PRESET default
|
||||
VISIBILITY_INLINES_HIDDEN OFF
|
||||
)
|
||||
else()
|
||||
set_target_properties(${CMAKE_PROJECT_NAME} PROPERTIES
|
||||
C_VISIBILITY_PRESET hidden
|
||||
CXX_VISIBILITY_PRESET hidden
|
||||
VISIBILITY_INLINES_HIDDEN ON
|
||||
)
|
||||
endif()
|
||||
|
||||
target_include_directories(${CMAKE_PROJECT_NAME} PUBLIC
|
||||
${MOBILEGL_INCLUDE_DIR}
|
||||
)
|
||||
target_include_directories(${CMAKE_PROJECT_NAME} PRIVATE ./includes)
|
||||
|
||||
target_link_libraries(${CMAKE_PROJECT_NAME}
|
||||
PUBLIC
|
||||
${MOBILEGL_LINK_LIBRARIES}
|
||||
)
|
||||
|
||||
target_compile_definitions(${CMAKE_PROJECT_NAME}
|
||||
PUBLIC
|
||||
${MOBILEGL_COMPILE_DEF}
|
||||
MOBILEGL_LOG_ACTIVE_LEVEL=${MOBILEGL_LOG_ACTIVE_LEVEL}
|
||||
$<$<BOOL:${MOBILEGL_TRACE_ANGLE_VARIANTS}>:MOBILEGL_TRACE_ANGLE_VARIANTS=1>
|
||||
)
|
||||
|
||||
if(UNIX AND NOT APPLE AND NOT ANDROID)
|
||||
foreach(MOBILEGL_LOADER_ALIAS
|
||||
libEGL.so libEGL.so.1)
|
||||
add_custom_command(TARGET ${CMAKE_PROJECT_NAME} POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E create_symlink
|
||||
"$<TARGET_FILE_NAME:${CMAKE_PROJECT_NAME}>"
|
||||
"$<TARGET_FILE_DIR:${CMAKE_PROJECT_NAME}>/${MOBILEGL_LOADER_ALIAS}"
|
||||
COMMENT "Creating ${MOBILEGL_LOADER_ALIAS} alias for Linux GL/EGL loaders"
|
||||
)
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
if(NOT ANDROID)
|
||||
add_library(${CMAKE_PROJECT_NAME}_s STATIC
|
||||
${SOURCE_FILES}
|
||||
)
|
||||
|
||||
if (CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||
set_target_properties(${CMAKE_PROJECT_NAME}_s PROPERTIES
|
||||
C_VISIBILITY_PRESET default
|
||||
CXX_VISIBILITY_PRESET default
|
||||
VISIBILITY_INLINES_HIDDEN OFF
|
||||
)
|
||||
else()
|
||||
set_target_properties(${CMAKE_PROJECT_NAME}_s PROPERTIES
|
||||
C_VISIBILITY_PRESET hidden
|
||||
CXX_VISIBILITY_PRESET hidden
|
||||
VISIBILITY_INLINES_HIDDEN ON
|
||||
)
|
||||
endif()
|
||||
|
||||
target_include_directories(${CMAKE_PROJECT_NAME}_s PUBLIC
|
||||
${MOBILEGL_INCLUDE_DIR}
|
||||
)
|
||||
|
||||
target_link_libraries(${CMAKE_PROJECT_NAME}_s
|
||||
PUBLIC
|
||||
${MOBILEGL_LINK_LIBRARIES}
|
||||
)
|
||||
|
||||
target_compile_definitions(${CMAKE_PROJECT_NAME}_s
|
||||
PUBLIC
|
||||
${MOBILEGL_COMPILE_DEF}
|
||||
MOBILEGL_LOG_ACTIVE_LEVEL=${MOBILEGL_LOG_ACTIVE_LEVEL}
|
||||
)
|
||||
endif()
|
||||
|
||||
if (TRACY_ENABLE)
|
||||
target_link_libraries(${CMAKE_PROJECT_NAME} PUBLIC Tracy::TracyClient)
|
||||
target_link_libraries(${CMAKE_PROJECT_NAME}_s PUBLIC Tracy::TracyClient)
|
||||
target_compile_definitions(${CMAKE_PROJECT_NAME} PUBLIC -DTRACY_ENABLE)
|
||||
target_compile_definitions(${CMAKE_PROJECT_NAME}_s PUBLIC -DTRACY_ENABLE)
|
||||
endif ()
|
||||
|
||||
if (ANDROID)
|
||||
target_link_libraries(${CMAKE_PROJECT_NAME} PUBLIC
|
||||
${CMAKE_SOURCE_DIR}/library/arm64-v8a/libglslang.a
|
||||
${CMAKE_SOURCE_DIR}/library/arm64-v8a/libspirv-cross-c-shared.so
|
||||
#${CMAKE_SOURCE_DIR}/library/arm64-v8a/libshaderconv.so
|
||||
${CMAKE_SOURCE_DIR}/library/arm64-v8a/libGenericCodeGen.a
|
||||
${CMAKE_SOURCE_DIR}/library/arm64-v8a/libglslang-default-resource-limits.a
|
||||
${CMAKE_SOURCE_DIR}/library/arm64-v8a/libMachineIndependent.a
|
||||
${CMAKE_SOURCE_DIR}/library/arm64-v8a/libOSDependent.a
|
||||
${CMAKE_SOURCE_DIR}/library/arm64-v8a/libSPIRV-Tools-diff.a
|
||||
${CMAKE_SOURCE_DIR}/library/arm64-v8a/libSPIRV-Tools-link.a
|
||||
${CMAKE_SOURCE_DIR}/library/arm64-v8a/libSPIRV-Tools-lint.a
|
||||
${CMAKE_SOURCE_DIR}/library/arm64-v8a/libSPIRV-Tools-opt.a
|
||||
${CMAKE_SOURCE_DIR}/library/arm64-v8a/libSPIRV-Tools-reduce.a
|
||||
${CMAKE_SOURCE_DIR}/library/arm64-v8a/libSPIRV-Tools.a
|
||||
${CMAKE_SOURCE_DIR}/library/arm64-v8a/libSPIRV.a
|
||||
${CMAKE_SOURCE_DIR}/library/arm64-v8a/libSPVRemapper.a
|
||||
android
|
||||
log
|
||||
EGL
|
||||
vulkan
|
||||
)
|
||||
endif()
|
||||
)
|
||||
|
||||
if (APPLE AND NOT MOBILEGL_IOS)
|
||||
target_link_libraries(${CMAKE_PROJECT_NAME} PUBLIC
|
||||
"-framework Cocoa"
|
||||
"-framework QuartzCore"
|
||||
"-framework Foundation"
|
||||
"-framework OpenGL"
|
||||
objc)
|
||||
if(TARGET ${CMAKE_PROJECT_NAME}_s)
|
||||
target_link_libraries(${CMAKE_PROJECT_NAME}_s PUBLIC
|
||||
"-framework Cocoa"
|
||||
"-framework QuartzCore"
|
||||
"-framework Foundation"
|
||||
"-framework OpenGL"
|
||||
objc)
|
||||
endif()
|
||||
endif()
|
||||
target_include_directories(MobileGL PUBLIC
|
||||
${CMAKE_SOURCE_DIR}/include
|
||||
)
|
||||
|
||||
if (APPLE AND MOBILEGL_IOS)
|
||||
target_compile_definitions(${CMAKE_PROJECT_NAME} PUBLIC MOBILEGL_IOS=1 _LIBCPP_DISABLE_AVAILABILITY)
|
||||
target_link_libraries(${CMAKE_PROJECT_NAME} PUBLIC
|
||||
"-framework CoreGraphics"
|
||||
"-framework Foundation"
|
||||
"-framework QuartzCore"
|
||||
objc)
|
||||
if (MOBILEGL_VULKAN_LIBRARY)
|
||||
target_link_libraries(${CMAKE_PROJECT_NAME} PUBLIC "${MOBILEGL_VULKAN_LIBRARY}")
|
||||
endif()
|
||||
|
||||
if(TARGET ${CMAKE_PROJECT_NAME}_s)
|
||||
target_compile_definitions(${CMAKE_PROJECT_NAME}_s PUBLIC MOBILEGL_IOS=1 _LIBCPP_DISABLE_AVAILABILITY)
|
||||
target_link_libraries(${CMAKE_PROJECT_NAME}_s PUBLIC
|
||||
"-framework CoreGraphics"
|
||||
"-framework Foundation"
|
||||
"-framework QuartzCore"
|
||||
objc)
|
||||
if (MOBILEGL_VULKAN_LIBRARY)
|
||||
target_link_libraries(${CMAKE_PROJECT_NAME}_s PUBLIC "${MOBILEGL_VULKAN_LIBRARY}")
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if (NOT ANDROID AND NOT MOBILEGL_IOS)
|
||||
find_package(Vulkan)
|
||||
if (Vulkan_FOUND)
|
||||
target_link_libraries(${CMAKE_PROJECT_NAME} PUBLIC Vulkan::Vulkan Vulkan::Headers)
|
||||
target_link_libraries(${CMAKE_PROJECT_NAME}_s PUBLIC Vulkan::Vulkan Vulkan::Headers)
|
||||
target_include_directories(${CMAKE_PROJECT_NAME} PUBLIC ${Vulkan_INCLUDE_DIR})
|
||||
target_include_directories(${CMAKE_PROJECT_NAME}_s PUBLIC ${Vulkan_INCLUDE_DIR})
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
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)
|
||||
add_subdirectory(MobileGL/MG_Test)
|
||||
endif()
|
||||
|
||||
if (MOBILEGL_BUILD_BENCHMARK)
|
||||
add_subdirectory(MobileGL/MG_Benchmark)
|
||||
endif()
|
||||
|
||||
if (MOBILEGL_BUILD_TRACE_REPLAY)
|
||||
add_subdirectory(tools/trace_replay)
|
||||
endif()
|
||||
if (PROFILING)
|
||||
find_package(Threads)
|
||||
include_directories(${CMAKE_SOURCE_DIR}/3rdparty/perfetto/sdk)
|
||||
add_library(perfetto STATIC ${CMAKE_SOURCE_DIR}/3rdparty/perfetto/sdk/perfetto.cc)
|
||||
target_link_libraries(MobileGL perfetto ${CMAKE_THREAD_LIBS_INIT})
|
||||
target_compile_definitions(MobileGL PUBLIC PROFILING=1)
|
||||
endif()
|
||||
|
||||
@@ -1,674 +0,0 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
||||
-165
@@ -1,165 +0,0 @@
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
|
||||
This version of the GNU Lesser General Public License incorporates
|
||||
the terms and conditions of version 3 of the GNU General Public
|
||||
License, supplemented by the additional permissions listed below.
|
||||
|
||||
0. Additional Definitions.
|
||||
|
||||
As used herein, "this License" refers to version 3 of the GNU Lesser
|
||||
General Public License, and the "GNU GPL" refers to version 3 of the GNU
|
||||
General Public License.
|
||||
|
||||
"The Library" refers to a covered work governed by this License,
|
||||
other than an Application or a Combined Work as defined below.
|
||||
|
||||
An "Application" is any work that makes use of an interface provided
|
||||
by the Library, but which is not otherwise based on the Library.
|
||||
Defining a subclass of a class defined by the Library is deemed a mode
|
||||
of using an interface provided by the Library.
|
||||
|
||||
A "Combined Work" is a work produced by combining or linking an
|
||||
Application with the Library. The particular version of the Library
|
||||
with which the Combined Work was made is also called the "Linked
|
||||
Version".
|
||||
|
||||
The "Minimal Corresponding Source" for a Combined Work means the
|
||||
Corresponding Source for the Combined Work, excluding any source code
|
||||
for portions of the Combined Work that, considered in isolation, are
|
||||
based on the Application, and not on the Linked Version.
|
||||
|
||||
The "Corresponding Application Code" for a Combined Work means the
|
||||
object code and/or source code for the Application, including any data
|
||||
and utility programs needed for reproducing the Combined Work from the
|
||||
Application, but excluding the System Libraries of the Combined Work.
|
||||
|
||||
1. Exception to Section 3 of the GNU GPL.
|
||||
|
||||
You may convey a covered work under sections 3 and 4 of this License
|
||||
without being bound by section 3 of the GNU GPL.
|
||||
|
||||
2. Conveying Modified Versions.
|
||||
|
||||
If you modify a copy of the Library, and, in your modifications, a
|
||||
facility refers to a function or data to be supplied by an Application
|
||||
that uses the facility (other than as an argument passed when the
|
||||
facility is invoked), then you may convey a copy of the modified
|
||||
version:
|
||||
|
||||
a) under this License, provided that you make a good faith effort to
|
||||
ensure that, in the event an Application does not supply the
|
||||
function or data, the facility still operates, and performs
|
||||
whatever part of its purpose remains meaningful, or
|
||||
|
||||
b) under the GNU GPL, with none of the additional permissions of
|
||||
this License applicable to that copy.
|
||||
|
||||
3. Object Code Incorporating Material from Library Header Files.
|
||||
|
||||
The object code form of an Application may incorporate material from
|
||||
a header file that is part of the Library. You may convey such object
|
||||
code under terms of your choice, provided that, if the incorporated
|
||||
material is not limited to numerical parameters, data structure
|
||||
layouts and accessors, or small macros, inline functions and templates
|
||||
(ten or fewer lines in length), you do both of the following:
|
||||
|
||||
a) Give prominent notice with each copy of the object code that the
|
||||
Library is used in it and that the Library and its use are
|
||||
covered by this License.
|
||||
|
||||
b) Accompany the object code with a copy of the GNU GPL and this license
|
||||
document.
|
||||
|
||||
4. Combined Works.
|
||||
|
||||
You may convey a Combined Work under terms of your choice that,
|
||||
taken together, effectively do not restrict modification of the
|
||||
portions of the Library contained in the Combined Work and reverse
|
||||
engineering for debugging such modifications, if you also do each of
|
||||
the following:
|
||||
|
||||
a) Give prominent notice with each copy of the Combined Work that
|
||||
the Library is used in it and that the Library and its use are
|
||||
covered by this License.
|
||||
|
||||
b) Accompany the Combined Work with a copy of the GNU GPL and this license
|
||||
document.
|
||||
|
||||
c) For a Combined Work that displays copyright notices during
|
||||
execution, include the copyright notice for the Library among
|
||||
these notices, as well as a reference directing the user to the
|
||||
copies of the GNU GPL and this license document.
|
||||
|
||||
d) Do one of the following:
|
||||
|
||||
0) Convey the Minimal Corresponding Source under the terms of this
|
||||
License, and the Corresponding Application Code in a form
|
||||
suitable for, and under terms that permit, the user to
|
||||
recombine or relink the Application with a modified version of
|
||||
the Linked Version to produce a modified Combined Work, in the
|
||||
manner specified by section 6 of the GNU GPL for conveying
|
||||
Corresponding Source.
|
||||
|
||||
1) Use a suitable shared library mechanism for linking with the
|
||||
Library. A suitable mechanism is one that (a) uses at run time
|
||||
a copy of the Library already present on the user's computer
|
||||
system, and (b) will operate properly with a modified version
|
||||
of the Library that is interface-compatible with the Linked
|
||||
Version.
|
||||
|
||||
e) Provide Installation Information, but only if you would otherwise
|
||||
be required to provide such information under section 6 of the
|
||||
GNU GPL, and only to the extent that such information is
|
||||
necessary to install and execute a modified version of the
|
||||
Combined Work produced by recombining or relinking the
|
||||
Application with a modified version of the Linked Version. (If
|
||||
you use option 4d0, the Installation Information must accompany
|
||||
the Minimal Corresponding Source and Corresponding Application
|
||||
Code. If you use option 4d1, you must provide the Installation
|
||||
Information in the manner specified by section 6 of the GNU GPL
|
||||
for conveying Corresponding Source.)
|
||||
|
||||
5. Combined Libraries.
|
||||
|
||||
You may place library facilities that are a work based on the
|
||||
Library side by side in a single library together with other library
|
||||
facilities that are not Applications and are not covered by this
|
||||
License, and convey such a combined library under terms of your
|
||||
choice, if you do both of the following:
|
||||
|
||||
a) Accompany the combined library with a copy of the same work based
|
||||
on the Library, uncombined with any other library facilities,
|
||||
conveyed under the terms of this License.
|
||||
|
||||
b) Give prominent notice with the combined library that part of it
|
||||
is a work based on the Library, and explaining where to find the
|
||||
accompanying uncombined form of the same work.
|
||||
|
||||
6. Revised Versions of the GNU Lesser General Public License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions
|
||||
of the GNU Lesser General Public License from time to time. Such new
|
||||
versions will be similar in spirit to the present version, but may
|
||||
differ in detail to address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Library as you received it specifies that a certain numbered version
|
||||
of the GNU Lesser General Public License "or any later version"
|
||||
applies to it, you have the option of following the terms and
|
||||
conditions either of that published version or of any later version
|
||||
published by the Free Software Foundation. If the Library as you
|
||||
received it does not specify a version number of the GNU Lesser
|
||||
General Public License, you may choose any version of the GNU Lesser
|
||||
General Public License ever published by the Free Software Foundation.
|
||||
|
||||
If the Library as you received it specifies that a proxy can decide
|
||||
whether future versions of the GNU Lesser General Public License shall
|
||||
apply, that proxy's public statement of acceptance of any version is
|
||||
permanent authorization for you to choose that version for the
|
||||
Library.
|
||||
@@ -1,4 +1,458 @@
|
||||
Copyright (c) 2025-2026 MobileGL-Dev
|
||||
Copyright (c) 2025 MobileGL-Dev
|
||||
|
||||
This project is licensed under the GNU Lesser General Public License v3.0.
|
||||
See COPYING and COPYING.LESSER for the full text of the LGPLv3 license.
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
Version 2.1, February 1999
|
||||
|
||||
Copyright (C) 1991, 1999 Free Software Foundation, Inc.
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
[This is the first released version of the Lesser GPL. It also counts
|
||||
as the successor of the GNU Library Public License, version 2, hence
|
||||
the version number 2.1.]
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
Licenses are intended to guarantee your freedom to share and change
|
||||
free software--to make sure the software is free for all its users.
|
||||
|
||||
This license, the Lesser General Public License, applies to some
|
||||
specially designated software packages--typically libraries--of the
|
||||
Free Software Foundation and other authors who decide to use it. You
|
||||
can use it too, but we suggest you first think carefully about whether
|
||||
this license or the ordinary General Public License is the better
|
||||
strategy to use in any particular case, based on the explanations below.
|
||||
|
||||
When we speak of free software, we are referring to freedom of use,
|
||||
not price. Our General Public Licenses are designed to make sure that
|
||||
you have the freedom to distribute copies of free software (and charge
|
||||
for this service if you wish); that you receive source code or can get
|
||||
it if you want it; that you can change the software and use pieces of
|
||||
it in new free programs; and that you are informed that you can do
|
||||
these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
distributors to deny you these rights or to ask you to surrender these
|
||||
rights. These restrictions translate to certain responsibilities for
|
||||
you if you distribute copies of the library or if you modify it.
|
||||
|
||||
For example, if you distribute copies of the library, whether gratis
|
||||
or for a fee, you must give the recipients all the rights that we gave
|
||||
you. You must make sure that they, too, receive or can get the source
|
||||
code. If you link other code with the library, you must provide
|
||||
complete object files to the recipients, so that they can relink them
|
||||
with the library after making changes to the library and recompiling
|
||||
it. And you must show them these terms so they know their rights.
|
||||
|
||||
We protect your rights with a two-step method: (1) we copyright the
|
||||
library, and (2) we offer you this license, which gives you legal
|
||||
permission to copy, distribute and/or modify the library.
|
||||
|
||||
To protect each distributor, we want to make it very clear that
|
||||
there is no warranty for the free library. Also, if the library is
|
||||
modified by someone else and passed on, the recipients should know
|
||||
that what they have is not the original version, so that the original
|
||||
author's reputation will not be affected by problems that might be
|
||||
introduced by others.
|
||||
|
||||
Finally, software patents pose a constant threat to the existence of
|
||||
any free program. We wish to make sure that a company cannot
|
||||
effectively restrict the users of a free program by obtaining a
|
||||
restrictive license from a patent holder. Therefore, we insist that
|
||||
any patent license obtained for a version of the library must be
|
||||
consistent with the full freedom of use specified in this license.
|
||||
|
||||
Most GNU software, including some libraries, is covered by the
|
||||
ordinary GNU General Public License. This license, the GNU Lesser
|
||||
General Public License, applies to certain designated libraries, and
|
||||
is quite different from the ordinary General Public License. We use
|
||||
this license for certain libraries in order to permit linking those
|
||||
libraries into non-free programs.
|
||||
|
||||
When a program is linked with a library, whether statically or using
|
||||
a shared library, the combination of the two is legally speaking a
|
||||
combined work, a derivative of the original library. The ordinary
|
||||
General Public License therefore permits such linking only if the
|
||||
entire combination fits its criteria of freedom. The Lesser General
|
||||
Public License permits more lax criteria for linking other code with
|
||||
the library.
|
||||
|
||||
We call this license the "Lesser" General Public License because it
|
||||
does Less to protect the user's freedom than the ordinary General
|
||||
Public License. It also provides other free software developers Less
|
||||
of an advantage over competing non-free programs. These disadvantages
|
||||
are the reason we use the ordinary General Public License for many
|
||||
libraries. However, the Lesser license provides advantages in certain
|
||||
special circumstances.
|
||||
|
||||
For example, on rare occasions, there may be a special need to
|
||||
encourage the widest possible use of a certain library, so that it becomes
|
||||
a de-facto standard. To achieve this, non-free programs must be
|
||||
allowed to use the library. A more frequent case is that a free
|
||||
library does the same job as widely used non-free libraries. In this
|
||||
case, there is little to gain by limiting the free library to free
|
||||
software only, so we use the Lesser General Public License.
|
||||
|
||||
In other cases, permission to use a particular library in non-free
|
||||
programs enables a greater number of people to use a large body of
|
||||
free software. For example, permission to use the GNU C Library in
|
||||
non-free programs enables many more people to use the whole GNU
|
||||
operating system, as well as its variant, the GNU/Linux operating
|
||||
system.
|
||||
|
||||
Although the Lesser General Public License is Less protective of the
|
||||
users' freedom, it does ensure that the user of a program that is
|
||||
linked with the Library has the freedom and the wherewithal to run
|
||||
that program using a modified version of the Library.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow. Pay close attention to the difference between a
|
||||
"work based on the library" and a "work that uses the library". The
|
||||
former contains code derived from the library, whereas the latter must
|
||||
be combined with the library in order to run.
|
||||
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License Agreement applies to any software library or other
|
||||
program which contains a notice placed by the copyright holder or
|
||||
other authorized party saying it may be distributed under the terms of
|
||||
this Lesser General Public License (also called "this License").
|
||||
Each licensee is addressed as "you".
|
||||
|
||||
A "library" means a collection of software functions and/or data
|
||||
prepared so as to be conveniently linked with application programs
|
||||
(which use some of those functions and data) to form executables.
|
||||
|
||||
The "Library", below, refers to any such software library or work
|
||||
which has been distributed under these terms. A "work based on the
|
||||
Library" means either the Library or any derivative work under
|
||||
copyright law: that is to say, a work containing the Library or a
|
||||
portion of it, either verbatim or with modifications and/or translated
|
||||
straightforwardly into another language. (Hereinafter, translation is
|
||||
included without limitation in the term "modification".)
|
||||
|
||||
"Source code" for a work means the preferred form of the work for
|
||||
making modifications to it. For a library, complete source code means
|
||||
all the source code for all modules it contains, plus any associated
|
||||
interface definition files, plus the scripts used to control compilation
|
||||
and installation of the library.
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running a program using the Library is not restricted, and output from
|
||||
such a program is covered only if its contents constitute a work based
|
||||
on the Library (independent of the use of the Library in a tool for
|
||||
writing it). Whether that is true depends on what the Library does
|
||||
and what the program that uses the Library does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Library's
|
||||
complete source code as you receive it, in any medium, provided that
|
||||
you conspicuously and appropriately publish on each copy an
|
||||
appropriate copyright notice and disclaimer of warranty; keep intact
|
||||
all the notices that refer to this License and to the absence of any
|
||||
warranty; and distribute a copy of this License along with the
|
||||
Library.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy,
|
||||
and you may at your option offer warranty protection in exchange for a
|
||||
fee.
|
||||
|
||||
2. You may modify your copy or copies of the Library or any portion
|
||||
of it, thus forming a work based on the Library, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) The modified work must itself be a software library.
|
||||
|
||||
b) You must cause the files modified to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
c) You must cause the whole of the work to be licensed at no
|
||||
charge to all third parties under the terms of this License.
|
||||
|
||||
d) If a facility in the modified Library refers to a function or a
|
||||
table of data to be supplied by an application program that uses
|
||||
the facility, other than as an argument passed when the facility
|
||||
is invoked, then you must make a good faith effort to ensure that,
|
||||
in the event an application does not supply such function or
|
||||
table, the facility still operates, and performs whatever part of
|
||||
its purpose remains meaningful.
|
||||
|
||||
(For example, a function in a library to compute square roots has
|
||||
a purpose that is entirely well-defined independent of the
|
||||
application. Therefore, Subsection 2d requires that any
|
||||
application-supplied function or table used by this function must
|
||||
be optional: if the application does not supply it, the square
|
||||
root function must still compute square roots.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Library,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Library, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote
|
||||
it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Library.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Library
|
||||
with the Library (or with a work based on the Library) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may opt to apply the terms of the ordinary GNU General Public
|
||||
License instead of this License to a given copy of the Library. To do
|
||||
this, you must alter all the notices that refer to this License, so
|
||||
that they refer to the ordinary GNU General Public License, version 2,
|
||||
instead of to this License. (If a newer version than version 2 of the
|
||||
ordinary GNU General Public License has appeared, then you can specify
|
||||
that version instead if you wish.) Do not make any other change in
|
||||
these notices.
|
||||
|
||||
Once this change is made in a given copy, it is irreversible for
|
||||
that copy, so the ordinary GNU General Public License applies to all
|
||||
subsequent copies and derivative works made from that copy.
|
||||
|
||||
This option is useful when you wish to copy part of the code of
|
||||
the Library into a program that is not a library.
|
||||
|
||||
4. You may copy and distribute the Library (or a portion or
|
||||
derivative of it, under Section 2) in object code or executable form
|
||||
under the terms of Sections 1 and 2 above provided that you accompany
|
||||
it with the complete corresponding machine-readable source code, which
|
||||
must be distributed under the terms of Sections 1 and 2 above on a
|
||||
medium customarily used for software interchange.
|
||||
|
||||
If distribution of object code is made by offering access to copy
|
||||
from a designated place, then offering equivalent access to copy the
|
||||
source code from the same place satisfies the requirement to
|
||||
distribute the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
5. A program that contains no derivative of any portion of the
|
||||
Library, but is designed to work with the Library by being compiled or
|
||||
linked with it, is called a "work that uses the Library". Such a
|
||||
work, in isolation, is not a derivative work of the Library, and
|
||||
therefore falls outside the scope of this License.
|
||||
|
||||
However, linking a "work that uses the Library" with the Library
|
||||
creates an executable that is a derivative of the Library (because it
|
||||
contains portions of the Library), rather than a "work that uses the
|
||||
library". The executable is therefore covered by this License.
|
||||
Section 6 states terms for distribution of such executables.
|
||||
|
||||
When a "work that uses the Library" uses material from a header file
|
||||
that is part of the Library, the object code for the work may be a
|
||||
derivative work of the Library even though the source code is not.
|
||||
Whether this is true is especially significant if the work can be
|
||||
linked without the Library, or if the work is itself a library. The
|
||||
threshold for this to be true is not precisely defined by law.
|
||||
|
||||
If such an object file uses only numerical parameters, data
|
||||
structure layouts and accessors, and small macros and small inline
|
||||
functions (ten lines or less in length), then the use of the object
|
||||
file is unrestricted, regardless of whether it is legally a derivative
|
||||
work. (Executables containing this object code plus portions of the
|
||||
Library will still fall under Section 6.)
|
||||
|
||||
Otherwise, if the work is a derivative of the Library, you may
|
||||
distribute the object code for the work under the terms of Section 6.
|
||||
Any executables containing that work also fall under Section 6,
|
||||
whether or not they are linked directly with the Library itself.
|
||||
|
||||
6. As an exception to the Sections above, you may also combine or
|
||||
link a "work that uses the Library" with the Library to produce a
|
||||
work containing portions of the Library, and distribute that work
|
||||
under terms of your choice, provided that the terms permit
|
||||
modification of the work for the customer's own use and reverse
|
||||
engineering for debugging such modifications.
|
||||
|
||||
You must give prominent notice with each copy of the work that the
|
||||
Library is used in it and that the Library and its use are covered by
|
||||
this License. You must supply a copy of this License. If the work
|
||||
during execution displays copyright notices, you must include the
|
||||
copyright notice for the Library among them, as well as a reference
|
||||
directing the user to the copy of this License. Also, you must do one
|
||||
of these things:
|
||||
|
||||
a) Accompany the work with the complete corresponding
|
||||
machine-readable source code for the Library including whatever
|
||||
changes were used in the work (which must be distributed under
|
||||
Sections 1 and 2 above); and, if the work is an executable linked
|
||||
with the Library, with the complete machine-readable "work that
|
||||
uses the Library", as object code and/or source code, so that the
|
||||
user can modify the Library and then relink to produce a modified
|
||||
executable containing the modified Library. (It is understood
|
||||
that the user who changes the contents of definitions files in the
|
||||
Library will not necessarily be able to recompile the application
|
||||
to use the modified definitions.)
|
||||
|
||||
b) Use a suitable shared library mechanism for linking with the
|
||||
Library. A suitable mechanism is one that (1) uses at run time a
|
||||
copy of the library already present on the user's computer system,
|
||||
rather than copying library functions into the executable, and (2)
|
||||
will operate properly with a modified version of the library, if
|
||||
the user installs one, as long as the modified version is
|
||||
interface-compatible with the version that the work was made with.
|
||||
|
||||
c) Accompany the work with a written offer, valid for at
|
||||
least three years, to give the same user the materials
|
||||
specified in Subsection 6a, above, for a charge no more
|
||||
than the cost of performing this distribution.
|
||||
|
||||
d) If distribution of the work is made by offering access to copy
|
||||
from a designated place, offer equivalent access to copy the above
|
||||
specified materials from the same place.
|
||||
|
||||
e) Verify that the user has already received a copy of these
|
||||
materials or that you have already sent this user a copy.
|
||||
|
||||
For an executable, the required form of the "work that uses the
|
||||
Library" must include any data and utility programs needed for
|
||||
reproducing the executable from it. However, as a special exception,
|
||||
the materials to be distributed need not include anything that is
|
||||
normally distributed (in either source or binary form) with the major
|
||||
components (compiler, kernel, and so on) of the operating system on
|
||||
which the executable runs, unless that component itself accompanies
|
||||
the executable.
|
||||
|
||||
It may happen that this requirement contradicts the license
|
||||
restrictions of other proprietary libraries that do not normally
|
||||
accompany the operating system. Such a contradiction means you cannot
|
||||
use both them and the Library together in an executable that you
|
||||
distribute.
|
||||
|
||||
7. You may place library facilities that are a work based on the
|
||||
Library side-by-side in a single library together with other library
|
||||
facilities not covered by this License, and distribute such a combined
|
||||
library, provided that the separate distribution of the work based on
|
||||
the Library and of the other library facilities is otherwise
|
||||
permitted, and provided that you do these two things:
|
||||
|
||||
a) Accompany the combined library with a copy of the same work
|
||||
based on the Library, uncombined with any other library
|
||||
facilities. This must be distributed under the terms of the
|
||||
Sections above.
|
||||
|
||||
b) Give prominent notice with the combined library of the fact
|
||||
that part of it is a work based on the Library, and explaining
|
||||
where to find the accompanying uncombined form of the same work.
|
||||
|
||||
8. You may not copy, modify, sublicense, link with, or distribute
|
||||
the Library except as expressly provided under this License. Any
|
||||
attempt otherwise to copy, modify, sublicense, link with, or
|
||||
distribute the Library is void, and will automatically terminate your
|
||||
rights under this License. However, parties who have received copies,
|
||||
or rights, from you under this License will not have their licenses
|
||||
terminated so long as such parties remain in full compliance.
|
||||
|
||||
9. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Library or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Library (or any work based on the
|
||||
Library), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Library or works based on it.
|
||||
|
||||
10. Each time you redistribute the Library (or any work based on the
|
||||
Library), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute, link with or modify the Library
|
||||
subject to these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties with
|
||||
this License.
|
||||
|
||||
11. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Library at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Library by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Library.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under any
|
||||
particular circumstance, the balance of the section is intended to apply,
|
||||
and the section as a whole is intended to apply in other circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
12. If the distribution and/or use of the Library is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Library under this License may add
|
||||
an explicit geographical distribution limitation excluding those countries,
|
||||
so that distribution is permitted only in or among countries not thus
|
||||
excluded. In such case, this License incorporates the limitation as if
|
||||
written in the body of this License.
|
||||
|
||||
13. The Free Software Foundation may publish revised and/or new
|
||||
versions of the Lesser General Public License from time to time.
|
||||
Such new versions will be similar in spirit to the present version,
|
||||
but may differ in detail to address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Library
|
||||
specifies a version number of this License which applies to it and
|
||||
"any later version", you have the option of following the terms and
|
||||
conditions either of that version or of any later version published by
|
||||
the Free Software Foundation. If the Library does not specify a
|
||||
license version number, you may choose any version ever published by
|
||||
the Free Software Foundation.
|
||||
|
||||
14. If you wish to incorporate parts of the Library into other free
|
||||
programs whose distribution conditions are incompatible with these,
|
||||
write to the author to ask for permission. For software which is
|
||||
copyrighted by the Free Software Foundation, write to the Free
|
||||
Software Foundation; we sometimes make exceptions for this. Our
|
||||
decision will be guided by the two goals of preserving the free status
|
||||
of all derivatives of our free software and of promoting the sharing
|
||||
and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
|
||||
WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
|
||||
EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
|
||||
OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
|
||||
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
|
||||
LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
|
||||
THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
|
||||
WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
|
||||
AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
|
||||
FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
|
||||
CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
|
||||
LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
|
||||
RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
|
||||
FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
|
||||
SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
|
||||
DAMAGES.
|
||||
|
||||
+185
@@ -0,0 +1,185 @@
|
||||
//
|
||||
// Created by BZLZHH on 2025/3/15.
|
||||
//
|
||||
|
||||
#ifndef MOBILEGL_CONSTANTS_H
|
||||
#define MOBILEGL_CONSTANTS_H
|
||||
|
||||
#include <array>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <GL/gl.h>
|
||||
#include <ankerl/unordered_dense.h>
|
||||
|
||||
#ifdef __ANDROID__
|
||||
#define __ANDROID_API__ 26
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
#define MG_EXPORT extern "C" __declspec(dllexport)
|
||||
#else
|
||||
#define MG_EXPORT extern "C" __attribute__((visibility("default")))
|
||||
#endif
|
||||
|
||||
namespace ankerl {
|
||||
template <typename K, typename V>
|
||||
using unordered_map = ankerl::unordered_dense::map<K, V>;
|
||||
|
||||
template <typename T>
|
||||
using unordered_set = ankerl::unordered_dense::set<T>;
|
||||
}
|
||||
|
||||
namespace MG_Constants {
|
||||
namespace Common {
|
||||
inline const int LOG_LEVEL_DEBUG = 0x0010;
|
||||
inline const int LOG_LEVEL_WARN = 0x0011;
|
||||
inline const int LOG_LEVEL_ERROR = 0x0012;
|
||||
inline const int LOG_LEVEL_INFO = 0x0013;
|
||||
inline const int LOG_LEVEL_FATAL = 0x0014;
|
||||
}
|
||||
|
||||
namespace Blend {
|
||||
static const ankerl::unordered_set<GLenum> VALID_FACTORS = {
|
||||
GL_ZERO, GL_ONE, GL_SRC_COLOR, GL_ONE_MINUS_SRC_COLOR,
|
||||
GL_DST_COLOR, GL_ONE_MINUS_DST_COLOR, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA,
|
||||
GL_DST_ALPHA, GL_ONE_MINUS_DST_ALPHA, GL_CONSTANT_COLOR, GL_ONE_MINUS_CONSTANT_COLOR,
|
||||
GL_CONSTANT_ALPHA, GL_ONE_MINUS_CONSTANT_ALPHA, GL_SRC_ALPHA_SATURATE
|
||||
}; // OpenGL 3
|
||||
}
|
||||
|
||||
namespace Depth {
|
||||
static const ankerl::unordered_set<GLenum> VALID_FUNCS = {
|
||||
GL_NEVER, GL_LESS, GL_EQUAL, GL_LEQUAL, GL_GREATER, GL_NOTEQUAL, GL_GEQUAL, GL_ALWAYS
|
||||
}; // OpenGL 3
|
||||
}
|
||||
|
||||
namespace CommonState {
|
||||
static const ankerl::unordered_set<GLenum> VALID_CAPS = {
|
||||
GL_BLEND, GL_DEPTH_TEST, GL_STENCIL_TEST, GL_SCISSOR_TEST, GL_CULL_FACE,
|
||||
GL_POLYGON_OFFSET_FILL, GL_SAMPLE_ALPHA_TO_COVERAGE, GL_SAMPLE_COVERAGE
|
||||
}; // OpenGL 3
|
||||
}
|
||||
|
||||
namespace Framebuffer {
|
||||
static const ankerl::unordered_set<GLenum> VALID_ATTACHMENTS = {
|
||||
GL_COLOR_ATTACHMENT0, GL_DEPTH_ATTACHMENT,
|
||||
GL_STENCIL_ATTACHMENT, GL_DEPTH_STENCIL_ATTACHMENT
|
||||
}; // OpenGL 3
|
||||
|
||||
static const ankerl::unordered_set<GLenum> VALID_TARGETS = {
|
||||
GL_FRAMEBUFFER, GL_DRAW_FRAMEBUFFER, GL_READ_FRAMEBUFFER
|
||||
}; // OpenGL 3
|
||||
|
||||
static const ankerl::unordered_set<GLenum> VALID_ACCESS = {
|
||||
GL_READ_ONLY, GL_WRITE_ONLY, GL_READ_WRITE
|
||||
}; // OpenGL 3
|
||||
}
|
||||
|
||||
namespace Buffer {
|
||||
static const ankerl::unordered_set<GLenum> VALID_PARAM_NAMES = {
|
||||
GL_BUFFER_ACCESS, GL_BUFFER_MAPPED, GL_BUFFER_SIZE, GL_BUFFER_USAGE
|
||||
}; // OpenGL 3
|
||||
|
||||
static const ankerl::unordered_set<GLenum> VALID_TARGETS = {
|
||||
GL_ARRAY_BUFFER, GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER, GL_ELEMENT_ARRAY_BUFFER,
|
||||
GL_PIXEL_PACK_BUFFER, GL_PIXEL_UNPACK_BUFFER, GL_TEXTURE_BUFFER,
|
||||
GL_TRANSFORM_FEEDBACK_BUFFER, GL_UNIFORM_BUFFER
|
||||
}; // OpenGL 3
|
||||
|
||||
static const ankerl::unordered_set<GLenum> VALID_ACCESS = {
|
||||
GL_READ_ONLY, GL_WRITE_ONLY, GL_READ_WRITE
|
||||
}; // OpenGL 3
|
||||
}
|
||||
|
||||
namespace PixelStore {
|
||||
static const ankerl::unordered_map<GLenum, GLint> DEFAULT_VALUES_MAP = {
|
||||
{GL_PACK_SWAP_BYTES, GL_FALSE},
|
||||
{GL_PACK_LSB_FIRST, GL_FALSE},
|
||||
{GL_PACK_ROW_LENGTH, 0},
|
||||
{GL_PACK_IMAGE_HEIGHT, 0},
|
||||
{GL_PACK_SKIP_ROWS, 0},
|
||||
{GL_PACK_SKIP_PIXELS, 0},
|
||||
{GL_PACK_SKIP_IMAGES, 0},
|
||||
{GL_PACK_ALIGNMENT, 4},
|
||||
{GL_UNPACK_SWAP_BYTES, GL_FALSE},
|
||||
{GL_UNPACK_LSB_FIRST, GL_FALSE},
|
||||
{GL_UNPACK_ROW_LENGTH, 0},
|
||||
{GL_UNPACK_IMAGE_HEIGHT, 0},
|
||||
{GL_UNPACK_SKIP_ROWS, 0},
|
||||
{GL_UNPACK_SKIP_PIXELS,0},
|
||||
{GL_UNPACK_SKIP_IMAGES,0},
|
||||
{GL_UNPACK_ALIGNMENT, 4}
|
||||
}; // OpenGL 3
|
||||
|
||||
static const ankerl::unordered_set<GLenum> VALID_PARAM_NAMES = {
|
||||
GL_PACK_SWAP_BYTES, GL_PACK_LSB_FIRST, GL_PACK_ROW_LENGTH, GL_PACK_IMAGE_HEIGHT,
|
||||
GL_PACK_SKIP_ROWS, GL_PACK_SKIP_PIXELS, GL_PACK_SKIP_IMAGES, GL_PACK_ALIGNMENT,
|
||||
GL_UNPACK_SWAP_BYTES, GL_UNPACK_LSB_FIRST, GL_UNPACK_ROW_LENGTH,
|
||||
GL_UNPACK_IMAGE_HEIGHT,
|
||||
GL_UNPACK_SKIP_ROWS, GL_UNPACK_SKIP_PIXELS, GL_UNPACK_SKIP_IMAGES,
|
||||
GL_UNPACK_ALIGNMENT
|
||||
}; // OpenGL 3
|
||||
}
|
||||
|
||||
namespace Texture {
|
||||
inline const GLuint MAX_TEXTURE_UNITS = 80;
|
||||
const GLsizei MAX_TEXTURE_SIZE = 32768;
|
||||
const GLsizei MAX_ARRAY_LAYERS = 256;
|
||||
inline const ankerl::unordered_set<GLenum> VALID_TARGETS = {
|
||||
GL_TEXTURE_2D, GL_PROXY_TEXTURE_2D, GL_TEXTURE_1D_ARRAY, GL_PROXY_TEXTURE_1D_ARRAY,
|
||||
GL_TEXTURE_RECTANGLE, GL_PROXY_TEXTURE_RECTANGLE, GL_TEXTURE_CUBE_MAP_POSITIVE_X,
|
||||
GL_TEXTURE_CUBE_MAP_NEGATIVE_X, GL_TEXTURE_CUBE_MAP_POSITIVE_Y, GL_TEXTURE_CUBE_MAP_NEGATIVE_Y,
|
||||
GL_TEXTURE_CUBE_MAP_POSITIVE_Z, GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, GL_PROXY_TEXTURE_CUBE_MAP
|
||||
}; // OpenGL 3
|
||||
|
||||
inline const ankerl::unordered_set<GLenum> VALID_FORMATS = {
|
||||
GL_RED, GL_RG, GL_RGB, GL_BGR, GL_RGBA, GL_BGRA, GL_DEPTH_COMPONENT, GL_DEPTH_STENCIL
|
||||
}; // OpenGL 3
|
||||
|
||||
inline const ankerl::unordered_set<GLenum> VALID_INTERNAL_FORMATS = {
|
||||
GL_RED, GL_RG, GL_RGB, GL_BGR, GL_RGBA, GL_BGRA, GL_DEPTH_COMPONENT, GL_DEPTH_STENCIL,
|
||||
GL_RGBA32F, GL_RGBA32I, GL_RGBA32UI, GL_RGBA16, GL_RGBA16F, GL_RGBA16I,
|
||||
GL_RGBA16UI, GL_RGBA8, GL_RGBA8UI, GL_SRGB8_ALPHA8, GL_RGB10_A2, GL_RGB10_A2UI,
|
||||
GL_R11F_G11F_B10F, GL_RG32F, GL_RG32I, GL_RG32UI, GL_RG16, GL_RG16F, GL_RGB16I,
|
||||
GL_RGB16UI, GL_RG8, GL_RG8I, GL_RG8UI, GL_R32F, GL_R32I, GL_R32UI, GL_R16F,
|
||||
GL_R16I, GL_R16UI, GL_R8, GL_R8I, GL_R8UI, GL_RGBA16_SNORM, GL_RGBA8_SNORM,
|
||||
GL_RGB32F, GL_RGB32I, GL_RGB32UI, GL_RGB16_SNORM, GL_RGB16F, GL_RGB16I, GL_RGB16UI,
|
||||
GL_RGB16, GL_RGB8_SNORM, GL_RGB8, GL_RGB8I, GL_RGB8UI, GL_SRGB8, GL_RGB9_E5,
|
||||
GL_RG16_SNORM, GL_RG8_SNORM, GL_COMPRESSED_RG_RGTC2, GL_COMPRESSED_SIGNED_RG_RGTC2,
|
||||
GL_R16_SNORM, GL_R8_SNORM, GL_COMPRESSED_RED_RGTC1, GL_COMPRESSED_SIGNED_RED_RGTC1,
|
||||
GL_DEPTH_COMPONENT32F, GL_DEPTH_COMPONENT32, GL_DEPTH_COMPONENT24, GL_DEPTH_COMPONENT16,
|
||||
GL_DEPTH32F_STENCIL8, GL_DEPTH24_STENCIL8
|
||||
}; // OpenGL 3
|
||||
|
||||
inline const ankerl::unordered_set<GLenum> VALID_TYPES = {
|
||||
GL_UNSIGNED_BYTE, GL_BYTE, GL_UNSIGNED_SHORT, GL_SHORT, GL_UNSIGNED_INT, GL_INT,
|
||||
GL_FLOAT, GL_UNSIGNED_BYTE_3_3_2, GL_UNSIGNED_BYTE_2_3_3_REV, GL_UNSIGNED_SHORT_5_6_5,
|
||||
GL_UNSIGNED_SHORT_5_6_5_REV, GL_UNSIGNED_SHORT_4_4_4_4, GL_UNSIGNED_SHORT_4_4_4_4_REV,
|
||||
GL_UNSIGNED_SHORT_5_5_5_1, GL_UNSIGNED_SHORT_1_5_5_5_REV, GL_UNSIGNED_INT_8_8_8_8,
|
||||
GL_UNSIGNED_INT_8_8_8_8_REV, GL_UNSIGNED_INT_10_10_10_2, GL_UNSIGNED_INT_2_10_10_10_REV
|
||||
}; // OpenGL 3
|
||||
|
||||
inline const ankerl::unordered_set<GLenum> VALID_TEXTURE_PARAM_NAMES = {
|
||||
GL_TEXTURE_BASE_LEVEL, GL_TEXTURE_COMPARE_FUNC, GL_TEXTURE_COMPARE_MODE, GL_TEXTURE_MIN_FILTER,
|
||||
GL_TEXTURE_MAG_FILTER, GL_TEXTURE_MAX_LEVEL, GL_TEXTURE_SWIZZLE_R, GL_TEXTURE_SWIZZLE_G,
|
||||
GL_TEXTURE_SWIZZLE_B, GL_TEXTURE_SWIZZLE_A, GL_TEXTURE_SWIZZLE_RGBA, GL_TEXTURE_WRAP_S,
|
||||
GL_TEXTURE_WRAP_T, GL_TEXTURE_WRAP_R, GL_TEXTURE_LOD_BIAS, GL_TEXTURE_MIN_LOD, GL_TEXTURE_MAX_LOD, GL_TEXTURE_BORDER_COLOR
|
||||
}; // OpenGL 3
|
||||
|
||||
inline const ankerl::unordered_set<GLenum> VALID_QUERY_LEVEL_PROPERTY_PARAM_NAMES = {
|
||||
GL_TEXTURE_WIDTH, GL_TEXTURE_HEIGHT, GL_TEXTURE_DEPTH, GL_TEXTURE_INTERNAL_FORMAT,
|
||||
GL_TEXTURE_RED_SIZE, GL_TEXTURE_GREEN_SIZE, GL_TEXTURE_BLUE_SIZE, GL_TEXTURE_ALPHA_SIZE,
|
||||
GL_TEXTURE_DEPTH_SIZE, GL_TEXTURE_COMPRESSED, GL_TEXTURE_COMPRESSED_IMAGE_SIZE
|
||||
}; // OpenGL 3
|
||||
}
|
||||
|
||||
namespace Backend {
|
||||
#define BACKEND_VULKAN 0x0015
|
||||
#define BACKEND_METAL 0x0016
|
||||
#define BACKEND_GLES 0x0017
|
||||
}
|
||||
|
||||
inline const char* RenderName = "MobileGL";
|
||||
}
|
||||
|
||||
#endif //MOBILEGL_CONSTANTS_H
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
//
|
||||
// Created by BZLZHH on 2025/3/15.
|
||||
//
|
||||
|
||||
#ifndef MOBILEGL_GLOBAL_H
|
||||
#define MOBILEGL_GLOBAL_H
|
||||
|
||||
#include "Includes.h"
|
||||
|
||||
namespace MG_Global {
|
||||
namespace MG {
|
||||
inline const int VersionMajor = 1;
|
||||
inline const int VersionMinor = 0;
|
||||
inline const int VersionRevision = 0;
|
||||
inline const int VersionPatch = 0;
|
||||
inline const std::string VersionSuffix = "-ShittilyDraw";
|
||||
}
|
||||
|
||||
namespace Backend {
|
||||
// BACKEND_TYPE is defined in Includes.h
|
||||
}
|
||||
|
||||
namespace GL {
|
||||
inline const int GLVersionMajor = 3;
|
||||
inline const int GLVersionMinor = 2;
|
||||
inline const int GLVersionRevision = 0;
|
||||
}
|
||||
|
||||
namespace Common {
|
||||
inline constexpr int LogLevel = MG_Constants::Common::LOG_LEVEL_INFO;
|
||||
|
||||
#ifdef __ANDROID__
|
||||
inline const char* LOG_FILE_PATH = "/sdcard/MG/latest.log";
|
||||
#else
|
||||
inline const char* LOG_FILE_PATH = nullptr;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
#endif //MOBILEGL_GLOBAL_H
|
||||
+136
@@ -0,0 +1,136 @@
|
||||
//
|
||||
// Created by BZLZHH on 2025/3/15.
|
||||
//
|
||||
|
||||
#include "Constants.h"
|
||||
#define BACKEND_TYPE BACKEND_GLES
|
||||
#include "Global.h"
|
||||
#ifndef MOBILEGL_GLES_LOADER_H
|
||||
#include "MG_GL/BackendLoader/GLES/Loader.h"
|
||||
#endif
|
||||
#ifndef MOBILEGL_GL_FRAMEBUFFER_H
|
||||
#include "MG_GL/Implementations/GL/Framebuffer/GL_Framebuffer.h"
|
||||
#endif
|
||||
#ifndef MOBILEGL_GL_GETTER_H
|
||||
#include "MG_GL/Implementations/GL/Getter/GL_Getter.h"
|
||||
#endif
|
||||
#ifndef MOBILEGL_BACKENDINFO_H
|
||||
#include "MG_GL/Implementations/GL/Getter/BackendInfo.h"
|
||||
#endif
|
||||
#ifndef MOBILEGL_GL_BUFFER_H
|
||||
#include "MG_GL/Implementations/GL/Buffer/GL_Buffer.h"
|
||||
#endif
|
||||
#ifndef MOBILEGL_GL_DRAWING_H
|
||||
#include "MG_GL/Implementations/GL/Drawing/GL_Drawing.h"
|
||||
#endif
|
||||
#ifndef MOBILEGL_GL_PROGRAM_H
|
||||
#include "MG_GL/Implementations/GL/Program/GL_Program.h"
|
||||
#endif
|
||||
#ifndef MOBILEGL_GL_TEXTURE_H
|
||||
#include "MG_GL/Implementations/GL/Texture/GL_Texture.h"
|
||||
#endif
|
||||
#ifndef MOBILEGL_GL_VERTEXARRAY_H
|
||||
#include "MG_GL/Implementations/GL/VertexArray/GL_VertexArray.h"
|
||||
#endif
|
||||
#ifndef MOBILEGL_GLSTATE_H
|
||||
#include "MG_GL/State/Core/GLState.h"
|
||||
#endif
|
||||
#ifndef MOBILEGL_COMMONSTATE_H
|
||||
#include "MG_GL/State/Common/CommonState.h"
|
||||
#endif
|
||||
#ifndef MOBILEGL_TEXTURESTATE_H
|
||||
#include "MG_GL/State/Texture/TextureState.h"
|
||||
#endif
|
||||
#ifndef MOBILEGL_VERTEXARRAYSTATE_H
|
||||
#include "MG_GL/State/VertexArray/VertexArrayState.h"
|
||||
#endif
|
||||
#ifndef MOBILEGL_BUFFERSTATE_H
|
||||
#include "MG_GL/State/Buffer/BufferState.h"
|
||||
#endif
|
||||
#ifndef MOBILEGL_SHADEROBJECT_H
|
||||
#include "MG_GL/State/Program/ShaderObject.h"
|
||||
#endif
|
||||
#ifndef MOBILEGL_PROGRAMSTATE_H
|
||||
#include "MG_GL/State/Program/ProgramState.h"
|
||||
#endif
|
||||
#ifndef MOBILEGL_FRAMEBUFFERSTATE_H
|
||||
#include "MG_GL/State/Framebuffer/FramebufferState.h"
|
||||
#endif
|
||||
#ifndef MOBILEGL_LOOKUP_H
|
||||
#include "MG_GL/Implementations/GLX/LookUp/LookUp.h"
|
||||
#endif
|
||||
#ifndef MOBILEGL_DEBUG_H
|
||||
#include "MG_UTIL/Debug/Debug.h"
|
||||
#endif
|
||||
#ifndef MOBILEGL_GLSLTOOL_H
|
||||
#include "MG_UTIL/Program/GLSLTool.h"
|
||||
#endif
|
||||
#ifndef MOBILEGL_PROGRAM_DEBUGTOOL_H
|
||||
#include "MG_UTIL/Program/DebugTool.h"
|
||||
#endif
|
||||
#ifndef MOBILEGL_GL_COMMON_H
|
||||
#include "MG_GL/Implementations/GL/Common/GL_Common.h"
|
||||
#endif
|
||||
#ifndef MOBILEGL_EGL_VK_EMU_H
|
||||
#include "MG_GL/Implementations/EGL/VK/EGL_EMU.h"
|
||||
#endif
|
||||
#ifndef MOBILEGL_EGL_GLES_EGL_WRAP_H
|
||||
#include "MG_GL/Implementations/EGL/GLES/EGL_WRAP.h"
|
||||
#endif
|
||||
#ifndef MOBILEGL_TESTDRAWING_H
|
||||
#include "MG_RHI/GLES/Test/TestDrawing.h"
|
||||
#endif
|
||||
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <cstdio>
|
||||
#include <ctime>
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
#include <stdexcept>
|
||||
#include <atomic>
|
||||
#include <regex>
|
||||
#include <strstream>
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <random>
|
||||
#include <optional>
|
||||
#include <unordered_map>
|
||||
#include <queue>
|
||||
#include <format>
|
||||
#include <glslang/Public/ShaderLang.h>
|
||||
#include <glslang/Include/Types.h>
|
||||
#include <glslang/Public/ShaderLang.h>
|
||||
#include <spirv_cross/spirv_cross_c.h>
|
||||
#include <glslang/SPIRV/GlslangToSpv.h>
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
#include <glm/glm.hpp>
|
||||
#include <ankerl/unordered_dense.h>
|
||||
#include <GLES3/gl32.h>
|
||||
|
||||
#include "MG_Include/UncertainBool.hpp"
|
||||
|
||||
#ifndef _WIN32
|
||||
#include <dlfcn.h>
|
||||
#include <unistd.h>
|
||||
#include <vulkan/vulkan.h>
|
||||
#endif
|
||||
|
||||
#ifdef __ANDROID__
|
||||
#include <android/log.h>
|
||||
#include <pthread.h>
|
||||
#include <vulkan/vulkan_android.h>
|
||||
#include <android/native_window.h>
|
||||
#elif _WIN32
|
||||
#include <windows.h>
|
||||
#include <processthreadsapi.h>
|
||||
#else
|
||||
#include <pthread.h>
|
||||
#endif
|
||||
|
||||
#include <EGL/egl.h>
|
||||
#include <GL/gl.h>
|
||||
#include <GL/glext.h>
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
//
|
||||
// Created by BZLZHH on 2025/3/15.
|
||||
//
|
||||
|
||||
#include "Includes.h"
|
||||
|
||||
__attribute__((constructor)) void MG_Initialize() {
|
||||
MG_Util::Debug::LogInit();
|
||||
MG_Util::Debug::LogI("MobileGL Initializing...");
|
||||
MG_State::Init();
|
||||
MG_GL::BackendLoader::Init();
|
||||
}
|
||||
|
||||
__attribute__((destructor)) void MG_Destroy() {
|
||||
MG_Util::Debug::LogI("MobileGL Exiting...");
|
||||
MG_State::Destroy();
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,179 @@
|
||||
//
|
||||
// Created by BZLZHH on 2025/4/26.
|
||||
//
|
||||
|
||||
#include "EGL_WRAP.h"
|
||||
|
||||
#if BACKEND_TYPE == BACKEND_GLES
|
||||
|
||||
MG_EXPORT EGLint eglGetError(void) {
|
||||
LOAD_EGL(eglGetError)
|
||||
return egl_eglGetError();
|
||||
}
|
||||
|
||||
MG_EXPORT EGLDisplay eglGetDisplay(EGLNativeDisplayType display_id) {
|
||||
LOAD_EGL(eglGetDisplay)
|
||||
return egl_eglGetDisplay(display_id);
|
||||
}
|
||||
|
||||
MG_EXPORT EGLBoolean eglInitialize(EGLDisplay dpy, EGLint *major, EGLint *minor) {
|
||||
LOAD_EGL(eglInitialize)
|
||||
return egl_eglInitialize(dpy, major, minor);
|
||||
}
|
||||
|
||||
MG_EXPORT EGLBoolean eglTerminate(EGLDisplay dpy) {
|
||||
LOAD_EGL(eglTerminate)
|
||||
return egl_eglTerminate(dpy);
|
||||
}
|
||||
|
||||
MG_EXPORT const char *eglQueryString(EGLDisplay dpy, EGLint name) {
|
||||
LOAD_EGL(eglQueryString)
|
||||
return egl_eglQueryString(dpy, name);
|
||||
}
|
||||
|
||||
MG_EXPORT EGLBoolean eglGetConfigs(EGLDisplay dpy, EGLConfig *configs, EGLint config_size, EGLint *num_config) {
|
||||
LOAD_EGL(eglGetConfigs)
|
||||
return egl_eglGetConfigs(dpy, configs, config_size, num_config);
|
||||
}
|
||||
|
||||
MG_EXPORT EGLBoolean eglChooseConfig(EGLDisplay dpy, const EGLint *attrib_list, EGLConfig *configs, EGLint config_size, EGLint *num_config) {
|
||||
LOAD_EGL(eglChooseConfig)
|
||||
return egl_eglChooseConfig(dpy, attrib_list, configs, config_size, num_config);
|
||||
}
|
||||
|
||||
MG_EXPORT EGLBoolean eglGetConfigAttrib(EGLDisplay dpy, EGLConfig config, EGLint attribute, EGLint *value) {
|
||||
LOAD_EGL(eglGetConfigAttrib)
|
||||
return egl_eglGetConfigAttrib(dpy, config, attribute, value);
|
||||
}
|
||||
|
||||
MG_EXPORT EGLSurface eglCreateWindowSurface(EGLDisplay dpy, EGLConfig config, EGLNativeWindowType win, const EGLint *attrib_list) {
|
||||
LOAD_EGL(eglCreateWindowSurface)
|
||||
return egl_eglCreateWindowSurface(dpy, config, win, attrib_list);
|
||||
}
|
||||
|
||||
MG_EXPORT EGLSurface eglCreatePbufferSurface(EGLDisplay dpy, EGLConfig config, const EGLint *attrib_list) {
|
||||
LOAD_EGL(eglCreatePbufferSurface)
|
||||
return egl_eglCreatePbufferSurface(dpy, config, attrib_list);
|
||||
}
|
||||
|
||||
MG_EXPORT EGLSurface eglCreatePixmapSurface(EGLDisplay dpy, EGLConfig config, EGLNativePixmapType pixmap, const EGLint *attrib_list) {
|
||||
LOAD_EGL(eglCreatePixmapSurface)
|
||||
return egl_eglCreatePixmapSurface(dpy, config, pixmap, attrib_list);
|
||||
}
|
||||
|
||||
MG_EXPORT EGLBoolean eglDestroySurface(EGLDisplay dpy, EGLSurface surface) {
|
||||
LOAD_EGL(eglDestroySurface)
|
||||
return egl_eglDestroySurface(dpy, surface);
|
||||
}
|
||||
|
||||
MG_EXPORT EGLBoolean eglQuerySurface(EGLDisplay dpy, EGLSurface surface, EGLint attribute, EGLint *value) {
|
||||
LOAD_EGL(eglQuerySurface)
|
||||
return egl_eglQuerySurface(dpy, surface, attribute, value);
|
||||
}
|
||||
|
||||
MG_EXPORT EGLBoolean eglBindAPI(EGLenum api) {
|
||||
LOAD_EGL(eglBindAPI)
|
||||
return egl_eglBindAPI(api);
|
||||
}
|
||||
|
||||
MG_EXPORT EGLenum eglQueryAPI(void) {
|
||||
LOAD_EGL(eglQueryAPI)
|
||||
return egl_eglQueryAPI();
|
||||
}
|
||||
|
||||
MG_EXPORT EGLBoolean eglWaitClient(void) {
|
||||
LOAD_EGL(eglWaitClient)
|
||||
return egl_eglWaitClient();
|
||||
}
|
||||
|
||||
MG_EXPORT EGLBoolean eglReleaseThread(void) {
|
||||
LOAD_EGL(eglReleaseThread)
|
||||
return egl_eglReleaseThread();
|
||||
}
|
||||
|
||||
MG_EXPORT EGLSurface eglCreatePbufferFromClientBuffer(EGLDisplay dpy, EGLenum buftype, EGLClientBuffer buffer, EGLConfig config, const EGLint *attrib_list) {
|
||||
LOAD_EGL(eglCreatePbufferFromClientBuffer)
|
||||
return egl_eglCreatePbufferFromClientBuffer(dpy, buftype, buffer, config, attrib_list);
|
||||
}
|
||||
|
||||
MG_EXPORT EGLBoolean eglSurfaceAttrib(EGLDisplay dpy, EGLSurface surface, EGLint attribute, EGLint value) {
|
||||
LOAD_EGL(eglSurfaceAttrib)
|
||||
return egl_eglSurfaceAttrib(dpy, surface, attribute, value);
|
||||
}
|
||||
|
||||
MG_EXPORT EGLBoolean eglBindTexImage(EGLDisplay dpy, EGLSurface surface, EGLint buffer) {
|
||||
LOAD_EGL(eglBindTexImage)
|
||||
return egl_eglBindTexImage(dpy, surface, buffer);
|
||||
}
|
||||
|
||||
MG_EXPORT EGLBoolean eglReleaseTexImage(EGLDisplay dpy, EGLSurface surface, EGLint buffer) {
|
||||
LOAD_EGL(eglReleaseTexImage)
|
||||
return egl_eglReleaseTexImage(dpy, surface, buffer);
|
||||
}
|
||||
|
||||
MG_EXPORT EGLBoolean eglSwapInterval(EGLDisplay dpy, EGLint interval) {
|
||||
LOAD_EGL(eglSwapInterval)
|
||||
return egl_eglSwapInterval(dpy, interval);
|
||||
}
|
||||
|
||||
MG_EXPORT EGLContext eglCreateContext(EGLDisplay dpy, EGLConfig config, EGLContext share_context, const EGLint *attrib_list) {
|
||||
LOAD_EGL(eglCreateContext)
|
||||
return egl_eglCreateContext(dpy, config, share_context, attrib_list);
|
||||
}
|
||||
|
||||
MG_EXPORT EGLBoolean eglDestroyContext(EGLDisplay dpy, EGLContext ctx) {
|
||||
LOAD_EGL(eglDestroyContext)
|
||||
return egl_eglDestroyContext(dpy, ctx);
|
||||
}
|
||||
|
||||
MG_EXPORT EGLBoolean eglMakeCurrent(EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx) {
|
||||
LOAD_EGL(eglMakeCurrent)
|
||||
return egl_eglMakeCurrent(dpy, draw, read, ctx);
|
||||
}
|
||||
|
||||
MG_EXPORT EGLContext eglGetCurrentContext(void) {
|
||||
LOAD_EGL(eglGetCurrentContext)
|
||||
return egl_eglGetCurrentContext();
|
||||
}
|
||||
|
||||
MG_EXPORT EGLSurface eglGetCurrentSurface(EGLint readdraw) {
|
||||
LOAD_EGL(eglGetCurrentSurface)
|
||||
return egl_eglGetCurrentSurface(readdraw);
|
||||
}
|
||||
|
||||
MG_EXPORT EGLDisplay eglGetCurrentDisplay(void) {
|
||||
LOAD_EGL(eglGetCurrentDisplay)
|
||||
return egl_eglGetCurrentDisplay();
|
||||
}
|
||||
|
||||
MG_EXPORT EGLBoolean eglQueryContext(EGLDisplay dpy, EGLContext ctx, EGLint attribute, EGLint *value) {
|
||||
LOAD_EGL(eglQueryContext)
|
||||
return egl_eglQueryContext(dpy, ctx, attribute, value);
|
||||
}
|
||||
|
||||
MG_EXPORT EGLBoolean eglWaitGL(void) {
|
||||
LOAD_EGL(eglWaitGL)
|
||||
return egl_eglWaitGL();
|
||||
}
|
||||
|
||||
MG_EXPORT EGLBoolean eglWaitNative(EGLint engine) {
|
||||
LOAD_EGL(eglWaitNative)
|
||||
return egl_eglWaitNative(engine);
|
||||
}
|
||||
|
||||
MG_EXPORT EGLBoolean eglSwapBuffers(EGLDisplay dpy, EGLSurface surface) {
|
||||
LOAD_EGL(eglSwapBuffers)
|
||||
return egl_eglSwapBuffers(dpy, surface);
|
||||
}
|
||||
|
||||
MG_EXPORT EGLBoolean eglCopyBuffers(EGLDisplay dpy, EGLSurface surface, EGLNativePixmapType target) {
|
||||
LOAD_EGL(eglCopyBuffers)
|
||||
return egl_eglCopyBuffers(dpy, surface, target);
|
||||
}
|
||||
|
||||
MG_EXPORT EGLDisplay eglGetPlatformDisplay(EGLenum platform, void *native_display, const EGLAttrib *attrib_list) {
|
||||
LOAD_EGL(eglGetPlatformDisplay)
|
||||
return egl_eglGetPlatformDisplay(platform, native_display, (const EGLint *)attrib_list);
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,14 @@
|
||||
//
|
||||
// Created by BZLZHH on 2025/4/26.
|
||||
//
|
||||
|
||||
#ifndef MOBILEGL_EGL_GLES_EGL_WRAP_H
|
||||
#define MOBILEGL_EGL_GLES_EGL_WRAP_H
|
||||
|
||||
#include "../../../../Includes.h"
|
||||
|
||||
#if BACKEND_TYPE == BACKEND_GLES
|
||||
|
||||
#endif
|
||||
|
||||
#endif //MOBILEGL_EGL_GLES_EGL_WRAP_H
|
||||
@@ -0,0 +1,551 @@
|
||||
//
|
||||
// Created by BZLZHH on 2025/3/30.
|
||||
//
|
||||
|
||||
#include "EGL_EMU.h"
|
||||
|
||||
#if BACKEND_TYPE == BACKEND_VULKAN
|
||||
|
||||
std::atomic<uintptr_t> g_nextContextId{1};
|
||||
std::atomic<uintptr_t> g_nextSurfaceId{1};
|
||||
EGLenum g_currentAPI = EGL_OPENGL_ES_API;
|
||||
|
||||
std::mutex g_globalMutex;
|
||||
ankerl::unordered_map<EGLDisplay, VulkanDisplay> g_displays;
|
||||
ankerl::unordered_map<EGLSurface, VulkanSurface> g_surfaces;
|
||||
ankerl::unordered_map<EGLContext, VulkanContext> g_contexts;
|
||||
EGLint g_lastError = EGL_SUCCESS;
|
||||
|
||||
thread_local EGLContext tls_currentContext = EGL_NO_CONTEXT;
|
||||
thread_local EGLSurface tls_drawSurface = EGL_NO_SURFACE;
|
||||
thread_local EGLSurface tls_readSurface = EGL_NO_SURFACE;
|
||||
|
||||
static VkResult CreateRenderPass(VulkanDisplay& display, VulkanContext& ctx) {
|
||||
VkAttachmentDescription colorAttachment{};
|
||||
colorAttachment.format = VK_FORMAT_B8G8R8A8_SRGB;
|
||||
colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT;
|
||||
colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
|
||||
colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
|
||||
colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
|
||||
colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
||||
colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
|
||||
|
||||
VkAttachmentReference colorRef{0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL};
|
||||
|
||||
VkSubpassDescription subpass{};
|
||||
subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
|
||||
subpass.colorAttachmentCount = 1;
|
||||
subpass.pColorAttachments = &colorRef;
|
||||
|
||||
VkRenderPassCreateInfo rpInfo{};
|
||||
rpInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
|
||||
rpInfo.subpassCount = 1;
|
||||
rpInfo.pSubpasses = &subpass;
|
||||
rpInfo.attachmentCount = 1;
|
||||
rpInfo.pAttachments = &colorAttachment;
|
||||
|
||||
return vkCreateRenderPass(display.device, &rpInfo, nullptr, &ctx.renderPass);
|
||||
}
|
||||
|
||||
EGLDisplay eglGetDisplay(NativeDisplayType display) {
|
||||
std::lock_guard<std::mutex> lock(g_globalMutex);
|
||||
static uintptr_t nextDisplayId = 1;
|
||||
EGLDisplay newDisplay = (EGLDisplay)nextDisplayId++;
|
||||
g_displays[newDisplay].initialized = false;
|
||||
return newDisplay;
|
||||
}
|
||||
|
||||
EGLBoolean eglInitialize(EGLDisplay dpy, EGLint* major, EGLint* minor) {
|
||||
VulkanDisplay& vkDpy = g_displays[dpy];
|
||||
if (vkDpy.initialized) return EGL_TRUE;
|
||||
|
||||
VkInstanceCreateInfo instInfo{};
|
||||
instInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
|
||||
const char* extensions[] = {
|
||||
VK_KHR_SURFACE_EXTENSION_NAME,
|
||||
VK_KHR_ANDROID_SURFACE_EXTENSION_NAME
|
||||
};
|
||||
instInfo.enabledExtensionCount = 2;
|
||||
instInfo.ppEnabledExtensionNames = extensions;
|
||||
|
||||
if (vkCreateInstance(&instInfo, nullptr, &vkDpy.instance) != VK_SUCCESS) {
|
||||
g_lastError = EGL_NOT_INITIALIZED;
|
||||
return EGL_FALSE;
|
||||
}
|
||||
|
||||
uint32_t devCount = 0;
|
||||
vkEnumeratePhysicalDevices(vkDpy.instance, &devCount, nullptr);
|
||||
std::vector<VkPhysicalDevice> devices(devCount);
|
||||
vkEnumeratePhysicalDevices(vkDpy.instance, &devCount, devices.data());
|
||||
vkDpy.physicalDevice = devices[0];
|
||||
|
||||
|
||||
uint32_t queueFamilyCount = 0;
|
||||
vkGetPhysicalDeviceQueueFamilyProperties(vkDpy.physicalDevice, &queueFamilyCount, nullptr);
|
||||
std::vector<VkQueueFamilyProperties> queueFamilies(queueFamilyCount);
|
||||
vkGetPhysicalDeviceQueueFamilyProperties(vkDpy.physicalDevice, &queueFamilyCount, queueFamilies.data());
|
||||
|
||||
// 寻找支持图形和呈现的队列家族
|
||||
bool foundQueue = false;
|
||||
for (uint32_t i = 0; i < queueFamilyCount; ++i) {
|
||||
VkBool32 presentSupport = VK_FALSE;
|
||||
vkGetPhysicalDeviceSurfaceSupportKHR(vkDpy.physicalDevice, i, VK_NULL_HANDLE, &presentSupport);
|
||||
|
||||
if ((queueFamilies[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) && presentSupport) {
|
||||
vkDpy.graphicsQueueFamily = i;
|
||||
foundQueue = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!foundQueue) {
|
||||
g_lastError = EGL_NOT_INITIALIZED;
|
||||
return EGL_FALSE;
|
||||
}
|
||||
|
||||
// 创建逻辑设备时请求队列
|
||||
float queuePriority = 1.0f;
|
||||
VkDeviceQueueCreateInfo queueInfo{};
|
||||
queueInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
|
||||
queueInfo.queueFamilyIndex = vkDpy.graphicsQueueFamily;
|
||||
queueInfo.queueCount = 1;
|
||||
queueInfo.pQueuePriorities = &queuePriority;
|
||||
|
||||
const char* devExtensions[] = {VK_KHR_SWAPCHAIN_EXTENSION_NAME};
|
||||
VkDeviceCreateInfo devInfo{};
|
||||
devInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
|
||||
devInfo.pQueueCreateInfos = &queueInfo;
|
||||
devInfo.queueCreateInfoCount = 1;
|
||||
devInfo.ppEnabledExtensionNames = devExtensions;
|
||||
devInfo.enabledExtensionCount = 1;
|
||||
|
||||
if (vkCreateDevice(vkDpy.physicalDevice, &devInfo, nullptr, &vkDpy.device) != VK_SUCCESS) {
|
||||
g_lastError = EGL_NOT_INITIALIZED;
|
||||
return EGL_FALSE;
|
||||
}
|
||||
|
||||
vkDpy.vkCreateSwapchainKHR = (PFN_vkCreateSwapchainKHR)vkGetDeviceProcAddr(vkDpy.device, "vkCreateSwapchainKHR");
|
||||
vkDpy.vkDestroySwapchainKHR = (PFN_vkDestroySwapchainKHR)vkGetDeviceProcAddr(vkDpy.device, "vkDestroySwapchainKHR");
|
||||
vkDpy.vkGetSwapchainImagesKHR = (PFN_vkGetSwapchainImagesKHR)vkGetDeviceProcAddr(vkDpy.device, "vkGetSwapchainImagesKHR");
|
||||
vkDpy.vkAcquireNextImageKHR = (PFN_vkAcquireNextImageKHR)vkGetDeviceProcAddr(vkDpy.device, "vkAcquireNextImageKHR");
|
||||
vkDpy.vkQueuePresentKHR = (PFN_vkQueuePresentKHR)vkGetDeviceProcAddr(vkDpy.device, "vkQueuePresentKHR");
|
||||
vkGetDeviceQueue(vkDpy.device, vkDpy.graphicsQueueFamily, 0, &vkDpy.graphicsQueue);
|
||||
vkDpy.presentQueue = vkDpy.graphicsQueue;
|
||||
vkDpy.initialized = true;
|
||||
if (major) *major = 1;
|
||||
if (minor) *minor = 5;
|
||||
return EGL_TRUE;
|
||||
}
|
||||
|
||||
EGLBoolean eglChooseConfig(EGLDisplay dpy, const EGLint* attrib_list, EGLConfig* configs, EGLint config_size, EGLint* num_config) {
|
||||
VulkanDisplay& vkDpy = g_displays[dpy];
|
||||
EGLInternalConfig defaultConfig{ VK_FORMAT_B8G8R8A8_SRGB, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR, 1 };
|
||||
vkDpy.configs = {defaultConfig};
|
||||
|
||||
EGLint returnCount = 1;
|
||||
if (configs && config_size > 0) {
|
||||
configs[0] = (EGLConfig)0;
|
||||
}
|
||||
|
||||
if (num_config) *num_config = returnCount;
|
||||
return EGL_TRUE;
|
||||
}
|
||||
|
||||
EGLSurface eglCreateWindowSurface(EGLDisplay dpy, EGLConfig config, NativeWindowType window, const EGLint* attrib_list) {
|
||||
VulkanDisplay& disp = g_displays[dpy];
|
||||
static uintptr_t nextSurfId = 1;
|
||||
|
||||
ANativeWindow* nativeWindow = static_cast<ANativeWindow*>(window);
|
||||
if (!nativeWindow || ANativeWindow_getWidth(nativeWindow) <= 0 || ANativeWindow_getHeight(nativeWindow) <= 0) {
|
||||
g_lastError = EGL_BAD_NATIVE_WINDOW;
|
||||
return EGL_NO_SURFACE;
|
||||
}
|
||||
|
||||
auto vkCreateAndroidSurfaceKHR = (PFN_vkCreateAndroidSurfaceKHR)vkGetInstanceProcAddr(
|
||||
disp.instance, "vkCreateAndroidSurfaceKHR");
|
||||
if (!vkCreateAndroidSurfaceKHR) {
|
||||
g_lastError = EGL_BAD_ALLOC;
|
||||
return EGL_NO_SURFACE;
|
||||
}
|
||||
|
||||
VulkanSurface surface{};
|
||||
surface.type = VulkanSurface::WINDOW;
|
||||
|
||||
VkAndroidSurfaceCreateInfoKHR createInfo{};
|
||||
createInfo.sType = VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR;
|
||||
createInfo.window = nativeWindow;
|
||||
VkResult result = vkCreateAndroidSurfaceKHR(disp.instance, &createInfo, nullptr, &surface.surface);
|
||||
if (result != VK_SUCCESS) {
|
||||
g_lastError = (result == VK_ERROR_NATIVE_WINDOW_IN_USE_KHR) ?
|
||||
EGL_BAD_NATIVE_WINDOW : EGL_BAD_ALLOC;
|
||||
return EGL_NO_SURFACE;
|
||||
}
|
||||
|
||||
VkBool32 supported = VK_FALSE;
|
||||
vkGetPhysicalDeviceSurfaceSupportKHR(disp.physicalDevice,
|
||||
disp.graphicsQueueFamily,
|
||||
surface.surface,
|
||||
&supported);
|
||||
if (!supported) {
|
||||
vkDestroySurfaceKHR(disp.instance, surface.surface, nullptr);
|
||||
g_lastError = EGL_BAD_MATCH;
|
||||
return EGL_NO_SURFACE;
|
||||
}
|
||||
|
||||
VkSurfaceCapabilitiesKHR caps;
|
||||
vkGetPhysicalDeviceSurfaceCapabilitiesKHR(disp.physicalDevice, surface.surface, &caps);
|
||||
|
||||
const VkExtent2D swapchainExtent = {
|
||||
(std::max)(caps.minImageExtent.width, (uint32_t)ANativeWindow_getWidth(nativeWindow)),
|
||||
(std::max)(caps.minImageExtent.height, (uint32_t)ANativeWindow_getHeight(nativeWindow))
|
||||
};
|
||||
|
||||
VkSwapchainCreateInfoKHR swapInfo{};
|
||||
swapInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
|
||||
swapInfo.surface = surface.surface;
|
||||
swapInfo.minImageCount = 3;
|
||||
swapInfo.imageFormat = VK_FORMAT_R8G8B8A8_SRGB;
|
||||
swapInfo.imageColorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR;
|
||||
swapInfo.imageExtent = swapchainExtent;
|
||||
swapInfo.imageArrayLayers = 1;
|
||||
swapInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
|
||||
swapInfo.preTransform = caps.currentTransform;
|
||||
swapInfo.compositeAlpha = VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR;
|
||||
swapInfo.presentMode = VK_PRESENT_MODE_MAILBOX_KHR;
|
||||
|
||||
if (disp.vkCreateSwapchainKHR(disp.device, &swapInfo, nullptr, &surface.swapchain) != VK_SUCCESS) {
|
||||
vkDestroySurfaceKHR(disp.instance, surface.surface, nullptr);
|
||||
g_lastError = EGL_BAD_ALLOC;
|
||||
return EGL_NO_SURFACE;
|
||||
}
|
||||
|
||||
uint32_t imageCount = 0;
|
||||
disp.vkGetSwapchainImagesKHR(disp.device, surface.swapchain, &imageCount, nullptr);
|
||||
surface.images.resize(imageCount);
|
||||
disp.vkGetSwapchainImagesKHR(disp.device, surface.swapchain, &imageCount, surface.images.data());
|
||||
|
||||
surface.imageViews.resize(imageCount);
|
||||
for (uint32_t i = 0; i < imageCount; ++i) {
|
||||
VkImageViewCreateInfo viewInfo{};
|
||||
viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
|
||||
viewInfo.image = surface.images[i];
|
||||
viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
|
||||
viewInfo.format = swapInfo.imageFormat;
|
||||
viewInfo.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
|
||||
|
||||
if (vkCreateImageView(disp.device, &viewInfo, nullptr, &surface.imageViews[i]) != VK_SUCCESS) {
|
||||
// Handle error
|
||||
}
|
||||
}
|
||||
|
||||
ANativeWindow_acquire(nativeWindow);
|
||||
ANativeWindow_setBuffersGeometry(nativeWindow,
|
||||
swapchainExtent.width,
|
||||
swapchainExtent.height,
|
||||
AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM);
|
||||
EGLSurface surfId = (EGLSurface)nextSurfId++;
|
||||
g_surfaces[surfId] = std::move(surface);
|
||||
return surfId;
|
||||
}
|
||||
|
||||
EGLContext eglCreateContext(EGLDisplay dpy, EGLConfig config, EGLContext shareCtx, const EGLint* attrib_list) {
|
||||
static uintptr_t nextCtxId = 1;
|
||||
VulkanDisplay &disp = g_displays[dpy];
|
||||
|
||||
VulkanContext ctx{};
|
||||
VkCommandPoolCreateInfo poolInfo{};
|
||||
poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
|
||||
poolInfo.queueFamilyIndex = 0;
|
||||
if (vkCreateCommandPool(disp.device, &poolInfo, nullptr, &ctx.cmdPool) != VK_SUCCESS) {
|
||||
g_lastError = EGL_BAD_ALLOC;
|
||||
return EGL_NO_CONTEXT;
|
||||
}
|
||||
|
||||
ctx.frameFences.resize(2);
|
||||
ctx.acquireSemaphores.resize(2);
|
||||
ctx.presentSemaphores.resize(2);
|
||||
for (uint32_t i = 0; i < 2; ++i) {
|
||||
VkFenceCreateInfo fenceInfo{VK_STRUCTURE_TYPE_FENCE_CREATE_INFO, nullptr,
|
||||
VK_FENCE_CREATE_SIGNALED_BIT};
|
||||
vkCreateFence(disp.device, &fenceInfo, nullptr, &ctx.frameFences[i]);
|
||||
|
||||
VkSemaphoreCreateInfo semInfo{VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO};
|
||||
vkCreateSemaphore(disp.device, &semInfo, nullptr, &ctx.acquireSemaphores[i]);
|
||||
vkCreateSemaphore(disp.device, &semInfo, nullptr, &ctx.presentSemaphores[i]);
|
||||
}
|
||||
|
||||
if (CreateRenderPass(disp, ctx) != VK_SUCCESS) {
|
||||
g_lastError = EGL_BAD_CONFIG;
|
||||
return EGL_NO_CONTEXT;
|
||||
}
|
||||
|
||||
EGLContext ctxId = (EGLContext)nextCtxId++;
|
||||
g_contexts[ctxId] = std::move(ctx);
|
||||
return ctxId;
|
||||
}
|
||||
|
||||
EGLint eglGetError() {
|
||||
return EGL_SUCCESS;
|
||||
}
|
||||
|
||||
EGLBoolean eglMakeCurrent(EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx) {
|
||||
std::lock_guard<std::mutex> lock(g_globalMutex);
|
||||
|
||||
if (dpy == EGL_NO_DISPLAY || (ctx != EGL_NO_CONTEXT && !g_contexts.count(ctx)) ||
|
||||
(draw != EGL_NO_SURFACE && !g_surfaces.count(draw)) ||
|
||||
(read != EGL_NO_SURFACE && !g_surfaces.count(read))) {
|
||||
g_lastError = EGL_BAD_PARAMETER;
|
||||
return EGL_FALSE;
|
||||
}
|
||||
|
||||
if (tls_currentContext != EGL_NO_CONTEXT) {
|
||||
VulkanContext& oldCtx = g_contexts[tls_currentContext];
|
||||
if (oldCtx.currentCmdBuffer) {
|
||||
vkEndCommandBuffer(oldCtx.currentCmdBuffer);
|
||||
oldCtx.currentCmdBuffer = VK_NULL_HANDLE;
|
||||
}
|
||||
}
|
||||
|
||||
if (ctx != EGL_NO_CONTEXT) {
|
||||
VulkanContext& newCtx = g_contexts[ctx];
|
||||
VulkanDisplay& disp = g_displays[dpy];
|
||||
|
||||
VkCommandBufferAllocateInfo allocInfo{};
|
||||
allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
|
||||
allocInfo.commandPool = newCtx.cmdPool;
|
||||
allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
|
||||
allocInfo.commandBufferCount = 1;
|
||||
|
||||
if (vkAllocateCommandBuffers(disp.device, &allocInfo, &newCtx.currentCmdBuffer) != VK_SUCCESS) {
|
||||
g_lastError = EGL_BAD_ALLOC;
|
||||
return EGL_FALSE;
|
||||
}
|
||||
|
||||
VkCommandBufferBeginInfo beginInfo{};
|
||||
beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
|
||||
vkBeginCommandBuffer(newCtx.currentCmdBuffer, &beginInfo);
|
||||
}
|
||||
|
||||
tls_currentContext = ctx;
|
||||
tls_drawSurface = draw;
|
||||
tls_readSurface = read;
|
||||
return EGL_TRUE;
|
||||
}
|
||||
|
||||
EGLBoolean eglDestroyContext(EGLDisplay dpy, EGLContext ctx) {
|
||||
std::lock_guard<std::mutex> lock(g_globalMutex);
|
||||
|
||||
if (!g_contexts.count(ctx)) {
|
||||
g_lastError = EGL_BAD_CONTEXT;
|
||||
return EGL_FALSE;
|
||||
}
|
||||
|
||||
VulkanContext& vkCtx = g_contexts[ctx];
|
||||
VulkanDisplay& disp = g_displays[dpy];
|
||||
|
||||
vkDeviceWaitIdle(disp.device);
|
||||
|
||||
vkDestroyCommandPool(disp.device, vkCtx.cmdPool, nullptr);
|
||||
vkDestroyRenderPass(disp.device, vkCtx.renderPass, nullptr);
|
||||
|
||||
for (auto& fence : vkCtx.frameFences)
|
||||
vkDestroyFence(disp.device, fence, nullptr);
|
||||
for (auto& sem : vkCtx.acquireSemaphores)
|
||||
vkDestroySemaphore(disp.device, sem, nullptr);
|
||||
for (auto& sem : vkCtx.presentSemaphores)
|
||||
vkDestroySemaphore(disp.device, sem, nullptr);
|
||||
|
||||
g_contexts.erase(ctx);
|
||||
if (tls_currentContext == ctx)
|
||||
tls_currentContext = EGL_NO_CONTEXT;
|
||||
|
||||
return EGL_TRUE;
|
||||
}
|
||||
|
||||
EGLBoolean eglDestroySurface(EGLDisplay dpy, EGLSurface surface) {
|
||||
std::lock_guard<std::mutex> lock(g_globalMutex);
|
||||
|
||||
if (!g_surfaces.count(surface)) {
|
||||
g_lastError = EGL_BAD_SURFACE;
|
||||
return EGL_FALSE;
|
||||
}
|
||||
|
||||
VulkanSurface& surf = g_surfaces[surface];
|
||||
VulkanDisplay& disp = g_displays[dpy];
|
||||
|
||||
vkDeviceWaitIdle(disp.device);
|
||||
|
||||
if (surf.type == VulkanSurface::WINDOW) {
|
||||
disp.vkDestroySwapchainKHR(disp.device, surf.swapchain, nullptr);
|
||||
vkDestroySurfaceKHR(disp.instance, surf.surface, nullptr);
|
||||
for (auto& view : surf.imageViews)
|
||||
vkDestroyImageView(disp.device, view, nullptr);
|
||||
} else {
|
||||
vkDestroyImageView(disp.device, surf.pbufferImageView, nullptr);
|
||||
vkDestroyImage(disp.device, surf.pbufferImage, nullptr);
|
||||
}
|
||||
|
||||
g_surfaces.erase(surface);
|
||||
return EGL_TRUE;
|
||||
}
|
||||
|
||||
EGLBoolean eglTerminate(EGLDisplay dpy) {
|
||||
std::lock_guard<std::mutex> lock(g_globalMutex);
|
||||
|
||||
if (!g_displays.count(dpy)) {
|
||||
g_lastError = EGL_BAD_DISPLAY;
|
||||
return EGL_FALSE;
|
||||
}
|
||||
|
||||
VulkanDisplay& disp = g_displays[dpy];
|
||||
|
||||
for (auto& [ctxId, ctx] : g_contexts)
|
||||
eglDestroyContext(dpy, ctxId);
|
||||
for (auto& [surfId, surf] : g_surfaces)
|
||||
eglDestroySurface(dpy, surfId);
|
||||
|
||||
vkDestroyDevice(disp.device, nullptr);
|
||||
vkDestroyInstance(disp.instance, nullptr);
|
||||
|
||||
g_displays.erase(dpy);
|
||||
return EGL_TRUE;
|
||||
}
|
||||
|
||||
EGLBoolean eglReleaseThread(void) {
|
||||
tls_currentContext = EGL_NO_CONTEXT;
|
||||
tls_drawSurface = EGL_NO_SURFACE;
|
||||
tls_readSurface = EGL_NO_SURFACE;
|
||||
return EGL_TRUE;
|
||||
}
|
||||
|
||||
EGLContext eglGetCurrentContext(void) {
|
||||
return tls_currentContext;
|
||||
}
|
||||
|
||||
EGLBoolean eglGetConfigAttrib(EGLDisplay dpy, EGLConfig config, EGLint attribute, EGLint *value) {
|
||||
VulkanDisplay& disp = g_displays[dpy];
|
||||
if (!disp.configs.empty() && config == (EGLConfig)0) {
|
||||
switch (attribute) {
|
||||
case EGL_BUFFER_SIZE: *value = 32; break;
|
||||
case EGL_RED_SIZE: *value = 8; break;
|
||||
case EGL_GREEN_SIZE: *value = 8; break;
|
||||
case EGL_BLUE_SIZE: *value = 8; break;
|
||||
case EGL_ALPHA_SIZE: *value = 8; break;
|
||||
case EGL_CONFIG_ID: *value = 1; break;
|
||||
default: return EGL_FALSE;
|
||||
}
|
||||
return EGL_TRUE;
|
||||
}
|
||||
return EGL_FALSE;
|
||||
}
|
||||
|
||||
EGLBoolean eglBindAPI(EGLenum api) {
|
||||
if (api == EGL_OPENGL_ES_API) {
|
||||
g_currentAPI = api;
|
||||
return EGL_TRUE;
|
||||
}
|
||||
return EGL_FALSE;
|
||||
}
|
||||
|
||||
EGLSurface eglCreatePbufferSurface(EGLDisplay dpy, EGLConfig config, const EGLint *attrib_list) {
|
||||
VulkanDisplay& disp = g_displays[dpy];
|
||||
VulkanSurface surface{};
|
||||
surface.type = VulkanSurface::PBUFFER;
|
||||
|
||||
VkImageCreateInfo imgInfo{};
|
||||
imgInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
|
||||
imgInfo.imageType = VK_IMAGE_TYPE_2D;
|
||||
imgInfo.format = VK_FORMAT_R8G8B8A8_SRGB;
|
||||
imgInfo.extent = {512, 512, 1};
|
||||
imgInfo.mipLevels = 1;
|
||||
imgInfo.arrayLayers = 1;
|
||||
imgInfo.samples = VK_SAMPLE_COUNT_1_BIT;
|
||||
imgInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
|
||||
imgInfo.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
|
||||
imgInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
|
||||
if (vkCreateImage(disp.device, &imgInfo, nullptr, &surface.pbufferImage) != VK_SUCCESS) {
|
||||
g_lastError = EGL_BAD_ALLOC;
|
||||
return EGL_NO_SURFACE;
|
||||
}
|
||||
|
||||
VkImageViewCreateInfo viewInfo{};
|
||||
viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
|
||||
viewInfo.image = surface.pbufferImage;
|
||||
viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
|
||||
viewInfo.format = imgInfo.format;
|
||||
viewInfo.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
|
||||
|
||||
if (vkCreateImageView(disp.device, &viewInfo, nullptr, &surface.pbufferImageView) != VK_SUCCESS) {
|
||||
vkDestroyImage(disp.device, surface.pbufferImage, nullptr);
|
||||
g_lastError = EGL_BAD_ALLOC;
|
||||
return EGL_NO_SURFACE;
|
||||
}
|
||||
|
||||
EGLSurface surfId = (EGLSurface)g_nextSurfaceId++;
|
||||
g_surfaces[surfId] = surface;
|
||||
return surfId;
|
||||
}
|
||||
|
||||
EGLBoolean eglSwapBuffers(EGLDisplay dpy, EGLSurface draw) {
|
||||
VulkanSurface& surf = g_surfaces[draw];
|
||||
VulkanDisplay& disp = g_displays[dpy];
|
||||
VulkanContext& ctx = g_contexts[tls_currentContext];
|
||||
|
||||
if (!disp.presentQueue) {
|
||||
g_lastError = EGL_BAD_DISPLAY;
|
||||
return EGL_FALSE;
|
||||
}
|
||||
|
||||
if (surf.type != VulkanSurface::WINDOW) {
|
||||
g_lastError = EGL_BAD_SURFACE;
|
||||
return EGL_FALSE;
|
||||
}
|
||||
|
||||
vkEndCommandBuffer(ctx.currentCmdBuffer);
|
||||
|
||||
VkSubmitInfo submitInfo{};
|
||||
submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
|
||||
submitInfo.commandBufferCount = 1;
|
||||
submitInfo.pCommandBuffers = &ctx.currentCmdBuffer;
|
||||
|
||||
vkQueueSubmit(disp.graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE);
|
||||
|
||||
VkPresentInfoKHR presentInfo{};
|
||||
presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
|
||||
presentInfo.swapchainCount = 1;
|
||||
presentInfo.pSwapchains = &surf.swapchain;
|
||||
presentInfo.pImageIndices = &ctx.frameIndex;
|
||||
|
||||
VkResult result = disp.vkQueuePresentKHR(disp.presentQueue, &presentInfo);
|
||||
if (result != VK_SUCCESS) {
|
||||
g_lastError = EGL_BAD_ALLOC;
|
||||
return EGL_FALSE;
|
||||
}
|
||||
|
||||
ctx.frameIndex = (ctx.frameIndex + 1) % surf.images.size();
|
||||
return EGL_TRUE;
|
||||
}
|
||||
|
||||
EGLBoolean eglSwapInterval(EGLDisplay dpy, EGLint interval) {
|
||||
// TODO
|
||||
return EGL_TRUE;
|
||||
}
|
||||
|
||||
EGLSurface eglGetCurrentSurface(EGLint readdraw) {
|
||||
return (readdraw == EGL_READ) ? tls_readSurface : tls_drawSurface;
|
||||
}
|
||||
|
||||
EGLBoolean eglQuerySurface(EGLDisplay display, EGLSurface surface, EGLint attribute, EGLint *value) {
|
||||
VulkanSurface& surf = g_surfaces[surface];
|
||||
switch (attribute) {
|
||||
case EGL_WIDTH: *value = surf.extent.width; break;
|
||||
case EGL_HEIGHT: *value = surf.extent.height; break;
|
||||
case EGL_CONFIG_ID: *value = 1; break;
|
||||
default: return EGL_FALSE;
|
||||
}
|
||||
return EGL_TRUE;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,103 @@
|
||||
//
|
||||
// Created by BZLZHH on 2025/3/30.
|
||||
//
|
||||
|
||||
#ifndef MOBILEGL_EGL_VK_EMU_H
|
||||
#define MOBILEGL_EGL_VK_EMU_H
|
||||
|
||||
#include "../../../../Includes.h"
|
||||
|
||||
#if BACKEND_TYPE == BACKEND_VULKAN
|
||||
|
||||
struct EGLInternalConfig {
|
||||
VkFormat format;
|
||||
VkColorSpaceKHR colorSpace;
|
||||
uint32_t samples;
|
||||
};
|
||||
|
||||
struct VulkanDisplay {
|
||||
VkInstance instance;
|
||||
VkPhysicalDevice physicalDevice;
|
||||
VkDevice device;
|
||||
uint32_t graphicsQueueFamily;
|
||||
VkQueue graphicsQueue;
|
||||
std::vector<EGLInternalConfig> configs;
|
||||
bool initialized = false;
|
||||
PFN_vkCreateSwapchainKHR vkCreateSwapchainKHR;
|
||||
PFN_vkDestroySwapchainKHR vkDestroySwapchainKHR;
|
||||
PFN_vkGetSwapchainImagesKHR vkGetSwapchainImagesKHR;
|
||||
PFN_vkAcquireNextImageKHR vkAcquireNextImageKHR;
|
||||
PFN_vkQueuePresentKHR vkQueuePresentKHR;
|
||||
VkRenderPass defaultRenderPass;
|
||||
VkQueue presentQueue = VK_NULL_HANDLE;
|
||||
};
|
||||
|
||||
struct VulkanSurface {
|
||||
enum Type { WINDOW, PBUFFER } type;
|
||||
VkSurfaceKHR surface;
|
||||
VkSwapchainKHR swapchain;
|
||||
std::vector<VkImage> images;
|
||||
std::vector<VkImageView> imageViews;
|
||||
VkExtent2D extent;
|
||||
VkFormat format;
|
||||
VkImage pbImage;
|
||||
VkImageView pbImageView;
|
||||
VkDeviceMemory pbMemory;
|
||||
VkFramebuffer framebuffer;
|
||||
VkImage pbufferImage;
|
||||
VkImageView pbufferImageView;
|
||||
};
|
||||
|
||||
struct VulkanContext {
|
||||
VkCommandPool cmdPool;
|
||||
std::vector<VkCommandBuffer> cmdBuffers;
|
||||
uint32_t currentFrame = 0;
|
||||
VkRenderPass renderPass;
|
||||
VkPipelineLayout pipelineLayout;
|
||||
VkPipeline graphicsPipeline;
|
||||
std::vector<VkFence> frameFences;
|
||||
std::vector<VkSemaphore> acquireSemaphores;
|
||||
std::vector<VkSemaphore> presentSemaphores;
|
||||
VulkanSurface* boundSurface = nullptr;
|
||||
VkCommandBuffer currentCmdBuffer;
|
||||
uint32_t frameIndex;
|
||||
VkClearColorValue clearColor;
|
||||
};
|
||||
|
||||
extern std::atomic<uintptr_t> g_nextContextId;
|
||||
extern std::atomic<uintptr_t> g_nextSurfaceId;
|
||||
extern EGLenum g_currentAPI;
|
||||
|
||||
extern std::mutex g_globalMutex;
|
||||
extern ankerl::unordered_map<EGLDisplay, VulkanDisplay> g_displays;
|
||||
extern ankerl::unordered_map<EGLSurface, VulkanSurface> g_surfaces;
|
||||
extern ankerl::unordered_map<EGLContext, VulkanContext> g_contexts;
|
||||
extern EGLint g_lastError;
|
||||
|
||||
extern thread_local EGLContext tls_currentContext;
|
||||
extern thread_local EGLSurface tls_drawSurface;
|
||||
extern thread_local EGLSurface tls_readSurface;
|
||||
|
||||
MG_EXPORT EGLSurface eglCreateWindowSurface(EGLDisplay dpy, EGLConfig config, NativeWindowType window, const EGLint* attrib_list);
|
||||
MG_EXPORT EGLBoolean eglChooseConfig(EGLDisplay dpy, const EGLint* attrib_list, EGLConfig* configs, EGLint config_size, EGLint* num_config);
|
||||
MG_EXPORT EGLContext eglCreateContext(EGLDisplay dpy, EGLConfig config, EGLContext shareCtx, const EGLint* attrib_list);
|
||||
MG_EXPORT EGLBoolean eglInitialize(EGLDisplay dpy, EGLint* major, EGLint* minor);
|
||||
MG_EXPORT EGLDisplay eglGetDisplay(NativeDisplayType display);
|
||||
MG_EXPORT EGLint eglGetError();
|
||||
MG_EXPORT EGLBoolean eglMakeCurrent(EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx);
|
||||
MG_EXPORT EGLBoolean eglDestroyContext(EGLDisplay dpy, EGLContext ctx);
|
||||
MG_EXPORT EGLBoolean eglDestroySurface(EGLDisplay dpy, EGLSurface surface);
|
||||
MG_EXPORT EGLBoolean eglTerminate(EGLDisplay dpy);
|
||||
MG_EXPORT EGLBoolean eglReleaseThread(void);
|
||||
MG_EXPORT EGLContext eglGetCurrentContext(void);
|
||||
MG_EXPORT EGLBoolean eglGetConfigAttrib(EGLDisplay dpy, EGLConfig config, EGLint attribute, EGLint *value);
|
||||
MG_EXPORT EGLBoolean eglBindAPI(EGLenum api);
|
||||
MG_EXPORT EGLSurface eglGetCurrentSurface(EGLint readdraw);
|
||||
MG_EXPORT EGLBoolean eglQuerySurface(EGLDisplay display, EGLSurface surface, EGLint attribute, EGLint *value);
|
||||
MG_EXPORT EGLBoolean eglSwapInterval(EGLDisplay dpy, EGLint interval);
|
||||
MG_EXPORT EGLBoolean eglSwapBuffers(EGLDisplay dpy, EGLSurface draw);
|
||||
MG_EXPORT EGLSurface eglCreatePbufferSurface(EGLDisplay dpy, EGLConfig config, const EGLint *attrib_list);
|
||||
|
||||
#endif
|
||||
|
||||
#endif //MOBILEGL_EGL_VK_EMU_H
|
||||
@@ -0,0 +1,188 @@
|
||||
//
|
||||
// Created by BZLZHH on 2025/3/15.
|
||||
//
|
||||
|
||||
#include "GL_Buffer.h"
|
||||
|
||||
namespace MG_GL::GL {
|
||||
void* MapBufferRange(GLenum target, GLintptr offset, GLsizeiptr length,
|
||||
GLbitfield access) {
|
||||
MG_Util::Debug::LogD("glMapBufferRange, target: %s, offset: %lld, length: %lld, access: 0x%X",
|
||||
MG_Util::Debug::GLEnumToString(target),
|
||||
static_cast<long long>(offset),
|
||||
static_cast<long long>(length),
|
||||
access);
|
||||
void* mappedPointer = nullptr;
|
||||
GLenum result = MG_State::AcquireBufferMemoryRange(target, offset, length, access, &mappedPointer);
|
||||
|
||||
if (result == GL_NO_ERROR) {
|
||||
MG_Util::Debug::LogD("Mapped pointer: %p", mappedPointer);
|
||||
return mappedPointer;
|
||||
}
|
||||
MG_State::SetError(result);
|
||||
MG_Util::Debug::LogE("Error mapping buffer range: %s", MG_Util::Debug::GLEnumToString(result));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void FlushMappedBufferRange(GLenum target, GLintptr offset, GLsizeiptr length) {
|
||||
MG_Util::Debug::LogD("glFlushMappedBufferRange, target: %s, offset: %lld, length: %lld",
|
||||
MG_Util::Debug::GLEnumToString(target),
|
||||
static_cast<long long>(offset),
|
||||
static_cast<long long>(length));
|
||||
GLenum result = MG_State::SyncBufferMemory(target, offset, length);
|
||||
if (result == GL_NO_ERROR) {
|
||||
return;
|
||||
}
|
||||
MG_State::SetError(result);
|
||||
MG_Util::Debug::LogE("Error flushing mapped buffer range: %s",
|
||||
MG_Util::Debug::GLEnumToString(result));
|
||||
}
|
||||
|
||||
void CopyBufferSubData(GLenum readTarget, GLenum writeTarget,
|
||||
GLintptr readOffset, GLintptr writeOffset,
|
||||
GLsizeiptr size) {
|
||||
MG_Util::Debug::LogD("glCopyBufferSubData, readTarget: %s, writeTarget: %s, "
|
||||
"readOffset: %lld, writeOffset: %lld, size: %lld",
|
||||
MG_Util::Debug::GLEnumToString(readTarget),
|
||||
MG_Util::Debug::GLEnumToString(writeTarget),
|
||||
static_cast<long long>(readOffset),
|
||||
static_cast<long long>(writeOffset),
|
||||
static_cast<long long>(size));
|
||||
GLenum result = MG_State::CopyBufferRange(readTarget, writeTarget, readOffset, writeOffset, size);
|
||||
if (result == GL_NO_ERROR) {
|
||||
return;
|
||||
}
|
||||
MG_State::SetError(result);
|
||||
MG_Util::Debug::LogE("Error copying buffer subdata: %s",
|
||||
MG_Util::Debug::GLEnumToString(result));
|
||||
}
|
||||
|
||||
void* MapBuffer(GLenum target, GLenum access) {
|
||||
MG_Util::Debug::LogD("glMapBuffer(target=0x%x, access=0x%x)", target, access);
|
||||
void* mappedPtr = nullptr;
|
||||
GLenum err = MG_State::AcquireBufferMemory(target, access, &mappedPtr);
|
||||
if (err != GL_NO_ERROR) {
|
||||
MG_State::SetError(err);
|
||||
MG_Util::Debug::LogE("glMapBuffer failed: %s", MG_Util::Debug::GLEnumToString(err));
|
||||
return nullptr;
|
||||
}
|
||||
MG_Util::Debug::LogD("glMapBuffer returns %p", mappedPtr);
|
||||
return mappedPtr;
|
||||
}
|
||||
|
||||
GLboolean UnmapBuffer(GLenum target) {
|
||||
MG_Util::Debug::LogD("glUnmapBuffer(target=0x%x)", target);
|
||||
GLenum err = MG_State::ReleaseBufferMemory(target);
|
||||
if (err != GL_NO_ERROR) {
|
||||
MG_State::SetError(err);
|
||||
MG_Util::Debug::LogE("glUnmapBuffer failed: %s", MG_Util::Debug::GLEnumToString(err));
|
||||
return GL_FALSE;
|
||||
}
|
||||
MG_Util::Debug::LogD("glUnmapBuffer succeeded");
|
||||
return GL_TRUE;
|
||||
}
|
||||
|
||||
void BindBuffer(GLenum target, GLuint buffer) {
|
||||
MG_Util::Debug::LogD("glBindBuffer, target: %s, buffer: %d", MG_Util::Debug::GLEnumToString(target), buffer);
|
||||
if (buffer != 0 &&
|
||||
MG_State::ValidateGeneratedName(buffer) &&
|
||||
!MG_State::ValidateAllocatedBufferHandle(buffer)) {
|
||||
MG_Util::Debug::LogD("Actually creating buffer: %u", buffer);
|
||||
GLenum result = MG_State::CreateBuffer(buffer);
|
||||
if (result != GL_NO_ERROR) {
|
||||
MG_State::SetError(result);
|
||||
MG_Util::Debug::LogE("Error from MG State: %s", MG_Util::Debug::GLEnumToString(result));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (buffer != 0 && !MG_State::ValidateAllocatedBufferHandle(buffer)) {
|
||||
MG_State::SetError(GL_INVALID_VALUE);
|
||||
MG_Util::Debug::LogE("Invalid buffer handle: %u", buffer);
|
||||
return;
|
||||
}
|
||||
GLenum result = MG_State::BindBuffer(target, buffer);
|
||||
if (result == GL_NO_ERROR)
|
||||
return;
|
||||
MG_State::SetError(result);
|
||||
MG_Util::Debug::LogE("Error from MG State: %s", MG_Util::Debug::GLEnumToString(result));
|
||||
}
|
||||
|
||||
void BufferData(GLenum target, GLsizeiptr size, const void* data, GLenum usage) {
|
||||
MG_Util::Debug::LogD("glBufferData, target: %s, size: %zd, data: %p, usage: %s",
|
||||
MG_Util::Debug::GLEnumToString(target), size, data, MG_Util::Debug::GLEnumToString(usage));
|
||||
GLenum result = MG_State::CommitBufferStorage(target, size, data, usage);
|
||||
if (result == GL_NO_ERROR)
|
||||
return;
|
||||
MG_State::SetError(result);
|
||||
MG_Util::Debug::LogE("Error from MG State: %s", MG_Util::Debug::GLEnumToString(result));
|
||||
}
|
||||
|
||||
void GetBufferParameteriv(GLenum target, GLenum pname, GLint* params) {
|
||||
MG_Util::Debug::LogD("glGetBufferParameteriv, target: %d, pname: %d, params: %p",
|
||||
target, pname, params);
|
||||
GLenum result = MG_State::QueryBufferPropertyIntVector(target, pname, params);
|
||||
if (result == GL_NO_ERROR)
|
||||
return;
|
||||
MG_State::SetError(result);
|
||||
MG_Util::Debug::LogE("GetBufferParameteriv not implemented");
|
||||
}
|
||||
|
||||
void GenBuffers(GLsizei n, GLuint* buffers) {
|
||||
MG_Util::Debug::LogD("glGenBuffers, n: %d, buffers: %p", n, buffers);
|
||||
|
||||
if (n < 0) {
|
||||
MG_State::SetError(GL_INVALID_VALUE);
|
||||
MG_Util::Debug::LogE("Invalid buffer count: %d", n);
|
||||
return;
|
||||
}
|
||||
|
||||
GLenum result = MG_State::GenBufferNames(n, buffers);
|
||||
if (result == GL_NO_ERROR) {
|
||||
MG_Util::Debug::LogD("Generated buffer names:");
|
||||
for (GLsizei i = 0; i < n; ++i) {
|
||||
MG_Util::Debug::LogD(" Buffer[%d] = %u", i, buffers[i]);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
MG_State::SetError(result);
|
||||
MG_Util::Debug::LogE("Error from MG State: %s", MG_Util::Debug::GLEnumToString(result));
|
||||
}
|
||||
|
||||
GLboolean IsBuffer(GLuint buffer) {
|
||||
MG_Util::Debug::LogD("glIsBuffer, buffer: %u", buffer);
|
||||
|
||||
if (buffer == 0) {
|
||||
return GL_FALSE;
|
||||
}
|
||||
|
||||
bool isValid = MG_State::ValidateAllocatedBufferHandle(buffer);
|
||||
// Should we report gl error here or in MG_State?
|
||||
MG_Util::Debug::LogD("Buffer %u is %s", buffer, isValid ? "valid" : "invalid");
|
||||
return isValid ? GL_TRUE : GL_FALSE;
|
||||
}
|
||||
|
||||
void BufferSubData(GLenum target, GLintptr offset, GLsizeiptr size, const void* data) {
|
||||
MG_Util::Debug::LogD("glBufferSubData, target: %s, offset: %lld, size: %lld, data: %p",
|
||||
MG_Util::Debug::GLEnumToString(target),
|
||||
static_cast<long long>(offset),
|
||||
static_cast<long long>(size),
|
||||
data);
|
||||
GLenum result = MG_State::CommitBufferStorageRegion(target, offset, size, data);
|
||||
if (result != GL_NO_ERROR) {
|
||||
MG_State::SetError(result);
|
||||
MG_Util::Debug::LogE("Error from MG State: %s", MG_Util::Debug::GLEnumToString(result));
|
||||
}
|
||||
}
|
||||
void DeleteBuffers(GLsizei n, const GLuint *buffers) {
|
||||
MG_Util::Debug::LogD("glDeleteBuffers, n: %d, buffers: %p", n, buffers);
|
||||
|
||||
GLenum result = MG_State::DeleteBuffers(n, buffers);
|
||||
if (result == GL_NO_ERROR)
|
||||
return;
|
||||
|
||||
MG_State::SetError(result);
|
||||
MG_Util::Debug::LogE("Error from MG State: %s", MG_Util::Debug::GLEnumToString(result));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
//
|
||||
// Created by BZLZHH on 2025/3/15.
|
||||
//
|
||||
|
||||
#ifndef MOBILEGL_GL_BUFFER_H
|
||||
#define MOBILEGL_GL_BUFFER_H
|
||||
|
||||
#include "../../../../Includes.h"
|
||||
|
||||
namespace MG_GL::GL {
|
||||
void* MapBufferRange(GLenum target, GLintptr offset, GLsizeiptr length, GLbitfield access);
|
||||
void FlushMappedBufferRange(GLenum target, GLintptr offset, GLsizeiptr length);
|
||||
void CopyBufferSubData(GLenum readTarget, GLenum writeTarget, GLintptr readOffset, GLintptr writeOffset, GLsizeiptr size);
|
||||
void* MapBuffer(GLenum target, GLenum access);
|
||||
GLboolean UnmapBuffer(GLenum target);
|
||||
void BindBuffer(GLenum target, GLuint buffer);
|
||||
void BufferData(GLenum target, GLsizeiptr size, const void* data, GLenum usage);
|
||||
void BufferSubData(GLenum target, GLintptr offset, GLsizeiptr size, const void* data);
|
||||
void GetBufferParameteriv(GLenum target, GLenum pname, GLint* params);
|
||||
void GenBuffers(GLsizei n, GLuint* buffers);
|
||||
void DeleteBuffers(GLsizei n, const GLuint *buffers);
|
||||
GLboolean IsBuffer(GLuint buffer);
|
||||
}
|
||||
|
||||
#endif //MOBILEGL_GL_BUFFER_H
|
||||
@@ -0,0 +1,103 @@
|
||||
//
|
||||
// Created by BZLZHH on 2025/3/15.
|
||||
//
|
||||
|
||||
#include "GL_Common.h"
|
||||
|
||||
namespace MG_GL::GL {
|
||||
void Enable(GLenum cap) {
|
||||
MG_Util::Debug::LogD("glEnable, cap: %s", MG_Util::Debug::GLEnumToString(cap));
|
||||
GLenum result = MG_State::glEnable(cap);
|
||||
if (result == GL_NO_ERROR) return;
|
||||
MG_State::SetError(result);
|
||||
MG_Util::Debug::LogE("Error enabling capability: %s", MG_Util::Debug::GLEnumToString(result));
|
||||
}
|
||||
|
||||
void Disable(GLenum cap) {
|
||||
MG_Util::Debug::LogD("glDisable, cap: %s", MG_Util::Debug::GLEnumToString(cap));
|
||||
GLenum result = MG_State::glDisable(cap);
|
||||
if (result == GL_NO_ERROR) return;
|
||||
MG_State::SetError(result);
|
||||
MG_Util::Debug::LogE("Error disabling capability: %s", MG_Util::Debug::GLEnumToString(result));
|
||||
}
|
||||
|
||||
void BlendFunc(GLenum sfactor, GLenum dfactor) {
|
||||
MG_Util::Debug::LogD("glBlendFunc, sfactor: %s, dfactor: %s",
|
||||
MG_Util::Debug::GLEnumToString(sfactor),
|
||||
MG_Util::Debug::GLEnumToString(dfactor));
|
||||
GLenum result = MG_State::glBlendFunc(sfactor, dfactor);
|
||||
if (result == GL_NO_ERROR) return;
|
||||
MG_State::SetError(result);
|
||||
MG_Util::Debug::LogE("Error setting blend func: %s", MG_Util::Debug::GLEnumToString(result));
|
||||
}
|
||||
|
||||
void BlendFuncSeparate(GLenum srcRGB, GLenum dstRGB, GLenum srcAlpha, GLenum dstAlpha) {
|
||||
MG_Util::Debug::LogD("glBlendFuncSeparate, srcRGB: %s, dstRGB: %s, srcAlpha: %s, dstAlpha: %s",
|
||||
MG_Util::Debug::GLEnumToString(srcRGB),
|
||||
MG_Util::Debug::GLEnumToString(dstRGB),
|
||||
MG_Util::Debug::GLEnumToString(srcAlpha),
|
||||
MG_Util::Debug::GLEnumToString(dstAlpha));
|
||||
GLenum result = MG_State::glBlendFuncSeparate(srcRGB, dstRGB, srcAlpha, dstAlpha);
|
||||
if (result == GL_NO_ERROR) return;
|
||||
MG_State::SetError(result);
|
||||
MG_Util::Debug::LogE("Error setting separate blend func: %s", MG_Util::Debug::GLEnumToString(result));
|
||||
}
|
||||
|
||||
void ClearColor(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha) {
|
||||
MG_Util::Debug::LogD("glClearColor, rgba: [%.2f, %.2f, %.2f, %.2f]", red, green, blue, alpha);
|
||||
GLenum result = MG_State::glClearColor(red, green, blue, alpha);
|
||||
if (result == GL_NO_ERROR) return;
|
||||
MG_State::SetError(result);
|
||||
MG_Util::Debug::LogE("Error setting clear color: %s", MG_Util::Debug::GLEnumToString(result));
|
||||
}
|
||||
|
||||
void ClearDepth(GLdouble depth) {
|
||||
MG_Util::Debug::LogD("glClearDepth, depth: %.3f", depth);
|
||||
GLenum result = MG_State::glClearDepth(depth);
|
||||
if (result == GL_NO_ERROR) return;
|
||||
MG_State::SetError(result);
|
||||
MG_Util::Debug::LogE("Error setting clear depth: %s", MG_Util::Debug::GLEnumToString(result));
|
||||
}
|
||||
|
||||
void ColorMask(GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha) {
|
||||
MG_Util::Debug::LogD("glColorMask, rgba: [%d, %d, %d, %d]", red, green, blue, alpha);
|
||||
GLenum result = MG_State::glColorMask(red, green, blue, alpha);
|
||||
if (result == GL_NO_ERROR) return;
|
||||
MG_State::SetError(result);
|
||||
MG_Util::Debug::LogE("Error setting color mask: %s", MG_Util::Debug::GLEnumToString(result));
|
||||
}
|
||||
|
||||
void DepthFunc(GLenum func) {
|
||||
MG_Util::Debug::LogD("glDepthFunc, func: %s", MG_Util::Debug::GLEnumToString(func));
|
||||
GLenum result = MG_State::glDepthFunc(func);
|
||||
if (result == GL_NO_ERROR) return;
|
||||
MG_State::SetError(result);
|
||||
MG_Util::Debug::LogE("Error setting depth func: %s", MG_Util::Debug::GLEnumToString(result));
|
||||
}
|
||||
|
||||
void DepthMask(GLboolean flag) {
|
||||
MG_Util::Debug::LogD("glDepthMask, flag: %d", flag);
|
||||
GLenum result = MG_State::glDepthMask(flag);
|
||||
if (result == GL_NO_ERROR) return;
|
||||
MG_State::SetError(result);
|
||||
MG_Util::Debug::LogE("Error setting depth mask: %s", MG_Util::Debug::GLEnumToString(result));
|
||||
}
|
||||
|
||||
void Viewport(GLint x, GLint y, GLsizei width, GLsizei height) {
|
||||
MG_Util::Debug::LogD("glViewport, x: %d, y: %d, width: %d, height: %d",
|
||||
x, y, width, height);
|
||||
GLenum result = MG_State::glViewport(x, y, width, height);
|
||||
if (result == GL_NO_ERROR) return;
|
||||
MG_State::SetError(result);
|
||||
MG_Util::Debug::LogE("Error setting viewport: %s", MG_Util::Debug::GLEnumToString(result));
|
||||
}
|
||||
|
||||
void PixelStorei(GLenum pname, GLint param) {
|
||||
MG_Util::Debug::LogD("glPixelStorei, pname: %d, param: %d", pname, param);
|
||||
GLenum result = MG_State::SetPixelStoreInt(pname,param);
|
||||
if (result == GL_NO_ERROR)
|
||||
return;
|
||||
MG_State::SetError(result);
|
||||
MG_Util::Debug::LogE("Error from MG State: %s", MG_Util::Debug::GLEnumToString(result));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
//
|
||||
// Created by BZLZHH on 2025/3/15.
|
||||
//
|
||||
|
||||
#ifndef MOBILEGL_GL_COMMON_H
|
||||
#define MOBILEGL_GL_COMMON_H
|
||||
|
||||
#include "../../../../Includes.h"
|
||||
|
||||
namespace MG_GL::GL {
|
||||
void ClearDepth(GLdouble depth);
|
||||
void ClearColor(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha);
|
||||
void Clear(GLbitfield mask);
|
||||
void PixelStorei(GLenum pname, GLint param);
|
||||
void Enable(GLenum cap);
|
||||
void Disable(GLenum cap);
|
||||
void BlendFunc(GLenum sfactor, GLenum dfactor);
|
||||
void BlendFuncSeparate(GLenum srcRGB, GLenum dstRGB, GLenum srcAlpha, GLenum dstAlpha);
|
||||
void ColorMask(GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha);
|
||||
void DepthFunc(GLenum func);
|
||||
void DepthMask(GLboolean flag);
|
||||
void Viewport(GLint x, GLint y, GLsizei width, GLsizei height);
|
||||
}
|
||||
|
||||
#endif //MOBILEGL_GL_COMMON_H
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,17 @@
|
||||
//
|
||||
// Created by BZLZHH on 2025/3/15.
|
||||
//
|
||||
|
||||
#ifndef MOBILEGL_GL_DRAWING_H
|
||||
#define MOBILEGL_GL_DRAWING_H
|
||||
#include "../../../../Includes.h"
|
||||
namespace MG_GL::GL {
|
||||
void DrawElements(GLenum mode, GLsizei count, GLenum type, const void *indices);
|
||||
void DrawArrays(GLenum mode, GLint first, GLsizei count);
|
||||
void DrawElementsBaseVertex(GLenum mode, GLsizei count, GLenum type, const GLvoid *indices, GLint basevertex);
|
||||
void MultiDrawElements(GLenum mode, const GLsizei *count, GLenum type, const GLvoid *const *indices, GLsizei drawcount);
|
||||
void MultiDrawElementsBaseVertex(GLenum mode, const GLsizei *count, GLenum type, const GLvoid *const *indices, GLsizei drawcount, const GLint *basevertex);
|
||||
void DrawBuffer(GLenum buf);
|
||||
}
|
||||
|
||||
#endif //MOBILEGL_GL_DRAWING_H
|
||||
@@ -0,0 +1,68 @@
|
||||
//
|
||||
// Created by BZLZHH on 2025/3/15.
|
||||
//
|
||||
|
||||
#include "GL_Framebuffer.h"
|
||||
|
||||
namespace MG_GL::GL {
|
||||
void GenFramebuffers(GLsizei n, GLuint* framebuffers) {
|
||||
MG_Util::Debug::LogD("glGenFramebuffers, n: %d, framebuffers: %p", n, framebuffers);
|
||||
GLenum result = MG_State::CreateFramebuffers(n, framebuffers);
|
||||
if (result == GL_NO_ERROR) return;
|
||||
MG_State::SetError(result);
|
||||
MG_Util::Debug::LogE("Framebuffer generation failed: %s", MG_Util::Debug::GLEnumToString(result));
|
||||
}
|
||||
|
||||
void DeleteFramebuffers(GLsizei n, const GLuint* framebuffers) {
|
||||
MG_Util::Debug::LogD("glDeleteFramebuffers, n: %d, framebuffers: %p", n, framebuffers);
|
||||
for (GLsizei i = 0; i < n; ++i) {
|
||||
GLenum result = MG_State::DeleteFramebuffer(framebuffers[i]);
|
||||
if (result != GL_NO_ERROR) {
|
||||
MG_State::SetError(result);
|
||||
MG_Util::Debug::LogE("Failed to delete framebuffer %u: %s",
|
||||
framebuffers[i], MG_Util::Debug::GLEnumToString(result));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BindFramebuffer(GLenum target, GLuint framebuffer) {
|
||||
MG_Util::Debug::LogD("glBindFramebuffer, target: %s, fb: %u",
|
||||
MG_Util::Debug::GLEnumToString(target), framebuffer);
|
||||
GLenum result = MG_State::BindFramebuffer(target, framebuffer);
|
||||
if (result == GL_NO_ERROR) return;
|
||||
MG_State::SetError(result);
|
||||
MG_Util::Debug::LogE("Framebuffer bind error: %s", MG_Util::Debug::GLEnumToString(result));
|
||||
}
|
||||
|
||||
void FramebufferTexture2D(GLenum target, GLenum attachment, GLenum textarget,
|
||||
GLuint texture, GLint level) {
|
||||
MG_Util::Debug::LogD("glFramebufferTexture2D, target: %s, attach: %s, textarget: %s, tex: %u, level: %d",
|
||||
MG_Util::Debug::GLEnumToString(target),
|
||||
MG_Util::Debug::GLEnumToString(attachment),
|
||||
MG_Util::Debug::GLEnumToString(textarget),
|
||||
texture, level);
|
||||
|
||||
GLenum result = MG_State::AttachTexture2DToFramebuffer(
|
||||
target, attachment, textarget, texture, level
|
||||
);
|
||||
|
||||
if (result == GL_NO_ERROR) return;
|
||||
MG_State::SetError(result);
|
||||
MG_Util::Debug::LogE("Texture attachment failed: %s", MG_Util::Debug::GLEnumToString(result));
|
||||
}
|
||||
|
||||
GLenum CheckFramebufferStatus(GLenum target) {
|
||||
MG_Util::Debug::LogD("glCheckFramebufferStatus, target: %s",
|
||||
MG_Util::Debug::GLEnumToString(target));
|
||||
GLenum result = MG_State::ValidateFramebufferCompleteness(target);
|
||||
if (result >= GL_FRAMEBUFFER_COMPLETE) {
|
||||
MG_Util::Debug::LogD("Framebuffer status: %s",
|
||||
MG_Util::Debug::GLEnumToString(result));
|
||||
return result;
|
||||
}
|
||||
MG_State::SetError(result);
|
||||
MG_Util::Debug::LogE("Framebuffer incomplete: %s",
|
||||
MG_Util::Debug::GLEnumToString(result));
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
//
|
||||
// Created by BZLZHH on 2025/3/15.
|
||||
//
|
||||
|
||||
#ifndef MOBILEGL_GL_FRAMEBUFFER_H
|
||||
#define MOBILEGL_GL_FRAMEBUFFER_H
|
||||
|
||||
#include "../../../../Includes.h"
|
||||
|
||||
namespace MG_GL::GL {
|
||||
void GenFramebuffers(GLsizei n, GLuint* framebuffers);
|
||||
void DeleteFramebuffers(GLsizei n, const GLuint* framebuffers);
|
||||
void BindFramebuffer(GLenum target, GLuint framebuffer);
|
||||
void FramebufferTexture2D(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level);
|
||||
GLenum CheckFramebufferStatus(GLenum target);
|
||||
void BlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter);
|
||||
}
|
||||
|
||||
#endif //MOBILEGL_GL_FRAMEBUFFER_H
|
||||
+2746
-3128
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,10 @@
|
||||
//
|
||||
// Created by BZLZHH on 2025/3/15.
|
||||
//
|
||||
|
||||
#ifndef MOBILEGL_GL_FUNCS_DEFINITIONS_H
|
||||
#define MOBILEGL_GL_FUNCS_DEFINITIONS_H
|
||||
|
||||
#include "../../../../Includes.h"
|
||||
|
||||
#endif //MOBILEGL_GL_FUNCS_DEFINITIONS_H
|
||||
@@ -0,0 +1,93 @@
|
||||
//
|
||||
// Created by BZLZHH on 2025/3/15.
|
||||
//
|
||||
|
||||
#include "BackendInfo.h"
|
||||
|
||||
namespace MG_GL::Getter {
|
||||
const std::string GetBackendName() {
|
||||
#if BACKEND_TYPE == BACKEND_VULKAN
|
||||
return "Vulkan";
|
||||
#elif BACKEND_TYPE == BACKEND_METAL
|
||||
return "Metal";
|
||||
#elif BACKEND_TYPE == BACKEND_GLES
|
||||
return "OpenGL ES";
|
||||
#else
|
||||
return "<Unknown Backend>";
|
||||
#endif
|
||||
}
|
||||
|
||||
const std::string GetMGName() {
|
||||
#if BACKEND_TYPE == BACKEND_VULKAN
|
||||
return "MobileGluvk";
|
||||
#elif BACKEND_TYPE == BACKEND_METAL
|
||||
return "MobileGlumtl";
|
||||
#elif BACKEND_TYPE == BACKEND_GLES
|
||||
return "MobileGlues";
|
||||
#else
|
||||
return "<Unknown MobileGL Version>";
|
||||
#endif
|
||||
}
|
||||
|
||||
void LogMGInfo() {
|
||||
std::string MGName = MG_GL::Getter::GetMGName() + " (MobileGL Core)";
|
||||
|
||||
std::string MGVersion = std::to_string(MG_Global::MG::VersionMajor) + "."
|
||||
+ std::to_string(MG_Global::MG::VersionMinor) + "."
|
||||
+ std::to_string(MG_Global::MG::VersionRevision);
|
||||
if (MG_Global::MG::VersionPatch != 0)
|
||||
MGVersion += "." + std::to_string(MG_Global::MG::VersionPatch);
|
||||
MGVersion += MG_Global::MG::VersionSuffix;
|
||||
|
||||
std::string GLVersion = std::to_string(MG_Global::GL::GLVersionMajor) + "."
|
||||
+ std::to_string(MG_Global::GL::GLVersionMinor) + "."
|
||||
+ std::to_string(MG_Global::GL::GLVersionRevision);
|
||||
|
||||
std::string backendName = MG_GL::Getter::GetBackendName();
|
||||
|
||||
std::string backendVersion;
|
||||
#if BACKEND_TYPE == BACKEND_VULKAN
|
||||
VkApplicationInfo appInfo = {};
|
||||
appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
|
||||
appInfo.pApplicationName = "Vulkan Version Query";
|
||||
appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0);
|
||||
appInfo.pEngineName = "No Engine";
|
||||
appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0);
|
||||
appInfo.apiVersion = VK_API_VERSION_1_0;
|
||||
VkInstanceCreateInfo createInfo = {};
|
||||
createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
|
||||
createInfo.pApplicationInfo = &appInfo;
|
||||
VkInstance instance;
|
||||
if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) {
|
||||
throw std::runtime_error("Failed to create Vulkan instance!");
|
||||
}
|
||||
uint32_t deviceCount = 0;
|
||||
vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr);
|
||||
if (deviceCount == 0) {
|
||||
throw std::runtime_error("No Vulkan devices found!");
|
||||
}
|
||||
VkPhysicalDevice physicalDevice;
|
||||
vkEnumeratePhysicalDevices(instance, &deviceCount, &physicalDevice);
|
||||
VkPhysicalDeviceProperties deviceProperties;
|
||||
vkGetPhysicalDeviceProperties(physicalDevice, &deviceProperties);
|
||||
uint32_t major = VK_VERSION_MAJOR(deviceProperties.apiVersion);
|
||||
uint32_t minor = VK_VERSION_MINOR(deviceProperties.apiVersion);
|
||||
uint32_t patch = VK_VERSION_PATCH(deviceProperties.apiVersion);
|
||||
vkDestroyInstance(instance, nullptr);
|
||||
|
||||
backendVersion = "Vulkan " + std::to_string(major) + "." +
|
||||
std::to_string(minor)+"." + std::to_string(patch);
|
||||
#elif BACKEND_TYPE == BACKEND_GLES
|
||||
backendVersion = std::string((const char*)::GLES::glGetString(GL_VERSION));
|
||||
#else
|
||||
backendVersion = "<Unknown Backend>";
|
||||
#endif
|
||||
|
||||
MG_Util::Debug::LogI("MobileGL Renderer Info:");
|
||||
MG_Util::Debug::LogI("----%s:", MGName.c_str());
|
||||
MG_Util::Debug::LogI("--------MobileGL Version: %s", MGVersion.c_str());
|
||||
MG_Util::Debug::LogI("--------Target OpenGL Implementation Version: %s", GLVersion.c_str());
|
||||
MG_Util::Debug::LogI("--------Backend: %s", backendName.c_str());
|
||||
MG_Util::Debug::LogI("--------Backend Version: %s", backendVersion.c_str());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
//
|
||||
// Created by BZLZHH on 2025/3/15.
|
||||
//
|
||||
|
||||
#ifndef MOBILEGL_BACKENDINFO_H
|
||||
#define MOBILEGL_BACKENDINFO_H
|
||||
|
||||
#include "../../../../Includes.h"
|
||||
|
||||
namespace MG_GL::Getter {
|
||||
const std::string GetBackendName();
|
||||
const std::string GetMGName();
|
||||
void LogMGInfo();
|
||||
}
|
||||
|
||||
#endif //MOBILEGL_BACKENDINFO_H
|
||||
@@ -0,0 +1,320 @@
|
||||
//
|
||||
// Created by BZLZHH on 2025/3/15.
|
||||
//
|
||||
|
||||
#include "GL_Getter.h"
|
||||
|
||||
namespace MG_GL::GL {
|
||||
static std::string rendererString;
|
||||
static bool loggedMGInfo;
|
||||
const GLubyte* GetString(GLenum name) {
|
||||
if (!loggedMGInfo) {
|
||||
loggedMGInfo = true;
|
||||
MG_GL::Getter::LogMGInfo();
|
||||
}
|
||||
MG_Util::Debug::LogD("glGetString, name: %s", MG_Util::Debug::GLEnumToString(name));
|
||||
switch (name) {
|
||||
case GL_VENDOR:
|
||||
return (const GLubyte *)"MobileGL-Dev";
|
||||
case GL_VERSION: {
|
||||
static std::string versionStr;
|
||||
if (versionStr.empty()) {
|
||||
versionStr = std::to_string(MG_Global::GL::GLVersionMajor) + "."
|
||||
+ std::to_string(MG_Global::GL::GLVersionMinor) + "."
|
||||
+ std::to_string(MG_Global::GL::GLVersionRevision)
|
||||
+ " "+ MG_GL::Getter::GetMGName() +
|
||||
", MobileGL Core, " + MG_GL::Getter::GetBackendName() + " Backend, Version "
|
||||
+ std::to_string(MG_Global::MG::VersionMajor) + "."
|
||||
+ std::to_string(MG_Global::MG::VersionMinor) + "."
|
||||
+ std::to_string(MG_Global::MG::VersionRevision);
|
||||
if (MG_Global::MG::VersionPatch != 0)
|
||||
versionStr += "." + std::to_string(MG_Global::MG::VersionPatch);
|
||||
versionStr += MG_Global::MG::VersionSuffix;
|
||||
}
|
||||
return (const GLubyte *)versionStr.c_str();
|
||||
}
|
||||
|
||||
case GL_RENDERER:
|
||||
{
|
||||
/*
|
||||
if (rendererString == std::string("")) {
|
||||
const char* gpuName = getGpuName();
|
||||
const char* glesName = getGLESName();
|
||||
rendererString = std::string(gpuName) + " | " + std::string(glesName);
|
||||
}
|
||||
return (const GLubyte *)rendererString.c_str();
|
||||
*/
|
||||
return (const GLubyte *)(MG_GL::Getter::GetMGName() + " (MobileGL Core) Renderer").c_str();
|
||||
}
|
||||
case GL_SHADING_LANGUAGE_VERSION:
|
||||
return (const GLubyte *) "4.50 MobileGL with glslang";
|
||||
case GL_EXTENSIONS:
|
||||
return (const GLubyte *) "OpenGL11 "
|
||||
"OpenGL12 "
|
||||
"OpenGL13 "
|
||||
"OpenGL14 "
|
||||
"OpenGL15 "
|
||||
"OpenGL20 "
|
||||
"OpenGL21 "
|
||||
"OpenGL30 ";
|
||||
default:
|
||||
return (const GLubyte *) "Unknown enum";
|
||||
}
|
||||
}
|
||||
|
||||
const GLubyte* GetStringi(GLenum name, GLuint index) {
|
||||
MG_Util::Debug::LogD("glGetStringi, name: %s, index: %d", MG_Util::Debug::GLEnumToString(name), index);
|
||||
if (name != GL_EXTENSIONS)
|
||||
return (const GLubyte*)"";
|
||||
typedef struct {
|
||||
GLenum name;
|
||||
const char** parts;
|
||||
GLuint count;
|
||||
} StringCache;
|
||||
static StringCache caches[] = {
|
||||
{GL_EXTENSIONS, nullptr, 0},
|
||||
};
|
||||
static int initialized = 0;
|
||||
if (!initialized) {
|
||||
for (auto & cache : caches) {
|
||||
GLenum target = cache.name;
|
||||
const GLubyte* str = nullptr;
|
||||
const char* delimiter = " ";
|
||||
str = GetString(GL_EXTENSIONS);
|
||||
|
||||
char* copy = strdup((const char*)str);
|
||||
char* token = strtok(copy, delimiter);
|
||||
while (token) {
|
||||
cache.parts = (const char**)realloc(cache.parts, (cache.count + 1) * sizeof(char*));
|
||||
cache.parts[cache.count++] = strdup(token);
|
||||
token = strtok(nullptr, delimiter);
|
||||
}
|
||||
free(copy);
|
||||
}
|
||||
initialized = 1;
|
||||
}
|
||||
|
||||
for (auto & cache : caches) {
|
||||
if (cache.name == name) {
|
||||
if (index >= cache.count) {
|
||||
return nullptr;
|
||||
}
|
||||
return (const GLubyte*)cache.parts[index];
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
GLenum GetError() {
|
||||
GLenum error = MG_State::GetError();
|
||||
MG_Util::Debug::LogD("glGetError -> %s", MG_Util::Debug::GLEnumToString(error));
|
||||
return error;
|
||||
}
|
||||
|
||||
void GetIntegerv(GLenum pname, GLint *params) {
|
||||
MG_Util::Debug::LogD("glGetIntegerv, pname: %s", MG_Util::Debug::GLEnumToString(pname));
|
||||
if (!params) {
|
||||
MG_State::SetError(GL_INVALID_VALUE);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (pname) {
|
||||
// General
|
||||
case GL_CONTEXT_PROFILE_MASK:
|
||||
params[0] = GL_CONTEXT_CORE_PROFILE_BIT;
|
||||
break;
|
||||
case GL_NUM_EXTENSIONS:
|
||||
static GLint num_extensions = -1;
|
||||
if (num_extensions == -1) {
|
||||
const GLubyte* ext_str = MG_GL::GL::GetString(GL_EXTENSIONS);
|
||||
char *copy = strdup((const char *) ext_str);
|
||||
char *token = strtok(copy, " ");
|
||||
num_extensions = 0;
|
||||
while (token) {
|
||||
num_extensions++;
|
||||
token = strtok(nullptr, " ");
|
||||
}
|
||||
free(copy);
|
||||
}
|
||||
params[0] = num_extensions;
|
||||
break;
|
||||
case GL_MAJOR_VERSION:
|
||||
params[0] = MG_Global::GL::GLVersionMajor;
|
||||
break;
|
||||
case GL_MINOR_VERSION:
|
||||
params[0] = MG_Global::GL::GLVersionMinor;
|
||||
break;
|
||||
|
||||
// Viewport and scissor
|
||||
case GL_VIEWPORT:
|
||||
memcpy(params, MG_State_T::commonState->viewport, 4 * sizeof(GLint));
|
||||
break;
|
||||
case GL_SCISSOR_BOX:
|
||||
// TODO: memcpy(params, MG_State_T::commonState->scissorBox, 4 * sizeof(GLint));
|
||||
break;
|
||||
case GL_MAX_VIEWPORT_DIMS: {
|
||||
static const GLint maxDims[2] = {16384, 16384};
|
||||
memcpy(params, maxDims, sizeof(maxDims));
|
||||
break;
|
||||
}
|
||||
|
||||
// Buffer bindings
|
||||
case GL_ARRAY_BUFFER_BINDING:
|
||||
params[0] = static_cast<GLint>(MG_State_T::bufferState->GetCurrentBinding(GL_ARRAY_BUFFER));
|
||||
break;
|
||||
|
||||
case GL_ELEMENT_ARRAY_BUFFER_BINDING:
|
||||
params[0] = static_cast<GLint>(MG_State_T::vertexArrayState->GetBoundElementBuffer());
|
||||
break;
|
||||
case GL_DRAW_FRAMEBUFFER_BINDING:
|
||||
params[0] = static_cast<GLint>(MG_State_T::framebufferState->currentBindings_[GL_DRAW_FRAMEBUFFER]);
|
||||
break;
|
||||
case GL_READ_FRAMEBUFFER_BINDING:
|
||||
params[0] = static_cast<GLint>(MG_State_T::framebufferState->currentBindings_[GL_READ_FRAMEBUFFER]);
|
||||
break;
|
||||
case GL_RENDERBUFFER_BINDING:
|
||||
// TODO: params[0] = static_cast<GLint>(MG_State_T::framebufferState->currentRenderbuffer_);
|
||||
break;
|
||||
|
||||
// Program state
|
||||
case GL_CURRENT_PROGRAM:
|
||||
params[0] = static_cast<GLint>(MG_State_T::programState->currentProgram_);
|
||||
break;
|
||||
case GL_MAX_VERTEX_ATTRIBS:
|
||||
// TODO: Check the real value
|
||||
params[0] = 64;
|
||||
break;
|
||||
|
||||
// Texture state
|
||||
case GL_TEXTURE_BINDING_2D: {
|
||||
GLuint unit = MG_State_T::textureState->activeTextureUnit_;
|
||||
auto& textures = MG_State_T::textureState->textureUnits_[unit].boundTextures;
|
||||
params[0] = textures.count(GL_TEXTURE_2D) ? static_cast<GLint>(textures[GL_TEXTURE_2D]) : 0;
|
||||
break;
|
||||
}
|
||||
case GL_ACTIVE_TEXTURE:
|
||||
params[0] = GL_TEXTURE0 + MG_State_T::textureState->activeTextureUnit_;
|
||||
break;
|
||||
case GL_MAX_TEXTURE_SIZE:
|
||||
params[0] = 8192;
|
||||
break;
|
||||
|
||||
// Blend state
|
||||
case GL_BLEND_SRC_RGB:
|
||||
params[0] = static_cast<GLint>(MG_State_T::commonState->blendSrcRGB);
|
||||
break;
|
||||
case GL_BLEND_DST_RGB:
|
||||
params[0] = static_cast<GLint>(MG_State_T::commonState->blendDstRGB);
|
||||
break;
|
||||
case GL_BLEND_SRC_ALPHA:
|
||||
params[0] = static_cast<GLint>(MG_State_T::commonState->blendSrcAlpha);
|
||||
break;
|
||||
case GL_BLEND_DST_ALPHA:
|
||||
params[0] = static_cast<GLint>(MG_State_T::commonState->blendDstAlpha);
|
||||
break;
|
||||
|
||||
// Depth and stencil
|
||||
case GL_DEPTH_FUNC:
|
||||
params[0] = static_cast<GLint>(MG_State_T::commonState->depthFunc);
|
||||
break;
|
||||
case GL_DEPTH_WRITEMASK:
|
||||
params[0] = static_cast<GLint>(MG_State_T::commonState->depthMask);
|
||||
break;
|
||||
case GL_STENCIL_REF:
|
||||
// TODO: params[0] = MG_State_T::commonState->stencilRef;
|
||||
break;
|
||||
|
||||
// Capability enables
|
||||
case GL_BLEND:
|
||||
params[0] = MG_State_T::commonState->capabilities[GL_BLEND] ? GL_TRUE : GL_FALSE;
|
||||
break;
|
||||
case GL_DEPTH_TEST:
|
||||
params[0] = MG_State_T::commonState->capabilities[GL_DEPTH_TEST] ? GL_TRUE : GL_FALSE;
|
||||
break;
|
||||
case GL_SCISSOR_TEST:
|
||||
params[0] = MG_State_T::commonState->capabilities[GL_SCISSOR_TEST] ? GL_TRUE : GL_FALSE;
|
||||
break;
|
||||
|
||||
// Pixel operations
|
||||
case GL_PACK_ALIGNMENT:
|
||||
params[0] = MG_State_T::commonState->QueryPixelStoreInt(GL_PACK_ALIGNMENT);
|
||||
break;
|
||||
case GL_UNPACK_ALIGNMENT:
|
||||
params[0] = MG_State_T::commonState->QueryPixelStoreInt(GL_UNPACK_ALIGNMENT);
|
||||
break;
|
||||
|
||||
// Hardware limits, TODO: Check the real values
|
||||
case GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS:
|
||||
params[0] = 32;
|
||||
break;
|
||||
case GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS:
|
||||
params[0] = 32;
|
||||
break;
|
||||
case GL_MAX_TEXTURE_IMAGE_UNITS:
|
||||
params[0] = 32;
|
||||
break;
|
||||
case GL_MAX_RENDERBUFFER_SIZE:
|
||||
params[0] = 65537;
|
||||
break;
|
||||
case GL_MAX_DRAW_BUFFERS:
|
||||
params[0] = 32;
|
||||
break;
|
||||
case GL_MAX_COLOR_ATTACHMENTS:
|
||||
params[0] = 32;
|
||||
break;
|
||||
|
||||
// Color state
|
||||
case GL_COLOR_CLEAR_VALUE: {
|
||||
const auto& c = MG_State_T::commonState->clearColor;
|
||||
params[0] = static_cast<GLint>(c[0] * 0x7FFF);
|
||||
params[1] = static_cast<GLint>(c[1] * 0x7FFF);
|
||||
params[2] = static_cast<GLint>(c[2] * 0x7FFF);
|
||||
params[3] = static_cast<GLint>(c[3] * 0x7FFF);
|
||||
break;
|
||||
}
|
||||
case GL_COLOR_WRITEMASK:
|
||||
params[0] = MG_State_T::commonState->colorMask[0];
|
||||
params[1] = MG_State_T::commonState->colorMask[1];
|
||||
params[2] = MG_State_T::commonState->colorMask[2];
|
||||
params[3] = MG_State_T::commonState->colorMask[3];
|
||||
break;
|
||||
|
||||
// Stencil operations
|
||||
case GL_STENCIL_CLEAR_VALUE:
|
||||
// TODO: params[0] = MG_State_T::commonState->stencilClearValue;
|
||||
break;
|
||||
case GL_STENCIL_BITS:
|
||||
// TODO: Check the real value
|
||||
params[0] = 8;
|
||||
break;
|
||||
|
||||
// Implementation limits
|
||||
case GL_SUBPIXEL_BITS:
|
||||
// TODO: Check the real value
|
||||
params[0] = 4;
|
||||
break;
|
||||
case GL_NUM_COMPRESSED_TEXTURE_FORMATS:
|
||||
// TODO: params[0] = static_cast<GLint>(MG_State_T::textureState->compressedFormats_.size());
|
||||
break;
|
||||
case GL_COMPRESSED_TEXTURE_FORMATS:
|
||||
// TODO: Implement GL_COMPRESSED_TEXTURE_FORMATS
|
||||
break;
|
||||
|
||||
// Polygon state
|
||||
case GL_POLYGON_OFFSET_FACTOR:
|
||||
// TODO: params[0] = static_cast<GLint>(MG_State_T::commonState->polygonOffsetFactor);
|
||||
break;
|
||||
case GL_POLYGON_OFFSET_UNITS:
|
||||
// TODO: params[0] = static_cast<GLint>(MG_State_T::commonState->polygonOffsetUnits);
|
||||
break;
|
||||
|
||||
// Others...
|
||||
default:
|
||||
MG_Util::Debug::LogE("glGetIntegerv: Invalid enum %s (0x%X)", MG_Util::Debug::GLEnumToString(pname), pname);
|
||||
MG_State::SetError(GL_INVALID_ENUM);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
//
|
||||
// Created by BZLZHH on 2025/3/15.
|
||||
//
|
||||
|
||||
#ifndef MOBILEGL_GL_GETTER_H
|
||||
#define MOBILEGL_GL_GETTER_H
|
||||
|
||||
#include "../../../../Includes.h"
|
||||
|
||||
namespace MG_GL::GL {
|
||||
const GLubyte* GetString(GLenum name);
|
||||
const GLubyte* GetStringi(GLenum name, GLuint index);
|
||||
GLenum GetError();
|
||||
void GetIntegerv(GLenum pname, GLint *params);
|
||||
}
|
||||
|
||||
#endif //MOBILEGL_GL_GETTER_H
|
||||
@@ -0,0 +1,298 @@
|
||||
//
|
||||
// Created by BZLZHH on 2025/3/15.
|
||||
//
|
||||
|
||||
#include "GL_Program.h"
|
||||
|
||||
namespace MG_GL::GL {
|
||||
GLuint CreateShader(GLenum type) {
|
||||
MG_Util::Debug::LogD("glCreateShader, type: %s", MG_Util::Debug::GLEnumToString(type));
|
||||
GLuint shader;
|
||||
GLenum result = MG_State::CreateShader(type, &shader);
|
||||
if (result == GL_NO_ERROR) {
|
||||
MG_Util::Debug::LogD("Created shader ID: %u", shader);
|
||||
return shader;
|
||||
}
|
||||
MG_State::SetError(result);
|
||||
MG_Util::Debug::LogE("Error creating shader: %s", MG_Util::Debug::GLEnumToString(result));
|
||||
return 0;
|
||||
}
|
||||
|
||||
GLuint CreateProgram() {
|
||||
MG_Util::Debug::LogD("glCreateProgram");
|
||||
GLuint program;
|
||||
GLenum result = MG_State::CreateProgram(&program);
|
||||
if (result == GL_NO_ERROR) {
|
||||
MG_Util::Debug::LogD("Created program ID: %u", program);
|
||||
return program;
|
||||
}
|
||||
MG_State::SetError(result);
|
||||
MG_Util::Debug::LogE("Error creating program: %s", MG_Util::Debug::GLEnumToString(result));
|
||||
return 0;
|
||||
}
|
||||
|
||||
void DeleteShader(GLuint shader) {
|
||||
MG_Util::Debug::LogD("glDeleteShader, shader: %u", shader);
|
||||
GLenum result = MG_State::DeleteShader(shader);
|
||||
if (result == GL_NO_ERROR) return;
|
||||
MG_State::SetError(result);
|
||||
MG_Util::Debug::LogE("Error deleting shader: %s", MG_Util::Debug::GLEnumToString(result));
|
||||
}
|
||||
|
||||
void DeleteProgram(GLuint program) {
|
||||
MG_Util::Debug::LogD("glDeleteProgram, program: %u", program);
|
||||
GLenum result = MG_State::DeleteProgram(program);
|
||||
if (result == GL_NO_ERROR) return;
|
||||
MG_State::SetError(result);
|
||||
MG_Util::Debug::LogE("Error deleting program: %s", MG_Util::Debug::GLEnumToString(result));
|
||||
}
|
||||
|
||||
void AttachShader(GLuint program, GLuint shader) {
|
||||
MG_Util::Debug::LogD("glAttachShader, program: %u, shader: %u", program, shader);
|
||||
GLenum result = MG_State::LinkShaderToProgram(program, shader);
|
||||
if (result == GL_NO_ERROR) return;
|
||||
MG_State::SetError(result);
|
||||
MG_Util::Debug::LogE("Error attaching shader: %s", MG_Util::Debug::GLEnumToString(result));
|
||||
}
|
||||
|
||||
void ShaderSource(GLuint shader, GLsizei count, const GLchar *const*string, const GLint* length) {
|
||||
MG_Util::Debug::LogD("glShaderSource, shader: %u, count: %d", shader, count);
|
||||
GLenum result = MG_State::UploadShaderSource(shader, count, (const GLchar **)string, length);
|
||||
if (result == GL_NO_ERROR) return;
|
||||
MG_State::SetError(result);
|
||||
MG_Util::Debug::LogE("Error setting shader source: %s", MG_Util::Debug::GLEnumToString(result));
|
||||
}
|
||||
|
||||
void CompileShader(GLuint shader) {
|
||||
MG_Util::Debug::LogD("glCompileShader, shader: %u", shader);
|
||||
GLenum result = MG_State::BuildShaderStage(shader);
|
||||
if (result == GL_NO_ERROR) return;
|
||||
MG_State::SetError(result);
|
||||
MG_Util::Debug::LogE("Error compiling shader: %s", MG_Util::Debug::GLEnumToString(result));
|
||||
}
|
||||
|
||||
void LinkProgram(GLuint program) {
|
||||
MG_Util::Debug::LogD("glLinkProgram, program: %u", program);
|
||||
GLenum result = MG_State::FinalizeProgramPipeline(program);
|
||||
if (result == GL_NO_ERROR) return;
|
||||
MG_State::SetError(result);
|
||||
MG_Util::Debug::LogE("Error linking program: %s", MG_Util::Debug::GLEnumToString(result));
|
||||
}
|
||||
|
||||
void UseProgram(GLuint program) {
|
||||
MG_Util::Debug::LogD("glUseProgram, program: %u", program);
|
||||
GLenum result = MG_State::ActivateRenderProgram(program);
|
||||
if (result == GL_NO_ERROR) return;
|
||||
MG_State::SetError(result);
|
||||
MG_Util::Debug::LogE("Error using program: %s", MG_Util::Debug::GLEnumToString(result));
|
||||
}
|
||||
|
||||
void BindAttribLocation(GLuint program, GLuint index, const GLchar* name) {
|
||||
MG_Util::Debug::LogD("glBindAttribLocation, program: %u, index: %u, name: %s", program, index, name);
|
||||
GLenum result = MG_State::DefineProgramAttributeBinding(program, index, name);
|
||||
if (result == GL_NO_ERROR) return;
|
||||
MG_State::SetError(result);
|
||||
MG_Util::Debug::LogE("Error binding attribute: %s", MG_Util::Debug::GLEnumToString(result));
|
||||
}
|
||||
|
||||
GLint GetAttribLocation(GLuint program, const GLchar* name) {
|
||||
MG_Util::Debug::LogD("glGetAttribLocation, program: %u, name: %s", program, name);
|
||||
GLint location = MG_State::QueryProgramAttributeBinding(program, name);
|
||||
if (location != -1) return location;
|
||||
MG_State::SetError(GL_INVALID_OPERATION);
|
||||
MG_Util::Debug::LogE("Attribute not found: %s", name);
|
||||
return -1;
|
||||
}
|
||||
|
||||
GLint GetUniformLocation(GLuint program, const GLchar* name) {
|
||||
MG_Util::Debug::LogD("glGetUniformLocation, program: %u, name: %s", program, name);
|
||||
GLint location = MG_State::QueryProgramUniformLocation(program, name);
|
||||
if (location != -1) return location;
|
||||
MG_State::SetError(GL_INVALID_OPERATION);
|
||||
MG_Util::Debug::LogE("Uniform not found: %s", name);
|
||||
return -1;
|
||||
}
|
||||
|
||||
void GetProgramiv(GLuint program, GLenum pname, GLint* params) {
|
||||
MG_Util::Debug::LogD("glGetProgramiv, program: %u, pname: %s",
|
||||
program, MG_Util::Debug::GLEnumToString(pname));
|
||||
GLenum result = MG_State::QueryProgramStateIntVector(program, pname, params);
|
||||
if (result == GL_NO_ERROR) return;
|
||||
MG_State::SetError(result);
|
||||
MG_Util::Debug::LogE("Error getting program param: %s", MG_Util::Debug::GLEnumToString(result));
|
||||
}
|
||||
|
||||
void GetShaderiv(GLuint shader, GLenum pname, GLint* params) {
|
||||
MG_Util::Debug::LogD("glGetShaderiv, shader: %u, pname: %s",
|
||||
shader, MG_Util::Debug::GLEnumToString(pname));
|
||||
GLenum result = MG_State::QueryShaderStateIntVector(shader, pname, params);
|
||||
if (result == GL_NO_ERROR) return;
|
||||
MG_State::SetError(result);
|
||||
MG_Util::Debug::LogE("Error getting shader param: %s", MG_Util::Debug::GLEnumToString(result));
|
||||
}
|
||||
|
||||
void Uniform1f(GLint location, GLfloat v0) {
|
||||
MG_Util::Debug::LogD("glUniform1f, location: %d, v0: %f", location, v0);
|
||||
MG_State::UpdateProgramUniformFloat1(location, v0);
|
||||
}
|
||||
|
||||
void Uniform2f(GLint location, GLfloat v0, GLfloat v1) {
|
||||
MG_Util::Debug::LogD("glUniform2f, location: %d, v0: %f, v1: %f", location, v0, v1);
|
||||
MG_State::UpdateProgramUniformFloat2(location, v0, v1);
|
||||
}
|
||||
|
||||
void Uniform3f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2) {
|
||||
MG_Util::Debug::LogD("glUniform3f, location: %d, v0: %f, v1: %f, v2: %f", location, v0, v1, v2);
|
||||
MG_State::UpdateProgramUniformFloat3(location, v0, v1, v2);
|
||||
}
|
||||
|
||||
void Uniform4f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3) {
|
||||
MG_Util::Debug::LogD("glUniform4f, location: %d, v0: %f, v1: %f, v2: %f, v3: %f", location, v0, v1, v2, v3);
|
||||
MG_State::UpdateProgramUniformFloat4(location, v0, v1, v2, v3);
|
||||
}
|
||||
|
||||
void Uniform1fv(GLint location, GLsizei count, const GLfloat* value) {
|
||||
MG_Util::Debug::LogD("glUniform1fv, location: %d, count: %d", location, count);
|
||||
MG_State::UpdateProgramUniformFloatVector1(location, count, value);
|
||||
}
|
||||
|
||||
void Uniform2fv(GLint location, GLsizei count, const GLfloat* value) {
|
||||
MG_Util::Debug::LogD("glUniform2fv, location: %d, count: %d", location, count);
|
||||
MG_State::UpdateProgramUniformFloatVector2(location, count, value);
|
||||
}
|
||||
|
||||
void Uniform3fv(GLint location, GLsizei count, const GLfloat* value) {
|
||||
MG_Util::Debug::LogD("glUniform3fv, location: %d, count: %d", location, count);
|
||||
MG_State::UpdateProgramUniformFloatVector3(location, count, value);
|
||||
}
|
||||
|
||||
void Uniform4fv(GLint location, GLsizei count, const GLfloat* value) {
|
||||
MG_Util::Debug::LogD("glUniform4fv, location: %d, count: %d", location, count);
|
||||
MG_State::UpdateProgramUniformFloatVector4(location, count, value);
|
||||
}
|
||||
|
||||
void Uniform1i(GLint location, GLint v0) {
|
||||
MG_Util::Debug::LogD("glUniform1i, location: %d, v0: %d", location, v0);
|
||||
MG_State::UpdateProgramUniformInt1(location, v0);
|
||||
}
|
||||
|
||||
void Uniform2i(GLint location, GLint v0, GLint v1) {
|
||||
MG_Util::Debug::LogD("glUniform2i, location: %d, v0: %d, v1: %d", location, v0, v1);
|
||||
MG_State::UpdateProgramUniformInt2(location, v0, v1);
|
||||
}
|
||||
|
||||
void Uniform3i(GLint location, GLint v0, GLint v1, GLint v2) {
|
||||
MG_Util::Debug::LogD("glUniform3i, location: %d, v0: %d, v1: %d, v2: %d", location, v0, v1, v2);
|
||||
MG_State::UpdateProgramUniformInt3(location, v0, v1, v2);
|
||||
}
|
||||
|
||||
void Uniform4i(GLint location, GLint v0, GLint v1, GLint v2, GLint v3) {
|
||||
MG_Util::Debug::LogD("glUniform4i, location: %d, v0: %d, v1: %d, v2: %d, v3: %d", location, v0, v1, v2, v3);
|
||||
MG_State::UpdateProgramUniformInt4(location, v0, v1, v2, v3);
|
||||
}
|
||||
|
||||
void Uniform1iv(GLint location, GLsizei count, const GLint* value) {
|
||||
MG_Util::Debug::LogD("glUniform1iv, location: %d, count: %d", location, count);
|
||||
MG_State::UpdateProgramUniformIntVector1(location, count, value);
|
||||
}
|
||||
|
||||
void Uniform2iv(GLint location, GLsizei count, const GLint* value) {
|
||||
MG_Util::Debug::LogD("glUniform2iv, location: %d, count: %d", location, count);
|
||||
MG_State::UpdateProgramUniformIntVector2(location, count, value);
|
||||
}
|
||||
|
||||
void Uniform3iv(GLint location, GLsizei count, const GLint* value) {
|
||||
MG_Util::Debug::LogD("glUniform3iv, location: %d, count: %d", location, count);
|
||||
MG_State::UpdateProgramUniformIntVector3(location, count, value);
|
||||
}
|
||||
|
||||
void Uniform4iv(GLint location, GLsizei count, const GLint* value) {
|
||||
MG_Util::Debug::LogD("glUniform4iv, location: %d, count: %d", location, count);
|
||||
MG_State::UpdateProgramUniformIntVector4(location, count, value);
|
||||
}
|
||||
|
||||
void Uniform1ui(GLint location, GLuint v0) {
|
||||
MG_Util::Debug::LogD("glUniform1ui, location: %d, v0: %u", location, v0);
|
||||
MG_State::UpdateProgramUniformUInt1(location, v0);
|
||||
}
|
||||
|
||||
void Uniform2ui(GLint location, GLuint v0, GLuint v1) {
|
||||
MG_Util::Debug::LogD("glUniform2ui, location: %d, v0: %u, v1: %u", location, v0, v1);
|
||||
MG_State::UpdateProgramUniformUInt2(location, v0, v1);
|
||||
}
|
||||
|
||||
void Uniform3ui(GLint location, GLuint v0, GLuint v1, GLuint v2) {
|
||||
MG_Util::Debug::LogD("glUniform3ui, location: %d, v0: %u, v1: %u, v2: %u", location, v0, v1, v2);
|
||||
MG_State::UpdateProgramUniformUInt3(location, v0, v1, v2);
|
||||
}
|
||||
|
||||
void Uniform4ui(GLint location, GLuint v0, GLuint v1, GLuint v2, GLuint v3) {
|
||||
MG_Util::Debug::LogD("glUniform4ui, location: %d, v0: %u, v1: %u, v2: %u, v3: %u", location, v0, v1, v2, v3);
|
||||
MG_State::UpdateProgramUniformUInt4(location, v0, v1, v2, v3);
|
||||
}
|
||||
|
||||
void Uniform1uiv(GLint location, GLsizei count, const GLuint* value) {
|
||||
MG_Util::Debug::LogD("glUniform1uiv, location: %d, count: %d", location, count);
|
||||
MG_State::UpdateProgramUniformUIntVector1(location, count, value);
|
||||
}
|
||||
|
||||
void Uniform2uiv(GLint location, GLsizei count, const GLuint* value) {
|
||||
MG_Util::Debug::LogD("glUniform2uiv, location: %d, count: %d", location, count);
|
||||
MG_State::UpdateProgramUniformUIntVector2(location, count, value);
|
||||
}
|
||||
|
||||
void Uniform3uiv(GLint location, GLsizei count, const GLuint* value) {
|
||||
MG_Util::Debug::LogD("glUniform3uiv, location: %d, count: %d", location, count);
|
||||
MG_State::UpdateProgramUniformUIntVector3(location, count, value);
|
||||
}
|
||||
|
||||
void Uniform4uiv(GLint location, GLsizei count, const GLuint* value) {
|
||||
MG_Util::Debug::LogD("glUniform4uiv, location: %d, count: %d", location, count);
|
||||
MG_State::UpdateProgramUniformUIntVector4(location, count, value);
|
||||
}
|
||||
|
||||
void UniformMatrix2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat* value) {
|
||||
MG_Util::Debug::LogD("glUniformMatrix2fv, location: %d, count: %d, transpose: %d", location, count, transpose);
|
||||
MG_State::UpdateProgramUniformMatrix2x2Vector(location, count, transpose, value);
|
||||
}
|
||||
|
||||
void UniformMatrix3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat* value) {
|
||||
MG_Util::Debug::LogD("glUniformMatrix3fv, location: %d, count: %d, transpose: %d", location, count, transpose);
|
||||
MG_State::UpdateProgramUniformMatrix3x3Vector(location, count, transpose, value);
|
||||
}
|
||||
|
||||
void UniformMatrix4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat* value) {
|
||||
MG_Util::Debug::LogD("glUniformMatrix4fv, location: %d, count: %d, transpose: %d", location, count, transpose);
|
||||
MG_State::UpdateProgramUniformMatrix4x4Vector(location, count, transpose, value);
|
||||
}
|
||||
|
||||
void UniformMatrix2x3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat* value) {
|
||||
MG_Util::Debug::LogD("glUniformMatrix2x3fv, location: %d, count: %d, transpose: %d", location, count, transpose);
|
||||
MG_State::UpdateProgramUniformMatrix2x3Vector(location, count, transpose, value);
|
||||
}
|
||||
|
||||
void UniformMatrix2x4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat* value) {
|
||||
MG_Util::Debug::LogD("glUniformMatrix2x4fv, location: %d, count: %d, transpose: %d", location, count, transpose);
|
||||
MG_State::UpdateProgramUniformMatrix2x4Vector(location, count, transpose, value);
|
||||
}
|
||||
|
||||
void UniformMatrix3x2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat* value) {
|
||||
MG_Util::Debug::LogD("glUniformMatrix3x2fv, location: %d, count: %d, transpose: %d", location, count, transpose);
|
||||
MG_State::UpdateProgramUniformMatrix3x2Vector(location, count, transpose, value);
|
||||
}
|
||||
|
||||
void UniformMatrix3x4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat* value) {
|
||||
MG_Util::Debug::LogD("glUniformMatrix3x4fv, location: %d, count: %d, transpose: %d", location, count, transpose);
|
||||
MG_State::UpdateProgramUniformMatrix3x4Vector(location, count, transpose, value);
|
||||
}
|
||||
|
||||
void UniformMatrix4x2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat* value) {
|
||||
MG_Util::Debug::LogD("glUniformMatrix4x2fv, location: %d, count: %d, transpose: %d", location, count, transpose);
|
||||
MG_State::UpdateProgramUniformMatrix4x2Vector(location, count, transpose, value);
|
||||
}
|
||||
|
||||
void UniformMatrix4x3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat* value) {
|
||||
MG_Util::Debug::LogD("glUniformMatrix4x3fv, location: %d, count: %d, transpose: %d", location, count, transpose);
|
||||
MG_State::UpdateProgramUniformMatrix4x3Vector(location, count, transpose, value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
//
|
||||
// Created by BZLZHH on 2025/3/15.
|
||||
//
|
||||
|
||||
#ifndef MOBILEGL_GL_PROGRAM_H
|
||||
#define MOBILEGL_GL_PROGRAM_H
|
||||
|
||||
#include "../../../../Includes.h"
|
||||
|
||||
namespace MG_GL::GL {
|
||||
GLuint CreateShader(GLenum type);
|
||||
GLuint CreateProgram();
|
||||
void DeleteShader(GLuint shader);
|
||||
void DeleteProgram(GLuint program);
|
||||
void AttachShader(GLuint program, GLuint shader);
|
||||
void ShaderSource(GLuint shader, GLsizei count, const GLchar *const*string, const GLint* length);
|
||||
void CompileShader(GLuint shader);
|
||||
void LinkProgram(GLuint program);
|
||||
void UseProgram(GLuint program);
|
||||
void BindAttribLocation(GLuint program, GLuint index, const GLchar* name);
|
||||
GLint GetAttribLocation(GLuint program, const GLchar* name);
|
||||
GLint GetUniformLocation(GLuint program, const GLchar* name);
|
||||
void GetProgramiv(GLuint program, GLenum pname, GLint* params);
|
||||
void GetShaderiv(GLuint shader, GLenum pname, GLint* params);
|
||||
void Uniform1f(GLint location, GLfloat v0);
|
||||
void Uniform2f(GLint location, GLfloat v0, GLfloat v1);
|
||||
void Uniform3f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2);
|
||||
void Uniform4f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3);
|
||||
void Uniform1fv(GLint location, GLsizei count, const GLfloat* value);
|
||||
void Uniform2fv(GLint location, GLsizei count, const GLfloat* value);
|
||||
void Uniform3fv(GLint location, GLsizei count, const GLfloat* value);
|
||||
void Uniform4fv(GLint location, GLsizei count, const GLfloat* value);
|
||||
void Uniform1i(GLint location, GLint v0);
|
||||
void Uniform2i(GLint location, GLint v0, GLint v1);
|
||||
void Uniform3i(GLint location, GLint v0, GLint v1, GLint v2);
|
||||
void Uniform4i(GLint location, GLint v0, GLint v1, GLint v2, GLint v3);
|
||||
void Uniform1iv(GLint location, GLsizei count, const GLint* value);
|
||||
void Uniform2iv(GLint location, GLsizei count, const GLint* value);
|
||||
void Uniform3iv(GLint location, GLsizei count, const GLint* value);
|
||||
void Uniform4iv(GLint location, GLsizei count, const GLint* value);
|
||||
void Uniform1ui(GLint location, GLuint v0);
|
||||
void Uniform2ui(GLint location, GLuint v0, GLuint v1);
|
||||
void Uniform3ui(GLint location, GLuint v0, GLuint v1, GLuint v2);
|
||||
void Uniform4ui(GLint location, GLuint v0, GLuint v1, GLuint v2, GLuint v3);
|
||||
void Uniform1uiv(GLint location, GLsizei count, const GLuint* value);
|
||||
void Uniform2uiv(GLint location, GLsizei count, const GLuint* value);
|
||||
void Uniform3uiv(GLint location, GLsizei count, const GLuint* value);
|
||||
void Uniform4uiv(GLint location, GLsizei count, const GLuint* value);
|
||||
void UniformMatrix2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat* value);
|
||||
void UniformMatrix3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat* value);
|
||||
void UniformMatrix4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat* value);
|
||||
void UniformMatrix2x3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat* value);
|
||||
void UniformMatrix2x4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat* value);
|
||||
void UniformMatrix3x2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat* value);
|
||||
void UniformMatrix3x4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat* value);
|
||||
void UniformMatrix4x2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat* value);
|
||||
void UniformMatrix4x3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat* value);
|
||||
}
|
||||
|
||||
#endif //MOBILEGL_GL_PROGRAM_H
|
||||
@@ -0,0 +1,93 @@
|
||||
//
|
||||
// Created by BZLZHH on 2025/3/15.
|
||||
//
|
||||
|
||||
#include "GL_Texture.h"
|
||||
|
||||
namespace MG_GL::GL {
|
||||
void ActiveTexture(GLenum texture) {
|
||||
MG_Util::Debug::LogD("glActiveTexture, texture: %d", texture);
|
||||
GLenum result = MG_State::BindTextureUnit(texture);
|
||||
if (result == GL_NO_ERROR)
|
||||
return;
|
||||
MG_State::SetError(result);
|
||||
MG_Util::Debug::LogE("Error from MG State: %s", MG_Util::Debug::GLEnumToString(result));
|
||||
}
|
||||
|
||||
void BindTexture(GLenum target, GLuint texture) {
|
||||
MG_Util::Debug::LogD("glBindTexture, target: %d, texture: %d", target, texture);
|
||||
GLenum result = MG_State::BindTexture(target, texture);
|
||||
if (result == GL_NO_ERROR)
|
||||
return;
|
||||
MG_State::SetError(result);
|
||||
MG_Util::Debug::LogE("Error from MG State: %s", MG_Util::Debug::GLEnumToString(result));
|
||||
}
|
||||
|
||||
void DeleteTextures(GLsizei n, const GLuint* textures) {
|
||||
MG_Util::Debug::LogD("glDeleteTextures, n: %d, textures: %p", n, textures);
|
||||
GLenum result = MG_State::DeleteTextures(n, textures);
|
||||
if (result == GL_NO_ERROR)
|
||||
return;
|
||||
MG_State::SetError(result);
|
||||
MG_Util::Debug::LogE("Error from MG State: %s", MG_Util::Debug::GLEnumToString(result));
|
||||
}
|
||||
|
||||
void GenTextures(GLsizei n, GLuint* textures) {
|
||||
MG_Util::Debug::LogD("glGenTextures, n: %d, textures: %p", n, textures);
|
||||
GLenum result = MG_State::CreateTextures(n, textures);
|
||||
if (result == GL_NO_ERROR)
|
||||
return;
|
||||
MG_State::SetError(result);
|
||||
MG_Util::Debug::LogE("Error from MG State: %s", MG_Util::Debug::GLEnumToString(result));
|
||||
}
|
||||
|
||||
void TexImage2D(GLenum target, GLint level, GLint internalFormat, GLsizei width, GLsizei height, GLint border,
|
||||
GLenum format, GLenum type, const void* data) {
|
||||
MG_Util::Debug::LogD("glTexImage2D, target: %d, level: %d, internalFormat: %d, width: %d, height: %d, format: %d, type: %d, data: %p",
|
||||
target, level, internalFormat, width, height, format, type, data);
|
||||
GLenum result = MG_State::UploadTexture2D(target, level, internalFormat, width, height, border, format, type, data);
|
||||
if (result == GL_NO_ERROR)
|
||||
return;
|
||||
MG_State::SetError(result);
|
||||
MG_Util::Debug::LogE("Error from MG State: %s", MG_Util::Debug::GLEnumToString(result));
|
||||
}
|
||||
|
||||
void TexParameterf(GLenum target, GLenum pname, GLfloat param) {
|
||||
MG_Util::Debug::LogD("glTexParameterf, target: %d, pname: %d, param: %f", target, pname, param);
|
||||
GLenum result = MG_State::SetTexturePropertyFloat(target, pname, param);
|
||||
if (result == GL_NO_ERROR)
|
||||
return;
|
||||
MG_State::SetError(result);
|
||||
MG_Util::Debug::LogE("Error from MG State: %s", MG_Util::Debug::GLEnumToString(result));
|
||||
}
|
||||
|
||||
void TexParameteri(GLenum target, GLenum pname, GLint param) {
|
||||
MG_Util::Debug::LogD("glTexParameteri, target: %d, pname: %d, param: %d", target, pname, param);
|
||||
GLenum result = MG_State::SetTexturePropertyInt(target, pname, param);
|
||||
if (result == GL_NO_ERROR)
|
||||
return;
|
||||
MG_State::SetError(result);
|
||||
MG_Util::Debug::LogE("Error from MG State: %s", MG_Util::Debug::GLEnumToString(result));
|
||||
}
|
||||
|
||||
void TexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width,
|
||||
GLsizei height, GLenum format, GLenum type, const void* pixels) {
|
||||
MG_Util::Debug::LogD("glTexSubImage2D, target: %d, level: %d, xoffset: %d, yoffset: %d, width: %d, height: %d, format: %d, type: %d, pixels: %p",
|
||||
target, level, xoffset, yoffset, width, height, format, type, pixels);
|
||||
GLenum result = MG_State::UpdateTextureRegion2D(target, level, xoffset, yoffset, width, height, format, type, pixels);
|
||||
if (result == GL_NO_ERROR)
|
||||
return;
|
||||
MG_State::SetError(result);
|
||||
MG_Util::Debug::LogE("Error from MG State: %s", MG_Util::Debug::GLEnumToString(result));
|
||||
}
|
||||
|
||||
void GetTexLevelParameteriv(GLenum target, GLint level, GLenum pname, GLint* params) {
|
||||
MG_Util::Debug::LogD("glGetTexLevelParameteriv, target: %d, level: %d, pname: %d, params: %p",
|
||||
target, level, pname, params);
|
||||
GLenum result = MG_State::QueryTextureLevelPropertyIntVector(target, level, pname, params);
|
||||
if (result == GL_NO_ERROR)
|
||||
return;
|
||||
MG_State::SetError(result);
|
||||
MG_Util::Debug::LogE("Error from MG State: %s", MG_Util::Debug::GLEnumToString(result));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
//
|
||||
// Created by BZLZHH on 2025/3/15.
|
||||
//
|
||||
|
||||
#ifndef MOBILEGL_GL_TEXTURE_H
|
||||
#define MOBILEGL_GL_TEXTURE_H
|
||||
|
||||
#include "../../../../Includes.h"
|
||||
|
||||
namespace MG_GL::GL {
|
||||
void ActiveTexture(GLenum texture);
|
||||
void BindTexture(GLenum target, GLuint texture);
|
||||
void DeleteTextures(GLsizei n, const GLuint* textures);
|
||||
void GenTextures(GLsizei n, GLuint* textures);
|
||||
void TexImage2D(GLenum target, GLint level, GLint internalFormat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const void* data);
|
||||
void TexParameterf(GLenum target, GLenum pname, GLfloat param);
|
||||
void TexParameteri(GLenum target, GLenum pname, GLint param);
|
||||
void TexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const void* pixels);
|
||||
void GetTexLevelParameteriv(GLenum target, GLint level, GLenum pname, GLint* params);
|
||||
}
|
||||
|
||||
#endif //MOBILEGL_GL_TEXTURE_H
|
||||
@@ -0,0 +1,53 @@
|
||||
//
|
||||
// Created by BZLZHH on 2025/3/15.
|
||||
//
|
||||
|
||||
#include "GL_VertexArray.h"
|
||||
|
||||
namespace MG_GL::GL {
|
||||
void GenVertexArrays(GLsizei n, GLuint* arrays) {
|
||||
MG_Util::Debug::LogD("glGenVertexArrays, n: %d, arrays: %p", n, arrays);
|
||||
GLenum result = MG_State::GenVertexArraysNames(n, arrays);
|
||||
if (result == GL_NO_ERROR) return;
|
||||
MG_State::SetError(result);
|
||||
MG_Util::Debug::LogE("Error from MG State: %s", MG_Util::Debug::GLEnumToString(result));
|
||||
}
|
||||
|
||||
void BindVertexArray(GLuint array) {
|
||||
MG_Util::Debug::LogD("glBindVertexArray, array: %u", array);
|
||||
GLenum result = MG_State::BindVertexArray(array);
|
||||
if (result == GL_NO_ERROR) return;
|
||||
MG_State::SetError(result);
|
||||
MG_Util::Debug::LogE("Error from MG State: %s", MG_Util::Debug::GLEnumToString(result));
|
||||
}
|
||||
|
||||
void EnableVertexAttribArray(GLuint index) {
|
||||
MG_Util::Debug::LogD("glEnableVertexAttribArray, index: %u", index);
|
||||
GLenum result = MG_State::EnableVertexAttribArray(index);
|
||||
if (result == GL_NO_ERROR) return;
|
||||
MG_State::SetError(result);
|
||||
MG_Util::Debug::LogE("Error from MG State: %s", MG_Util::Debug::GLEnumToString(result));
|
||||
}
|
||||
|
||||
void VertexAttribPointer(GLuint index, GLint size, GLenum type,
|
||||
GLboolean normalized, GLsizei stride, const void* pointer) {
|
||||
MG_Util::Debug::LogD("glVertexAttribPointer, index: %u, size: %d, type: %s, norm: %d, stride: %d, ptr: %p",
|
||||
index, size, MG_Util::Debug::GLEnumToString(type), normalized, stride, pointer);
|
||||
|
||||
GLenum result = MG_State::SetVertexAttributeLayout(index, size, type, normalized, stride, pointer);
|
||||
if (result == GL_NO_ERROR) return;
|
||||
MG_State::SetError(result);
|
||||
MG_Util::Debug::LogE("Error from MG State: %s", MG_Util::Debug::GLEnumToString(result));
|
||||
}
|
||||
|
||||
void VertexAttribIPointer(GLuint index, GLint size, GLenum type,
|
||||
GLsizei stride, const void* pointer) {
|
||||
MG_Util::Debug::LogD("glVertexAttribIPointer, index: %u, size: %d, type: %s, stride: %d, ptr: %p",
|
||||
index, size, MG_Util::Debug::GLEnumToString(type), stride, pointer);
|
||||
|
||||
GLenum result = MG_State::SetVertexAttributeLayoutInt(index, size, type, stride, pointer);
|
||||
if (result == GL_NO_ERROR) return;
|
||||
MG_State::SetError(result);
|
||||
MG_Util::Debug::LogE("Error from MG State: %s", MG_Util::Debug::GLEnumToString(result));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
//
|
||||
// Created by BZLZHH on 2025/3/15.
|
||||
//
|
||||
|
||||
#ifndef MOBILEGL_GL_VERTEXARRAY_H
|
||||
#define MOBILEGL_GL_VERTEXARRAY_H
|
||||
|
||||
#include "../../../../Includes.h"
|
||||
|
||||
namespace MG_GL::GL {
|
||||
void GenVertexArrays(GLsizei n, GLuint* arrays);
|
||||
void BindVertexArray(GLuint array);
|
||||
void EnableVertexAttribArray(GLuint index);
|
||||
void VertexAttribPointer(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void* pointer);
|
||||
void VertexAttribIPointer(GLuint index, GLint size, GLenum type, GLsizei stride, const void* pointer);
|
||||
}
|
||||
|
||||
#endif //MOBILEGL_GL_VERTEXARRAY_H
|
||||
@@ -0,0 +1,24 @@
|
||||
//
|
||||
// Created by BZLZHH on 2025/3/15.
|
||||
//
|
||||
|
||||
#include "LookUp.h"
|
||||
|
||||
#include "../../../../Includes.h"
|
||||
|
||||
namespace MG_GL::GLX {
|
||||
void* GetProcAddress(const char *name) {
|
||||
void* proc = dlsym(RTLD_DEFAULT, (const char*)name);
|
||||
|
||||
if (!proc) {
|
||||
MG_Util::Debug::LogW("Failed to get function: %s", (const char*)name);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return proc;
|
||||
}
|
||||
|
||||
void* GetProcAddressARB(const char *name) {
|
||||
return GetProcAddress(name);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
//
|
||||
// Created by BZLZHH on 2025/3/15.
|
||||
//
|
||||
|
||||
#ifndef MOBILEGL_LOOKUP_H
|
||||
#define MOBILEGL_LOOKUP_H
|
||||
|
||||
namespace MG_GL::GLX {
|
||||
void* GetProcAddress(const char *name);
|
||||
void* GetProcAddressARB(const char *name);
|
||||
}
|
||||
#endif //MOBILEGL_LOOKUP_H
|
||||
@@ -0,0 +1,328 @@
|
||||
//
|
||||
// Created by BZLZHH on 2025/5/1.
|
||||
//
|
||||
|
||||
// TODO: Add more gl error check for buffer state manager.
|
||||
|
||||
#include "BufferState.h"
|
||||
|
||||
GLenum BufferState::GenName(GLuint *buffer) {
|
||||
MG_Util::Debug::LogD("MG_State: Buffer: GenName");
|
||||
if (!buffer)
|
||||
return GL_INVALID_VALUE;
|
||||
|
||||
GLuint id = 0;
|
||||
if (freeId_.empty()) {
|
||||
id = lastId_++;
|
||||
} else {
|
||||
id = freeId_.back();
|
||||
freeId_.pop_back();
|
||||
}
|
||||
|
||||
*buffer = id;
|
||||
MG_Util::Debug::LogD("MG_State: Buffer: Gen new name %d", id);
|
||||
|
||||
return GL_NO_ERROR;
|
||||
}
|
||||
|
||||
GLenum BufferState::GenNameN(GLsizei n, GLuint* buffers) {
|
||||
MG_Util::Debug::LogD("MG_State: Buffer: GenNameN called with n=%d", n);
|
||||
if (n < 0) return GL_INVALID_VALUE;
|
||||
|
||||
for (GLsizei i = 0; i < n; ++i) {
|
||||
GLenum result = GenName(&buffers[i]);
|
||||
if (result != GL_NO_ERROR) {
|
||||
MG_Util::Debug::LogE("MG_State: Buffer: GenNameN failed with error 0x%x", result);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
MG_Util::Debug::LogD("MG_State: Buffer: GenNameN created buffers successfully");
|
||||
return GL_NO_ERROR;
|
||||
}
|
||||
|
||||
GLenum BufferState::Create(GLuint buffer) {
|
||||
MG_Util::Debug::LogD("MG_State: Buffer: Create called");
|
||||
if (!buffer)
|
||||
return GL_INVALID_VALUE;
|
||||
|
||||
if (ValidateAllocatedHandle(buffer))
|
||||
return GL_INVALID_VALUE;
|
||||
|
||||
BufferObject& obj = buffers_[buffer];
|
||||
MG_Util::Debug::LogD("MG_State: Buffer: Create created buffer %d", buffer);
|
||||
obj.generated = true;
|
||||
obj.dirty = true;
|
||||
|
||||
return GL_NO_ERROR;
|
||||
}
|
||||
|
||||
GLenum BufferState::Bind(GLenum target, GLuint buffer) {
|
||||
// We don't handle unallocated buffer names here, just plain bind
|
||||
if (!IsValidTarget_(target)) return GL_INVALID_ENUM;
|
||||
MG_Util::Debug::LogD("MG_State: Buffer: Bind called with target=%s, buffer=%u", MG_Util::Debug::GLEnumToString(target), buffer);
|
||||
currentBindings_[target] = buffer;
|
||||
MG_Util::Debug::LogD("MG_State: Buffer: Bind succeed bind buffer %u to target 0x%x", buffer, target);
|
||||
return GL_NO_ERROR;
|
||||
}
|
||||
|
||||
GLenum BufferState::CommitStorage(GLenum target, GLsizeiptr size, const void* data, GLenum usage) {
|
||||
MG_Util::Debug::LogD("MG_State: Buffer: CommitStorage called on target 0x%x",target);
|
||||
auto it = currentBindings_.find(target);
|
||||
if (it == currentBindings_.end() || it->second == 0)
|
||||
return GL_INVALID_OPERATION;
|
||||
|
||||
BufferObject& obj = buffers_[it->second];
|
||||
MG_Util::Debug::LogD("MG_State: Buffer: CommitStorage get buffer object %u at target 0x%x",it->second,target);
|
||||
obj.usage = usage;
|
||||
obj.data.resize(size);
|
||||
if (data) {
|
||||
memcpy(obj.data.data(), data, size);
|
||||
obj.dataValid = true;
|
||||
}
|
||||
obj.dirty = true;
|
||||
MG_Util::Debug::LogD("MG_State: Buffer: CommitStorage buffer at target 0x%x committed storage, size = %zu, usage=0x%x", target, size, usage);
|
||||
|
||||
return GL_NO_ERROR;
|
||||
}
|
||||
|
||||
GLenum BufferState::CommitStorageRegion(GLenum target, GLintptr offset, GLsizeiptr size, const void* data) {
|
||||
MG_Util::Debug::LogD("MG_State: Buffer: BufferSubData called on target 0x%x, offset=%ld, size=%ld", target, offset, size);
|
||||
if (!IsValidTarget_(target)) return GL_INVALID_ENUM;
|
||||
if (offset < 0 || size < 0) return GL_INVALID_VALUE;
|
||||
|
||||
auto it = currentBindings_.find(target);
|
||||
if (it == currentBindings_.end() || it->second == 0)
|
||||
return GL_INVALID_OPERATION;
|
||||
|
||||
BufferObject& obj = buffers_[it->second];
|
||||
MG_Util::Debug::LogD("MG_State: Buffer: BufferSubData get buffer object %u at target 0x%x", it->second, target);
|
||||
|
||||
if (static_cast<size_t>(offset + size) > obj.data.size()) return GL_INVALID_VALUE;
|
||||
|
||||
if (data) {
|
||||
memcpy(obj.data.data() + offset, data, size);
|
||||
obj.dirty = true;
|
||||
}
|
||||
return GL_NO_ERROR;
|
||||
}
|
||||
|
||||
GLenum BufferState::AcquireBufferMemoryRange(GLenum target, GLintptr offset, GLsizeiptr length, GLbitfield access, void** mappedPointer) {
|
||||
MG_Util::Debug::LogD("MG_State: Buffer: AcquireBufferMemoryRange called with target=0x%x, offset=%ld, length=%ld, access=0x%x", target, offset, length, access);
|
||||
if (!IsValidTarget_(target)) {
|
||||
MG_Util::Debug::LogE("MG_State: Buffer: AcquireBufferMemoryRange failed: invalid target 0x%x", target);
|
||||
return GL_INVALID_ENUM;
|
||||
}
|
||||
if (offset < 0 || length <= 0) {
|
||||
MG_Util::Debug::LogE("MG_State: Buffer: AcquireBufferMemoryRange failed: invalid offset %ld or length %ld", offset, length);
|
||||
return GL_INVALID_VALUE;
|
||||
}
|
||||
|
||||
auto it = currentBindings_.find(target);
|
||||
if (it == currentBindings_.end() || it->second == 0) {
|
||||
MG_Util::Debug::LogE("MG_State: Buffer: AcquireBufferMemoryRange failed: no buffer bound to target 0x%x", target);
|
||||
return GL_INVALID_OPERATION;
|
||||
}
|
||||
|
||||
BufferObject& obj = buffers_[it->second];
|
||||
MG_Util::Debug::LogD("MG_State: Buffer: AcquireBufferMemoryRange operating on buffer %u", it->second);
|
||||
if (obj.isMapped) {
|
||||
MG_Util::Debug::LogE("MG_State: Buffer: AcquireBufferMemoryRange failed: buffer %u is already mapped", it->second);
|
||||
return GL_INVALID_OPERATION;
|
||||
}
|
||||
|
||||
const GLbitfield validFlags = GL_MAP_READ_BIT | GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_RANGE_BIT | GL_MAP_FLUSH_EXPLICIT_BIT | GL_MAP_UNSYNCHRONIZED_BIT | GL_MAP_INVALIDATE_BUFFER_BIT ;
|
||||
if ((access & ~validFlags) != 0) {
|
||||
MG_Util::Debug::LogE("MG_State: Buffer: AcquireBufferMemoryRange failed: invalid access flags 0x%x", access);
|
||||
return GL_INVALID_VALUE;
|
||||
}
|
||||
|
||||
if (static_cast<size_t>(offset + length) > obj.data.size()) {
|
||||
MG_Util::Debug::LogE("MG_State: Buffer: AcquireBufferMemoryRange failed: requested range [%ld, %ld) exceeds buffer size %zu", offset, offset + length, obj.data.size());
|
||||
return GL_INVALID_VALUE;
|
||||
}
|
||||
|
||||
obj.isMapped = true;
|
||||
obj.mapOffset = offset;
|
||||
obj.mapLength = length;
|
||||
obj.mapAccessFlags = access;
|
||||
obj.dirty = true;
|
||||
*mappedPointer = obj.data.data() + offset;
|
||||
MG_Util::Debug::LogD("MG_State: Buffer: AcquireBufferMemoryRange succeeded for buffer %u. Mapped pointer: %p", it->second, *mappedPointer);
|
||||
return GL_NO_ERROR;
|
||||
}
|
||||
|
||||
GLenum BufferState::SyncBufferMemory(GLenum target, GLintptr offset, GLsizeiptr length) {
|
||||
if (!IsValidTarget_(target)) return GL_INVALID_ENUM;
|
||||
if (offset < 0 || length <= 0) return GL_INVALID_VALUE;
|
||||
|
||||
auto it = currentBindings_.find(target);
|
||||
if (it == currentBindings_.end() || it->second == 0)
|
||||
return GL_INVALID_OPERATION;
|
||||
|
||||
BufferObject& obj = buffers_[it->second];
|
||||
if (!obj.isMapped || !(obj.mapAccessFlags & GL_MAP_FLUSH_EXPLICIT_BIT))
|
||||
return GL_INVALID_OPERATION;
|
||||
|
||||
if (offset < obj.mapOffset || offset + length > obj.mapOffset + obj.mapLength)
|
||||
return GL_INVALID_VALUE;
|
||||
|
||||
// TODO: Add support for flush range.
|
||||
// Now only mark it as dirty.
|
||||
obj.dirty = true;
|
||||
return GL_NO_ERROR;
|
||||
}
|
||||
|
||||
GLenum BufferState::CopyBufferRange(GLenum readTarget, GLenum writeTarget, GLintptr readOffset, GLintptr writeOffset, GLsizeiptr size) {
|
||||
if (!IsValidTarget_(readTarget) || !IsValidTarget_(writeTarget))
|
||||
return GL_INVALID_ENUM;
|
||||
if (readOffset < 0 || writeOffset < 0 || size < 0)
|
||||
return GL_INVALID_VALUE;
|
||||
|
||||
GLuint readBuffer = currentBindings_[readTarget];
|
||||
GLuint writeBuffer = currentBindings_[writeTarget];
|
||||
if (readBuffer == 0 || writeBuffer == 0 || readBuffer == writeBuffer)
|
||||
return GL_INVALID_OPERATION;
|
||||
|
||||
BufferObject& src = buffers_[readBuffer];
|
||||
BufferObject& dst = buffers_[writeBuffer];
|
||||
|
||||
if (static_cast<size_t>(readOffset + size) > src.data.size() ||
|
||||
static_cast<size_t>(writeOffset + size) > dst.data.size())
|
||||
return GL_INVALID_VALUE;
|
||||
|
||||
memcpy(dst.data.data() + writeOffset, src.data.data() + readOffset, size);
|
||||
dst.dirty = true;
|
||||
return GL_NO_ERROR;
|
||||
}
|
||||
|
||||
GLenum BufferState::AcquireBufferMemory(GLenum target, GLenum access, void** mappedPointer) {
|
||||
GLbitfield flags = 0;
|
||||
switch (access) {
|
||||
case GL_READ_ONLY: flags = GL_MAP_READ_BIT; break;
|
||||
case GL_WRITE_ONLY: flags = GL_MAP_WRITE_BIT; break;
|
||||
case GL_READ_WRITE: flags = GL_MAP_READ_BIT | GL_MAP_WRITE_BIT; break;
|
||||
default:
|
||||
flags = access;
|
||||
}
|
||||
return AcquireBufferMemoryRange(target, 0, buffers_[currentBindings_[target]].data.size(), flags, mappedPointer);
|
||||
}
|
||||
|
||||
GLenum BufferState::ReleaseBufferMemory(GLenum target) {
|
||||
if (!IsValidTarget_(target))
|
||||
return GL_INVALID_ENUM;
|
||||
|
||||
auto it = currentBindings_.find(target);
|
||||
if (it == currentBindings_.end() || it->second == 0)
|
||||
return GL_INVALID_OPERATION;
|
||||
|
||||
BufferObject& obj = buffers_[it->second];
|
||||
if (!obj.isMapped)
|
||||
return GL_INVALID_OPERATION;
|
||||
|
||||
if ((obj.mapAccessFlags & GL_MAP_FLUSH_EXPLICIT_BIT) == 0) {
|
||||
obj.dirty = true;
|
||||
}
|
||||
|
||||
obj.isMapped = false;
|
||||
obj.mapOffset = 0;
|
||||
obj.mapLength = 0;
|
||||
obj.mapAccessFlags = 0;
|
||||
obj.accessMode = GL_READ_WRITE;
|
||||
|
||||
return GL_NO_ERROR;
|
||||
}
|
||||
|
||||
bool BufferState::ValidateAllocatedHandle(GLuint buffer) {
|
||||
bool isValid = buffers_.find(buffer) != buffers_.end();
|
||||
MG_Util::Debug::LogD("MG_State: Buffer: ValidateAllocatedHandle called on buffer %u returns %d", buffer, isValid);
|
||||
return isValid;
|
||||
}
|
||||
|
||||
bool BufferState::ValidateGeneratedName(GLuint buffer) {
|
||||
bool inFreeList = std::find(freeId_.begin(), freeId_.end(), buffer) != freeId_.end();
|
||||
bool lessThanLast = buffer < lastId_; // lastId_ is not generated yet
|
||||
MG_Util::Debug::LogD("MG_State: Buffer: ValidateGeneratedName called on buffer %u returns %d", buffer, lessThanLast && !inFreeList);
|
||||
return lessThanLast && !inFreeList;
|
||||
}
|
||||
|
||||
void BufferState::Delete(GLuint buffer) {
|
||||
buffers_.erase(buffer);
|
||||
if (ValidateGeneratedName(buffer))
|
||||
// TODO: Prevent the object that uses a freeId from conflicting with legacy object in the backend.
|
||||
//freeId_.emplace_back(buffer);
|
||||
for (auto& [target, id] : currentBindings_) {
|
||||
if (id == buffer) id = 0;
|
||||
}
|
||||
MG_Util::Debug::LogD("MG_State: Buffer: Delete buffer %u", buffer);
|
||||
}
|
||||
|
||||
GLenum BufferState::DeleteN(GLsizei n, const GLuint* buffers) {
|
||||
MG_Util::Debug::LogD("MG_State: Buffer: DeleteN called with n=%d", n);
|
||||
if (n < 0) return GL_INVALID_VALUE;
|
||||
|
||||
for (GLsizei i = 0; i < n; ++i) {
|
||||
Delete(buffers[i]);
|
||||
}
|
||||
MG_Util::Debug::LogD("MG_State: Buffer: DeleteN deleted buffers successfully");
|
||||
return GL_NO_ERROR;
|
||||
}
|
||||
|
||||
bool BufferState::IsValidTarget_(GLenum target) {
|
||||
MG_Util::Debug::LogD("MG_State: Buffer: IsValidTarget_ called with target=0x%x,result=%d", target, !(MG_Constants::Buffer::VALID_TARGETS.find(target) == MG_Constants::Buffer::VALID_TARGETS.end()));
|
||||
return !(MG_Constants::Buffer::VALID_TARGETS.find(target) == MG_Constants::Buffer::VALID_TARGETS.end());
|
||||
}
|
||||
|
||||
GLenum BufferState::QueryPropertyIntVector(GLenum target, GLenum pname, GLint* params) const {
|
||||
MG_Util::Debug::LogD("MG_State: Buffer: QueryPropertyIntVector called at target 0x%x,param name = 0x%x",target,pname);
|
||||
static const std::set<GLenum> validTargets = {
|
||||
GL_ARRAY_BUFFER, GL_ELEMENT_ARRAY_BUFFER,
|
||||
GL_PIXEL_PACK_BUFFER, GL_PIXEL_UNPACK_BUFFER
|
||||
};
|
||||
if (validTargets.find(target) == validTargets.end()) {
|
||||
return GL_INVALID_ENUM;
|
||||
}
|
||||
|
||||
if (MG_Constants::Buffer::VALID_PARAM_NAMES.find(pname) == MG_Constants::Buffer::VALID_PARAM_NAMES.end()) {
|
||||
return GL_INVALID_ENUM;
|
||||
}
|
||||
|
||||
auto bindingIt = currentBindings_.find(target);
|
||||
if (bindingIt == currentBindings_.end() || bindingIt->second == 0) {
|
||||
return GL_INVALID_OPERATION;
|
||||
}
|
||||
GLuint bufferId = bindingIt->second;
|
||||
auto bufferIt = buffers_.find(bufferId);
|
||||
if (bufferIt == buffers_.end()) {
|
||||
return GL_INVALID_OPERATION;
|
||||
}
|
||||
|
||||
const BufferObject& buffer = bufferIt->second;
|
||||
|
||||
switch (pname) {
|
||||
case GL_BUFFER_ACCESS:
|
||||
*params = static_cast<GLint>(buffer.accessMode);
|
||||
break;
|
||||
case GL_BUFFER_MAPPED:
|
||||
*params = buffer.isMapped ? GL_TRUE : GL_FALSE;
|
||||
break;
|
||||
case GL_BUFFER_SIZE:
|
||||
*params = static_cast<GLint>(buffer.data.size());
|
||||
break;
|
||||
case GL_BUFFER_USAGE:
|
||||
*params = static_cast<GLint>(buffer.usage);
|
||||
break;
|
||||
MG_Util::Debug::LogD("MG_State: Buffer: QueryPropertyIntVector Query info about buffer %u succeed", target);
|
||||
default:
|
||||
return GL_INVALID_ENUM;
|
||||
}
|
||||
return GL_NO_ERROR;
|
||||
}
|
||||
|
||||
|
||||
GLuint BufferState::GetCurrentBinding(GLenum target) const {
|
||||
auto it = currentBindings_.find(target);
|
||||
if (it != currentBindings_.end()) {
|
||||
return it->second;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
//
|
||||
// Created by BZLZHH on 2025/5/1.
|
||||
//
|
||||
|
||||
#ifndef MOBILEGL_BUFFERSTATE_H
|
||||
#define MOBILEGL_BUFFERSTATE_H
|
||||
#define MOBILEGL_GLSTATE_H
|
||||
|
||||
#include "../../../Includes.h"
|
||||
|
||||
class BufferState {
|
||||
template <typename K, typename V>
|
||||
using unordered_map = ankerl::unordered_dense::map<K, V>;
|
||||
|
||||
public:
|
||||
struct BufferObject {
|
||||
GLenum usage = GL_STATIC_DRAW;
|
||||
std::vector<GLubyte> data;
|
||||
bool dataValid = false;
|
||||
bool dirty = false; // TODO: encapsulate this with an public API to RHI
|
||||
bool isMapped = false;
|
||||
bool generated = false;
|
||||
GLenum accessMode = GL_READ_WRITE;
|
||||
GLintptr mapOffset = 0;
|
||||
GLsizeiptr mapLength = 0;
|
||||
GLbitfield mapAccessFlags = 0;
|
||||
};
|
||||
|
||||
// Return: the validity of the operation, according to OpenGL 3 standard
|
||||
GLenum GenName(GLuint* buffer);
|
||||
GLenum GenNameN(GLsizei n, GLuint* buffers);
|
||||
GLenum Create(GLuint buffer);
|
||||
GLenum Bind(GLenum target, GLuint buffer);
|
||||
GLenum CommitStorage(GLenum target, GLsizeiptr size, const void* data, GLenum usage);
|
||||
GLenum CommitStorageRegion(GLenum target, GLintptr offset, GLsizeiptr size, const void* data);
|
||||
GLenum AcquireBufferMemoryRange(GLenum target, GLintptr offset, GLsizeiptr length, GLbitfield access, void** mappedPointer);
|
||||
GLenum SyncBufferMemory(GLenum target, GLintptr offset, GLsizeiptr length);
|
||||
GLenum CopyBufferRange(GLenum readTarget, GLenum writeTarget, GLintptr readOffset, GLintptr writeOffset, GLsizeiptr size);
|
||||
GLenum AcquireBufferMemory(GLenum target, GLenum access, void** mappedPointer);
|
||||
GLenum ReleaseBufferMemory(GLenum target);
|
||||
GLenum QueryPropertyIntVector(GLenum target, GLenum pname, GLint* params) const;
|
||||
GLenum DeleteN(GLsizei n, const GLuint* buffers);
|
||||
|
||||
bool ValidateAllocatedHandle(GLuint buffer);
|
||||
bool ValidateGeneratedName(GLuint buffer);
|
||||
void Delete(GLuint buffer);
|
||||
GLuint GetCurrentBinding(GLenum target) const;
|
||||
|
||||
unordered_map<GLenum, GLuint> currentBindings_;
|
||||
unordered_map<GLuint, BufferObject> buffers_;
|
||||
private:
|
||||
std::vector<GLuint> freeId_;
|
||||
GLuint lastId_ = 1;
|
||||
|
||||
static bool IsValidTarget_(GLenum target);
|
||||
};
|
||||
|
||||
#endif //MOBILEGL_BUFFERSTATE_H
|
||||
@@ -0,0 +1,158 @@
|
||||
//
|
||||
// Created by BZLZHH on 2025/4/30.
|
||||
//
|
||||
|
||||
#include "CommonState.h"
|
||||
|
||||
CommonState::CommonState() {
|
||||
pixelStoreParams[GL_PACK_ALIGNMENT] = 4;
|
||||
pixelStoreParams[GL_UNPACK_ALIGNMENT] = 4;
|
||||
}
|
||||
|
||||
GLenum CommonState::SetPixelStoreInt(GLenum pname, GLint param) {
|
||||
if (MG_Constants::PixelStore::VALID_PARAM_NAMES.find(pname) == MG_Constants::PixelStore::VALID_PARAM_NAMES.end()) {
|
||||
return GL_INVALID_ENUM;
|
||||
}
|
||||
|
||||
switch (pname) {
|
||||
case GL_PACK_SWAP_BYTES:
|
||||
case GL_PACK_LSB_FIRST:
|
||||
case GL_UNPACK_SWAP_BYTES:
|
||||
case GL_UNPACK_LSB_FIRST:
|
||||
break;
|
||||
case GL_PACK_ROW_LENGTH:
|
||||
case GL_PACK_IMAGE_HEIGHT:
|
||||
case GL_PACK_SKIP_ROWS:
|
||||
case GL_PACK_SKIP_PIXELS:
|
||||
case GL_PACK_SKIP_IMAGES:
|
||||
case GL_UNPACK_ROW_LENGTH:
|
||||
case GL_UNPACK_IMAGE_HEIGHT:
|
||||
case GL_UNPACK_SKIP_ROWS:
|
||||
case GL_UNPACK_SKIP_PIXELS:
|
||||
case GL_UNPACK_SKIP_IMAGES:
|
||||
if (param < 0) {
|
||||
return GL_INVALID_VALUE;
|
||||
}
|
||||
break;
|
||||
|
||||
case GL_PACK_ALIGNMENT:
|
||||
case GL_UNPACK_ALIGNMENT:
|
||||
if (param != 1 && param != 2 && param != 4 && param != 8) {
|
||||
return GL_INVALID_VALUE;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
return GL_INVALID_ENUM;
|
||||
}
|
||||
|
||||
pixelStoreParams[pname] = param;
|
||||
return GL_NO_ERROR;
|
||||
}
|
||||
|
||||
GLint CommonState::QueryPixelStoreInt(GLenum pname) {
|
||||
auto it = pixelStoreParams.find(pname);
|
||||
if (it != pixelStoreParams.end()) {
|
||||
return it->second;
|
||||
}
|
||||
|
||||
auto defaultIt = MG_Constants::PixelStore::DEFAULT_VALUES_MAP.find(pname);
|
||||
return (defaultIt != MG_Constants::PixelStore::DEFAULT_VALUES_MAP.end()) ? defaultIt->second : 0;
|
||||
}
|
||||
|
||||
GLenum CommonState::Enable(GLenum cap) {
|
||||
if (MG_Constants::CommonState::VALID_CAPS.find(cap) == MG_Constants::CommonState::VALID_CAPS.end()) {
|
||||
return GL_INVALID_ENUM;
|
||||
}
|
||||
capabilities[cap] = true;
|
||||
return GL_NO_ERROR;
|
||||
}
|
||||
|
||||
GLenum CommonState::Disable(GLenum cap) {
|
||||
if (MG_Constants::CommonState::VALID_CAPS.find(cap) == MG_Constants::CommonState::VALID_CAPS.end()) {
|
||||
return GL_INVALID_ENUM;
|
||||
}
|
||||
capabilities[cap] = false;
|
||||
return GL_NO_ERROR;
|
||||
}
|
||||
|
||||
GLenum CommonState::BlendFunc(GLenum sfactor, GLenum dfactor) {
|
||||
if (MG_Constants::Blend::VALID_FACTORS.find(sfactor) == MG_Constants::Blend::VALID_FACTORS.end() ||
|
||||
MG_Constants::Blend::VALID_FACTORS.find(dfactor) == MG_Constants::Blend::VALID_FACTORS.end()) {
|
||||
return GL_INVALID_ENUM;
|
||||
}
|
||||
|
||||
blendSrcRGB = blendSrcAlpha = sfactor;
|
||||
blendDstRGB = blendDstAlpha = dfactor;
|
||||
return GL_NO_ERROR;
|
||||
}
|
||||
|
||||
GLenum CommonState::BlendFuncSeparate(GLenum srcRGB, GLenum dstRGB,
|
||||
GLenum srcAlpha, GLenum dstAlpha) {
|
||||
if (MG_Constants::Blend::VALID_FACTORS.find(srcRGB) == MG_Constants::Blend::VALID_FACTORS.end() ||
|
||||
MG_Constants::Blend::VALID_FACTORS.find(dstRGB) == MG_Constants::Blend::VALID_FACTORS.end() ||
|
||||
MG_Constants::Blend::VALID_FACTORS.find(srcAlpha) == MG_Constants::Blend::VALID_FACTORS.end() ||
|
||||
MG_Constants::Blend::VALID_FACTORS.find(dstAlpha) == MG_Constants::Blend::VALID_FACTORS.end()) {
|
||||
return GL_INVALID_ENUM;
|
||||
}
|
||||
blendSrcRGB = srcRGB;
|
||||
blendDstRGB = dstRGB;
|
||||
blendSrcAlpha = srcAlpha;
|
||||
blendDstAlpha = dstAlpha;
|
||||
return GL_NO_ERROR;
|
||||
}
|
||||
|
||||
GLenum CommonState::Clear(GLbitfield mask) {
|
||||
const GLbitfield validBits = GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT;
|
||||
if ((mask & ~validBits) != 0) {
|
||||
return GL_INVALID_VALUE;
|
||||
}
|
||||
clearMask = mask;
|
||||
return GL_NO_ERROR;
|
||||
}
|
||||
|
||||
GLenum CommonState::ClearColor(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha) {
|
||||
clearColor[0] = red;
|
||||
clearColor[1] = green;
|
||||
clearColor[2] = blue;
|
||||
clearColor[3] = alpha;
|
||||
return GL_NO_ERROR;
|
||||
}
|
||||
|
||||
GLenum CommonState::ClearDepth(GLfloat depth) {
|
||||
clearDepth = depth;
|
||||
return GL_NO_ERROR;
|
||||
}
|
||||
|
||||
GLenum CommonState::ColorMask(GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha) {
|
||||
colorMask[0] = red;
|
||||
colorMask[1] = green;
|
||||
colorMask[2] = blue;
|
||||
colorMask[3] = alpha;
|
||||
return GL_NO_ERROR;
|
||||
}
|
||||
|
||||
GLenum CommonState::DepthFunc(GLenum func) {
|
||||
if (MG_Constants::Depth::VALID_FUNCS.find(func) == MG_Constants::Depth::VALID_FUNCS.end()) {
|
||||
return GL_INVALID_ENUM;
|
||||
}
|
||||
depthFunc = func;
|
||||
return GL_NO_ERROR;
|
||||
}
|
||||
|
||||
GLenum CommonState::DepthMask(GLboolean flag) {
|
||||
depthMask = flag;
|
||||
return GL_NO_ERROR;
|
||||
}
|
||||
|
||||
GLenum CommonState::Viewport(GLint x, GLint y, GLsizei width, GLsizei height) {
|
||||
if (width < 0 || height < 0) {
|
||||
return GL_INVALID_VALUE;
|
||||
}
|
||||
|
||||
viewport[0] = x;
|
||||
viewport[1] = y;
|
||||
viewport[2] = width;
|
||||
viewport[3] = height;
|
||||
return GL_NO_ERROR;
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
//
|
||||
// Created by BZLZHH on 2025/4/30.
|
||||
//
|
||||
|
||||
#ifndef MOBILEGL_COMMONSTATE_H
|
||||
#define MOBILEGL_COMMONSTATE_H
|
||||
#define MOBILEGL_GLSTATE_H
|
||||
|
||||
#include "../../../Includes.h"
|
||||
|
||||
class CommonState {
|
||||
template <typename K, typename V>
|
||||
using unordered_map = ankerl::unordered_dense::map<K, V>;
|
||||
public:
|
||||
// Viewport
|
||||
GLint viewport[4] = {0, 0, 0, 0};
|
||||
|
||||
// Color mask
|
||||
GLboolean colorMask[4] = {GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE};
|
||||
|
||||
// Pixel storage parameters
|
||||
unordered_map<GLenum, GLint> pixelStoreParams;
|
||||
|
||||
// Blend state
|
||||
GLenum blendSrcRGB = GL_ONE;
|
||||
GLenum blendDstRGB = GL_ZERO;
|
||||
GLenum blendSrcAlpha = GL_ONE;
|
||||
GLenum blendDstAlpha = GL_ZERO;
|
||||
|
||||
// Clear state
|
||||
GLfloat clearColor[4] = {0.0f, 0.0f, 0.0f, 0.0f};
|
||||
GLfloat clearDepth = 1.0f;
|
||||
GLenum clearMask;
|
||||
|
||||
// Depth state
|
||||
GLenum depthFunc = GL_LESS;
|
||||
GLboolean depthMask = GL_TRUE;
|
||||
|
||||
// Capability enables
|
||||
unordered_map<GLenum, bool> capabilities;
|
||||
|
||||
CommonState();
|
||||
|
||||
// Return: the validity of the operation, according to OpenGL 3 standard
|
||||
GLenum SetPixelStoreInt(GLenum pname, GLint param);
|
||||
|
||||
GLenum Enable(GLenum cap);
|
||||
GLenum Disable(GLenum cap);
|
||||
GLenum BlendFunc(GLenum sfactor, GLenum dfactor);
|
||||
GLenum BlendFuncSeparate(GLenum srcRGB, GLenum dstRGB,
|
||||
GLenum srcAlpha, GLenum dstAlpha);
|
||||
GLenum Clear(GLbitfield mask);
|
||||
GLenum ClearColor(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha);
|
||||
GLenum ClearDepth(GLfloat depth);
|
||||
GLenum ColorMask(GLboolean red, GLboolean green,
|
||||
GLboolean blue, GLboolean alpha);
|
||||
GLenum DepthFunc(GLenum func);
|
||||
GLenum DepthMask(GLboolean flag);
|
||||
GLenum Viewport(GLint x, GLint y, GLsizei width, GLsizei height);
|
||||
|
||||
GLint QueryPixelStoreInt(GLenum pname);
|
||||
|
||||
};
|
||||
|
||||
#endif //MOBILEGL_COMMONSTATE_H
|
||||
@@ -0,0 +1,407 @@
|
||||
//
|
||||
// Created by BZLZHH on 2025/4/4.
|
||||
//
|
||||
|
||||
#include "GLState.h"
|
||||
|
||||
std::queue<GLenum> MG_State_T::glErrorQueue;
|
||||
TextureState* MG_State_T::textureState = nullptr;
|
||||
CommonState* MG_State_T::commonState = nullptr;
|
||||
BufferState* MG_State_T::bufferState = nullptr;
|
||||
VertexArrayState* MG_State_T::vertexArrayState = nullptr;
|
||||
ProgramState* MG_State_T::programState = nullptr;
|
||||
FramebufferState* MG_State_T::framebufferState = nullptr;
|
||||
|
||||
namespace MG_State {
|
||||
void Init() {
|
||||
MG_State_T::textureState = new TextureState();
|
||||
MG_State_T::commonState = new CommonState();
|
||||
MG_State_T::bufferState = new BufferState();
|
||||
MG_State_T::vertexArrayState = new VertexArrayState();
|
||||
MG_State_T::programState = new ProgramState();
|
||||
MG_State_T::framebufferState = new FramebufferState();
|
||||
}
|
||||
|
||||
void Destroy() {
|
||||
delete MG_State_T::textureState;
|
||||
delete MG_State_T::commonState;
|
||||
delete MG_State_T::bufferState;
|
||||
delete MG_State_T::vertexArrayState;
|
||||
delete MG_State_T::programState;
|
||||
delete MG_State_T::framebufferState;
|
||||
}
|
||||
|
||||
void SetError(GLenum error) {
|
||||
if (error == GL_NO_ERROR) {
|
||||
return;
|
||||
}
|
||||
MG_State_T::glErrorQueue.push(error);
|
||||
}
|
||||
|
||||
GLenum GetError() {
|
||||
if (MG_State_T::glErrorQueue.empty()) {
|
||||
return GL_NO_ERROR;
|
||||
}
|
||||
GLenum error = MG_State_T::glErrorQueue.front();
|
||||
MG_State_T::glErrorQueue.pop();
|
||||
return error;
|
||||
}
|
||||
|
||||
// TODO: Take these functions apart into multiple files.
|
||||
|
||||
// Texture
|
||||
GLenum BindTextureUnit(GLenum textureUnit) {
|
||||
return MG_State_T::textureState->BindUnit(textureUnit);
|
||||
}
|
||||
|
||||
GLenum CreateTexture(GLuint* texture) {
|
||||
return MG_State_T::textureState->Create(texture);
|
||||
}
|
||||
|
||||
GLenum CreateTextures(GLsizei n, GLuint* textures) {
|
||||
return MG_State_T::textureState->CreateN(n, textures);
|
||||
}
|
||||
|
||||
GLenum BindTexture(GLenum target, GLuint texture) {
|
||||
return MG_State_T::textureState->Bind(target, texture);
|
||||
}
|
||||
|
||||
GLenum UploadTexture2D(GLenum target, GLint level, GLint internalFormat,
|
||||
GLsizei width, GLsizei height, GLint border, GLenum format,
|
||||
GLenum type, const void* data) {
|
||||
return MG_State_T::textureState->Upload2D(target, level, internalFormat, width, height, border, format, type, data);
|
||||
}
|
||||
|
||||
GLenum UpdateTextureRegion2D(GLenum target, GLint level, GLint xoffset,
|
||||
GLint yoffset, GLsizei width, GLsizei height, GLenum format,
|
||||
GLenum type, const GLvoid* data) {
|
||||
return MG_State_T::textureState->UpdateRegion2D(target, level, xoffset, yoffset, width, height, format, type, data);
|
||||
}
|
||||
|
||||
GLenum SetTexturePropertyInt(GLenum target, GLenum pname, GLint param) {
|
||||
return MG_State_T::textureState->SetTexturePropertyInt(target, pname, param);
|
||||
}
|
||||
|
||||
GLenum SetTexturePropertyFloat(GLenum target, GLenum pname, GLfloat param) {
|
||||
return MG_State_T::textureState->SetTexturePropertyFloat(target, pname, param);
|
||||
}
|
||||
|
||||
GLenum DeleteTexture(GLuint texture) {
|
||||
return MG_State_T::textureState->Delete(texture);
|
||||
}
|
||||
|
||||
GLenum DeleteTextures(GLsizei n, const GLuint* textures) {
|
||||
return MG_State_T::textureState->DeleteN(n, textures);
|
||||
}
|
||||
|
||||
GLenum QueryTextureLevelPropertyIntVector(GLenum target, GLint level, GLenum pname, GLint* params) {
|
||||
return MG_State_T::textureState->QueryLevelPropertyIntVector(target, level, pname, params);
|
||||
}
|
||||
|
||||
// Common
|
||||
GLenum SetPixelStoreInt(GLenum pname, GLint param) {
|
||||
return MG_State_T::commonState->SetPixelStoreInt(pname, param);
|
||||
}
|
||||
|
||||
GLint QueryPixelStoreInt(GLenum pname) {
|
||||
return MG_State_T::commonState->QueryPixelStoreInt(pname);
|
||||
}
|
||||
|
||||
GLenum glEnable(GLenum cap) {
|
||||
return MG_State_T::commonState->Enable(cap);
|
||||
}
|
||||
|
||||
GLenum glDisable(GLenum cap) {
|
||||
return MG_State_T::commonState->Disable(cap);
|
||||
}
|
||||
|
||||
GLenum glBlendFunc(GLenum sfactor, GLenum dfactor) {
|
||||
return MG_State_T::commonState->BlendFunc(sfactor, dfactor);
|
||||
}
|
||||
|
||||
GLenum glBlendFuncSeparate(GLenum srcRGB, GLenum dstRGB, GLenum srcAlpha, GLenum dstAlpha) {
|
||||
return MG_State_T::commonState->BlendFuncSeparate(srcRGB, dstRGB, srcAlpha, dstAlpha);
|
||||
}
|
||||
|
||||
GLenum glClear(GLbitfield mask) {
|
||||
return MG_State_T::commonState->Clear(mask);
|
||||
}
|
||||
|
||||
GLenum glClearColor(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha) {
|
||||
return MG_State_T::commonState->ClearColor(red, green, blue, alpha);
|
||||
}
|
||||
|
||||
GLenum glClearDepth(GLdouble depth) {
|
||||
return MG_State_T::commonState->ClearDepth(static_cast<GLfloat>(depth));
|
||||
}
|
||||
|
||||
GLenum glColorMask(GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha) {
|
||||
return MG_State_T::commonState->ColorMask(red, green, blue, alpha);
|
||||
}
|
||||
|
||||
GLenum glDepthFunc(GLenum func) {
|
||||
return MG_State_T::commonState->DepthFunc(func);
|
||||
}
|
||||
|
||||
GLenum glDepthMask(GLboolean flag) {
|
||||
return MG_State_T::commonState->DepthMask(flag);
|
||||
}
|
||||
|
||||
GLenum glViewport(GLint x, GLint y, GLsizei width, GLsizei height) {
|
||||
return MG_State_T::commonState->Viewport(x, y, width, height);
|
||||
}
|
||||
|
||||
// Buffer
|
||||
GLenum AcquireBufferMemory(GLenum target, GLenum access, void** mappedPtr) {
|
||||
return MG_State_T::bufferState->AcquireBufferMemory(target, access, mappedPtr);
|
||||
}
|
||||
|
||||
GLenum AcquireBufferMemoryRange(GLenum target, GLintptr offset, GLsizeiptr length, GLbitfield access, void** mappedPointer) {
|
||||
return MG_State_T::bufferState->AcquireBufferMemoryRange(target, offset, length, access, mappedPointer);
|
||||
}
|
||||
|
||||
GLenum SyncBufferMemory(GLenum target, GLintptr offset, GLsizeiptr length) {
|
||||
return MG_State_T::bufferState->SyncBufferMemory(target, offset, length);
|
||||
}
|
||||
|
||||
GLenum CopyBufferRange(GLenum readTarget, GLenum writeTarget, GLintptr readOffset, GLintptr writeOffset, GLsizeiptr size) {
|
||||
return MG_State_T::bufferState->CopyBufferRange(readTarget, writeTarget, readOffset,
|
||||
writeOffset, size);
|
||||
}
|
||||
|
||||
GLenum ReleaseBufferMemory(GLenum target) {
|
||||
return MG_State_T::bufferState->ReleaseBufferMemory(target);
|
||||
}
|
||||
|
||||
GLenum CreateBuffer(GLuint buffer) {
|
||||
return MG_State_T::bufferState->Create(buffer);
|
||||
}
|
||||
|
||||
GLenum GenBufferNames(GLsizei n, GLuint* buffers) {
|
||||
return MG_State_T::bufferState->GenNameN(n, buffers);
|
||||
}
|
||||
|
||||
GLenum BindBuffer(GLenum target, GLuint buffer) {
|
||||
if (target == GL_ELEMENT_ARRAY_BUFFER) {
|
||||
auto* vao = MG_State_T::vertexArrayState->GetCurrentVAO();
|
||||
if (!vao) return GL_INVALID_OPERATION;
|
||||
vao->elementBuffer = (GLuint)buffer;
|
||||
vao->eboDirty = true;
|
||||
}
|
||||
return MG_State_T::bufferState->Bind(target, buffer);
|
||||
}
|
||||
|
||||
GLenum CommitBufferStorage(GLenum target, GLsizeiptr size, const void* data, GLenum usage) {
|
||||
return MG_State_T::bufferState->CommitStorage(target, size, data, usage);
|
||||
}
|
||||
|
||||
GLenum CommitBufferStorageRegion(GLenum target, GLintptr offset, GLsizeiptr size, const void* data) {
|
||||
return MG_State_T::bufferState->CommitStorageRegion(target, offset, size, data);
|
||||
}
|
||||
|
||||
bool ValidateAllocatedBufferHandle(GLuint buffer) {
|
||||
return MG_State_T::bufferState->ValidateAllocatedHandle(buffer);
|
||||
}
|
||||
|
||||
bool ValidateGeneratedName(GLuint buffer) {
|
||||
return MG_State_T::bufferState->ValidateGeneratedName(buffer);
|
||||
}
|
||||
|
||||
void DeleteBuffer(GLuint buffer) {
|
||||
return MG_State_T::bufferState->Delete(buffer);
|
||||
}
|
||||
|
||||
GLenum DeleteBuffers(GLsizei n, const GLuint* buffers) {
|
||||
return MG_State_T::bufferState->DeleteN(n, buffers);
|
||||
}
|
||||
|
||||
GLenum QueryBufferPropertyIntVector(GLenum target, GLenum pname, GLint* params) {
|
||||
return MG_State_T::bufferState->QueryPropertyIntVector(target, pname, params);
|
||||
}
|
||||
|
||||
// VertexArray
|
||||
GLenum BindVertexArray(GLuint array) {
|
||||
return MG_State_T::vertexArrayState->Bind(array);
|
||||
}
|
||||
|
||||
GLenum CreateVertexArray(GLuint* array) {
|
||||
return MG_State_T::vertexArrayState->Create(array);
|
||||
}
|
||||
|
||||
GLenum CreateVertexArrays(GLsizei n, GLuint* arrays) {
|
||||
return MG_State_T::vertexArrayState->CreateN(n, arrays);
|
||||
}
|
||||
|
||||
GLenum GenVertexArraysNames(GLsizei n, GLuint* arrays) {
|
||||
return MG_State_T::vertexArrayState->GenNameN(n, arrays);
|
||||
}
|
||||
|
||||
GLenum EnableVertexAttribArray(GLuint index) {
|
||||
return MG_State_T::vertexArrayState->EnableAttrib(index);
|
||||
}
|
||||
|
||||
GLenum DisableVertexAttribArray(GLuint index) {
|
||||
return MG_State_T::vertexArrayState->DisableAttrib(index);
|
||||
}
|
||||
|
||||
GLenum SetVertexAttributeLayout(GLuint index, GLint size, GLenum type,
|
||||
GLboolean normalized, GLsizei stride,
|
||||
const void* pointer) {
|
||||
GLuint currentBuffer = MG_State_T::bufferState->GetCurrentBinding(GL_ARRAY_BUFFER);
|
||||
return MG_State_T::vertexArrayState->SetAttribPointer(
|
||||
index, size, type, normalized, stride, pointer, false, currentBuffer
|
||||
);
|
||||
}
|
||||
|
||||
GLenum SetVertexAttributeLayoutInt(GLuint index, GLint size, GLenum type,
|
||||
GLsizei stride, const void* pointer) {
|
||||
GLuint currentBuffer = MG_State_T::bufferState->GetCurrentBinding(GL_ARRAY_BUFFER);
|
||||
return MG_State_T::vertexArrayState->SetAttribPointer(
|
||||
index, size, type, GL_FALSE, stride, pointer, true, currentBuffer
|
||||
);
|
||||
}
|
||||
|
||||
// Program
|
||||
GLenum CreateShader(GLenum type, GLuint* shader) {
|
||||
return MG_State_T::programState->CreateShader(type, shader);
|
||||
}
|
||||
|
||||
GLenum CreateProgram(GLuint* program) {
|
||||
return MG_State_T::programState->CreateProgram(program);
|
||||
}
|
||||
|
||||
GLenum DeleteShader(GLuint shader) {
|
||||
return MG_State_T::programState->DeleteShader(shader);
|
||||
}
|
||||
|
||||
GLenum DeleteProgram(GLuint program) {
|
||||
return MG_State_T::programState->DeleteProgram(program);
|
||||
}
|
||||
|
||||
GLenum LinkShaderToProgram(GLuint program, GLuint shader) {
|
||||
return MG_State_T::programState->LinkShaderToProgram(program, shader);
|
||||
}
|
||||
|
||||
GLenum UploadShaderSource(GLuint shader, GLsizei count, const GLchar** string, const GLint* length) {
|
||||
return MG_State_T::programState->UploadShaderSource(shader, count, string, length);
|
||||
}
|
||||
|
||||
GLenum BuildShaderStage(GLuint shader) {
|
||||
return MG_State_T::programState->BuildShaderStage(shader);
|
||||
}
|
||||
|
||||
GLenum FinalizeProgramPipeline(GLuint program) {
|
||||
return MG_State_T::programState->FinalizeProgramPipeline(program);
|
||||
}
|
||||
|
||||
GLenum ActivateRenderProgram(GLuint program) {
|
||||
return MG_State_T::programState->ActivateRenderProgram(program);
|
||||
}
|
||||
|
||||
GLenum DefineProgramAttributeBinding(GLuint program, GLuint index, const GLchar* name) {
|
||||
return MG_State_T::programState->DefineProgramAttributeBinding(program, index, name);
|
||||
}
|
||||
|
||||
GLint QueryProgramAttributeBinding(GLuint program, const GLchar* name) {
|
||||
return MG_State_T::programState->QueryProgramAttributeBinding(program, name);
|
||||
}
|
||||
|
||||
GLint QueryProgramUniformLocation(GLuint program, const GLchar* name) {
|
||||
return MG_State_T::programState->QueryProgramUniformLocation(program, name);
|
||||
}
|
||||
|
||||
GLenum QueryProgramStateIntVector(GLuint program, GLenum pname, GLint* params) {
|
||||
return MG_State_T::programState->QueryProgramStateIntVector(program, pname, params);
|
||||
}
|
||||
|
||||
GLenum QueryShaderStateIntVector(GLuint shader, GLenum pname, GLint* params) {
|
||||
return MG_State_T::programState->QueryShaderStateIntVector(shader, pname, params);
|
||||
}
|
||||
|
||||
#define IMPLEMENT_UNIFORM_FUNCTIONS(type, suffix, vecType) \
|
||||
void UpdateProgramUniform##suffix##1(GLint location, type v0) { \
|
||||
MG_State_T::programState->UpdateUniform##suffix##1(location, v0); \
|
||||
} \
|
||||
void UpdateProgramUniform##suffix##2(GLint location, type v0, type v1) { \
|
||||
MG_State_T::programState->UpdateUniform##suffix##2(location, v0, v1); \
|
||||
} \
|
||||
void UpdateProgramUniform##suffix##3(GLint location, type v0, type v1, type v2) { \
|
||||
MG_State_T::programState->UpdateUniform##suffix##3(location, v0, v1, v2); \
|
||||
} \
|
||||
void UpdateProgramUniform##suffix##4(GLint location, type v0, type v1, type v2, type v3) { \
|
||||
MG_State_T::programState->UpdateUniform##suffix##4(location, v0, v1, v2, v3); \
|
||||
} \
|
||||
void UpdateProgramUniform##suffix##Vector1(GLint location, GLsizei count, const type* value) { \
|
||||
MG_State_T::programState->UpdateUniform##suffix##Vector1(location, count,value); \
|
||||
} \
|
||||
void UpdateProgramUniform##suffix##Vector2(GLint location, GLsizei count, const type* value) { \
|
||||
MG_State_T::programState->UpdateUniform##suffix##Vector2(location, count, value); \
|
||||
} \
|
||||
void UpdateProgramUniform##suffix##Vector3(GLint location, GLsizei count, const type* value) { \
|
||||
MG_State_T::programState->UpdateUniform##suffix##Vector3(location, count, value); \
|
||||
} \
|
||||
void UpdateProgramUniform##suffix##Vector4(GLint location, GLsizei count, const type* value) { \
|
||||
MG_State_T::programState->UpdateUniform##suffix##Vector4(location, count, value); \
|
||||
}
|
||||
|
||||
IMPLEMENT_UNIFORM_FUNCTIONS(GLfloat, Float, GL_FLOAT_VEC4)
|
||||
IMPLEMENT_UNIFORM_FUNCTIONS(GLint, Int, GL_INT_VEC4)
|
||||
IMPLEMENT_UNIFORM_FUNCTIONS(GLuint, UInt, GL_UNSIGNED_INT_VEC4)
|
||||
IMPLEMENT_UNIFORM_FUNCTIONS(GLboolean, Bool, GL_BOOL_VEC4)
|
||||
#undef IMPLEMENT_UNIFORM_FUNCTIONS
|
||||
|
||||
#define IMPLEMENT_MATRIX_FUNCTIONS(suffix, matrixType) \
|
||||
void UpdateProgramUniformMatrix##suffix##Vector(GLint location, GLsizei count, GLboolean transpose, const GLfloat* value) { \
|
||||
MG_State_T::programState->UpdateUniformMatrix##suffix##Vector(location, count, transpose, value); \
|
||||
}
|
||||
|
||||
IMPLEMENT_MATRIX_FUNCTIONS(2x2, GL_FLOAT_MAT2)
|
||||
IMPLEMENT_MATRIX_FUNCTIONS(3x3, GL_FLOAT_MAT3)
|
||||
IMPLEMENT_MATRIX_FUNCTIONS(4x4, GL_FLOAT_MAT4)
|
||||
IMPLEMENT_MATRIX_FUNCTIONS(2x3, GL_FLOAT_MAT2x3)
|
||||
IMPLEMENT_MATRIX_FUNCTIONS(2x4, GL_FLOAT_MAT2x4)
|
||||
IMPLEMENT_MATRIX_FUNCTIONS(3x2, GL_FLOAT_MAT3x2)
|
||||
IMPLEMENT_MATRIX_FUNCTIONS(3x4, GL_FLOAT_MAT3x4)
|
||||
IMPLEMENT_MATRIX_FUNCTIONS(4x2, GL_FLOAT_MAT4x2)
|
||||
IMPLEMENT_MATRIX_FUNCTIONS(4x3, GL_FLOAT_MAT4x3)
|
||||
#undef IMPLEMENT_MATRIX_FUNCTIONS
|
||||
|
||||
GLuint GetCurrentProgram() {
|
||||
return MG_State_T::programState->GetCurrentProgram();
|
||||
}
|
||||
|
||||
bool SetProgramStatus(GLuint program, GLboolean status) {
|
||||
return MG_State_T::programState->SetProgramStatus(program, status);
|
||||
}
|
||||
|
||||
bool SetShaderStatus(GLuint shader, GLboolean status) {
|
||||
return MG_State_T::programState->SetShaderStatus(shader, status);
|
||||
}
|
||||
|
||||
// Framebuffer
|
||||
GLenum MG_State::CreateFramebuffer(GLuint* framebuffer) {
|
||||
return MG_State_T::framebufferState->Create(framebuffer);
|
||||
}
|
||||
|
||||
GLenum MG_State::CreateFramebuffers(GLsizei n, GLuint* framebuffers) {
|
||||
return MG_State_T::framebufferState->CreateN(n, framebuffers);
|
||||
}
|
||||
|
||||
GLenum MG_State::DeleteFramebuffer(GLuint framebuffer) {
|
||||
return MG_State_T::framebufferState->Delete(framebuffer);
|
||||
}
|
||||
|
||||
GLenum MG_State::BindFramebuffer(GLenum target, GLuint framebuffer) {
|
||||
return MG_State_T::framebufferState->Bind(target, framebuffer);
|
||||
}
|
||||
|
||||
GLenum MG_State::AttachTexture2DToFramebuffer(GLenum target, GLenum attachment, GLenum textarget,
|
||||
GLuint texture, GLint level) {
|
||||
return MG_State_T::framebufferState->AttachTexture2D(target, attachment, textarget,
|
||||
texture, level);
|
||||
}
|
||||
|
||||
GLenum MG_State::ValidateFramebufferCompleteness(GLenum target) {
|
||||
return MG_State_T::framebufferState->ValidateCompleteness(target);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
//
|
||||
// Created by BZLZHH on 2025/4/4.
|
||||
//
|
||||
|
||||
#ifndef MOBILEGL_GLSTATE_H
|
||||
#define MOBILEGL_GLSTATE_H
|
||||
|
||||
#include "../../../Includes.h"
|
||||
|
||||
struct MG_State_T {
|
||||
static std::queue<GLenum> glErrorQueue;
|
||||
static TextureState* textureState;
|
||||
static CommonState* commonState;
|
||||
static BufferState* bufferState;
|
||||
static VertexArrayState* vertexArrayState;
|
||||
static ProgramState* programState;
|
||||
static FramebufferState* framebufferState;
|
||||
};
|
||||
|
||||
namespace MG_State {
|
||||
void Init();
|
||||
void Destroy();
|
||||
void SetError(GLenum error);
|
||||
GLenum GetError();
|
||||
|
||||
//Texture
|
||||
GLenum BindTextureUnit(GLenum textureUnit);
|
||||
GLenum CreateTexture(GLuint* texture);
|
||||
GLenum CreateTextures(GLsizei n, GLuint* textures);
|
||||
GLenum BindTexture(GLenum target, GLuint texture);
|
||||
GLenum UploadTexture2D(GLenum target, GLint level, GLint internalFormat,
|
||||
GLsizei width, GLsizei height, GLint border, GLenum format,
|
||||
GLenum type, const void* data);
|
||||
GLenum UpdateTextureRegion2D(GLenum target, GLint level, GLint xoffset,
|
||||
GLint yoffset, GLsizei width, GLsizei height, GLenum format,
|
||||
GLenum type, const GLvoid* data);
|
||||
GLenum SetTexturePropertyInt(GLenum target, GLenum pname, GLint param);
|
||||
GLenum SetTexturePropertyFloat(GLenum target, GLenum pname, GLfloat param);
|
||||
GLenum DeleteTexture(GLuint texture);
|
||||
GLenum DeleteTextures(GLsizei n, const GLuint* textures);
|
||||
GLenum QueryTextureLevelPropertyIntVector(GLenum target, GLint level, GLenum pname, GLint* params);
|
||||
|
||||
// Common
|
||||
GLenum SetPixelStoreInt(GLenum pname, GLint param);
|
||||
GLint QueryPixelStoreInt(GLenum pname);
|
||||
GLenum glEnable(GLenum cap);
|
||||
GLenum glDisable(GLenum cap);
|
||||
GLenum glBlendFunc(GLenum sfactor, GLenum dfactor);
|
||||
GLenum glBlendFuncSeparate(GLenum srcRGB, GLenum dstRGB, GLenum srcAlpha, GLenum dstAlpha);
|
||||
GLenum glClear(GLbitfield mask);
|
||||
GLenum glClearColor(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha);
|
||||
GLenum glClearDepth(GLdouble depth);
|
||||
GLenum glColorMask(GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha);
|
||||
GLenum glDepthFunc(GLenum func);
|
||||
GLenum glDepthMask(GLboolean flag);
|
||||
GLenum glViewport(GLint x, GLint y, GLsizei width, GLsizei height);
|
||||
|
||||
// Buffer
|
||||
GLenum AcquireBufferMemory(GLenum target, GLenum access, void** mappedPtr);
|
||||
GLenum AcquireBufferMemoryRange(GLenum target, GLintptr offset, GLsizeiptr length, GLbitfield access, void** mappedPointer);
|
||||
GLenum SyncBufferMemory(GLenum target, GLintptr offset, GLsizeiptr length);
|
||||
GLenum CopyBufferRange(GLenum readTarget, GLenum writeTarget, GLintptr readOffset, GLintptr writeOffset, GLsizeiptr size);
|
||||
GLenum ReleaseBufferMemory(GLenum target);
|
||||
GLenum CreateBuffer(GLuint buffer);
|
||||
GLenum GenBufferNames(GLsizei n, GLuint* buffers);
|
||||
GLenum BindBuffer(GLenum target, GLuint buffer);
|
||||
GLenum CommitBufferStorage(GLenum target, GLsizeiptr size, const void* data, GLenum usage);
|
||||
GLenum CommitBufferStorageRegion(GLenum target, GLintptr offset, GLsizeiptr size, const void* data);
|
||||
bool ValidateAllocatedBufferHandle(GLuint buffer);
|
||||
bool ValidateGeneratedName(GLuint buffer);
|
||||
void DeleteBuffer(GLuint buffer);
|
||||
GLenum DeleteBuffers(GLsizei n, const GLuint* buffers);
|
||||
GLenum QueryBufferPropertyIntVector(GLenum target, GLenum pname, GLint* params);
|
||||
|
||||
// VertexArray
|
||||
GLenum CreateVertexArray(GLuint* array);
|
||||
GLenum CreateVertexArrays(GLsizei n, GLuint* arrays);
|
||||
GLenum GenVertexArraysNames(GLsizei n, GLuint* arrays);
|
||||
GLenum BindVertexArray(GLuint array);
|
||||
GLenum EnableVertexAttribArray(GLuint index);
|
||||
GLenum DisableVertexAttribArray(GLuint index);
|
||||
GLenum SetVertexAttributeLayout(GLuint index, GLint size, GLenum type,
|
||||
GLboolean normalized, GLsizei stride,
|
||||
const void* pointer);
|
||||
GLenum SetVertexAttributeLayoutInt(GLuint index, GLint size, GLenum type,
|
||||
GLsizei stride, const void* pointer);
|
||||
|
||||
// Program
|
||||
GLenum CreateShader(GLenum type, GLuint* shader);
|
||||
GLenum CreateProgram(GLuint* program);
|
||||
GLenum DeleteShader(GLuint shader);
|
||||
GLenum DeleteProgram(GLuint program);
|
||||
GLenum LinkShaderToProgram(GLuint program, GLuint shader);
|
||||
GLenum UploadShaderSource(GLuint shader, GLsizei count, const GLchar** string, const GLint* length);
|
||||
GLenum BuildShaderStage(GLuint shader);
|
||||
GLenum FinalizeProgramPipeline(GLuint program);
|
||||
GLenum ActivateRenderProgram(GLuint program);
|
||||
GLenum DefineProgramAttributeBinding(GLuint program, GLuint index, const GLchar* name);
|
||||
GLint QueryProgramAttributeBinding(GLuint program, const GLchar* name);
|
||||
GLint QueryProgramUniformLocation(GLuint program, const GLchar* name);
|
||||
GLenum QueryProgramStateIntVector(GLuint program, GLenum pname, GLint* params);
|
||||
GLenum QueryShaderStateIntVector(GLuint shader, GLenum pname, GLint* params);
|
||||
template<typename T>
|
||||
GLenum SetUniform(GLuint program, GLint location, GLsizei count, const T* value, GLenum type);
|
||||
|
||||
template<typename T>
|
||||
GLenum SetUniformMatrix(GLuint program, GLint location, GLsizei count, GLboolean transpose, const T* value, GLenum matrixType);
|
||||
|
||||
#define DECLARE_UNIFORM_FUNCTIONS(type, suffix, vecType) \
|
||||
void UpdateProgramUniform##suffix##1(GLint location, type v0); \
|
||||
void UpdateProgramUniform##suffix##2(GLint location, type v0, type v1); \
|
||||
void UpdateProgramUniform##suffix##3(GLint location, type v0, type v1, type v2); \
|
||||
void UpdateProgramUniform##suffix##4(GLint location, type v0, type v1, type v2, type v3); \
|
||||
void UpdateProgramUniform##suffix##Vector1(GLint location, GLsizei count, const type* value); \
|
||||
void UpdateProgramUniform##suffix##Vector2(GLint location, GLsizei count, const type* value); \
|
||||
void UpdateProgramUniform##suffix##Vector3(GLint location, GLsizei count, const type* value); \
|
||||
void UpdateProgramUniform##suffix##Vector4(GLint location, GLsizei count, const type* value);
|
||||
DECLARE_UNIFORM_FUNCTIONS(GLfloat, Float, GL_FLOAT)
|
||||
DECLARE_UNIFORM_FUNCTIONS(GLint, Int, GL_INT)
|
||||
DECLARE_UNIFORM_FUNCTIONS(GLuint, UInt, GL_UNSIGNED_INT)
|
||||
DECLARE_UNIFORM_FUNCTIONS(GLboolean, Bool, GL_BOOL)
|
||||
#undef DECLARE_UNIFORM_FUNCTIONS
|
||||
#define DECLARE_MATRIX_FUNCTIONS(suffix, matrixType) \
|
||||
void UpdateProgramUniformMatrix##suffix##Vector(GLint location, GLsizei count, GLboolean transpose, const GLfloat* value);
|
||||
DECLARE_MATRIX_FUNCTIONS(2x2, GL_FLOAT_MAT2)
|
||||
DECLARE_MATRIX_FUNCTIONS(3x3, GL_FLOAT_MAT3)
|
||||
DECLARE_MATRIX_FUNCTIONS(4x4, GL_FLOAT_MAT4)
|
||||
DECLARE_MATRIX_FUNCTIONS(2x3, GL_FLOAT_MAT2x3)
|
||||
DECLARE_MATRIX_FUNCTIONS(2x4, GL_FLOAT_MAT2x4)
|
||||
DECLARE_MATRIX_FUNCTIONS(3x2, GL_FLOAT_MAT3x2)
|
||||
DECLARE_MATRIX_FUNCTIONS(3x4, GL_FLOAT_MAT3x4)
|
||||
DECLARE_MATRIX_FUNCTIONS(4x2, GL_FLOAT_MAT4x2)
|
||||
DECLARE_MATRIX_FUNCTIONS(4x3, GL_FLOAT_MAT4x3)
|
||||
#undef DECLARE_MATRIX_FUNCTIONS
|
||||
|
||||
GLuint GetCurrentProgram();
|
||||
bool SetProgramStatus(GLuint program, GLboolean status);
|
||||
bool SetShaderStatus(GLuint shader, GLboolean status);
|
||||
|
||||
// Framebuffer
|
||||
GLenum CreateFramebuffer(GLuint* framebuffer);
|
||||
GLenum CreateFramebuffers(GLsizei n, GLuint* framebuffers);
|
||||
GLenum DeleteFramebuffer(GLuint framebuffer);
|
||||
GLenum BindFramebuffer(GLenum target, GLuint framebuffer);
|
||||
GLenum AttachTexture2DToFramebuffer(GLenum target, GLenum attachment, GLenum textarget,
|
||||
GLuint texture, GLint level);
|
||||
GLenum ValidateFramebufferCompleteness(GLenum target);
|
||||
}
|
||||
|
||||
|
||||
#endif //MOBILEGL_GLSTATE_H
|
||||
@@ -0,0 +1,165 @@
|
||||
//
|
||||
// Created by BZLZHH on 2025/5/3.
|
||||
//
|
||||
|
||||
// TODO: Add more gl error check for framebuffer state manager.
|
||||
// TODO: Check if the state machine really meets up to OpenGL 3 standard.
|
||||
|
||||
#include "FramebufferState.h"
|
||||
|
||||
// Re-Include Includes.h, cuz of the absence of GLState.h
|
||||
#undef MOBILEGL_GLSTATE_H
|
||||
#include "../../../Includes.h"
|
||||
|
||||
GLenum FramebufferState::Create(GLuint* framebuffer) {
|
||||
if (!framebuffer) return GL_INVALID_VALUE;
|
||||
GLuint id = freeIds_.empty() ? ++lastId_ : *freeIds_.begin();
|
||||
if (!freeIds_.empty()) freeIds_.erase(freeIds_.begin());
|
||||
*framebuffer = id;
|
||||
framebuffers_[id].generated = true;
|
||||
return GL_NO_ERROR;
|
||||
}
|
||||
|
||||
GLenum FramebufferState::CreateN(GLsizei n, GLuint* framebuffers) {
|
||||
if (n < 0) return GL_INVALID_VALUE;
|
||||
for (GLsizei i = 0; i < n; ++i) {
|
||||
if (GLenum err = Create(&framebuffers[i])) return err;
|
||||
}
|
||||
return GL_NO_ERROR;
|
||||
}
|
||||
|
||||
GLenum FramebufferState::Delete(GLuint framebuffer) {
|
||||
if (framebuffer == 0) return GL_INVALID_VALUE;
|
||||
|
||||
if (framebuffers_.erase(framebuffer)) {
|
||||
// TODO: Prevent the object that uses a freeId from conflicting with legacy object in the backend.
|
||||
//freeIds_.insert(framebuffer);
|
||||
for (auto& [target, id] : currentBindings_) {
|
||||
if (id == framebuffer) id = 0;
|
||||
}
|
||||
}
|
||||
return GL_NO_ERROR;
|
||||
}
|
||||
|
||||
GLenum FramebufferState::Bind(GLenum target, GLuint framebuffer) {
|
||||
if (MG_Constants::Framebuffer::VALID_TARGETS.find(target) == MG_Constants::Framebuffer::VALID_TARGETS.end())
|
||||
return GL_INVALID_ENUM;
|
||||
if (framebuffer != 0 && !ValidateHandle(framebuffer))
|
||||
return GL_INVALID_OPERATION;
|
||||
if (target == GL_FRAMEBUFFER) {
|
||||
currentBindings_[GL_READ_FRAMEBUFFER] = framebuffer;
|
||||
currentBindings_[GL_DRAW_FRAMEBUFFER] = framebuffer;
|
||||
} else {
|
||||
currentBindings_[target] = framebuffer;
|
||||
}
|
||||
return GL_NO_ERROR;
|
||||
}
|
||||
|
||||
GLenum FramebufferState::AttachTexture2D(GLenum target, GLenum attachment,
|
||||
GLenum textarget, GLuint texture, GLint level) {
|
||||
if (MG_Constants::Framebuffer::VALID_ATTACHMENTS.find(attachment) == MG_Constants::Framebuffer::VALID_ATTACHMENTS.end() ||
|
||||
MG_Constants::Texture::VALID_TARGETS.find(textarget) == MG_Constants::Texture::VALID_TARGETS.end())
|
||||
return GL_INVALID_ENUM;
|
||||
if (target == GL_FRAMEBUFFER) target = GL_DRAW_FRAMEBUFFER;
|
||||
FramebufferObject* fbo = GetCurrentFBO(target);
|
||||
if (!fbo) return GL_INVALID_OPERATION;
|
||||
if (texture != 0 && !MG_State_T::textureState->IsTexture(texture))
|
||||
return GL_INVALID_VALUE;
|
||||
FramebufferAttachment att;
|
||||
att.type = GL_TEXTURE_2D;
|
||||
att.handle = texture;
|
||||
att.mipLevel = level;
|
||||
fbo->attachments[attachment] = att;
|
||||
|
||||
if (texture != 0) {
|
||||
GLint params = 0;
|
||||
MG_State_T::textureState->QueryLevelPropertyIntVector(
|
||||
textarget, level, GL_TEXTURE_WIDTH, ¶ms);
|
||||
fbo->width = params;
|
||||
MG_State_T::textureState->QueryLevelPropertyIntVector(
|
||||
textarget, level, GL_TEXTURE_HEIGHT, ¶ms);
|
||||
fbo->height = params;
|
||||
}
|
||||
UpdateCompleteness(*fbo);
|
||||
return GL_NO_ERROR;
|
||||
}
|
||||
|
||||
GLenum FramebufferState::ValidateCompleteness(GLenum target) {
|
||||
if (target == GL_FRAMEBUFFER) target = GL_DRAW_FRAMEBUFFER;
|
||||
FramebufferObject* fbo = GetCurrentFBO(target);
|
||||
if (!fbo) return GL_INVALID_OPERATION;
|
||||
return fbo->status;
|
||||
}
|
||||
|
||||
void FramebufferState::UpdateCompleteness(FramebufferObject& fbo) {
|
||||
if (fbo.attachments.empty()) {
|
||||
fbo.status = GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT;
|
||||
return;
|
||||
}
|
||||
bool hasColor = false;
|
||||
bool hasDepth = false;
|
||||
bool hasStencil = false;
|
||||
GLsizei width = 0, height = 0;
|
||||
for (const auto& [attach, att] : fbo.attachments) {
|
||||
GLsizei attWidth = 0, attHeight = 0;
|
||||
if (att.handle != 0) {
|
||||
GLint params = 0;
|
||||
MG_State_T::textureState->QueryLevelPropertyIntVector(
|
||||
GL_TEXTURE_2D, att.mipLevel, GL_TEXTURE_WIDTH, ¶ms);
|
||||
attWidth = params;
|
||||
MG_State_T::textureState->QueryLevelPropertyIntVector(
|
||||
GL_TEXTURE_2D, att.mipLevel, GL_TEXTURE_HEIGHT, ¶ms);
|
||||
attHeight = params;
|
||||
}
|
||||
if (width == 0) {
|
||||
width = attWidth;
|
||||
height = attHeight;
|
||||
} else if (attWidth != width || attHeight != height) {
|
||||
fbo.status = GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS;
|
||||
return;
|
||||
}
|
||||
|
||||
if (attach == GL_DEPTH_ATTACHMENT) hasDepth = true;
|
||||
if (attach == GL_STENCIL_ATTACHMENT) hasStencil = true;
|
||||
if (attach >= GL_COLOR_ATTACHMENT0 &&
|
||||
attach <= GL_COLOR_ATTACHMENT31) hasColor = true;
|
||||
}
|
||||
|
||||
if (!ValidateAttachmentCombination(fbo)) {
|
||||
fbo.status = GL_FRAMEBUFFER_UNSUPPORTED;
|
||||
return;
|
||||
}
|
||||
fbo.status = GL_FRAMEBUFFER_COMPLETE;
|
||||
}
|
||||
|
||||
bool FramebufferState::ValidateAttachmentCombination(const FramebufferObject& fbo) {
|
||||
bool depthStencilConflict = false;
|
||||
for (const auto& [attach, att] : fbo.attachments) {
|
||||
if (attach == GL_DEPTH_STENCIL_ATTACHMENT) {
|
||||
GLint internalFormat = 0;
|
||||
MG_State_T::textureState->QueryLevelPropertyIntVector(
|
||||
GL_TEXTURE_2D, att.mipLevel, GL_TEXTURE_INTERNAL_FORMAT, &internalFormat);
|
||||
if (internalFormat != GL_DEPTH24_STENCIL8 &&
|
||||
internalFormat != GL_DEPTH32F_STENCIL8) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
FramebufferObject* FramebufferState::GetCurrentFBO(GLenum target) {
|
||||
auto it = currentBindings_.find(target);
|
||||
if (it == currentBindings_.end()) return nullptr;
|
||||
|
||||
GLuint id = it->second;
|
||||
if (id == 0) return nullptr;
|
||||
|
||||
auto fboIt = framebuffers_.find(id);
|
||||
return (fboIt != framebuffers_.end()) ? &fboIt->second : nullptr;
|
||||
}
|
||||
|
||||
bool FramebufferState::ValidateHandle(GLuint framebuffer) {
|
||||
return framebuffers_.count(framebuffer) &&
|
||||
framebuffers_[framebuffer].generated;
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
//
|
||||
// Created by BZLZHH on 2025/5/3.
|
||||
//
|
||||
|
||||
#ifndef MOBILEGL_FRAMEBUFFERSTATE_H
|
||||
#define MOBILEGL_FRAMEBUFFERSTATE_H
|
||||
#define MOBILEGL_GLSTATE_H
|
||||
|
||||
#include "../../../Includes.h"
|
||||
|
||||
struct FramebufferAttachment {
|
||||
GLenum type = GL_NONE;
|
||||
GLuint handle = 0;
|
||||
GLint mipLevel = 0;
|
||||
GLint zoffset = 0;
|
||||
};
|
||||
|
||||
struct FramebufferObject {
|
||||
bool generated = false;
|
||||
ankerl::unordered_map<GLenum, FramebufferAttachment> attachments;
|
||||
GLenum status = GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT;
|
||||
GLsizei width = 0;
|
||||
GLsizei height = 0;
|
||||
};
|
||||
|
||||
class FramebufferState {
|
||||
public:
|
||||
// Return: the validity of the operation
|
||||
GLenum Create(GLuint* framebuffer);
|
||||
GLenum CreateN(GLsizei n, GLuint* framebuffers);
|
||||
GLenum Delete(GLuint framebuffer);
|
||||
GLenum Bind(GLenum target, GLuint framebuffer);
|
||||
GLenum AttachTexture2D(GLenum target, GLenum attachment,
|
||||
GLenum textarget, GLuint texture, GLint level);
|
||||
GLenum ValidateCompleteness(GLenum target);
|
||||
FramebufferObject* GetCurrentFBO(GLenum target);
|
||||
|
||||
ankerl::unordered_map<GLenum, GLuint> currentBindings_; // READ/DRAW
|
||||
|
||||
private:
|
||||
ankerl::unordered_map<GLuint, FramebufferObject> framebuffers_;
|
||||
std::set<GLuint> freeIds_;
|
||||
GLuint lastId_ = 0;
|
||||
bool ValidateHandle(GLuint framebuffer);
|
||||
static void UpdateCompleteness(FramebufferObject& fbo);
|
||||
static bool ValidateAttachmentCombination(const FramebufferObject& fbo);
|
||||
};
|
||||
|
||||
#endif //MOBILEGL_FRAMEBUFFERSTATE_H
|
||||
@@ -0,0 +1,577 @@
|
||||
//
|
||||
// Created by BZLZHH on 2025/5/1.
|
||||
|
||||
// TODO: Add more gl error check for buffer state manager.
|
||||
// TODO: Check if the state machine really meets up to OpenGL 3 standard.
|
||||
|
||||
#include "ProgramState.h"
|
||||
|
||||
// Re-Include Includes.h, cuz of the absence of Program/DebugTool.h and Program/GLSLTool.cpp.
|
||||
#undef MOBILEGL_GLSLTOOL_H
|
||||
#undef MOBILEGL_PROGRAM_DEBUGTOOL_H
|
||||
#include "../../../Includes.h"
|
||||
|
||||
ProgramState::ProgramState() {
|
||||
glslang::InitializeProcess();
|
||||
}
|
||||
|
||||
ProgramState::~ProgramState() {
|
||||
glslang::FinalizeProcess();
|
||||
}
|
||||
|
||||
GLenum ProgramState::CreateProgram(GLuint* program) {
|
||||
MG_Util::Debug::LogD("MG_State: Program: CreateProgram called, program ptr=%p", program);
|
||||
if (!program) {
|
||||
MG_Util::Debug::LogE("MG_State: Program: CreateProgram error: program pointer is null");
|
||||
return GL_INVALID_VALUE;
|
||||
}
|
||||
|
||||
GLuint id = freeProgramIds_.empty() ? ++lastProgramId_ : *freeProgramIds_.begin();
|
||||
if (!freeProgramIds_.empty())
|
||||
freeProgramIds_.erase(freeProgramIds_.begin());
|
||||
|
||||
ProgramObject obj;
|
||||
obj.linked = {UncertainBool::Unknown, UncertainBool::False};
|
||||
obj.linkStatus = GL_FALSE;
|
||||
obj.markedForDeletion = false;
|
||||
programs_[id] = obj;
|
||||
*program = id;
|
||||
MG_Util::Debug::LogD("MG_State: Program: CreateProgram success, new id=%u", id);
|
||||
return GL_NO_ERROR;
|
||||
}
|
||||
|
||||
GLenum ProgramState::DeleteProgram(GLuint program) {
|
||||
MG_Util::Debug::LogD("MG_State: Program: DeleteProgram called, id=%u", program);
|
||||
if (!ValidateProgram_(program)) {
|
||||
MG_Util::Debug::LogE("MG_State: Program: DeleteProgram error: invalid program id %u", program);
|
||||
return GL_INVALID_VALUE;
|
||||
}
|
||||
|
||||
programs_.erase(program);
|
||||
//freeProgramIds_.insert(program);
|
||||
if (currentProgram_ == program)
|
||||
currentProgram_ = 0;
|
||||
MG_Util::Debug::LogD("MG_State: Program: DeleteProgram success, id=%u", program);
|
||||
return GL_NO_ERROR;
|
||||
}
|
||||
|
||||
GLenum ProgramState::LinkShaderToProgram(GLuint program, GLuint shader) {
|
||||
MG_Util::Debug::LogD("MG_State: Program: LinkShaderToProgram called, program=%u, shader=%u",
|
||||
program, shader);
|
||||
if (!ValidateProgram_(program) || !ValidateShader_(shader)) {
|
||||
MG_Util::Debug::LogE("MG_State: Program: LinkShaderToProgram error: invalid ids program=%u shader=%u",
|
||||
program, shader);
|
||||
return GL_INVALID_VALUE;
|
||||
}
|
||||
|
||||
auto& prog = programs_[program];
|
||||
if (std::find(prog.attachedShaders.begin(),
|
||||
prog.attachedShaders.end(), shader) != prog.attachedShaders.end()) {
|
||||
MG_Util::Debug::LogW("MG_State: Program: LinkShaderToProgram warning: shader %u already attached to program %u",
|
||||
shader, program);
|
||||
return GL_INVALID_OPERATION;
|
||||
}
|
||||
|
||||
prog.attachedShaders.push_back(shader);
|
||||
MG_Util::Debug::LogD("MG_State: Program: LinkShaderToProgram success: shader %u attached to program %u",
|
||||
shader, program);
|
||||
return GL_NO_ERROR;
|
||||
}
|
||||
|
||||
GLenum ProgramState::FinalizeProgramPipeline(GLuint program) {
|
||||
MG_Util::Debug::LogD("MG_State: Program: FinalizeProgramPipeline called, id=%u", program);
|
||||
if (!ValidateProgram_(program)) {
|
||||
MG_Util::Debug::LogE("MG_State: Program: FinalizeProgramPipeline error: invalid program id %u", program);
|
||||
return GL_INVALID_VALUE;
|
||||
}
|
||||
auto& prog = programs_[program];
|
||||
|
||||
for (GLuint shader : prog.attachedShaders) {
|
||||
if (!shaders_[shader].compiled.toBool()) {
|
||||
MG_Util::Debug::LogE("MG_State: Program: FinalizeProgramPipeline error: shader %u not compiled", shader);
|
||||
return GL_INVALID_OPERATION;
|
||||
}
|
||||
}
|
||||
|
||||
prog.infoLog.clear();
|
||||
|
||||
for (GLuint shaderId : prog.attachedShaders) {
|
||||
auto& shader = shaders_[shaderId];
|
||||
if (!shader.compiled.toBool() || shader.compiledSpirv.empty()) {
|
||||
prog.infoLog = "Program linking failed: \n" +
|
||||
MG_Util::Program::GetShaderTypeName(shader.type) + " is not compiled.";
|
||||
prog.linked = UncertainBool::False;
|
||||
prog.linkStatus = GL_FALSE;
|
||||
MG_Util::Debug::LogE("MG_State: Program: FinalizeProgramPipeline error: %s", prog.infoLog.c_str());
|
||||
return GL_INVALID_OPERATION;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::vector<unsigned>> allSpirv =
|
||||
MG_Util::Program::CompileMultipleShadersToSPIRV(*this, prog, prog.infoLog);
|
||||
|
||||
MG_Util::Program::ReflectSPIRVUniforms(allSpirv, prog, prog.infoLog);
|
||||
if (!prog.infoLog.empty()) {
|
||||
prog.linked = UncertainBool::False;
|
||||
prog.linkStatus = GL_FALSE;
|
||||
prog.infoLog = "Program linking failed: " + prog.infoLog;
|
||||
MG_Util::Debug::LogE("MG_State: Program: FinalizeProgramPipeline error: %s", prog.infoLog.c_str());
|
||||
return GL_INVALID_OPERATION;
|
||||
}
|
||||
|
||||
for (auto& [name, index] : prog.attribBindings) {
|
||||
prog.attribLocations[name] = index;
|
||||
}
|
||||
|
||||
prog.linked = {UncertainBool::Unknown, UncertainBool::True};
|
||||
prog.linkStatus = GL_TRUE;
|
||||
|
||||
CleanupShaders_();
|
||||
|
||||
MG_Util::Program::DumpUniforms(*this, program);
|
||||
MG_Util::Debug::LogD("MG_State: Program: FinalizeProgramPipeline success, id=%u", program);
|
||||
return GL_NO_ERROR;
|
||||
}
|
||||
|
||||
GLenum ProgramState::ActivateRenderProgram(GLuint program) {
|
||||
MG_Util::Debug::LogD("MG_State: Program: ActivateRenderProgram called, id=%u", program);
|
||||
if (program != 0 && !ValidateProgram_(program)) {
|
||||
MG_Util::Debug::LogE("MG_State: Program: ActivateRenderProgram error: invalid program id %u", program);
|
||||
return GL_INVALID_VALUE;
|
||||
}
|
||||
|
||||
MG_Util::Program::DumpCurrentUniforms(*this);
|
||||
currentProgram_ = program;
|
||||
MG_Util::Program::DumpCurrentUniforms(*this);
|
||||
MG_Util::Debug::LogD("MG_State: Program: ActivateRenderProgram success, current program=%u", currentProgram_);
|
||||
return GL_NO_ERROR;
|
||||
}
|
||||
|
||||
GLenum ProgramState::DefineProgramAttributeBinding(GLuint program, GLuint index, const GLchar* name) {
|
||||
MG_Util::Debug::LogD("MG_State: Program: DefineProgramAttributeBinding called, program=%u, index=%u, name=%s",
|
||||
program, index, name);
|
||||
if (!ValidateProgram_(program)) {
|
||||
MG_Util::Debug::LogE("MG_State: Program: DefineProgramAttributeBinding error: invalid program id %u", program);
|
||||
return GL_INVALID_VALUE;
|
||||
}
|
||||
if (index >= GL_MAX_VERTEX_ATTRIBS) {
|
||||
MG_Util::Debug::LogE("MG_State: Program: DefineProgramAttributeBinding error: index %u out of range", index);
|
||||
return GL_INVALID_VALUE;
|
||||
}
|
||||
|
||||
programs_[program].attribBindings[name] = index;
|
||||
MG_Util::Debug::LogD("MG_State: Program: DefineProgramAttributeBinding success: %s -> %u", name, index);
|
||||
return GL_NO_ERROR;
|
||||
}
|
||||
|
||||
GLint ProgramState::QueryProgramAttributeBinding(GLuint program, const GLchar* name) {
|
||||
MG_Util::Debug::LogD("MG_State: Program: QueryProgramAttributeBinding called, program=%u, name=%s",
|
||||
program, name);
|
||||
if (!ValidateProgram_(program)) {
|
||||
MG_Util::Debug::LogE("MG_State: Program: QueryProgramAttributeBinding error: invalid program id %u", program);
|
||||
return -1;
|
||||
}
|
||||
|
||||
auto& prog = programs_[program];
|
||||
auto it = prog.attribLocations.find(name);
|
||||
if (it != prog.attribLocations.end()) {
|
||||
GLint loc = it->second;
|
||||
MG_Util::Debug::LogD("MG_State: Program: QueryProgramAttributeBinding success: %s -> %d", name, loc);
|
||||
return loc;
|
||||
}
|
||||
MG_Util::Debug::LogW("MG_State: Program: QueryProgramAttributeBinding warning: name '%s' not found", name);
|
||||
return -1;
|
||||
}
|
||||
|
||||
GLenum ProgramState::QueryProgramStateIntVector(GLuint program, GLenum pname, GLint* params) {
|
||||
MG_Util::Debug::LogD("MG_State: Program: QueryProgramStateIntVector called, program=%u, pname=0x%X",
|
||||
program, pname);
|
||||
if (!ValidateProgram_(program)) {
|
||||
MG_Util::Debug::LogE("MG_State: Program: QueryProgramStateIntVector error: invalid program id %u", program);
|
||||
return GL_INVALID_VALUE;
|
||||
}
|
||||
auto& prog = programs_[program];
|
||||
|
||||
switch (pname) {
|
||||
case GL_LINK_STATUS: *params = prog.linkStatus; break;
|
||||
case GL_DELETE_STATUS: *params = prog.markedForDeletion; break;
|
||||
case GL_ATTACHED_SHADERS:*params = (GLint)prog.attachedShaders.size(); break;
|
||||
default:
|
||||
MG_Util::Debug::LogE("MG_State: Program: QueryProgramStateIntVector error: invalid pname 0x%X", pname);
|
||||
return GL_INVALID_ENUM;
|
||||
}
|
||||
MG_Util::Debug::LogD("MG_State: Program: QueryProgramStateIntVector success: value=%d", *params);
|
||||
return GL_NO_ERROR;
|
||||
}
|
||||
|
||||
GLenum ProgramState::QueryShaderStateIntVector(GLuint shader, GLenum pname, GLint* params) {
|
||||
MG_Util::Debug::LogD("MG_State: Program: QueryShaderStateIntVector called, shader=%u, pname=0x%X",
|
||||
shader, pname);
|
||||
if (!ValidateShader_(shader)) {
|
||||
MG_Util::Debug::LogE("MG_State: Program: QueryShaderStateIntVector error: invalid shader id %u", shader);
|
||||
return GL_INVALID_VALUE;
|
||||
}
|
||||
auto& obj = shaders_[shader];
|
||||
|
||||
switch (pname) {
|
||||
case GL_COMPILE_STATUS: *params = obj.compileStatus; break;
|
||||
case GL_DELETE_STATUS: *params = obj.markedForDeletion; break;
|
||||
case GL_SHADER_TYPE: *params = obj.type; break;
|
||||
default:
|
||||
MG_Util::Debug::LogE("MG_State: Program: QueryShaderStateIntVector error: invalid pname 0x%X", pname);
|
||||
return GL_INVALID_ENUM;
|
||||
}
|
||||
MG_Util::Debug::LogD("MG_State: Program: QueryShaderStateIntVector success: value=%d", *params);
|
||||
return GL_NO_ERROR;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
GLenum ProgramState::SetUniform(GLuint program, GLint location, GLsizei count, const T* value, GLenum type) {
|
||||
MG_Util::Debug::LogD("MG_State: Program: SetUniform called, program=%u, location=%d, count=%d, type=0x%X",
|
||||
program, location, count, type);
|
||||
if (!ValidateProgram_(program)) {
|
||||
MG_Util::Debug::LogE("MG_State: Program: SetUniform error: invalid program id %u", program);
|
||||
return GL_INVALID_VALUE;
|
||||
}
|
||||
if (location < -1) {
|
||||
MG_Util::Debug::LogE("MG_State: Program: SetUniform error: invalid location %d", location);
|
||||
return GL_INVALID_VALUE;
|
||||
}
|
||||
if (count < 0) {
|
||||
MG_Util::Debug::LogE("MG_State: Program: SetUniform error: invalid count %d", count);
|
||||
return GL_INVALID_VALUE;
|
||||
}
|
||||
if (location == -1) {
|
||||
MG_Util::Debug::LogW("MG_State: Program: SetUniform warning: location -1, no-op");
|
||||
return GL_NO_ERROR;
|
||||
}
|
||||
|
||||
ProgramObject& prog = programs_[program];
|
||||
std::string uniformName;
|
||||
for (const auto& pair : prog.uniformLocations) {
|
||||
if (pair.second == location) {
|
||||
uniformName = pair.first;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (uniformName.empty()) {
|
||||
MG_Util::Debug::LogE("MG_State: Program: SetUniform error: no uniform at location %d in program %u",
|
||||
location, program);
|
||||
return GL_INVALID_OPERATION;
|
||||
}
|
||||
|
||||
UniformValue& uniform = prog.uniformValues[uniformName];
|
||||
|
||||
uniform.setData(type, count, value);
|
||||
MG_Util::Debug::LogD("MG_State: Program: SetUniform success: %s set", uniformName.c_str());
|
||||
return GL_NO_ERROR;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
GLenum ProgramState::SetUniformMatrix(GLuint program, GLint location, GLsizei count, GLboolean transpose, const T* value, GLenum matrixType) {
|
||||
MG_Util::Debug::LogD("MG_State: Program: SetUniformMatrix called, program=%u, location=%d, count=%d, matrixType=0x%X",
|
||||
program, location, count, matrixType);
|
||||
(void)transpose;
|
||||
if (!ValidateProgram_(program)) {
|
||||
MG_Util::Debug::LogE("MG_State: Program: SetUniformMatrix error: invalid program id %u", program);
|
||||
return GL_INVALID_VALUE;
|
||||
}
|
||||
if (location < -1) {
|
||||
MG_Util::Debug::LogE("MG_State: Program: SetUniformMatrix error: invalid location %d", location);
|
||||
return GL_INVALID_VALUE;
|
||||
}
|
||||
if (count < 0) {
|
||||
MG_Util::Debug::LogE("MG_State: Program: SetUniformMatrix error: invalid count %d", count);
|
||||
return GL_INVALID_VALUE;
|
||||
}
|
||||
if (location == -1) {
|
||||
MG_Util::Debug::LogW("MG_State: Program: SetUniformMatrix warning: location -1, no-op");
|
||||
return GL_NO_ERROR;
|
||||
}
|
||||
|
||||
ProgramObject& prog = programs_[program];
|
||||
std::string uniformName;
|
||||
for (const auto& pair : prog.uniformLocations) {
|
||||
if (pair.second == location) {
|
||||
uniformName = pair.first;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (uniformName.empty()) {
|
||||
MG_Util::Debug::LogE("MG_State: Program: SetUniformMatrix error: no uniform at location %d in program %u",
|
||||
location, program);
|
||||
return GL_INVALID_OPERATION;
|
||||
}
|
||||
|
||||
UniformValue& uniform = prog.uniformValues[uniformName];
|
||||
uniform.setData(matrixType, count, value);
|
||||
MG_Util::Debug::LogD("MG_State: Program: SetUniformMatrix success: %s set", uniformName.c_str());
|
||||
return GL_NO_ERROR;
|
||||
}
|
||||
|
||||
#define IMPLEMENT_UNIFORM_FUNCTIONS(type, suffix, vecType) \
|
||||
void ProgramState::UpdateUniform##suffix##1(GLint location, type v0) { \
|
||||
MG_Util::Debug::LogD("MG_State: Program: UpdateUniform" #suffix "1 called, location=%d", location); \
|
||||
SetUniform<type>(currentProgram_, location, 1, &v0, vecType); \
|
||||
} \
|
||||
void ProgramState::UpdateUniform##suffix##2(GLint location, type v0, type v1) { \
|
||||
type values[2] = {v0, v1}; \
|
||||
MG_Util::Debug::LogD("MG_State: Program: UpdateUniform" #suffix "2 called, location=%d", location); \
|
||||
SetUniform<type>(currentProgram_, location, 1, values, vecType); \
|
||||
} \
|
||||
void ProgramState::UpdateUniform##suffix##3(GLint location, type v0, type v1, type v2) { \
|
||||
type values[3] = {v0, v1, v2}; \
|
||||
MG_Util::Debug::LogD("MG_State: Program: UpdateUniform" #suffix "3 called, location=%d", location); \
|
||||
SetUniform<type>(currentProgram_, location, 1, values, vecType); \
|
||||
} \
|
||||
void ProgramState::UpdateUniform##suffix##4(GLint location, type v0, type v1, type v2, type v3) { \
|
||||
type values[4] = {v0, v1, v2, v3}; \
|
||||
MG_Util::Debug::LogD("MG_State: Program: UpdateUniform" #suffix "4 called, location=%d", location); \
|
||||
SetUniform<type>(currentProgram_, location, 1, values, vecType); \
|
||||
} \
|
||||
void ProgramState::UpdateUniform##suffix##Vector1(GLint location, GLsizei count, const type* value) { \
|
||||
MG_Util::Debug::LogD("MG_State: Program: UpdateUniform" #suffix "Vector1 called, location=%d, count=%d", location, count); \
|
||||
SetUniform<type>(currentProgram_, location, count, value, vecType); \
|
||||
} \
|
||||
void ProgramState::UpdateUniform##suffix##Vector2(GLint location, GLsizei count, const type* value) { \
|
||||
MG_Util::Debug::LogD("MG_State: Program: UpdateUniform" #suffix "Vector2 called, location=%d, count=%d", location, count); \
|
||||
SetUniform<type>(currentProgram_, location, count, value, vecType); \
|
||||
} \
|
||||
void ProgramState::UpdateUniform##suffix##Vector3(GLint location, GLsizei count, const type* value) { \
|
||||
MG_Util::Debug::LogD("MG_State: Program: UpdateUniform" #suffix "Vector3 called, location=%d, count=%d", location, count); \
|
||||
SetUniform<type>(currentProgram_, location, count, value, vecType); \
|
||||
} \
|
||||
void ProgramState::UpdateUniform##suffix##Vector4(GLint location, GLsizei count, const type* value) { \
|
||||
MG_Util::Debug::LogD("MG_State: Program: UpdateUniform" #suffix "Vector4 called, location=%d, count=%d", location, count); \
|
||||
SetUniform<type>(currentProgram_, location, count, value, vecType); \
|
||||
}
|
||||
|
||||
IMPLEMENT_UNIFORM_FUNCTIONS(GLfloat, Float, GL_FLOAT_VEC4)
|
||||
IMPLEMENT_UNIFORM_FUNCTIONS(GLint, Int, GL_INT_VEC4)
|
||||
IMPLEMENT_UNIFORM_FUNCTIONS(GLuint, UInt, GL_UNSIGNED_INT_VEC4)
|
||||
IMPLEMENT_UNIFORM_FUNCTIONS(GLboolean, Bool, GL_BOOL_VEC4)
|
||||
#undef IMPLEMENT_UNIFORM_FUNCTIONS
|
||||
|
||||
#define IMPLEMENT_MATRIX_FUNCTIONS(suffix, matrixType) \
|
||||
void ProgramState::UpdateUniformMatrix##suffix##Vector(GLint location, GLsizei count, GLboolean transpose, const GLfloat* value) { \
|
||||
MG_Util::Debug::LogD("MG_State: Program: UpdateUniformMatrix" #suffix "Vector called, location=%d, count=%d", location, count); \
|
||||
SetUniformMatrix<GLfloat>(currentProgram_, location, count, transpose, value, matrixType); \
|
||||
}
|
||||
|
||||
IMPLEMENT_MATRIX_FUNCTIONS(2x2, GL_FLOAT_MAT2)
|
||||
IMPLEMENT_MATRIX_FUNCTIONS(3x3, GL_FLOAT_MAT3)
|
||||
IMPLEMENT_MATRIX_FUNCTIONS(4x4, GL_FLOAT_MAT4)
|
||||
IMPLEMENT_MATRIX_FUNCTIONS(2x3, GL_FLOAT_MAT2x3)
|
||||
IMPLEMENT_MATRIX_FUNCTIONS(2x4, GL_FLOAT_MAT2x4)
|
||||
IMPLEMENT_MATRIX_FUNCTIONS(3x2, GL_FLOAT_MAT3x2)
|
||||
IMPLEMENT_MATRIX_FUNCTIONS(3x4, GL_FLOAT_MAT3x4)
|
||||
IMPLEMENT_MATRIX_FUNCTIONS(4x2, GL_FLOAT_MAT4x2)
|
||||
IMPLEMENT_MATRIX_FUNCTIONS(4x3, GL_FLOAT_MAT4x3)
|
||||
#undef IMPLEMENT_MATRIX_FUNCTIONS
|
||||
|
||||
GLint ProgramState::QueryProgramUniformLocation(GLuint program, const GLchar* name) {
|
||||
MG_Util::Debug::LogD("MG_State: Program: QueryProgramUniformLocation called, program=%u, name=%s", program, name);
|
||||
if (!ValidateProgram_(program)) {
|
||||
MG_Util::Debug::LogE("MG_State: Program: QueryProgramUniformLocation error: invalid program id %u", program);
|
||||
return -1;
|
||||
}
|
||||
auto& prog = programs_[program];
|
||||
auto it = prog.uniformLocations.find(name);
|
||||
if (it != prog.uniformLocations.end()) {
|
||||
GLint loc = it->second;
|
||||
MG_Util::Debug::LogD("MG_State: Program: QueryProgramUniformLocation success: %s -> %d", name, loc);
|
||||
return loc;
|
||||
}
|
||||
MG_Util::Debug::LogW("MG_State: Program: QueryProgramUniformLocation warning: name '%s' not found", name);
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool ProgramState::SetProgramStatus(GLuint program, GLboolean status) {
|
||||
MG_Util::Debug::LogD("MG_State: Program: SetProgramStatus called, program=%u, status=%d", program, status);
|
||||
if (!ValidateProgram_(program)) {
|
||||
MG_Util::Debug::LogE("MG_State: Program: SetProgramStatus error: invalid program id %u", program);
|
||||
return false;
|
||||
}
|
||||
auto& prog = programs_[program];
|
||||
if (prog.linked.getValue() != UncertainBool::Unknown) {
|
||||
MG_Util::Debug::LogW("MG_State: Program: SetProgramStatus warning: link state already known");
|
||||
return false;
|
||||
}
|
||||
prog.linked = (status == GL_TRUE) ? UncertainBool::True : UncertainBool::False;
|
||||
prog.linkStatus = status;
|
||||
MG_Util::Debug::LogD("MG_State: Program: SetProgramStatus success, status=%d", status);
|
||||
return true;
|
||||
}
|
||||
|
||||
GLuint ProgramState::GetCurrentProgram() const {
|
||||
MG_Util::Debug::LogD("MG_State: Program: GetCurrentProgram called, returning %u", currentProgram_);
|
||||
return currentProgram_;
|
||||
}
|
||||
|
||||
ShaderObject ProgramState::GetShaderObject(GLuint shader) const {
|
||||
MG_Util::Debug::LogD("MG_State: Program: GetShaderObject called, shader=%u", shader);
|
||||
auto it = shaders_.find(shader);
|
||||
if (it == shaders_.end()) {
|
||||
MG_Util::Debug::LogW("MG_State: Program: GetShaderObject warning: invalid shader id %u", shader);
|
||||
return ShaderObject();
|
||||
}
|
||||
MG_Util::Debug::LogD("MG_State: Program: GetShaderObject success, returning object");
|
||||
return it->second;
|
||||
}
|
||||
|
||||
ProgramObject ProgramState::GetProgramObject(GLuint program) const {
|
||||
MG_Util::Debug::LogD("MG_State: Program: GetProgramObject called, program=%u", program);
|
||||
auto it = programs_.find(program);
|
||||
if (it == programs_.end()) {
|
||||
MG_Util::Debug::LogW("MG_State: Program: GetProgramObject warning: invalid program id %u", program);
|
||||
return ProgramObject();
|
||||
}
|
||||
MG_Util::Debug::LogD("MG_State: Program: GetProgramObject success, returning object");
|
||||
return it->second;
|
||||
}
|
||||
|
||||
bool ProgramState::ValidateProgram_(GLuint program) {
|
||||
bool ok = programs_.count(program) && !programs_[program].markedForDeletion;
|
||||
MG_Util::Debug::LogD("MG_State: Program: ValidateProgram_ called, program=%u, result=%d", program, ok);
|
||||
return ok;
|
||||
}
|
||||
|
||||
void ProgramState::CleanupShaders_() {
|
||||
MG_Util::Debug::LogD("MG_State: Program: CleanupShaders_ start");
|
||||
auto it = shaders_.begin();
|
||||
while (it != shaders_.end()) {
|
||||
if (it->second.markedForDeletion) {
|
||||
bool inUse = false;
|
||||
for (auto& [progId, program] : programs_) {
|
||||
if (std::find(program.attachedShaders.begin(),
|
||||
program.attachedShaders.end(), it->first) != program.attachedShaders.end()) {
|
||||
inUse = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!inUse) {
|
||||
MG_Util::Debug::LogD("MG_State: Program: CleanupShaders_ deleting shader %u", it->first);
|
||||
freeShaderIds_.insert(it->first);
|
||||
it = shaders_.erase(it);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
++it;
|
||||
}
|
||||
MG_Util::Debug::LogD("MG_State: Program: CleanupShaders_ end");
|
||||
}
|
||||
|
||||
// UniformValue
|
||||
template<typename T>
|
||||
void UniformValue::setData(GLenum uniformType, GLsizei count_, const T* values) {
|
||||
MG_Util::Debug::LogD("MG_State: Program: UniformValue::setData called, type=0x%X, count=%d", uniformType, count_);
|
||||
//type = uniformType;
|
||||
count = count_;
|
||||
|
||||
floatData.clear();
|
||||
intData.clear();
|
||||
uintData.clear();
|
||||
boolData.clear();
|
||||
|
||||
switch (type) {
|
||||
case GL_FLOAT: numElements = count_ * 1; break;
|
||||
case GL_FLOAT_VEC2: numElements = count_ * 2; break;
|
||||
case GL_FLOAT_VEC3: numElements = count_ * 3; break;
|
||||
case GL_FLOAT_VEC4: numElements = count_ * 4; break;
|
||||
case GL_INT: numElements = count_ * 1; break;
|
||||
case GL_INT_VEC2: numElements = count_ * 2; break;
|
||||
case GL_INT_VEC3: numElements = count_ * 3; break;
|
||||
case GL_INT_VEC4: numElements = count_ * 4; break;
|
||||
case GL_UNSIGNED_INT: numElements = count_ * 1; break;
|
||||
case GL_UNSIGNED_INT_VEC2: numElements = count_ * 2; break;
|
||||
case GL_UNSIGNED_INT_VEC3: numElements = count_ * 3; break;
|
||||
case GL_UNSIGNED_INT_VEC4: numElements = count_ * 4; break;
|
||||
case GL_BOOL: numElements = count_ * 1; break;
|
||||
case GL_BOOL_VEC2: numElements = count_ * 2; break;
|
||||
case GL_BOOL_VEC3: numElements = count_ * 3; break;
|
||||
case GL_BOOL_VEC4: numElements = count_ * 4; break;
|
||||
case GL_FLOAT_MAT2: numElements = count_ * 4; break; // 2x2
|
||||
case GL_FLOAT_MAT3: numElements = count_ * 9; break; // 3x3
|
||||
case GL_FLOAT_MAT4: numElements = count_ * 16; break; // 4x4
|
||||
case GL_FLOAT_MAT2x3: numElements = count_ * 6; break; // 2x3
|
||||
case GL_FLOAT_MAT2x4: numElements = count_ * 8; break; // 2x4
|
||||
case GL_FLOAT_MAT3x2: numElements = count_ * 6; break; // 3x2
|
||||
case GL_FLOAT_MAT3x4: numElements = count_ * 12; break; // 3x4
|
||||
case GL_FLOAT_MAT4x2: numElements = count_ * 8; break; // 4x2
|
||||
case GL_FLOAT_MAT4x3: numElements = count_ * 12; break; // 4x3
|
||||
case GL_SAMPLER_1D:
|
||||
case GL_SAMPLER_2D:
|
||||
case GL_SAMPLER_3D:
|
||||
case GL_SAMPLER_CUBE:
|
||||
case GL_SAMPLER_1D_SHADOW:
|
||||
case GL_SAMPLER_2D_SHADOW:
|
||||
case GL_SAMPLER_CUBE_SHADOW:
|
||||
case GL_SAMPLER_1D_ARRAY:
|
||||
case GL_SAMPLER_2D_ARRAY:
|
||||
case GL_SAMPLER_1D_ARRAY_SHADOW:
|
||||
case GL_SAMPLER_2D_ARRAY_SHADOW:
|
||||
case GL_SAMPLER_BUFFER:
|
||||
case GL_SAMPLER_2D_MULTISAMPLE:
|
||||
case GL_SAMPLER_2D_MULTISAMPLE_ARRAY: numElements = count_ * 1; break;
|
||||
default:
|
||||
MG_Util::Debug::LogW("MG_State: Program: UniformValue::setData warning: unsupported type 0x%X for numElements", uniformType);
|
||||
numElements = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
for (GLsizei i = 0; i < numElements; ++i) {
|
||||
switch (uniformType) {
|
||||
case GL_FLOAT:
|
||||
case GL_FLOAT_VEC2:
|
||||
case GL_FLOAT_VEC3:
|
||||
case GL_FLOAT_VEC4:
|
||||
case GL_FLOAT_MAT2:
|
||||
case GL_FLOAT_MAT3:
|
||||
case GL_FLOAT_MAT4:
|
||||
case GL_FLOAT_MAT2x3:
|
||||
case GL_FLOAT_MAT2x4:
|
||||
case GL_FLOAT_MAT3x2:
|
||||
case GL_FLOAT_MAT3x4:
|
||||
case GL_FLOAT_MAT4x2:
|
||||
case GL_FLOAT_MAT4x3:
|
||||
floatData.push_back(static_cast<GLfloat>(values[i]));
|
||||
break;
|
||||
case GL_INT:
|
||||
case GL_INT_VEC2:
|
||||
case GL_INT_VEC3:
|
||||
case GL_INT_VEC4:
|
||||
case GL_SAMPLER_1D:
|
||||
case GL_SAMPLER_2D:
|
||||
case GL_SAMPLER_3D:
|
||||
case GL_SAMPLER_CUBE:
|
||||
case GL_SAMPLER_1D_SHADOW:
|
||||
case GL_SAMPLER_2D_SHADOW:
|
||||
case GL_SAMPLER_CUBE_SHADOW:
|
||||
case GL_SAMPLER_1D_ARRAY:
|
||||
case GL_SAMPLER_2D_ARRAY:
|
||||
case GL_SAMPLER_1D_ARRAY_SHADOW:
|
||||
case GL_SAMPLER_2D_ARRAY_SHADOW:
|
||||
case GL_SAMPLER_BUFFER:
|
||||
case GL_SAMPLER_2D_MULTISAMPLE:
|
||||
case GL_SAMPLER_2D_MULTISAMPLE_ARRAY:
|
||||
intData.push_back(static_cast<GLint>(values[i]));
|
||||
break;
|
||||
case GL_UNSIGNED_INT:
|
||||
case GL_UNSIGNED_INT_VEC2:
|
||||
case GL_UNSIGNED_INT_VEC3:
|
||||
case GL_UNSIGNED_INT_VEC4:
|
||||
uintData.push_back(static_cast<GLuint>(values[i]));
|
||||
break;
|
||||
case GL_BOOL:
|
||||
case GL_BOOL_VEC2:
|
||||
case GL_BOOL_VEC3:
|
||||
case GL_BOOL_VEC4:
|
||||
boolData.push_back(values[i] ? GL_TRUE : GL_FALSE);
|
||||
break;
|
||||
default:
|
||||
MG_Util::Debug::LogW("MG_State: Program: UniformValue::setData warning: unsupported type 0x%X", uniformType);
|
||||
break;
|
||||
}
|
||||
}
|
||||
MG_Util::Debug::LogD("MG_State: Program: UniformValue::setData end");
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
//
|
||||
// Created by BZLZHH on 2025/5/1.
|
||||
//
|
||||
|
||||
#ifndef MOBILEGL_PROGRAMSTATE_H
|
||||
#define MOBILEGL_PROGRAMSTATE_H
|
||||
#define MOBILEGL_GLSTATE_H
|
||||
#define MOBILEGL_PROGRAM_DEBUGTOOL_H
|
||||
#define MOBILEGL_GLSLTOOL_H
|
||||
|
||||
#include "../../../Includes.h"
|
||||
|
||||
struct ShaderObject {
|
||||
GLenum type;
|
||||
std::string source;
|
||||
std::vector<unsigned int> compiledSpirv;
|
||||
UncertainBool compiled;
|
||||
GLint compileStatus;
|
||||
std::string infoLog;
|
||||
bool markedForDeletion;
|
||||
};
|
||||
|
||||
struct UniformValue {
|
||||
GLenum type;
|
||||
std::vector<GLfloat> floatData;
|
||||
std::vector<GLint> intData;
|
||||
std::vector<GLuint> uintData;
|
||||
std::vector<GLboolean> boolData;
|
||||
GLsizei count;
|
||||
GLsizei numElements;
|
||||
|
||||
UniformValue() : type(0), count(0) {}
|
||||
|
||||
template<typename T>
|
||||
void setData(GLenum uniformType, GLsizei count_, const T* values);
|
||||
};
|
||||
|
||||
struct ProgramObject {
|
||||
std::vector<GLuint> attachedShaders;
|
||||
UncertainBool linked;
|
||||
bool dirty;
|
||||
GLint linkStatus;
|
||||
std::string infoLog;
|
||||
ankerl::unordered_map<std::string, GLint> attribBindings;
|
||||
ankerl::unordered_map<std::string, GLint> attribLocations;
|
||||
ankerl::unordered_map<std::string, GLint> uniformLocations;
|
||||
ankerl::unordered_map<std::string, UniformValue> uniformValues;
|
||||
bool markedForDeletion;
|
||||
};
|
||||
|
||||
class ProgramState {
|
||||
public:
|
||||
ankerl::unordered_map<GLuint, ShaderObject> shaders_;
|
||||
ankerl::unordered_map<GLuint, ProgramObject> programs_;
|
||||
std::set<GLuint> freeShaderIds_;
|
||||
std::set<GLuint> freeProgramIds_;
|
||||
GLuint lastShaderId_ = 0;
|
||||
GLuint lastProgramId_ = 0;
|
||||
GLuint currentProgram_ = 0;
|
||||
|
||||
ProgramState();
|
||||
~ProgramState();
|
||||
|
||||
// Return: the validity of the operation, according to OpenGL 3 standard
|
||||
GLenum CreateShader(GLenum type, GLuint* shader);
|
||||
GLenum CreateProgram(GLuint* program);
|
||||
GLenum DeleteShader(GLuint shader);
|
||||
GLenum DeleteProgram(GLuint program);
|
||||
GLenum LinkShaderToProgram(GLuint program, GLuint shader);
|
||||
GLenum UploadShaderSource(GLuint shader, GLsizei count, const GLchar** string, const GLint* length);
|
||||
GLenum BuildShaderStage(GLuint shader);
|
||||
GLenum FinalizeProgramPipeline(GLuint program);
|
||||
GLenum ActivateRenderProgram(GLuint program);
|
||||
GLenum DefineProgramAttributeBinding(GLuint program, GLuint index, const GLchar* name);
|
||||
GLint QueryProgramAttributeBinding(GLuint program, const GLchar* name);
|
||||
GLint QueryProgramUniformLocation(GLuint program, const GLchar* name);
|
||||
GLenum QueryProgramStateIntVector(GLuint program, GLenum pname, GLint* params);
|
||||
GLenum QueryShaderStateIntVector(GLuint shader, GLenum pname, GLint* params);
|
||||
|
||||
template<typename T>
|
||||
GLenum SetUniform(GLuint program, GLint location, GLsizei count, const T* value, GLenum type);
|
||||
|
||||
template<typename T>
|
||||
GLenum SetUniformMatrix(GLuint program, GLint location, GLsizei count, GLboolean transpose, const T* value, GLenum matrixType);
|
||||
|
||||
#define DECLARE_UNIFORM_FUNCTIONS(type, suffix, vecType) \
|
||||
void UpdateUniform##suffix##1(GLint location, type v0); \
|
||||
void UpdateUniform##suffix##2(GLint location, type v0, type v1); \
|
||||
void UpdateUniform##suffix##3(GLint location, type v0, type v1, type v2); \
|
||||
void UpdateUniform##suffix##4(GLint location, type v0, type v1, type v2, type v3); \
|
||||
void UpdateUniform##suffix##Vector1(GLint location, GLsizei count, const type* value); \
|
||||
void UpdateUniform##suffix##Vector2(GLint location, GLsizei count, const type* value); \
|
||||
void UpdateUniform##suffix##Vector3(GLint location, GLsizei count, const type* value); \
|
||||
void UpdateUniform##suffix##Vector4(GLint location, GLsizei count, const type* value);
|
||||
DECLARE_UNIFORM_FUNCTIONS(GLfloat, Float, GL_FLOAT)
|
||||
DECLARE_UNIFORM_FUNCTIONS(GLint, Int, GL_INT)
|
||||
DECLARE_UNIFORM_FUNCTIONS(GLuint, UInt, GL_UNSIGNED_INT)
|
||||
DECLARE_UNIFORM_FUNCTIONS(GLboolean, Bool, GL_BOOL)
|
||||
#undef DECLARE_UNIFORM_FUNCTIONS
|
||||
#define DECLARE_MATRIX_FUNCTIONS(suffix, matrixType) \
|
||||
void UpdateUniformMatrix##suffix##Vector(GLint location, GLsizei count, GLboolean transpose, const GLfloat* value);
|
||||
DECLARE_MATRIX_FUNCTIONS(2x2, GL_FLOAT_MAT2)
|
||||
DECLARE_MATRIX_FUNCTIONS(3x3, GL_FLOAT_MAT3)
|
||||
DECLARE_MATRIX_FUNCTIONS(4x4, GL_FLOAT_MAT4)
|
||||
DECLARE_MATRIX_FUNCTIONS(2x3, GL_FLOAT_MAT2x3)
|
||||
DECLARE_MATRIX_FUNCTIONS(2x4, GL_FLOAT_MAT2x4)
|
||||
DECLARE_MATRIX_FUNCTIONS(3x2, GL_FLOAT_MAT3x2)
|
||||
DECLARE_MATRIX_FUNCTIONS(3x4, GL_FLOAT_MAT3x4)
|
||||
DECLARE_MATRIX_FUNCTIONS(4x2, GL_FLOAT_MAT4x2)
|
||||
DECLARE_MATRIX_FUNCTIONS(4x3, GL_FLOAT_MAT4x3)
|
||||
#undef DECLARE_MATRIX_FUNCTIONS
|
||||
|
||||
ProgramObject GetProgramObject(GLuint program) const;
|
||||
ShaderObject GetShaderObject(GLuint program) const;
|
||||
GLuint GetCurrentProgram() const;
|
||||
bool SetProgramStatus(GLuint program, GLboolean status);
|
||||
bool SetShaderStatus(GLuint shader, GLboolean status);
|
||||
|
||||
private:
|
||||
bool ValidateShader_(GLuint shader);
|
||||
bool ValidateProgram_(GLuint program);
|
||||
void CleanupShaders_();
|
||||
GLint GetUniformLocation_(GLuint program, GLint location, const GLchar* name);
|
||||
};
|
||||
|
||||
#endif // MOBILEGL_PROGRAMSTATE_H
|
||||
@@ -0,0 +1,139 @@
|
||||
//
|
||||
// Created by BZLZHH on 2025/5/3.
|
||||
//
|
||||
|
||||
#include "ShaderObject.h"
|
||||
|
||||
// Re-Include Includes.h, cuz of the absence of Program/DebugTool.h and Program/GLSLTool.cpp.
|
||||
#undef MOBILEGL_GLSLTOOL_H
|
||||
#undef MOBILEGL_PROGRAM_DEBUGTOOL_H
|
||||
#include "../../../Includes.h"
|
||||
|
||||
GLenum ProgramState::CreateShader(GLenum type, GLuint* shader) {
|
||||
MG_Util::Debug::LogD("MG_State: Program: CreateShader called, type=0x%X, shader ptr=%p", type, shader);
|
||||
if (!shader) {
|
||||
MG_Util::Debug::LogE("MG_State: Program: CreateShader error: shader pointer is null");
|
||||
return GL_INVALID_VALUE;
|
||||
}
|
||||
if (type != GL_VERTEX_SHADER && type != GL_FRAGMENT_SHADER) {
|
||||
MG_Util::Debug::LogE("MG_State: Program: CreateShader error: invalid shader type 0x%X", type);
|
||||
return GL_INVALID_ENUM;
|
||||
}
|
||||
|
||||
GLuint id = freeShaderIds_.empty() ? ++lastShaderId_ : *freeShaderIds_.begin();
|
||||
if (!freeShaderIds_.empty())
|
||||
freeShaderIds_.erase(freeShaderIds_.begin());
|
||||
|
||||
ShaderObject obj;
|
||||
obj.type = type;
|
||||
obj.compiled = {UncertainBool::Unknown, UncertainBool::False};
|
||||
obj.compiledSpirv = {};
|
||||
obj.markedForDeletion = false;
|
||||
obj.compileStatus = GL_FALSE;
|
||||
shaders_[id] = obj;
|
||||
*shader = id;
|
||||
MG_Util::Debug::LogD("MG_State: Program: CreateShader success, new shader id=%u", id);
|
||||
return GL_NO_ERROR;
|
||||
}
|
||||
|
||||
GLenum ProgramState::DeleteShader(GLuint shader) {
|
||||
MG_Util::Debug::LogD("MG_State: Program: DeleteShader called, shader id=%u", shader);
|
||||
if (!ValidateShader_(shader)) {
|
||||
MG_Util::Debug::LogE("MG_State: Program: DeleteShader error: invalid shader id %u", shader);
|
||||
return GL_INVALID_VALUE;
|
||||
}
|
||||
if (shaders_[shader].markedForDeletion) {
|
||||
MG_Util::Debug::LogW("MG_State: Program: DeleteShader warning: shader %u already marked for deletion", shader);
|
||||
return GL_NO_ERROR;
|
||||
}
|
||||
|
||||
bool inUse = false;
|
||||
for (auto& [progId, program] : programs_) {
|
||||
if (std::find(program.attachedShaders.begin(),
|
||||
program.attachedShaders.end(), shader) != program.attachedShaders.end()) {
|
||||
inUse = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (inUse) {
|
||||
shaders_[shader].markedForDeletion = true;
|
||||
MG_Util::Debug::LogD("MG_State: Program: DeleteShader info: shader %u marked for deferred deletion (in use)", shader);
|
||||
} else {
|
||||
shaders_.erase(shader);
|
||||
// TODO: Prevent the object that uses a freeId from conflicting with legacy object in the backend.
|
||||
//freeShaderIds_.insert(shader);
|
||||
MG_Util::Debug::LogD("MG_State: Program: DeleteShader success: shader %u deleted immediately", shader);
|
||||
}
|
||||
return GL_NO_ERROR;
|
||||
}
|
||||
|
||||
GLenum ProgramState::UploadShaderSource(GLuint shader, GLsizei count,
|
||||
const GLchar** string, const GLint* length) {
|
||||
MG_Util::Debug::LogD("MG_State: Program: UploadShaderSource called, shader id=%u, count=%d", shader, count);
|
||||
if (!ValidateShader_(shader)) {
|
||||
MG_Util::Debug::LogE("MG_State: Program: UploadShaderSource error: invalid shader id %u", shader);
|
||||
return GL_INVALID_VALUE;
|
||||
}
|
||||
if (count < 0) {
|
||||
MG_Util::Debug::LogE("MG_State: Program: UploadShaderSource error: negative count %d", count);
|
||||
return GL_INVALID_VALUE;
|
||||
}
|
||||
|
||||
std::string source;
|
||||
for (GLsizei i = 0; i < count; ++i) {
|
||||
const GLchar* str = string[i];
|
||||
GLint len = (length && length[i] >= 0) ? length[i] : strlen(str);
|
||||
source.append(str, len);
|
||||
}
|
||||
shaders_[shader].source = std::move(source);
|
||||
MG_Util::Debug::LogD("MG_State: Program: UploadShaderSource success: source uploaded (length=%zu)", shaders_[shader].source.size());
|
||||
return GL_NO_ERROR;
|
||||
}
|
||||
|
||||
GLenum ProgramState::BuildShaderStage(GLuint shader) {
|
||||
MG_Util::Debug::LogD("MG_State: Program: BuildShaderStage called, shader id=%u", shader);
|
||||
if (!ValidateShader_(shader)) {
|
||||
MG_Util::Debug::LogE("MG_State: Program: BuildShaderStage error: invalid shader id %u", shader);
|
||||
return GL_INVALID_VALUE;
|
||||
}
|
||||
auto& obj = shaders_[shader];
|
||||
|
||||
std::string compilationLog;
|
||||
auto spirv = MG_Util::Program::CompileGLSLToSPIRV(obj.type, obj.source, compilationLog);
|
||||
if (spirv.empty()) {
|
||||
obj.infoLog = "Shader compilation failed: " + compilationLog;
|
||||
obj.compiled = UncertainBool::False;
|
||||
obj.compileStatus = GL_FALSE;
|
||||
MG_Util::Debug::LogE("MG_State: Program: BuildShaderStage error: %s", compilationLog.c_str());
|
||||
return GL_INVALID_OPERATION;
|
||||
}
|
||||
obj.compiledSpirv = spirv;
|
||||
obj.compiled = {UncertainBool::Unknown, UncertainBool::True};
|
||||
obj.compileStatus = GL_TRUE;
|
||||
obj.infoLog.clear();
|
||||
MG_Util::Debug::LogD("MG_State: Program: BuildShaderStage success: shader %u compiled to SPIR-V (size=%zu bytes)", shader, spirv.size() * sizeof(unsigned));
|
||||
return GL_NO_ERROR;
|
||||
}
|
||||
|
||||
bool ProgramState::SetShaderStatus(GLuint shader, GLboolean status) {
|
||||
MG_Util::Debug::LogD("MG_State: Program: SetShaderStatus called, shader id=%u, status=%d", shader, status);
|
||||
if (!ValidateShader_(shader)) {
|
||||
MG_Util::Debug::LogE("MG_State: Program: SetShaderStatus error: invalid shader id %u", shader);
|
||||
return false;
|
||||
}
|
||||
auto& obj = shaders_[shader];
|
||||
if (obj.compiled.getValue() != UncertainBool::Unknown) {
|
||||
MG_Util::Debug::LogW("MG_State: Program: SetShaderStatus warning: shader %u status already set", shader);
|
||||
return false;
|
||||
}
|
||||
obj.compiled = (status == GL_TRUE) ? UncertainBool::True : UncertainBool::False;
|
||||
obj.compileStatus = status;
|
||||
MG_Util::Debug::LogD("MG_State: Program: SetShaderStatus success: shader %u status=%d", shader, status);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ProgramState::ValidateShader_(GLuint shader) {
|
||||
bool valid = shaders_.count(shader) && !shaders_[shader].markedForDeletion;
|
||||
MG_Util::Debug::LogD("MG_State: Program: ValidateShader_ called, shader id=%u, result=%d", shader, valid);
|
||||
return valid;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
//
|
||||
// Created by BZLZHH on 2025/5/3.
|
||||
//
|
||||
|
||||
#ifndef MOBILEGL_SHADEROBJECT_H
|
||||
#define MOBILEGL_SHADEROBJECT_H
|
||||
|
||||
#define MOBILEGL_GLSTATE_H
|
||||
|
||||
#include "../../../Includes.h"
|
||||
|
||||
#endif //MOBILEGL_SHADEROBJECT_H
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,117 @@
|
||||
//
|
||||
// Created by BZLZHH on 2025/4/4.
|
||||
//
|
||||
|
||||
#ifndef MOBILEGL_TEXTURESTATE_H
|
||||
#define MOBILEGL_TEXTURESTATE_H
|
||||
#define MOBILEGL_GLSTATE_H
|
||||
|
||||
#include "../../../Includes.h"
|
||||
|
||||
struct ComponentSizes {
|
||||
GLint red = 0;
|
||||
GLint green = 0;
|
||||
GLint blue = 0;
|
||||
GLint alpha = 0;
|
||||
GLint depth = 0;
|
||||
GLint stencil = 0;
|
||||
bool isCompressed = false;
|
||||
};
|
||||
|
||||
struct TextureParams {
|
||||
template <typename K, typename V>
|
||||
using unordered_map = ankerl::unordered_dense::map<K, V>;
|
||||
unordered_map<GLenum, GLfloat> texPropertiesFloat;
|
||||
unordered_map<GLenum, GLint> texPropertiesInt;
|
||||
struct MipmapLevel {
|
||||
GLsizei width = 0;
|
||||
GLsizei height = 0;
|
||||
GLenum format = GL_RGBA;
|
||||
GLenum internalFormat = GL_RGBA;
|
||||
GLenum type = GL_UNSIGNED_BYTE;
|
||||
std::vector<GLubyte> pixelData;
|
||||
bool hasData = false;
|
||||
bool dirty = false; // TODO: encapsulate this with an public API to RHI
|
||||
};
|
||||
unordered_map<GLint, MipmapLevel> mipmapData;
|
||||
};
|
||||
|
||||
class TextureObject {
|
||||
public:
|
||||
bool generated = false;
|
||||
GLenum target = GL_NONE;
|
||||
TextureParams params;
|
||||
const void* data = nullptr;
|
||||
bool IsImmutable() const;
|
||||
uint64_t createTimestamp{};
|
||||
};
|
||||
|
||||
class TextureUnitState {
|
||||
template <typename K, typename V>
|
||||
using unordered_map = ankerl::unordered_dense::map<K, V>;
|
||||
private:
|
||||
GLenum activeTarget = GL_TEXTURE_2D;
|
||||
|
||||
public:
|
||||
unordered_map<GLenum, GLuint> boundTextures;
|
||||
void Bind(GLenum target, GLuint texture);
|
||||
GLuint GetBoundTexture(GLenum target);
|
||||
};
|
||||
|
||||
class TextureState {
|
||||
template <typename K, typename V>
|
||||
using unordered_map = ankerl::unordered_dense::map<K, V>;
|
||||
private:
|
||||
|
||||
GLuint lastUsedID_ = 1;
|
||||
// ankerl::unordered_set<GLuint> freeIDs_;
|
||||
std::vector<GLuint> freeID_;
|
||||
|
||||
unordered_map<GLenum, TextureObject> proxyTextures_;
|
||||
|
||||
static GLint GetUnpackParam_(GLenum pname);
|
||||
public:
|
||||
TextureState();
|
||||
|
||||
bool IsTextureGenerated(GLuint texture);
|
||||
bool IsTexture(GLuint texture);
|
||||
unordered_map<GLuint, TextureObject> textures;
|
||||
|
||||
// Return: the validity of the operation, according to OpenGL 3 standard
|
||||
GLenum BindUnit(GLenum textureUnit);
|
||||
GLenum Create(GLuint* texture);
|
||||
GLenum CreateN(GLsizei n, GLuint* textures);
|
||||
GLenum Bind(GLenum target, GLuint texture);
|
||||
GLenum Upload2D(GLenum target, GLint level, GLint internalFormat,
|
||||
GLsizei width, GLsizei height, GLint border, GLenum format,
|
||||
GLenum type, const void* data);
|
||||
GLenum UpdateRegion2D(GLenum target, GLint level, GLint xoffset,
|
||||
GLint yoffset, GLsizei width, GLsizei height, GLenum format,
|
||||
GLenum type, const GLvoid* data);
|
||||
GLenum SetTexturePropertyInt(GLenum target, GLenum pname, GLint param);
|
||||
GLenum SetTexturePropertyFloat(GLenum target, GLenum pname, GLfloat param);
|
||||
GLenum Delete(GLuint texture);
|
||||
GLenum DeleteN(GLsizei n, const GLuint* textures);
|
||||
GLenum QueryLevelPropertyIntVector(GLenum target, GLint level, GLenum pname, GLint* params);
|
||||
|
||||
std::vector<TextureUnitState> textureUnits_;
|
||||
GLuint activeTextureUnit_ = 0;
|
||||
private:
|
||||
size_t CalculatePixelDataSize_(GLenum format, GLenum type, GLsizei width, GLsizei height);
|
||||
void InvalidateTextureInAllUnits_(GLuint texture);
|
||||
static ComponentSizes GetComponentSize_s_(GLenum internalFormat);
|
||||
size_t CalculateBytesPerPixel_(GLenum format, GLenum type);
|
||||
static size_t GetComponentSize_(GLenum type);
|
||||
void SwapBytesForTexture_(GLenum format, GLenum type, const GLubyte* src, GLubyte* dst, GLsizei width);
|
||||
static void ReverseBitOrder_(const GLubyte* src, GLubyte* dst, size_t size);
|
||||
static GLubyte ReverseBits_(GLubyte b);
|
||||
void SwapPixelBytes_(GLenum format, GLenum type, const GLubyte* src, GLubyte* dst);
|
||||
GLenum CheckUploadingTexture2DValidity_(GLenum target, GLint level, GLint internalFormat,
|
||||
GLsizei width, GLsizei height, GLint border, GLenum format,
|
||||
GLenum type, const void* data);
|
||||
GLenum CheckUpdatingTextureRegion2DValidity_(GLenum target, GLint level, GLint xoffset,
|
||||
GLint yoffset, GLsizei width, GLsizei height, GLenum format,
|
||||
GLenum type, const GLvoid* data);
|
||||
};
|
||||
|
||||
#endif //MOBILEGL_TEXTURESTATE_H
|
||||
@@ -0,0 +1,162 @@
|
||||
//
|
||||
// Created by BZLZHH on 2025/5/1.
|
||||
//
|
||||
|
||||
// TODO: Add more gl error check for vertex array state manager.
|
||||
|
||||
#include "VertexArrayState.h"
|
||||
|
||||
VertexArrayState::VertexArrayState() {
|
||||
vaos_[0];
|
||||
}
|
||||
|
||||
GLenum VertexArrayState::GenName(GLuint *array) {
|
||||
MG_Util::Debug::LogD("MG_State: VAO: GenName");
|
||||
if (!array)
|
||||
return GL_INVALID_VALUE;
|
||||
GLuint id;
|
||||
if (freeIds_.empty()) {
|
||||
id = ++lastId_;
|
||||
} else {
|
||||
id = *freeIds_.begin();
|
||||
freeIds_.erase(freeIds_.begin());
|
||||
}
|
||||
*array = id;
|
||||
MG_Util::Debug::LogD("MG_State: VAO: Generated new name %d", id);
|
||||
return GL_NO_ERROR;
|
||||
}
|
||||
|
||||
GLenum VertexArrayState::GenNameN(GLsizei n, GLuint* arrays) {
|
||||
MG_Util::Debug::LogD("MG_State: VAO: GenNameN called with n=%d", n);
|
||||
if (n < 0)
|
||||
return GL_INVALID_VALUE;
|
||||
for (GLsizei i = 0; i < n; ++i) {
|
||||
GLenum result = GenName(&arrays[i]);
|
||||
if (result != GL_NO_ERROR) {
|
||||
MG_Util::Debug::LogE("MG_State: VAO: GenNameN failed at index %d with error 0x%x", i, result);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
MG_Util::Debug::LogD("MG_State: VAO: GenNameN created %d names", n);
|
||||
return GL_NO_ERROR;
|
||||
}
|
||||
|
||||
GLenum VertexArrayState::Create(GLuint* array) {
|
||||
if (array == nullptr) return GL_INVALID_VALUE;
|
||||
|
||||
GLuint id = freeIds_.empty() ? ++lastId_ : *freeIds_.begin();
|
||||
if (!freeIds_.empty()) {
|
||||
freeIds_.erase(freeIds_.begin());
|
||||
}
|
||||
|
||||
*array = id;
|
||||
vaos_[id].generated = true;
|
||||
return GL_NO_ERROR;
|
||||
}
|
||||
|
||||
GLenum VertexArrayState::CreateN(GLsizei n, GLuint* arrays) {
|
||||
if (n < 0) {
|
||||
return GL_INVALID_VALUE;
|
||||
}
|
||||
|
||||
if (n > 0 && arrays == nullptr) {
|
||||
return GL_INVALID_VALUE;
|
||||
}
|
||||
|
||||
for (GLsizei i = 0; i < n; ++i) {
|
||||
if (GLenum error = Create(&arrays[i]); error != GL_NO_ERROR) {
|
||||
return error;
|
||||
}
|
||||
}
|
||||
return GL_NO_ERROR;
|
||||
}
|
||||
|
||||
GLenum VertexArrayState::Bind(GLuint array) {
|
||||
MG_Util::Debug::LogD("MG_State: VAO: Bind called for %u", array);
|
||||
if (array != 0) {
|
||||
if (!ValidateGeneratedName(array)) {
|
||||
MG_Util::Debug::LogE("MG_State: VAO: Bind invalid name %u", array);
|
||||
return GL_INVALID_OPERATION;
|
||||
}
|
||||
auto& vao = vaos_[array];
|
||||
if (!vao.generated) {
|
||||
MG_Util::Debug::LogD("MG_State: VAO: Creating VAO %u during bind", array);
|
||||
vao.generated = true;
|
||||
}
|
||||
}
|
||||
currentVao_ = array;
|
||||
MG_Util::Debug::LogD("MG_State: VAO: Bound to %u", array);
|
||||
return GL_NO_ERROR;
|
||||
}
|
||||
|
||||
bool VertexArrayState::ValidateGeneratedName(GLuint array) {
|
||||
if (array == 0)
|
||||
return true;
|
||||
bool inFreeList = freeIds_.count(array) > 0;
|
||||
bool valid = (array <= lastId_) && !inFreeList;
|
||||
MG_Util::Debug::LogD("MG_State: VAO: ValidateGeneratedName %u: %d", array, valid);
|
||||
return valid;
|
||||
}
|
||||
|
||||
bool VertexArrayState::ValidateAllocatedHandle(GLuint array) {
|
||||
bool exists = vaos_.count(array) && vaos_[array].generated;
|
||||
MG_Util::Debug::LogD("MG_State: VAO: ValidateAllocatedHandle %u: %d", array, exists);
|
||||
return exists;
|
||||
}
|
||||
|
||||
GLenum VertexArrayState::EnableAttrib(GLuint index) {
|
||||
if (!ValidateAllocatedHandle(currentVao_))
|
||||
return GL_INVALID_OPERATION;
|
||||
if (index >= GL_MAX_VERTEX_ATTRIBS) return GL_INVALID_VALUE;
|
||||
GetCurrentVAO()->attribs[index].enabled = true;
|
||||
MG_Util::Debug::LogD("Attrib vaos_[%u].attribs[%u].enabled = %d", currentVao_, index, vaos_[currentVao_].attribs[index].enabled);
|
||||
|
||||
GetCurrentVAO()->attribDirty = true;
|
||||
return GL_NO_ERROR;
|
||||
}
|
||||
|
||||
GLenum VertexArrayState::DisableAttrib(GLuint index) {
|
||||
if (!ValidateAllocatedHandle(currentVao_))
|
||||
return GL_INVALID_OPERATION;
|
||||
if (index >= GL_MAX_VERTEX_ATTRIBS) return GL_INVALID_VALUE;
|
||||
GetCurrentVAO()->attribs[index].enabled = false;
|
||||
MG_Util::Debug::LogD("Attrib vaos_[%u].attribs[%u].enabled = %d", currentVao_, index, vaos_[currentVao_].attribs[index].enabled);
|
||||
|
||||
GetCurrentVAO()->attribDirty = true;
|
||||
return GL_NO_ERROR;
|
||||
}
|
||||
|
||||
GLenum VertexArrayState::SetAttribPointer(GLuint index, GLint size, GLenum type,
|
||||
GLboolean normalized, GLsizei stride,
|
||||
const void* pointer, bool isInteger,
|
||||
GLuint currentArrayBuffer) {
|
||||
if (!ValidateAllocatedHandle(currentVao_))
|
||||
return GL_INVALID_OPERATION;
|
||||
if (index >= GL_MAX_VERTEX_ATTRIBS) return GL_INVALID_VALUE;
|
||||
|
||||
VertexAttribState state;
|
||||
state.size = size;
|
||||
state.type = type;
|
||||
state.normalized = normalized;
|
||||
state.stride = stride;
|
||||
state.pointer = pointer;
|
||||
state.buffer = currentArrayBuffer;
|
||||
state.isInteger = isInteger;
|
||||
if (vaos_[currentVao_].attribs.count(index) && vaos_[currentVao_].attribs[index].enabled)
|
||||
state.enabled = true;
|
||||
|
||||
vaos_[currentVao_].attribs[index] = state;
|
||||
|
||||
GetCurrentVAO()->attribDirty = true;
|
||||
return GL_NO_ERROR;
|
||||
}
|
||||
|
||||
GLuint VertexArrayState::GetBoundElementBuffer() {
|
||||
auto it = vaos_.find(currentVao_);
|
||||
return (it != vaos_.end()) ? it->second.elementBuffer : 0;
|
||||
}
|
||||
|
||||
VertexArrayObject* VertexArrayState::GetCurrentVAO() {
|
||||
auto it = vaos_.find(currentVao_);
|
||||
return (it != vaos_.end()) ? &it->second : &vaos_.at(0);
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
//
|
||||
// Created by BZLZHH on 2025/5/1.
|
||||
//
|
||||
|
||||
#ifndef MOBILEGL_VERTEXARRAYSTATE_H
|
||||
#define MOBILEGL_VERTEXARRAYSTATE_H
|
||||
#define MOBILEGL_GLSTATE_H
|
||||
|
||||
#include "../../../Includes.h"
|
||||
|
||||
struct VertexAttribState {
|
||||
bool enabled;
|
||||
GLint size = 4;
|
||||
GLenum type = GL_FLOAT;
|
||||
GLboolean normalized = GL_FALSE;
|
||||
GLsizei stride = 0;
|
||||
const GLvoid* pointer = nullptr;
|
||||
GLuint buffer = 0;
|
||||
bool isInteger = false;
|
||||
};
|
||||
|
||||
struct VertexArrayObject {
|
||||
bool generated = false;
|
||||
bool attribDirty = false;
|
||||
bool eboDirty = false;
|
||||
GLuint elementBuffer = 0;
|
||||
ankerl::unordered_map<GLuint, VertexAttribState> attribs;
|
||||
};
|
||||
|
||||
class VertexArrayState {
|
||||
public:
|
||||
VertexArrayState();
|
||||
|
||||
// Return: the validity of the operation, according to OpenGL 3 standard
|
||||
GLenum GenName(GLuint* array);
|
||||
GLenum GenNameN(GLsizei n, GLuint* arrays);
|
||||
GLenum Create(GLuint* array);
|
||||
GLenum CreateN(GLsizei n, GLuint* arrays);
|
||||
GLenum Bind(GLuint array);
|
||||
GLenum EnableAttrib(GLuint index);
|
||||
GLenum DisableAttrib(GLuint index);
|
||||
GLenum SetAttribPointer(GLuint index, GLint size, GLenum type,
|
||||
GLboolean normalized, GLsizei stride,
|
||||
const void* pointer, bool isInteger,
|
||||
GLuint currentArrayBuffer);
|
||||
|
||||
GLuint GetBoundElementBuffer();
|
||||
VertexArrayObject* GetCurrentVAO();
|
||||
bool ValidateGeneratedName(GLuint array);
|
||||
bool ValidateAllocatedHandle(GLuint array);
|
||||
|
||||
GLuint currentVao_ = 0;
|
||||
ankerl::unordered_map<GLuint, VertexArrayObject> vaos_;
|
||||
private:
|
||||
std::set<GLuint> freeIds_;
|
||||
GLuint lastId_ = 0;
|
||||
};
|
||||
|
||||
#endif //MOBILEGL_VERTEXARRAYSTATE_H
|
||||
@@ -0,0 +1,127 @@
|
||||
//
|
||||
// Created by BZLZHH on 2025/5/3.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#include <initializer_list>
|
||||
#include <stdexcept>
|
||||
#include <utility>
|
||||
|
||||
class UncertainBool {
|
||||
public:
|
||||
enum Value { False = 0, True = 1, Unknown = 2 };
|
||||
|
||||
UncertainBool() : value_(False), default_(false) {}
|
||||
|
||||
explicit UncertainBool(bool b) : value_(b ? True : False), default_(false) {}
|
||||
|
||||
UncertainBool(Value val, bool def) : value_(val), default_(def) {
|
||||
validateValue();
|
||||
}
|
||||
|
||||
UncertainBool(bool val, bool def) : value_(val ? True : False), default_(def) {
|
||||
validateValue();
|
||||
}
|
||||
|
||||
UncertainBool& operator=(Value val) {
|
||||
value_ = val;
|
||||
validateValue();
|
||||
return *this;
|
||||
}
|
||||
|
||||
UncertainBool& operator=(bool b) {
|
||||
value_ = b ? True : False;
|
||||
return *this;
|
||||
}
|
||||
|
||||
UncertainBool& operator=(std::initializer_list<std::pair<bool, bool>> init) {
|
||||
if (init.size() != 1) throw std::invalid_argument("Invalid initializer");
|
||||
value_ = init.begin()->first ? True : False;
|
||||
default_ = init.begin()->second;
|
||||
return *this;
|
||||
}
|
||||
|
||||
explicit operator bool() const {
|
||||
return toBool();
|
||||
}
|
||||
|
||||
bool toBool() const {
|
||||
if (value_ == True) return true;
|
||||
else if (value_ == False) return false;
|
||||
else return default_;
|
||||
}
|
||||
|
||||
UncertainBool operator!() const {
|
||||
return {static_cast<bool>(*this) ? False : True, false};
|
||||
}
|
||||
|
||||
bool operator==(const UncertainBool& rhs) const {
|
||||
return static_cast<bool>(*this) == static_cast<bool>(rhs);
|
||||
}
|
||||
bool operator!=(const UncertainBool& rhs) const { return !(*this == rhs); }
|
||||
|
||||
friend UncertainBool operator&&(const UncertainBool& lhs, const UncertainBool& rhs) {
|
||||
bool actualDefault = lhs.default_ && rhs.default_;
|
||||
if(lhs.value_ == False || rhs.value_ == False) return {False,actualDefault};
|
||||
else if(lhs.value_ == True && rhs.value_ == True) return {True,actualDefault};
|
||||
else return {Unknown, actualDefault};
|
||||
}
|
||||
|
||||
friend UncertainBool operator||(const UncertainBool& lhs, const UncertainBool& rhs) {
|
||||
bool actualDefault = lhs.default_ || rhs.default_;
|
||||
if(lhs.value_ == True || rhs.value_ == True) return {True,actualDefault};
|
||||
else if(lhs.value_ == False && rhs.value_ == False) return {False,actualDefault};
|
||||
else return {Unknown, actualDefault};
|
||||
}
|
||||
|
||||
friend UncertainBool operator&&(bool lhs,const UncertainBool& rhs){
|
||||
return UncertainBool(lhs, lhs) && rhs;
|
||||
}
|
||||
|
||||
friend UncertainBool operator||(bool lhs,const UncertainBool& rhs){
|
||||
return UncertainBool(lhs, lhs) || rhs;
|
||||
}
|
||||
|
||||
UncertainBool& operator&=(const UncertainBool& rhs) {
|
||||
*this = *this && rhs;
|
||||
return *this;
|
||||
}
|
||||
|
||||
UncertainBool& operator|=(const UncertainBool& rhs) {
|
||||
*this = *this || rhs;
|
||||
return *this;
|
||||
}
|
||||
|
||||
UncertainBool& operator=(std::initializer_list<std::pair<Value, bool>> init) {
|
||||
if (init.size() != 1) throw std::invalid_argument("Invalid initializer");
|
||||
value_ = init.begin()->first;
|
||||
default_ = init.begin()->second;
|
||||
validateValue();
|
||||
return *this;
|
||||
}
|
||||
|
||||
Value getValue() const { return value_; }
|
||||
bool getDefault() const { return default_; }
|
||||
|
||||
private:
|
||||
Value value_;
|
||||
bool default_;
|
||||
|
||||
void validateValue() {
|
||||
if (value_ != False && value_ != True && value_ != Unknown) {
|
||||
throw std::invalid_argument("Invalid value");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
inline UncertainBool operator!(const UncertainBool& lhs) {
|
||||
return lhs.operator!();
|
||||
}
|
||||
|
||||
inline bool operator==(bool lhs, const UncertainBool& rhs) {
|
||||
return static_cast<bool>(rhs) == lhs;
|
||||
}
|
||||
|
||||
inline bool operator!=(bool lhs, const UncertainBool& rhs) {
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
@@ -0,0 +1,234 @@
|
||||
//
|
||||
// Created by BZLZHH on 2025/4/26.
|
||||
//
|
||||
|
||||
#include "TestDrawing.h"
|
||||
|
||||
namespace MG_RHI::GLES::Test {
|
||||
constexpr int DISPLAY_COLUMNS = 8;
|
||||
constexpr int DISPLAY_ROWS = 10;
|
||||
constexpr int MAX_DISPLAY_TEXTURES = DISPLAY_COLUMNS * DISPLAY_ROWS;
|
||||
|
||||
bool IsTextureComplete(const TextureObject& tex) {
|
||||
return !tex.params.mipmapData.empty();
|
||||
}
|
||||
|
||||
bool CheckShaderCompileStatus(GLuint shader, const char* type) {
|
||||
GLint success;
|
||||
::GLES::glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
|
||||
if (!success) {
|
||||
GLchar infoLog[512];
|
||||
::GLES::glGetShaderInfoLog(shader, 512, NULL, infoLog);
|
||||
MG_Util::Debug::LogE("Shader compilation error (%s): %s", type, infoLog);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CheckProgramLinkStatus(GLuint program) {
|
||||
GLint success;
|
||||
::GLES::glGetProgramiv(program, GL_LINK_STATUS, &success);
|
||||
if (!success) {
|
||||
GLchar infoLog[512];
|
||||
::GLES::glGetProgramInfoLog(program, 512, NULL, infoLog);
|
||||
MG_Util::Debug::LogE("Program link error: %s", infoLog);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO: Use the textures state from MG_RHI(another state?) rather than directly from MG_State
|
||||
void DrawAllTextures() {
|
||||
::GLES::glClearColor(1,1,1,1);
|
||||
::GLES::glClear(GL_COLOR_BUFFER_BIT);
|
||||
|
||||
struct ScreenResources {
|
||||
GLuint program = 0;
|
||||
GLuint quadVAO = 0;
|
||||
GLuint quadVBO = 0;
|
||||
GLint uTexLoc = -1;
|
||||
GLint uMVPLoc = -1;
|
||||
ankerl::unordered_map<GLuint, GLuint> textureCache;
|
||||
ankerl::unordered_map<GLuint, bool> textureDirty;
|
||||
};
|
||||
|
||||
static ScreenResources sRes;
|
||||
static glm::ivec2 lastScreenSize{0, 0};
|
||||
|
||||
GLint viewport[4];
|
||||
::GLES::glGetIntegerv(GL_VIEWPORT, viewport);
|
||||
|
||||
const glm::ivec2 screenSize(viewport[2], viewport[3]);
|
||||
|
||||
if (screenSize != lastScreenSize) {
|
||||
if (sRes.quadVAO) {
|
||||
::GLES::glDeleteVertexArrays(1, &sRes.quadVAO);
|
||||
::GLES::glDeleteBuffers(1, &sRes.quadVBO);
|
||||
sRes.quadVAO = sRes.quadVBO = 0;
|
||||
}
|
||||
lastScreenSize = screenSize;
|
||||
}
|
||||
|
||||
if (!sRes.program) {
|
||||
const char* vsSrc = R"(#version 320 es
|
||||
layout(location=0) in vec2 aPos;
|
||||
layout(location=1) in vec2 aTexUV;
|
||||
out vec2 vUV;
|
||||
uniform mat4 uMVP;
|
||||
void main() {
|
||||
gl_Position = uMVP * vec4(aPos, 0.0, 1.0);
|
||||
vUV = aTexUV;
|
||||
})";
|
||||
|
||||
const char* fsSrc = R"(#version 320 es
|
||||
precision mediump float;
|
||||
in vec2 vUV;
|
||||
uniform sampler2D uTex;
|
||||
out vec4 FragColor;
|
||||
void main() {
|
||||
FragColor = texture(uTex, vUV);
|
||||
})";
|
||||
|
||||
GLuint vs = ::GLES::glCreateShader(GL_VERTEX_SHADER);
|
||||
::GLES::glShaderSource(vs, 1, &vsSrc, NULL);
|
||||
::GLES::glCompileShader(vs);
|
||||
if (!CheckShaderCompileStatus(vs, "vertex")) return;
|
||||
|
||||
GLuint fs = ::GLES::glCreateShader(GL_FRAGMENT_SHADER);
|
||||
::GLES::glShaderSource(fs, 1, &fsSrc, NULL);
|
||||
::GLES::glCompileShader(fs);
|
||||
if (!CheckShaderCompileStatus(fs, "fragment")) return;
|
||||
|
||||
sRes.program = ::GLES::glCreateProgram();
|
||||
::GLES::glAttachShader(sRes.program, vs);
|
||||
::GLES::glAttachShader(sRes.program, fs);
|
||||
::GLES::glLinkProgram(sRes.program);
|
||||
if (!CheckProgramLinkStatus(sRes.program)) return;
|
||||
|
||||
sRes.uTexLoc = ::GLES::glGetUniformLocation(sRes.program, "uTex");
|
||||
sRes.uMVPLoc = ::GLES::glGetUniformLocation(sRes.program, "uMVP");
|
||||
|
||||
::GLES::glDeleteShader(vs);
|
||||
::GLES::glDeleteShader(fs);
|
||||
}
|
||||
|
||||
if (!sRes.quadVAO) {
|
||||
const float margin = 0.01f;
|
||||
const float gridWidth = (2.0f - 2*margin) / DISPLAY_COLUMNS;
|
||||
const float gridHeight = (2.0f - 2*margin) / DISPLAY_ROWS;
|
||||
|
||||
std::vector<float> vertices;
|
||||
vertices.reserve(MAX_DISPLAY_TEXTURES * 16);
|
||||
|
||||
for (int col = 0; col < DISPLAY_COLUMNS; ++col) {
|
||||
for (int row = 0; row < DISPLAY_ROWS; ++row) {
|
||||
const float xStart = -1.0f + margin + col * gridWidth;
|
||||
const float yStart = -1.0f + margin + row * gridHeight;
|
||||
const float xEnd = xStart + gridWidth - margin;
|
||||
const float yEnd = yStart + gridHeight - margin;
|
||||
|
||||
vertices.insert(vertices.end(), {
|
||||
xStart, yStart, 0.0f, 0.0f,
|
||||
xEnd, yStart, 1.0f, 0.0f,
|
||||
xEnd, yEnd, 1.0f, 1.0f,
|
||||
xStart, yEnd, 0.0f, 1.0f
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
::GLES::glGenVertexArrays(1, &sRes.quadVAO);
|
||||
::GLES::glGenBuffers(1, &sRes.quadVBO);
|
||||
::GLES::glBindVertexArray(sRes.quadVAO);
|
||||
::GLES::glBindBuffer(GL_ARRAY_BUFFER, sRes.quadVBO);
|
||||
::GLES::glBufferData(GL_ARRAY_BUFFER,
|
||||
vertices.size() * sizeof(float),
|
||||
vertices.data(),
|
||||
GL_STATIC_DRAW
|
||||
);
|
||||
|
||||
::GLES::glEnableVertexAttribArray(0);
|
||||
::GLES::glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE,
|
||||
4 * sizeof(float), (void*)0
|
||||
);
|
||||
::GLES::glEnableVertexAttribArray(1);
|
||||
::GLES::glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE,
|
||||
4 * sizeof(float), (void*)(2 * sizeof(float))
|
||||
);
|
||||
::GLES::glBindVertexArray(0);
|
||||
}
|
||||
|
||||
std::vector<std::pair<GLuint, TextureObject*>> sortedTextures;
|
||||
for (auto&& [stateID, texObj] : MG_State_T::textureState->textures) {
|
||||
if (!texObj.generated || texObj.target != GL_TEXTURE_2D) continue;
|
||||
if (texObj.params.mipmapData.empty()) continue;
|
||||
sortedTextures.emplace_back(stateID, &texObj);
|
||||
}
|
||||
std::sort(sortedTextures.begin(), sortedTextures.end(),
|
||||
[](auto& a, auto& b) {
|
||||
return a.second->createTimestamp > b.second->createTimestamp;
|
||||
}
|
||||
);
|
||||
sortedTextures.resize(std::min<size_t>(sortedTextures.size(), MAX_DISPLAY_TEXTURES));
|
||||
|
||||
for (auto&& [stateID, texObj] : sortedTextures) {
|
||||
auto& dirtyFlag = sRes.textureDirty[stateID];
|
||||
bool needCreate = !sRes.textureCache.count(stateID);
|
||||
|
||||
if (needCreate) {
|
||||
GLuint glTexID;
|
||||
::GLES::glGenTextures(1, &glTexID);
|
||||
sRes.textureCache[stateID] = glTexID;
|
||||
dirtyFlag = true;
|
||||
}
|
||||
|
||||
if (dirtyFlag || needCreate) {
|
||||
const auto& mip0 = texObj->params.mipmapData.begin()->second;
|
||||
if (mip0.hasData) {
|
||||
GLuint glTexID = sRes.textureCache[stateID];
|
||||
::GLES::glBindTexture(GL_TEXTURE_2D, glTexID);
|
||||
::GLES::glTexImage2D(GL_TEXTURE_2D, 0, mip0.internalFormat,
|
||||
mip0.width, mip0.height, 0,
|
||||
mip0.format, mip0.type, mip0.pixelData.data()
|
||||
);
|
||||
::GLES::glTexParameteri(GL_TEXTURE_2D,
|
||||
GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
::GLES::glTexParameteri(GL_TEXTURE_2D,
|
||||
GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
::GLES::glTexParameteri(GL_TEXTURE_2D,
|
||||
GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
::GLES::glTexParameteri(GL_TEXTURE_2D,
|
||||
GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
if (texObj->params.mipmapData.size() > 1) {
|
||||
::GLES::glGenerateMipmap(GL_TEXTURE_2D);
|
||||
}
|
||||
dirtyFlag = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GLboolean origDepthTest = ::GLES::glIsEnabled(GL_DEPTH_TEST);
|
||||
GLint origProgram, origViewport[4];
|
||||
::GLES::glGetIntegerv(GL_VIEWPORT, origViewport);
|
||||
::GLES::glGetIntegerv(GL_CURRENT_PROGRAM, &origProgram);
|
||||
|
||||
::GLES::glDisable(GL_DEPTH_TEST);
|
||||
::GLES::glUseProgram(sRes.program);
|
||||
::GLES::glUniformMatrix4fv(sRes.uMVPLoc, 1, GL_FALSE,
|
||||
&glm::mat4(1.0f)[0][0]
|
||||
);
|
||||
::GLES::glUniform1i(sRes.uTexLoc, 0);
|
||||
::GLES::glBindVertexArray(sRes.quadVAO);
|
||||
|
||||
for (size_t i = 0; i < sortedTextures.size(); ++i) {
|
||||
const GLuint glTexID = sRes.textureCache[sortedTextures[i].first];
|
||||
::GLES::glActiveTexture(GL_TEXTURE0);
|
||||
::GLES::glBindTexture(GL_TEXTURE_2D, glTexID);
|
||||
::GLES::glDrawArrays(GL_TRIANGLE_FAN, static_cast<GLint>(i*4), 4);
|
||||
}
|
||||
|
||||
if (origDepthTest) ::GLES::glEnable(GL_DEPTH_TEST);
|
||||
::GLES::glUseProgram(origProgram);
|
||||
::GLES::glViewport(origViewport[0], origViewport[1],
|
||||
origViewport[2], origViewport[3]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
//
|
||||
// Created by BZLZHH on 2025/4/26.
|
||||
//
|
||||
|
||||
#ifndef MOBILEGL_TESTDRAWING_H
|
||||
#define MOBILEGL_TESTDRAWING_H
|
||||
|
||||
#include "../../../Includes.h"
|
||||
|
||||
namespace MG_RHI::GLES::Test {
|
||||
void DrawAllTextures();
|
||||
}
|
||||
|
||||
#endif //MOBILEGL_TESTDRAWING_H
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,31 @@
|
||||
//
|
||||
// Created by BZLZHH on 2025/1/26.
|
||||
//
|
||||
|
||||
#ifndef MOBILEGL_DEBUG_H
|
||||
#define MOBILEGL_DEBUG_H
|
||||
|
||||
#include "../../Includes.h"
|
||||
|
||||
#define FORCE_SYNC_WITH_LOG_FILE 0
|
||||
|
||||
namespace MG_Util {
|
||||
namespace Debug {
|
||||
const char* GetOSName();
|
||||
|
||||
void LogD (const char* format, ...);
|
||||
void LogW (const char* format, ...);
|
||||
void LogE (const char* format, ...);
|
||||
void LogI (const char* format, ...);
|
||||
void LogF (const char* format, ...);
|
||||
void LogClear();
|
||||
void LogInit();
|
||||
void LogWrite(const char* format, va_list args);
|
||||
|
||||
const char* GLEnumToString(::GLenum value);
|
||||
}
|
||||
}
|
||||
|
||||
#define MOBILEGL_DEBUG_H
|
||||
|
||||
#endif //MOBILEGL_DEBUG_H
|
||||
@@ -0,0 +1,150 @@
|
||||
//
|
||||
// Created by BZLZHH on 2025/5/3.
|
||||
//
|
||||
|
||||
#include "DebugTool.h"
|
||||
|
||||
namespace MG_Util::Program {
|
||||
std::string GLTypeToString(GLenum type) {
|
||||
switch(type) {
|
||||
case GL_FLOAT: return "float";
|
||||
case GL_FLOAT_VEC2: return "vec2";
|
||||
case GL_FLOAT_VEC3: return "vec3";
|
||||
case GL_FLOAT_VEC4: return "vec4";
|
||||
case GL_INT: return "int";
|
||||
case GL_INT_VEC2: return "ivec2";
|
||||
case GL_INT_VEC3: return "ivec3";
|
||||
case GL_INT_VEC4: return "ivec4";
|
||||
case GL_UNSIGNED_INT: return "uint";
|
||||
case GL_UNSIGNED_INT_VEC2: return "uvec2";
|
||||
case GL_UNSIGNED_INT_VEC3: return "uvec3";
|
||||
case GL_UNSIGNED_INT_VEC4: return "uvec4";
|
||||
case GL_BOOL: return "bool";
|
||||
case GL_BOOL_VEC2: return "bvec2";
|
||||
case GL_BOOL_VEC3: return "bvec3";
|
||||
case GL_BOOL_VEC4: return "bvec4";
|
||||
case GL_FLOAT_MAT2: return "mat2";
|
||||
case GL_FLOAT_MAT3: return "mat3";
|
||||
case GL_FLOAT_MAT4: return "mat4";
|
||||
case GL_FLOAT_MAT2x3: return "mat2x3";
|
||||
case GL_FLOAT_MAT2x4: return "mat2x4";
|
||||
case GL_FLOAT_MAT3x2: return "mat3x2";
|
||||
case GL_FLOAT_MAT3x4: return "mat3x4";
|
||||
case GL_FLOAT_MAT4x2: return "mat4x2";
|
||||
case GL_FLOAT_MAT4x3: return "mat4x3";
|
||||
case GL_SAMPLER_1D: return "sampler1D";
|
||||
case GL_SAMPLER_2D: return "sampler2D";
|
||||
case GL_SAMPLER_3D: return "sampler3D";
|
||||
case GL_SAMPLER_CUBE: return "samplerCube";
|
||||
case GL_SAMPLER_1D_SHADOW: return "sampler1DShadow";
|
||||
case GL_SAMPLER_2D_SHADOW: return "sampler2DShadow";
|
||||
case GL_SAMPLER_CUBE_SHADOW: return "samplerCubeShadow";
|
||||
case GL_SAMPLER_1D_ARRAY: return "sampler1DArray";
|
||||
case GL_SAMPLER_2D_ARRAY: return "sampler2DArray";
|
||||
case GL_SAMPLER_1D_ARRAY_SHADOW: return "sampler1DArrayShadow";
|
||||
case GL_SAMPLER_2D_ARRAY_SHADOW: return "sampler2DArrayShadow";
|
||||
case GL_SAMPLER_BUFFER: return "samplerBuffer";
|
||||
case GL_SAMPLER_2D_MULTISAMPLE: return "sampler2DMS";
|
||||
case GL_SAMPLER_2D_MULTISAMPLE_ARRAY: return "sampler2DMSArray";
|
||||
default: return "UnknownType(0x" + std::to_string(type) + ")";
|
||||
}
|
||||
}
|
||||
|
||||
std::string FormatUniformValue(const UniformValue& value) {
|
||||
std::ostringstream ss;
|
||||
ss << "[";
|
||||
|
||||
const size_t elemCount = std::min<size_t>(value.count, 16);
|
||||
|
||||
switch(value.type) {
|
||||
case GL_FLOAT:
|
||||
case GL_FLOAT_VEC2:
|
||||
case GL_FLOAT_VEC3:
|
||||
case GL_FLOAT_VEC4:
|
||||
case GL_FLOAT_MAT2:
|
||||
case GL_FLOAT_MAT3:
|
||||
case GL_FLOAT_MAT4:
|
||||
case GL_FLOAT_MAT2x3:
|
||||
case GL_FLOAT_MAT2x4:
|
||||
case GL_FLOAT_MAT3x2:
|
||||
case GL_FLOAT_MAT3x4:
|
||||
case GL_FLOAT_MAT4x2:
|
||||
case GL_FLOAT_MAT4x3:
|
||||
for(size_t i=0; i<elemCount; ++i) {
|
||||
if (i > 0) ss << ", ";
|
||||
ss << (i < value.floatData.size() ? value.floatData[i] : 0.0f);
|
||||
}
|
||||
break;
|
||||
|
||||
case GL_INT:
|
||||
case GL_INT_VEC2:
|
||||
case GL_INT_VEC3:
|
||||
case GL_INT_VEC4:
|
||||
for(size_t i=0; i<elemCount; ++i) {
|
||||
if (i > 0) ss << ", ";
|
||||
ss << (i < value.intData.size() ? value.intData[i] : 0);
|
||||
}
|
||||
break;
|
||||
|
||||
case GL_UNSIGNED_INT:
|
||||
case GL_UNSIGNED_INT_VEC2:
|
||||
case GL_UNSIGNED_INT_VEC3:
|
||||
case GL_UNSIGNED_INT_VEC4:
|
||||
for(size_t i=0; i<elemCount; ++i) {
|
||||
if (i > 0) ss << ", ";
|
||||
ss << (i < value.uintData.size() ? value.uintData[i] : 0);
|
||||
}
|
||||
break;
|
||||
|
||||
case GL_BOOL:
|
||||
case GL_BOOL_VEC2:
|
||||
case GL_BOOL_VEC3:
|
||||
case GL_BOOL_VEC4:
|
||||
for(size_t i=0; i<elemCount; ++i) {
|
||||
if (i > 0) ss << ", ";
|
||||
ss << (i < value.boolData.size() ? (value.boolData[i] ? "true" : "false") : "false");
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
ss << "N/A";
|
||||
}
|
||||
|
||||
if (value.count > elemCount) ss << ", ...";
|
||||
ss << "]";
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
void DumpUniforms(const ProgramState& state, GLuint program) {
|
||||
if (MG_Global::Common::LogLevel > MG_Constants::Common::LOG_LEVEL_DEBUG)
|
||||
return;
|
||||
|
||||
auto prog = (ProgramObject)state.GetProgramObject(program);
|
||||
if (!prog.linked.toBool()) {
|
||||
MG_Util::Debug::LogE("Program %u not linked", program);
|
||||
return;
|
||||
}
|
||||
MG_Util::Debug::LogD("=== Dumping uniforms for program %u ===", program);
|
||||
for(const auto& [name, loc] : prog.uniformLocations) {
|
||||
const auto& value = prog.uniformValues.at(name);
|
||||
MG_Util::Debug::LogD("Uniform: %-24s Location: %-4d Type: %-16s Count: %-3d Value: %s",
|
||||
name.c_str(),
|
||||
loc,
|
||||
GLTypeToString(value.type).c_str(),
|
||||
value.count,
|
||||
FormatUniformValue(value).c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void DumpCurrentUniforms(const ProgramState& state) {
|
||||
if (MG_Global::Common::LogLevel > MG_Constants::Common::LOG_LEVEL_DEBUG)
|
||||
return;
|
||||
|
||||
GLuint current = state.GetCurrentProgram();
|
||||
if (current == 0) {
|
||||
MG_Util::Debug::LogE("No active program");
|
||||
return;
|
||||
}
|
||||
DumpUniforms(state, current);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
//
|
||||
// Created by BZLZHH on 2025/5/3.
|
||||
//
|
||||
|
||||
#ifndef MOBILEGL_PROGRAM_DEBUGTOOL_H
|
||||
#define MOBILEGL_PROGRAM_DEBUGTOOL_H
|
||||
#include "../../Includes.h"
|
||||
|
||||
namespace MG_Util::Program {
|
||||
std::string GLTypeToString(GLenum type);
|
||||
std::string FormatUniformValue(const UniformValue& value);
|
||||
void DumpUniforms(const ProgramState& state, GLuint program);
|
||||
void DumpCurrentUniforms(const ProgramState& state);
|
||||
}
|
||||
|
||||
#endif //MOBILEGL_PROGRAM_DEBUGTOOL_H
|
||||
@@ -0,0 +1,438 @@
|
||||
//
|
||||
// Created by BZLZHH on 2025/5/3.
|
||||
//
|
||||
|
||||
#include "GLSLTool.h"
|
||||
|
||||
namespace MG_Util::Program {
|
||||
std::string CompileGLSLToTShader(GLenum shaderType, const std::string& source, glslang::TShader *&shader) {
|
||||
std::string infoLog;
|
||||
using namespace glslang;
|
||||
|
||||
int glslVersion = QueryGLSLVersion(source);
|
||||
EShLanguage language = GetEShLanguageByShaderType(shaderType);
|
||||
if (language == EShLanguage::EShLangCount) {
|
||||
infoLog += "Error: [Preprocess] Unsupported shader type: " +
|
||||
std::to_string(shaderType);
|
||||
TShader tmpShader(EShLanguage::EShLangVertex);
|
||||
return infoLog;
|
||||
}
|
||||
|
||||
shader = new TShader(language);
|
||||
const char *src = source.c_str();
|
||||
shader->setStrings(&src, 1);
|
||||
shader->setEnvInput(EShSourceGlsl, language, EShClientVulkan, glslVersion);
|
||||
shader->setEnvClient(EShClientOpenGL, EShTargetOpenGL_450);
|
||||
shader->setEnvTarget(EShTargetSpv, EShTargetSpv_1_6);
|
||||
shader->setAutoMapLocations(true);
|
||||
shader->setAutoMapBindings(true);
|
||||
|
||||
// Is InitResources() really correct?
|
||||
TBuiltInResource resources = InitResources();
|
||||
|
||||
if (!shader->parse(&resources, glslVersion, true, EShMsgDefault)) {
|
||||
infoLog += "Error: [glslang] Cannot compile the " + GetShaderTypeName(shaderType) + ":\n"
|
||||
+ std::to_string(shader->getInfoLog());
|
||||
return infoLog;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<std::vector<unsigned>> CompileMultipleShadersToSPIRV(const ProgramState& state, ProgramObject& prog, std::string& infoLog) {
|
||||
using namespace glslang;
|
||||
|
||||
std::vector<EShLanguage> usedShaderTypes;
|
||||
TProgram program;
|
||||
for (GLuint shaderId : prog.attachedShaders) {
|
||||
auto shaderObject = state.GetShaderObject(shaderId);
|
||||
EShLanguage shLanguage = GetEShLanguageByShaderType(shaderObject.type);
|
||||
TShader* shader = nullptr;
|
||||
std::string infoLogOfShader = CompileGLSLToTShader(shaderObject.type, shaderObject.source, shader);
|
||||
if (!infoLogOfShader.empty()) {
|
||||
infoLog = "Error: [glslang] Cannot compile " + GetShaderTypeName(shaderObject.type) +
|
||||
" :\n" + infoLogOfShader;
|
||||
return {};
|
||||
}
|
||||
program.addShader(shader);
|
||||
usedShaderTypes.push_back(shLanguage);
|
||||
}
|
||||
if (!program.link(EShMsgDefault)) {
|
||||
infoLog = "Error: [glslang] Cannot link the program:\n" + std::to_string(program.getInfoLog());
|
||||
return {};
|
||||
}
|
||||
|
||||
SpvOptions spvOptions;
|
||||
spvOptions.disableOptimizer = false;
|
||||
|
||||
std::vector<std::vector<unsigned>> allSpirv;
|
||||
for(auto type : usedShaderTypes) {
|
||||
std::vector<unsigned> spirv;
|
||||
GlslangToSpv(*program.getIntermediate(type), spirv, &spvOptions);
|
||||
allSpirv.push_back(spirv);
|
||||
}
|
||||
|
||||
return allSpirv;
|
||||
}
|
||||
|
||||
std::vector<unsigned> CompileGLSLToSPIRV(GLenum shaderType, const std::string &source, std::string &infoLog) {
|
||||
using namespace glslang;
|
||||
|
||||
TShader* shader = nullptr;
|
||||
std::string infoLogOfShader = CompileGLSLToTShader(shaderType, source, shader);
|
||||
if (!infoLogOfShader.empty()) {
|
||||
infoLog = "Error: [glslang] Cannot compile " + GetShaderTypeName(shaderType) + ":\n" + infoLogOfShader;
|
||||
return {};
|
||||
}
|
||||
TProgram program;
|
||||
program.addShader(shader);
|
||||
if (!program.link(EShMsgDefault)) {
|
||||
infoLog = "Error: [glslang] Cannot link the program of the single shader:\n" + std::to_string(program.getInfoLog());
|
||||
return {};
|
||||
}
|
||||
std::vector<unsigned> spirv;
|
||||
|
||||
SpvOptions spvOptions;
|
||||
spvOptions.disableOptimizer = false;
|
||||
EShLanguage language = GetEShLanguageByShaderType(shaderType);
|
||||
GlslangToSpv(*program.getIntermediate(language), spirv, &spvOptions);
|
||||
|
||||
return spirv;
|
||||
}
|
||||
|
||||
void ReflectSPIRVUniforms(const std::vector<std::vector<unsigned>>& allSpirv, ProgramObject& prog, std::string& infoLog) {
|
||||
spvc_context context = nullptr;
|
||||
|
||||
if (spvc_context_create(&context) != SPVC_SUCCESS) {
|
||||
infoLog = "Failed to create SPIRV-Cross context";
|
||||
return;
|
||||
}
|
||||
|
||||
ankerl::unordered_set<GLint> used_locations;
|
||||
GLint auto_location = 0;
|
||||
for (auto spirv: allSpirv) {
|
||||
spvc_parsed_ir ir = nullptr;
|
||||
spvc_compiler compiler = nullptr;
|
||||
spvc_resources resources = nullptr;
|
||||
|
||||
spvc_result result = spvc_context_parse_spirv(context, spirv.data(), spirv.size(), &ir);
|
||||
if (result != SPVC_SUCCESS) {
|
||||
infoLog = "SPIR-V parsing failed";
|
||||
spvc_context_destroy(context);
|
||||
return;
|
||||
}
|
||||
|
||||
result = spvc_context_create_compiler(context, SPVC_BACKEND_GLSL, ir,
|
||||
SPVC_CAPTURE_MODE_TAKE_OWNERSHIP, &compiler);
|
||||
if (result != SPVC_SUCCESS) {
|
||||
infoLog = "Failed to create SPIRV-Cross compiler";
|
||||
spvc_context_destroy(context);
|
||||
return;
|
||||
}
|
||||
|
||||
result = spvc_compiler_create_shader_resources(compiler, &resources);
|
||||
if (result != SPVC_SUCCESS) {
|
||||
infoLog = "Failed to get shader resources";
|
||||
spvc_context_destroy(context);
|
||||
return;
|
||||
}
|
||||
|
||||
const spvc_reflected_resource *uniform_buffers = nullptr;
|
||||
const spvc_reflected_resource *uniform_vars = nullptr;
|
||||
const spvc_reflected_resource *sampled_images = nullptr;
|
||||
size_t uniform_buffer_count = 0;
|
||||
size_t uniform_var_count = 0;
|
||||
size_t sampled_image_count = 0;
|
||||
|
||||
spvc_resources_get_resource_list_for_type(resources, SPVC_RESOURCE_TYPE_UNIFORM_BUFFER,
|
||||
&uniform_buffers, &uniform_buffer_count);
|
||||
spvc_resources_get_resource_list_for_type(resources,
|
||||
SPVC_RESOURCE_TYPE_GL_PLAIN_UNIFORM,
|
||||
&uniform_vars, &uniform_var_count);
|
||||
spvc_resources_get_resource_list_for_type(resources, SPVC_RESOURCE_TYPE_SAMPLED_IMAGE,
|
||||
&sampled_images, &sampled_image_count);
|
||||
|
||||
std::vector<const spvc_reflected_resource *> all_resources;
|
||||
for (size_t i = 0; i < uniform_buffer_count; ++i)
|
||||
all_resources.push_back(&uniform_buffers[i]);
|
||||
for (size_t i = 0; i < uniform_var_count; ++i)
|
||||
all_resources.push_back(&uniform_vars[i]);
|
||||
for (size_t i = 0; i < sampled_image_count; ++i)
|
||||
all_resources.push_back(&sampled_images[i]);
|
||||
|
||||
for (const auto res_ptr: all_resources) {
|
||||
const spvc_reflected_resource &res = *res_ptr;
|
||||
const char *name = res.name;
|
||||
if (name[0] == '_') continue;
|
||||
|
||||
unsigned spirv_location = spvc_compiler_get_decoration(compiler, res.id,
|
||||
SpvDecorationLocation);
|
||||
GLint final_location;
|
||||
|
||||
if (spirv_location > 0) {
|
||||
if (used_locations.find(spirv_location) != used_locations.end()) {
|
||||
infoLog += "\nLocation conflict for uniform: " + std::string(name);
|
||||
continue;
|
||||
}
|
||||
final_location = spirv_location;
|
||||
} else {
|
||||
while (used_locations.find(auto_location) != used_locations.end()) {
|
||||
auto_location++;
|
||||
}
|
||||
final_location = auto_location;
|
||||
}
|
||||
|
||||
used_locations.insert(final_location);
|
||||
|
||||
spvc_type_id type_id = res.type_id;
|
||||
spvc_type type = spvc_compiler_get_type_handle(compiler, type_id);
|
||||
spvc_basetype base_type = spvc_type_get_basetype(type);
|
||||
GLenum gl_type = GL_NONE;
|
||||
|
||||
if (base_type == SPVC_BASETYPE_SAMPLED_IMAGE) {
|
||||
SpvDim dim = spvc_type_get_image_dimension(type);
|
||||
bool is_array = spvc_type_get_image_arrayed(type);
|
||||
bool is_shadow = spvc_type_get_image_is_depth(type);
|
||||
bool is_ms = spvc_type_get_image_multisampled(type);
|
||||
|
||||
switch (dim) {
|
||||
case SpvDim1D:
|
||||
gl_type = is_array ? GL_SAMPLER_1D_ARRAY : GL_SAMPLER_1D;
|
||||
if (is_shadow)
|
||||
gl_type = is_array ? GL_SAMPLER_1D_ARRAY_SHADOW
|
||||
: GL_SAMPLER_1D_SHADOW;
|
||||
break;
|
||||
case SpvDim2D:
|
||||
if (is_ms) {
|
||||
gl_type = is_array ? GL_SAMPLER_2D_MULTISAMPLE_ARRAY
|
||||
: GL_SAMPLER_2D_MULTISAMPLE;
|
||||
} else {
|
||||
gl_type = is_array ? GL_SAMPLER_2D_ARRAY : GL_SAMPLER_2D;
|
||||
if (is_shadow)
|
||||
gl_type = is_array ? GL_SAMPLER_2D_ARRAY_SHADOW
|
||||
: GL_SAMPLER_2D_SHADOW;
|
||||
}
|
||||
break;
|
||||
case SpvDim3D:
|
||||
gl_type = GL_SAMPLER_3D;
|
||||
break;
|
||||
case SpvDimCube:
|
||||
gl_type = is_array ? GL_SAMPLER_CUBE_MAP_ARRAY : GL_SAMPLER_CUBE;
|
||||
if (is_shadow)
|
||||
gl_type = is_array ? GL_SAMPLER_CUBE_MAP_ARRAY_SHADOW
|
||||
: GL_SAMPLER_CUBE_SHADOW;
|
||||
break;
|
||||
case SpvDimRect:
|
||||
gl_type = is_shadow ? GL_SAMPLER_2D_RECT_SHADOW : GL_SAMPLER_2D_RECT;
|
||||
break;
|
||||
case SpvDimBuffer:
|
||||
gl_type = GL_SAMPLER_BUFFER;
|
||||
break;
|
||||
default:
|
||||
gl_type = GL_SAMPLER_2D;
|
||||
}
|
||||
|
||||
spvc_basetype sampled_base = spvc_type_get_basetype(type);
|
||||
if (sampled_base == SPVC_BASETYPE_INT32) {
|
||||
switch (gl_type) {
|
||||
case GL_SAMPLER_1D:
|
||||
gl_type = GL_INT_SAMPLER_1D;
|
||||
break;
|
||||
case GL_SAMPLER_2D:
|
||||
gl_type = GL_INT_SAMPLER_2D;
|
||||
break;
|
||||
case GL_SAMPLER_3D:
|
||||
gl_type = GL_INT_SAMPLER_3D;
|
||||
break;
|
||||
case GL_SAMPLER_CUBE:
|
||||
gl_type = GL_INT_SAMPLER_CUBE;
|
||||
break;
|
||||
case GL_SAMPLER_1D_ARRAY:
|
||||
gl_type = GL_INT_SAMPLER_1D_ARRAY;
|
||||
break;
|
||||
case GL_SAMPLER_2D_ARRAY:
|
||||
gl_type = GL_INT_SAMPLER_2D_ARRAY;
|
||||
break;
|
||||
case GL_SAMPLER_CUBE_MAP_ARRAY:
|
||||
gl_type = GL_INT_SAMPLER_CUBE_MAP_ARRAY;
|
||||
break;
|
||||
case GL_SAMPLER_2D_RECT:
|
||||
gl_type = GL_INT_SAMPLER_2D_RECT;
|
||||
break;
|
||||
case GL_SAMPLER_BUFFER:
|
||||
gl_type = GL_INT_SAMPLER_BUFFER;
|
||||
break;
|
||||
case GL_SAMPLER_2D_MULTISAMPLE:
|
||||
gl_type = GL_INT_SAMPLER_2D_MULTISAMPLE;
|
||||
break;
|
||||
case GL_SAMPLER_2D_MULTISAMPLE_ARRAY:
|
||||
gl_type = GL_INT_SAMPLER_2D_MULTISAMPLE_ARRAY;
|
||||
break;
|
||||
}
|
||||
} else if (sampled_base == SPVC_BASETYPE_UINT32) {
|
||||
switch (gl_type) {
|
||||
case GL_SAMPLER_1D:
|
||||
gl_type = GL_UNSIGNED_INT_SAMPLER_1D;
|
||||
break;
|
||||
case GL_SAMPLER_2D:
|
||||
gl_type = GL_UNSIGNED_INT_SAMPLER_2D;
|
||||
break;
|
||||
case GL_SAMPLER_3D:
|
||||
gl_type = GL_UNSIGNED_INT_SAMPLER_3D;
|
||||
break;
|
||||
case GL_SAMPLER_CUBE:
|
||||
gl_type = GL_UNSIGNED_INT_SAMPLER_CUBE;
|
||||
break;
|
||||
case GL_SAMPLER_1D_ARRAY:
|
||||
gl_type = GL_UNSIGNED_INT_SAMPLER_1D_ARRAY;
|
||||
break;
|
||||
case GL_SAMPLER_2D_ARRAY:
|
||||
gl_type = GL_UNSIGNED_INT_SAMPLER_2D_ARRAY;
|
||||
break;
|
||||
case GL_SAMPLER_CUBE_MAP_ARRAY:
|
||||
gl_type = GL_UNSIGNED_INT_SAMPLER_CUBE_MAP_ARRAY;
|
||||
break;
|
||||
case GL_SAMPLER_2D_RECT:
|
||||
gl_type = GL_UNSIGNED_INT_SAMPLER_2D_RECT;
|
||||
break;
|
||||
case GL_SAMPLER_BUFFER:
|
||||
gl_type = GL_UNSIGNED_INT_SAMPLER_BUFFER;
|
||||
break;
|
||||
case GL_SAMPLER_2D_MULTISAMPLE:
|
||||
gl_type = GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE;
|
||||
break;
|
||||
case GL_SAMPLER_2D_MULTISAMPLE_ARRAY:
|
||||
gl_type = GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE_ARRAY;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
unsigned vec_size = spvc_type_get_vector_size(type);
|
||||
unsigned columns = spvc_type_get_columns(type);
|
||||
|
||||
switch (base_type) {
|
||||
case SPVC_BASETYPE_FP32:
|
||||
if (columns > 1) {
|
||||
if (vec_size == 2 && columns == 2) gl_type = GL_FLOAT_MAT2;
|
||||
else if (vec_size == 3 && columns == 3) gl_type = GL_FLOAT_MAT3;
|
||||
else if (vec_size == 4 && columns == 4) gl_type = GL_FLOAT_MAT4;
|
||||
else if (vec_size == 3 && columns == 2) gl_type = GL_FLOAT_MAT2x3;
|
||||
else if (vec_size == 4 && columns == 2) gl_type = GL_FLOAT_MAT2x4;
|
||||
else if (vec_size == 2 && columns == 3) gl_type = GL_FLOAT_MAT3x2;
|
||||
else if (vec_size == 4 && columns == 3) gl_type = GL_FLOAT_MAT3x4;
|
||||
else if (vec_size == 2 && columns == 4) gl_type = GL_FLOAT_MAT4x2;
|
||||
else if (vec_size == 3 && columns == 4) gl_type = GL_FLOAT_MAT4x3;
|
||||
} else {
|
||||
switch (vec_size) {
|
||||
case 1:
|
||||
gl_type = GL_FLOAT;
|
||||
break;
|
||||
case 2:
|
||||
gl_type = GL_FLOAT_VEC2;
|
||||
break;
|
||||
case 3:
|
||||
gl_type = GL_FLOAT_VEC3;
|
||||
break;
|
||||
case 4:
|
||||
gl_type = GL_FLOAT_VEC4;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case SPVC_BASETYPE_INT32:
|
||||
switch (vec_size) {
|
||||
case 1:
|
||||
gl_type = GL_INT;
|
||||
break;
|
||||
case 2:
|
||||
gl_type = GL_INT_VEC2;
|
||||
break;
|
||||
case 3:
|
||||
gl_type = GL_INT_VEC3;
|
||||
break;
|
||||
case 4:
|
||||
gl_type = GL_INT_VEC4;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case SPVC_BASETYPE_UINT32:
|
||||
switch (vec_size) {
|
||||
case 1:
|
||||
gl_type = GL_UNSIGNED_INT;
|
||||
break;
|
||||
case 2:
|
||||
gl_type = GL_UNSIGNED_INT_VEC2;
|
||||
break;
|
||||
case 3:
|
||||
gl_type = GL_UNSIGNED_INT_VEC3;
|
||||
break;
|
||||
case 4:
|
||||
gl_type = GL_UNSIGNED_INT_VEC4;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case SPVC_BASETYPE_BOOLEAN:
|
||||
switch (vec_size) {
|
||||
case 1:
|
||||
gl_type = GL_BOOL;
|
||||
break;
|
||||
case 2:
|
||||
gl_type = GL_BOOL_VEC2;
|
||||
break;
|
||||
case 3:
|
||||
gl_type = GL_BOOL_VEC3;
|
||||
break;
|
||||
case 4:
|
||||
gl_type = GL_BOOL_VEC4;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
prog.uniformLocations[name] = final_location;
|
||||
UniformValue uniformValue;
|
||||
uniformValue.type = gl_type;
|
||||
prog.uniformValues[name] = uniformValue;
|
||||
}
|
||||
}
|
||||
spvc_context_destroy(context);
|
||||
}
|
||||
|
||||
std::string CompileSPIRVToGLSL(std::vector<unsigned int> spirv, uint glslVersion, bool isES) {
|
||||
spvc_context context = nullptr;
|
||||
spvc_parsed_ir ir = nullptr;
|
||||
spvc_compiler compiler_glsl = nullptr;
|
||||
spvc_compiler_options options = nullptr;
|
||||
spvc_resources resources = nullptr;
|
||||
const spvc_reflected_resource *list = nullptr;
|
||||
const char *result = nullptr;
|
||||
size_t count;
|
||||
|
||||
const SpvId *p_spirv = spirv.data();
|
||||
size_t word_count = spirv.size();
|
||||
|
||||
spvc_context_create(&context);
|
||||
spvc_context_parse_spirv(context, p_spirv, word_count, &ir);
|
||||
spvc_context_create_compiler(context, SPVC_BACKEND_GLSL, ir, SPVC_CAPTURE_MODE_TAKE_OWNERSHIP, &compiler_glsl);
|
||||
spvc_compiler_create_shader_resources(compiler_glsl, &resources);
|
||||
spvc_resources_get_resource_list_for_type(resources, SPVC_RESOURCE_TYPE_UNIFORM_BUFFER, &list, &count);
|
||||
spvc_compiler_create_compiler_options(compiler_glsl, &options);
|
||||
spvc_compiler_options_set_uint(options, SPVC_COMPILER_OPTION_GLSL_VERSION, glslVersion);
|
||||
spvc_compiler_options_set_bool(options, SPVC_COMPILER_OPTION_GLSL_ES, isES ? SPVC_TRUE : SPVC_FALSE);
|
||||
spvc_compiler_install_compiler_options(compiler_glsl, options);
|
||||
spvc_compiler_compile(compiler_glsl, &result);
|
||||
|
||||
if (!result) {
|
||||
return{};
|
||||
}
|
||||
|
||||
std::string essl = result;
|
||||
|
||||
spvc_context_destroy(context);
|
||||
|
||||
return essl;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
//
|
||||
// Created by BZLZHH on 2025/5/3.
|
||||
//
|
||||
|
||||
#ifndef MOBILEGL_GLSLTOOL_H
|
||||
#define MOBILEGL_GLSLTOOL_H
|
||||
|
||||
#include "../../Includes.h"
|
||||
|
||||
namespace MG_Util::Program {
|
||||
inline static TBuiltInResource InitResources()
|
||||
{
|
||||
TBuiltInResource Resources{};
|
||||
Resources.maxLights = 32;
|
||||
Resources.maxClipPlanes = 6;
|
||||
Resources.maxTextureUnits = 32;
|
||||
Resources.maxTextureCoords = 32;
|
||||
Resources.maxVertexAttribs = 64;
|
||||
Resources.maxVertexUniformComponents = 4096;
|
||||
Resources.maxVaryingFloats = 64;
|
||||
Resources.maxVertexTextureImageUnits = 32;
|
||||
Resources.maxCombinedTextureImageUnits = 80;
|
||||
Resources.maxTextureImageUnits = 32;
|
||||
Resources.maxFragmentUniformComponents = 4096;
|
||||
Resources.maxDrawBuffers = 32;
|
||||
Resources.maxVertexUniformVectors = 128;
|
||||
Resources.maxVaryingVectors = 8;
|
||||
Resources.maxFragmentUniformVectors = 16;
|
||||
Resources.maxVertexOutputVectors = 16;
|
||||
Resources.maxFragmentInputVectors = 15;
|
||||
Resources.minProgramTexelOffset = -8;
|
||||
Resources.maxProgramTexelOffset = 7;
|
||||
Resources.maxClipDistances = 8;
|
||||
Resources.maxComputeWorkGroupCountX = 65535;
|
||||
Resources.maxComputeWorkGroupCountY = 65535;
|
||||
Resources.maxComputeWorkGroupCountZ = 65535;
|
||||
Resources.maxComputeWorkGroupSizeX = 1024;
|
||||
Resources.maxComputeWorkGroupSizeY = 1024;
|
||||
Resources.maxComputeWorkGroupSizeZ = 64;
|
||||
Resources.maxComputeUniformComponents = 1024;
|
||||
Resources.maxComputeTextureImageUnits = 16;
|
||||
Resources.maxComputeImageUniforms = 8;
|
||||
Resources.maxComputeAtomicCounters = 8;
|
||||
Resources.maxComputeAtomicCounterBuffers = 1;
|
||||
Resources.maxVaryingComponents = 60;
|
||||
Resources.maxVertexOutputComponents = 64;
|
||||
Resources.maxGeometryInputComponents = 64;
|
||||
Resources.maxGeometryOutputComponents = 128;
|
||||
Resources.maxFragmentInputComponents = 128;
|
||||
Resources.maxImageUnits = 8;
|
||||
Resources.maxCombinedImageUnitsAndFragmentOutputs = 8;
|
||||
Resources.maxCombinedShaderOutputResources = 8;
|
||||
Resources.maxImageSamples = 0;
|
||||
Resources.maxVertexImageUniforms = 0;
|
||||
Resources.maxTessControlImageUniforms = 0;
|
||||
Resources.maxTessEvaluationImageUniforms = 0;
|
||||
Resources.maxGeometryImageUniforms = 0;
|
||||
Resources.maxFragmentImageUniforms = 8;
|
||||
Resources.maxCombinedImageUniforms = 8;
|
||||
Resources.maxGeometryTextureImageUnits = 16;
|
||||
Resources.maxGeometryOutputVertices = 256;
|
||||
Resources.maxGeometryTotalOutputComponents = 1024;
|
||||
Resources.maxGeometryUniformComponents = 1024;
|
||||
Resources.maxGeometryVaryingComponents = 64;
|
||||
Resources.maxTessControlInputComponents = 128;
|
||||
Resources.maxTessControlOutputComponents = 128;
|
||||
Resources.maxTessControlTextureImageUnits = 16;
|
||||
Resources.maxTessControlUniformComponents = 1024;
|
||||
Resources.maxTessControlTotalOutputComponents = 4096;
|
||||
Resources.maxTessEvaluationInputComponents = 128;
|
||||
Resources.maxTessEvaluationOutputComponents = 128;
|
||||
Resources.maxTessEvaluationTextureImageUnits = 16;
|
||||
Resources.maxTessEvaluationUniformComponents = 1024;
|
||||
Resources.maxTessPatchComponents = 120;
|
||||
Resources.maxPatchVertices = 32;
|
||||
Resources.maxTessGenLevel = 64;
|
||||
Resources.maxViewports = 16;
|
||||
Resources.maxVertexAtomicCounters = 0;
|
||||
Resources.maxTessControlAtomicCounters = 0;
|
||||
Resources.maxTessEvaluationAtomicCounters = 0;
|
||||
Resources.maxGeometryAtomicCounters = 0;
|
||||
Resources.maxFragmentAtomicCounters = 8;
|
||||
Resources.maxCombinedAtomicCounters = 8;
|
||||
Resources.maxAtomicCounterBindings = 1;
|
||||
Resources.maxVertexAtomicCounterBuffers = 0;
|
||||
Resources.maxTessControlAtomicCounterBuffers = 0;
|
||||
Resources.maxTessEvaluationAtomicCounterBuffers = 0;
|
||||
Resources.maxGeometryAtomicCounterBuffers = 0;
|
||||
Resources.maxFragmentAtomicCounterBuffers = 1;
|
||||
Resources.maxCombinedAtomicCounterBuffers = 1;
|
||||
Resources.maxAtomicCounterBufferSize = 16384;
|
||||
Resources.maxTransformFeedbackBuffers = 4;
|
||||
Resources.maxTransformFeedbackInterleavedComponents = 64;
|
||||
Resources.maxCullDistances = 8;
|
||||
Resources.maxCombinedClipAndCullDistances = 8;
|
||||
Resources.maxSamples = 4;
|
||||
Resources.maxMeshOutputVerticesNV = 256;
|
||||
Resources.maxMeshOutputPrimitivesNV = 512;
|
||||
Resources.maxMeshWorkGroupSizeX_NV = 32;
|
||||
Resources.maxMeshWorkGroupSizeY_NV = 1;
|
||||
Resources.maxMeshWorkGroupSizeZ_NV = 1;
|
||||
Resources.maxTaskWorkGroupSizeX_NV = 32;
|
||||
Resources.maxTaskWorkGroupSizeY_NV = 1;
|
||||
Resources.maxTaskWorkGroupSizeZ_NV = 1;
|
||||
Resources.maxMeshViewCountNV = 4;
|
||||
|
||||
Resources.limits.nonInductiveForLoops = true;
|
||||
Resources.limits.whileLoops = true;
|
||||
Resources.limits.doWhileLoops = true;
|
||||
Resources.limits.generalUniformIndexing = true;
|
||||
Resources.limits.generalAttributeMatrixVectorIndexing = true;
|
||||
Resources.limits.generalVaryingIndexing = true;
|
||||
Resources.limits.generalSamplerIndexing = true;
|
||||
Resources.limits.generalVariableIndexing = true;
|
||||
Resources.limits.generalConstantMatrixVectorIndexing = true;
|
||||
|
||||
return Resources;
|
||||
}
|
||||
|
||||
inline int QueryGLSLVersion(const std::string& code) {
|
||||
static std::regex version_pattern(R"(#version\s+(\d{3}))");
|
||||
std::smatch match;
|
||||
if (std::regex_search(code, match, version_pattern)) {
|
||||
return std::stoi(match[1].str());
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
inline GLsizei GetMatrixElementCount(GLenum matrixType) {
|
||||
switch(matrixType) {
|
||||
case GL_FLOAT_MAT2: return 4;
|
||||
case GL_FLOAT_MAT3: return 9;
|
||||
case GL_FLOAT_MAT4: return 16;
|
||||
case GL_FLOAT_MAT2x3: return 6;
|
||||
case GL_FLOAT_MAT2x4: return 8;
|
||||
case GL_FLOAT_MAT3x2: return 6;
|
||||
case GL_FLOAT_MAT3x4: return 12;
|
||||
case GL_FLOAT_MAT4x2: return 8;
|
||||
case GL_FLOAT_MAT4x3: return 12;
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
||||
inline std::string GetShaderTypeName(GLenum shaderType) {
|
||||
switch(shaderType) {
|
||||
case GL_VERTEX_SHADER: return "Vertex Shader";
|
||||
case GL_FRAGMENT_SHADER: return "Fragment Shader";
|
||||
case GL_GEOMETRY_SHADER: return "Geometry Shader";
|
||||
case GL_TESS_CONTROL_SHADER: return "Tessellation Control Shader";
|
||||
case GL_TESS_EVALUATION_SHADER: return "Tessellation Evaluation Shader";
|
||||
case GL_COMPUTE_SHADER: return "Compute Shader";
|
||||
default: return "Unknown Shader Type (" + std::to_string(shaderType) + ")";
|
||||
}
|
||||
}
|
||||
|
||||
inline EShLanguage GetEShLanguageByShaderType(GLenum shaderType) {
|
||||
switch (shaderType) {
|
||||
case GL_VERTEX_SHADER:
|
||||
return EShLanguage::EShLangVertex;
|
||||
case GL_FRAGMENT_SHADER:
|
||||
return EShLanguage::EShLangFragment;
|
||||
case GL_COMPUTE_SHADER:
|
||||
return EShLanguage::EShLangCompute;
|
||||
case GL_TESS_CONTROL_SHADER:
|
||||
return EShLanguage::EShLangTessControl;
|
||||
case GL_TESS_EVALUATION_SHADER:
|
||||
return EShLanguage::EShLangTessEvaluation;
|
||||
case GL_GEOMETRY_SHADER:
|
||||
return EShLanguage::EShLangGeometry;
|
||||
default:
|
||||
return EShLanguage::EShLangCount;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<unsigned> CompileGLSLToSPIRV(GLenum shaderType, const std::string& source, std::string& infoLog);
|
||||
std::string CompileGLSLToTShader(GLenum shaderType, const std::string& source, glslang::TShader *&shader);
|
||||
std::vector<std::vector<unsigned>> CompileMultipleShadersToSPIRV(const ProgramState& state, ProgramObject& prog, std::string& infoLog);
|
||||
void ReflectSPIRVUniforms(const std::vector<std::vector<unsigned>>& allSpirv, ProgramObject& prog, std::string& infoLog);
|
||||
std::string CompileSPIRVToGLSL(std::vector<unsigned int> spirv, uint glslVersion, bool isES);
|
||||
}
|
||||
|
||||
#endif //MOBILEGL_GLSLTOOL_H
|
||||
@@ -1,72 +0,0 @@
|
||||
// MobileGL - MobileGL/Config.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 <MG_Backend/BackendObjects.h>
|
||||
|
||||
namespace MobileGL::MG_Config {
|
||||
inline const String ProjectName = "MobileGL";
|
||||
inline const String CoreName = "MobileGL Core";
|
||||
inline const String CoreVendor = "MobileGL-Dev (BZLZHH, Swung0x48, Tungsten)";
|
||||
inline const Version CoreVersion = {26, 7, 0, "-dev", VersionType::Development};
|
||||
inline const VersionStringFormatAttrib DefaultVersionStringFormatAttrib = {2, 2, 0, true, true};
|
||||
inline const Uint64 CacheVersion = 0;
|
||||
|
||||
extern BackendType ActiveBackendType;
|
||||
|
||||
// Feature toggles parsed once from environment variables in MG_ConfigLoader::Init()
|
||||
// (ConfigLoader.cpp), before the accepted-env map is destroyed. All Bool fields share
|
||||
// one truthy rule: the variable is set, non-empty, not "0", and not "false"
|
||||
// (case-insensitive).
|
||||
//
|
||||
// Env variables intentionally NOT mirrored here (kept as live std::getenv at their
|
||||
// call sites):
|
||||
// - DISPLAY: X11 session variable, not MobileGL configuration.
|
||||
// - MOBILEGL_LOG_FILE_PATH: log-file init runs before MG_ConfigLoader::Init
|
||||
// (see MG_Util/Debug/Log.cpp).
|
||||
struct FeaturesTable {
|
||||
// MOBILEGL_DISABLE_TIMERQUERY: do not advertise or use GPU timer queries.
|
||||
Bool DisableTimerQuery = false;
|
||||
// MOBILEGL_USE_ANGLE: load ANGLE EGL/GLES libraries.
|
||||
Bool UseAngle = false;
|
||||
#if defined(MOBILEGL_TRACE_ANGLE_VARIANTS)
|
||||
// MOBILEGL_TRACE_ANGLE_VARIANT: signed trace-APK ANGLE build short hash.
|
||||
String TraceAngleVariant;
|
||||
#endif
|
||||
// MOBILEGL_DISABLE_SUBGROUP: force-disable Vulkan shader subgroup support.
|
||||
Bool DisableSubgroup = false;
|
||||
// MOBILEGL_MAGMA_R11G11B10F_FALLBACK: use fallback format for R11G11B10F on Vulkan.
|
||||
Bool MagmaR11G11B10FFallback = false;
|
||||
// MOBILEGL_MAGMA_FRAMESINFLIGHT: requested Magma frames in flight, defaulting to 3.
|
||||
Uint32 MagmaFramesInFlight = 3;
|
||||
// MOBILEGL_AVOID_SAMPLER_MIPMAP_MIN_FILTER: avoid mipmap min filters in samplers,
|
||||
// resolves certain rendering bugs on ANGLE + llvmpipe.
|
||||
Bool AvoidSamplerMipmapMinFilter = false;
|
||||
// MOBILEGL_COHERENT_AS_FLUSH: app-compat for engines (e.g. Flywheel) that write
|
||||
// GPU-read data through persistent GL_MAP_FLUSH_EXPLICIT_BIT maps they never
|
||||
// flush. Persistent FLUSH_EXPLICIT map requests are rewritten to coherent
|
||||
// semantics: writes reach the backend without glFlushMappedBufferRange, and
|
||||
// flush calls on rewritten maps become error-free no-ops. Non-persistent maps
|
||||
// keep spec FLUSH_EXPLICIT behavior.
|
||||
Bool CoherentAsFlush = false;
|
||||
// MOBILEGL_TRACE_SKIP_AUTODESTROY: skip teardown in the ELF destructor (Init.cpp).
|
||||
Bool TraceSkipAutodestroy = false;
|
||||
// MOBILEGL_DISABLE_UBO_RING: force the DirectGLES global-UBO upload back to the
|
||||
// per-draw glBufferSubData path instead of the persistent-mapped ring allocator
|
||||
// (negative control / driver-bug escape hatch).
|
||||
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;
|
||||
} // namespace MobileGL::MG_Config
|
||||
@@ -1,154 +0,0 @@
|
||||
// MobileGL - MobileGL/ConfigLoader.cpp
|
||||
// Copyright (c) 2025-2026 MobileGL-Dev
|
||||
// Licensed under the GNU Lesser General Public License v3.0:
|
||||
// https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
// https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
// End of Source File Header
|
||||
|
||||
#include "Config.h"
|
||||
|
||||
#include <cerrno>
|
||||
#include <cstdlib>
|
||||
|
||||
#ifndef _WIN32
|
||||
extern char** environ;
|
||||
#endif
|
||||
|
||||
namespace MobileGL::MG_Config {
|
||||
// Zero/default-initialized at static-init time (all fields have constexpr-friendly
|
||||
// defaults), so it is safe to read even if MG_ConfigLoader::Init has not run yet.
|
||||
FeaturesTable Features;
|
||||
} // namespace MobileGL::MG_Config
|
||||
|
||||
namespace MobileGL::MG_ConfigLoader {
|
||||
static UniquePtr<UnorderedMap<String, String>> acceptedEnvVariablesMap;
|
||||
|
||||
static Bool IsAcceptedPrefix(const String& key) {
|
||||
return (key.compare(0, 6, "LIBGL_") == 0 || key.compare(0, 9, "MOBILEGL_") == 0);
|
||||
}
|
||||
|
||||
inline void InitializeAcceptedEnvVariables() {
|
||||
if (!acceptedEnvVariablesMap) {
|
||||
acceptedEnvVariablesMap = MakeUnique<UnorderedMap<String, String>>();
|
||||
} else {
|
||||
acceptedEnvVariablesMap->clear();
|
||||
}
|
||||
|
||||
char** envPtr = nullptr;
|
||||
|
||||
#ifdef _WIN32
|
||||
envPtr = _environ;
|
||||
#else // POSIX
|
||||
envPtr = ::environ;
|
||||
#endif
|
||||
|
||||
if (envPtr == nullptr) return;
|
||||
|
||||
for (char** env = envPtr; *env != nullptr; ++env) {
|
||||
String entry(*env);
|
||||
SizeT pos = entry.find('=');
|
||||
if (pos != String::npos) {
|
||||
String key = entry.substr(0, pos);
|
||||
String value = entry.substr(pos + 1);
|
||||
|
||||
if (IsAcceptedPrefix(key)) {
|
||||
(*acceptedEnvVariablesMap)[key] = value;
|
||||
MGLOG_D("Config: Accepted env variable: %s=%s", key.c_str(), value.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline void QueryEnvVariable(const String& key, String& outValue, const String& defaultValue) {
|
||||
auto it = acceptedEnvVariablesMap->find(key);
|
||||
if (it != acceptedEnvVariablesMap->end()) {
|
||||
outValue = it->second;
|
||||
} else {
|
||||
outValue = defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
// Unified truthy rule for boolean feature env variables: set, non-empty, not "0",
|
||||
// and not "false" (case-insensitive).
|
||||
static Bool IsTruthyValue(const String& value) {
|
||||
if (value.empty() || value == "0") {
|
||||
return false;
|
||||
}
|
||||
String lowered = value;
|
||||
std::transform(lowered.begin(), lowered.end(), lowered.begin(),
|
||||
[](unsigned char c) { return static_cast<char>(std::tolower(c)); });
|
||||
return lowered != "false";
|
||||
}
|
||||
|
||||
inline Bool QueryEnvFlag(const String& key) {
|
||||
auto it = acceptedEnvVariablesMap->find(key);
|
||||
return it != acceptedEnvVariablesMap->end() && IsTruthyValue(it->second);
|
||||
}
|
||||
|
||||
inline Uint32 QueryEnvUint32(const String& key, Uint32 defaultValue, Uint32 minValue, Uint32 maxValue) {
|
||||
auto it = acceptedEnvVariablesMap->find(key);
|
||||
if (it == acceptedEnvVariablesMap->end()) {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
const String& value = it->second;
|
||||
char* parseEnd = nullptr;
|
||||
errno = 0;
|
||||
const unsigned long parsedValue = std::strtoul(value.c_str(), &parseEnd, 10);
|
||||
if (parseEnd == value.c_str() || *parseEnd != '\0' || errno == ERANGE || parsedValue < minValue ||
|
||||
parsedValue > maxValue) {
|
||||
MGLOG_W("Config: Ignoring invalid env variable %s='%s'; expected an integer in range [%u, %u], "
|
||||
"using default %u",
|
||||
key.c_str(), value.c_str(), minValue, maxValue, defaultValue);
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
return static_cast<Uint32>(parsedValue);
|
||||
}
|
||||
|
||||
inline void InitFeatures() {
|
||||
auto& features = MG_Config::Features;
|
||||
features.DisableTimerQuery = QueryEnvFlag("MOBILEGL_DISABLE_TIMERQUERY");
|
||||
features.UseAngle = QueryEnvFlag("MOBILEGL_USE_ANGLE");
|
||||
#if defined(MOBILEGL_TRACE_ANGLE_VARIANTS)
|
||||
QueryEnvVariable("MOBILEGL_TRACE_ANGLE_VARIANT", features.TraceAngleVariant, "");
|
||||
#endif
|
||||
features.DisableSubgroup = QueryEnvFlag("MOBILEGL_DISABLE_SUBGROUP");
|
||||
features.MagmaR11G11B10FFallback = QueryEnvFlag("MOBILEGL_MAGMA_R11G11B10F_FALLBACK");
|
||||
features.MagmaFramesInFlight = QueryEnvUint32("MOBILEGL_MAGMA_FRAMESINFLIGHT", 3, 1, 64);
|
||||
features.AvoidSamplerMipmapMinFilter =
|
||||
QueryEnvFlag("MOBILEGL_AVOID_SAMPLER_MIPMAP_MIN_FILTER");
|
||||
features.CoherentAsFlush = QueryEnvFlag("MOBILEGL_COHERENT_AS_FLUSH");
|
||||
features.TraceSkipAutodestroy = QueryEnvFlag("MOBILEGL_TRACE_SKIP_AUTODESTROY");
|
||||
features.DisableUboRing = QueryEnvFlag("MOBILEGL_DISABLE_UBO_RING");
|
||||
features.RelaxedSemantics = QueryEnvFlag("MOBILEGL_RELAXED_SEMANTICS");
|
||||
}
|
||||
|
||||
inline void InitBackendType() {
|
||||
String backendTypeStr;
|
||||
QueryEnvVariable("MOBILEGL_BACKEND_TYPE", backendTypeStr, "DirectGLES");
|
||||
#define ENTRY(backendType) \
|
||||
if (backendTypeStr == #backendType) { \
|
||||
MG_Config::ActiveBackendType = BackendType::backendType; \
|
||||
MGLOG_I("Config: Active backend type set to " #backendType); \
|
||||
return; \
|
||||
}
|
||||
ENTRY(DirectGLES)
|
||||
ENTRY(DirectVulkan)
|
||||
ENTRY(Unknown)
|
||||
MG_Config::ActiveBackendType = BackendType::Unknown;
|
||||
#undef ENTRY
|
||||
}
|
||||
|
||||
void Init() {
|
||||
MGLOG_D("Loading configuration from environment variables...");
|
||||
InitializeAcceptedEnvVariables();
|
||||
|
||||
InitBackendType();
|
||||
InitFeatures();
|
||||
|
||||
// Destroy the map since we won't need it anymore
|
||||
acceptedEnvVariablesMap.reset();
|
||||
}
|
||||
} // namespace MobileGL::MG_ConfigLoader
|
||||
@@ -1,81 +0,0 @@
|
||||
// MobileGL - MobileGL/Defines.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
|
||||
|
||||
// ============== Platform-specific definitions and macros ============== //
|
||||
#ifdef __ANDROID__
|
||||
#undef __ANDROID_API__
|
||||
#define __ANDROID_API__ 26 // force Android API level to 26 for compatibility
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX 1 // prevent Windows.h from defining min and max macros
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// ================== MobileGL definitions and macros =================== //
|
||||
#ifdef _WIN32
|
||||
#define MOBILEGL_EXPORT extern "C" __declspec(dllexport)
|
||||
#else
|
||||
#define MOBILEGL_EXPORT extern "C" __attribute__((visibility("default")))
|
||||
#endif
|
||||
|
||||
#define MOBILEGL_API MOBILEGL_EXPORT
|
||||
|
||||
#define MOBILEGL_GLX_API MOBILEGL_API
|
||||
#define MOBILEGL_GL_API MOBILEGL_API
|
||||
#define MOBILEGL_EGL_API MOBILEGL_API
|
||||
#define MOBILEGL_CGL_API MOBILEGL_API
|
||||
#define MOBILEGL_NSOPENGL_API MOBILEGL_API
|
||||
|
||||
// ====================== MobileGL configurations ======================= //
|
||||
#ifndef MOBILEGL_LOG_ACTIVE_LEVEL
|
||||
#define MOBILEGL_LOG_ACTIVE_LEVEL MOBILEGL_LOG_LEVEL_INFO
|
||||
#endif
|
||||
|
||||
#define MOBILEGL_LOG_ENABLE_CONSOLE 0
|
||||
#define MOBILEGL_LOG_ENABLE_FILE 1
|
||||
#define MOBILEGL_LOG_ENABLE_ANDROID 1
|
||||
#define MOBILEGL_ENABLE_SCOPE_MARKER 0
|
||||
|
||||
// Require C++23
|
||||
// Clang/Android NDK still doesn't have support for that :(
|
||||
#if __cplusplus >= 202302L && !__ANDROID__
|
||||
#define MOBILEGL_LOG_ENABLE_STACKTRACE 0
|
||||
#endif
|
||||
|
||||
#ifdef __ANDROID__
|
||||
#define MOBILEGL_LOG_FILE_PATH "/sdcard/MG/latest.log"
|
||||
#else
|
||||
#define MOBILEGL_LOG_FILE_PATH ""
|
||||
#endif
|
||||
|
||||
#if defined _MSC_VER or defined __MINGW32__ or defined __MINGW64__
|
||||
#define TRAP assert(false)
|
||||
#elif __clang__
|
||||
#define TRAP __builtin_debugtrap()
|
||||
#else
|
||||
#include <signal.h>
|
||||
#define TRAP raise(SIGTRAP)
|
||||
#endif
|
||||
|
||||
// =============================== Utils ================================ //
|
||||
#if MOBILEGL_LOG_ACTIVE_LEVEL <= MOBILEGL_LOG_LEVEL_DEBUG
|
||||
#define MOBILEGL_ASSERT(condition, ...) \
|
||||
do { \
|
||||
if (!(condition)) { \
|
||||
MGLOG_F("Assertion failed" __VA_OPT__(": ") __VA_ARGS__); \
|
||||
MGLOG_F(" at %s:%d (%s)", __FILE__, __LINE__, __func__); \
|
||||
TRAP; \
|
||||
} \
|
||||
} while (0)
|
||||
#else
|
||||
#define MOBILEGL_ASSERT(condition, ...)
|
||||
#endif
|
||||
@@ -1,20 +0,0 @@
|
||||
// MobileGL - MobileGL/GlobalObjects.cpp
|
||||
// Copyright (c) 2025-2026 MobileGL-Dev
|
||||
// Licensed under the GNU Lesser General Public License v3.0:
|
||||
// https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
// https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
// End of Source File Header
|
||||
|
||||
#include "Config.h"
|
||||
|
||||
namespace MobileGL {
|
||||
namespace MG_Config {
|
||||
BackendType ActiveBackendType;
|
||||
} // namespace MG_Config
|
||||
|
||||
namespace MG_Backend {
|
||||
UniquePtr<BackendObject> pActiveBackendObject;
|
||||
GlobalBackendFunctionsTable gBackendFunctionsTable;
|
||||
} // namespace MG_Backend
|
||||
} // namespace MobileGL
|
||||
@@ -1,149 +0,0 @@
|
||||
// MobileGL - MobileGL/Includes.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 significant project headers
|
||||
#include "Defines.h"
|
||||
#include "MG_Util/PlatformStubs.h"
|
||||
|
||||
// Include necessary standard headers
|
||||
#include <bit>
|
||||
#include <map>
|
||||
#include <array>
|
||||
#include <ctime>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
#include <regex>
|
||||
#include <atomic>
|
||||
#include <bitset>
|
||||
#include <cctype>
|
||||
#include <chrono>
|
||||
#include <cstdio>
|
||||
#include <format>
|
||||
#include <memory>
|
||||
#include <random>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
#include <cassert>
|
||||
#include <climits>
|
||||
#include <cstdlib>
|
||||
#include <cstdarg>
|
||||
#include <cstring>
|
||||
#include <numeric>
|
||||
#include <expected>
|
||||
#include <iostream>
|
||||
#include <optional>
|
||||
#include <algorithm>
|
||||
#include <stdexcept>
|
||||
#include <functional>
|
||||
#include <string_view>
|
||||
#include <unordered_map>
|
||||
#if __cplusplus >= 202302L && MOBILEGL_LOG_ENABLE_STACKTRACE
|
||||
#include <stacktrace>
|
||||
#endif
|
||||
|
||||
// Include FastSTL
|
||||
#include <FastSTL/UnorderedMap.h>
|
||||
|
||||
// Include xxHash
|
||||
#include <xxhash.h>
|
||||
|
||||
// Include spirv_cross
|
||||
#include <spirv_cross/spirv_cross_c.h>
|
||||
|
||||
// Include OpenGL and EGL headers
|
||||
#ifdef GLAPI
|
||||
#undef GLAPI
|
||||
#endif
|
||||
#include <EGL/egl.h>
|
||||
#define GL_GLEXT_PROTOTYPES
|
||||
#ifndef NO_GL_H
|
||||
#include "GL/gl.h"
|
||||
#endif
|
||||
#include <GL/glcorearb.h>
|
||||
#undef GL_GLEXT_PROTOTYPES
|
||||
|
||||
#include <GL/glext.h>
|
||||
|
||||
#define GL_GLES_PROTOTYPES 0
|
||||
#include <GLES3/gl32.h>
|
||||
#undef GL_GLES_PROTOTYPES
|
||||
|
||||
// Include glslang headers
|
||||
#include <glslang/Include/Types.h>
|
||||
#include <glslang/Public/ShaderLang.h>
|
||||
#include <glslang/SPIRV/GlslangToSpv.h>
|
||||
#include <glslang/Include/intermediate.h>
|
||||
#include <glslang/MachineIndependent/localintermediate.h>
|
||||
|
||||
// Include headers for platform-specific functionality
|
||||
#ifdef __linux__
|
||||
#include <dlfcn.h>
|
||||
#include <pthread.h>
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN 1
|
||||
#endif
|
||||
#include <windows.h>
|
||||
#include <processthreadsapi.h>
|
||||
#endif
|
||||
|
||||
#ifdef __ANDROID__
|
||||
#include <unistd.h>
|
||||
#include <pthread.h>
|
||||
#include <android/log.h>
|
||||
#include <android/native_window.h>
|
||||
#endif
|
||||
|
||||
#ifdef __ANDROID__
|
||||
#define VK_USE_PLATFORM_ANDROID_KHR
|
||||
#elif _WIN32
|
||||
#define VK_USE_PLATFORM_WIN32_KHR
|
||||
#elif defined(__APPLE__)
|
||||
#define VK_USE_PLATFORM_METAL_EXT
|
||||
#elif defined(__linux__)
|
||||
#define VK_USE_PLATFORM_XLIB_KHR
|
||||
typedef struct _XDisplay Display;
|
||||
typedef unsigned long XID;
|
||||
typedef XID Window;
|
||||
typedef unsigned long VisualID;
|
||||
#else
|
||||
#warning "VK_USE_PLATFORM_*_KHR not defined for this platform!"
|
||||
#endif
|
||||
#if defined(VK_USE_PLATFORM_XLIB_KHR)
|
||||
#pragma push_macro("Bool")
|
||||
#pragma push_macro("None")
|
||||
#pragma push_macro("Always")
|
||||
#pragma push_macro("Status")
|
||||
#pragma push_macro("LSBFirst")
|
||||
#pragma push_macro("DestroyAll")
|
||||
#endif
|
||||
#include <vulkan/vulkan.h>
|
||||
#if defined(VK_USE_PLATFORM_XLIB_KHR)
|
||||
#pragma pop_macro("DestroyAll")
|
||||
#pragma pop_macro("LSBFirst")
|
||||
#pragma pop_macro("Status")
|
||||
#pragma pop_macro("Always")
|
||||
#pragma pop_macro("None")
|
||||
#pragma pop_macro("Bool")
|
||||
#endif
|
||||
|
||||
#ifdef TRACY_ENABLE
|
||||
#include <tracy/Tracy.hpp>
|
||||
#define TRACY_ZONECOLOR_ENTRY 0xFF0000
|
||||
#define TRACY_ZONECOLOR_FRONTEND 0x00FF00
|
||||
#define TRACY_ZONECOLOR_BACKEND 0x00FF00
|
||||
#endif
|
||||
|
||||
// Post-includes for significant project headers
|
||||
#include "MG_Util/Debug/Log.h"
|
||||
#include "MG_Util/Types.h"
|
||||
@@ -1,103 +0,0 @@
|
||||
// MobileGL - MobileGL/Init.cpp
|
||||
// Copyright (c) 2025-2026 MobileGL-Dev
|
||||
// Licensed under the GNU Lesser General Public License v3.0:
|
||||
// https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
// https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
// End of Source File Header
|
||||
|
||||
#include "Init.h"
|
||||
#include "Config.h"
|
||||
#include <MG_Backend/BackendObjects.h>
|
||||
#include <MG_State/GLState/Core.h>
|
||||
#include <MG_State/EGLState/Core.h>
|
||||
#include <MG_Impl/GLImpl/Texture/ProxyTexture.h>
|
||||
#include <MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.h>
|
||||
|
||||
namespace MobileGL {
|
||||
namespace {
|
||||
Bool g_isInitialized = false;
|
||||
|
||||
void DestroyImpl(Bool logLifecycle) {
|
||||
if (!g_isInitialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (logLifecycle) {
|
||||
MGLOG_I("MobileGL closing...");
|
||||
}
|
||||
glslang::FinalizeProcess();
|
||||
MG_Backend::pActiveBackendObject.reset();
|
||||
MG_State::pGLContext.reset();
|
||||
MG_State::pEGLContext.reset();
|
||||
MG_Impl::GLImpl::TextureImpl::pProxyTextureManager.reset();
|
||||
MG_Impl::GLImpl::FramebufferImpl::pDefaultFramebufferInfo.reset();
|
||||
MG_Backend::gBackendFunctionsTable = {};
|
||||
g_isInitialized = false;
|
||||
if (logLifecycle) {
|
||||
MG_Util::Debug::Close();
|
||||
}
|
||||
|
||||
// TODO: add and use Destroy functions for other subsystems
|
||||
}
|
||||
}
|
||||
|
||||
void Initialize() {
|
||||
if (g_isInitialized) {
|
||||
MGLOG_D("MobileGL already initialized; skipping duplicate Initialize()");
|
||||
return;
|
||||
}
|
||||
|
||||
MG_Util::Debug::InitFile();
|
||||
MGLOG_I("Initializing MobileGL...");
|
||||
MG_ConfigLoader::Init();
|
||||
MGLOG_I("Config loaded");
|
||||
MG_State::Init();
|
||||
MGLOG_D("MG_State initialized");
|
||||
MG_Backend::Init();
|
||||
MGLOG_D("MG_Backend initialized");
|
||||
MG_Impl::Init();
|
||||
MGLOG_D("MG_Impl initialized");
|
||||
glslang::InitializeProcess();
|
||||
MGLOG_D("glslang initialized");
|
||||
g_isInitialized = true;
|
||||
MGLOG_I("MobileGL initialized");
|
||||
}
|
||||
|
||||
void Destroy() {
|
||||
DestroyImpl(true);
|
||||
}
|
||||
|
||||
#if defined(__linux__) || defined(__APPLE__)
|
||||
__attribute__((constructor)) static void AutoInit() {
|
||||
Initialize();
|
||||
}
|
||||
|
||||
__attribute__((destructor)) static void AutoDestroy() {
|
||||
if (MG_Config::Features.TraceSkipAutodestroy) {
|
||||
return;
|
||||
}
|
||||
#if defined(__APPLE__)
|
||||
// macOS injected dylibs can run destructors after logging/backend static state is already torn down.
|
||||
return;
|
||||
#else
|
||||
DestroyImpl(false);
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
BOOL WINAPI DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
|
||||
switch (ul_reason_for_call) {
|
||||
case DLL_PROCESS_ATTACH:
|
||||
Initialize();
|
||||
break;
|
||||
|
||||
case DLL_PROCESS_DETACH:
|
||||
Destroy();
|
||||
break;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
#endif
|
||||
} // namespace MobileGL
|
||||
@@ -1,31 +0,0 @@
|
||||
// MobileGL - MobileGL/Init.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 {
|
||||
void Initialize();
|
||||
void Destroy();
|
||||
|
||||
namespace MG_Util::Debug {
|
||||
void InitFile();
|
||||
} // namespace MG_Util::Debug
|
||||
|
||||
namespace MG_ConfigLoader {
|
||||
void Init();
|
||||
} // namespace MG_ConfigLoader
|
||||
|
||||
namespace MG_Backend {
|
||||
void Init();
|
||||
} // namespace MG_Backend
|
||||
|
||||
namespace MG_Impl {
|
||||
void Init();
|
||||
} // namespace MG_Impl
|
||||
} // namespace MobileGL
|
||||
@@ -1,495 +0,0 @@
|
||||
// MobileGL - MobileGL/MG_Backend/BackendObject.cpp
|
||||
// Copyright (c) 2025-2026 MobileGL-Dev
|
||||
// Licensed under the GNU Lesser General Public License v3.0:
|
||||
// https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
// https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
// End of Source File Header
|
||||
|
||||
#include "BackendObject.h"
|
||||
#include "MG_Util/Converters/MGToStr/TextureEnumConverter.h"
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
|
||||
namespace MobileGL::MG_Backend {
|
||||
namespace {
|
||||
Bool IsReleaseCurrentRequest(EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx) {
|
||||
(void)dpy;
|
||||
return draw == EGL_NO_SURFACE && read == EGL_NO_SURFACE && ctx == EGL_NO_CONTEXT;
|
||||
}
|
||||
|
||||
std::thread::id CurrentThreadKey() {
|
||||
return std::this_thread::get_id();
|
||||
}
|
||||
|
||||
const char* GetFormatCapabilitySupportString(const FormatCapabilityCache& cache,
|
||||
SizeT targetIndex,
|
||||
SizeT formatIndex,
|
||||
FormatCapability capability) {
|
||||
if (HasFormatCapability(cache.FullCaps[targetIndex][formatIndex], capability)) return "Full";
|
||||
if (HasFormatCapability(cache.CaveatCaps[targetIndex][formatIndex], capability)) return "Caveat";
|
||||
return "None";
|
||||
}
|
||||
|
||||
SizeT GetPrintedFormatNameWidth() {
|
||||
SizeT width = 0;
|
||||
for (SizeT formatIndex = 0; formatIndex < kFormatCapabilityFormatCount; ++formatIndex) {
|
||||
const auto format = static_cast<TextureInternalFormat>(formatIndex);
|
||||
width = std::max(width, MG_Util::ConvertTextureInternalFormatToString(format).size());
|
||||
}
|
||||
return width;
|
||||
}
|
||||
|
||||
SizeT GetCapabilityColumnWidth(FormatCapability capability) {
|
||||
SizeT width = std::strlen(GetFormatCapabilityName(capability));
|
||||
width = std::max<SizeT>(width, std::strlen("Caveat"));
|
||||
return width;
|
||||
}
|
||||
|
||||
String BuildFormatCapabilityHeader(SizeT formatNameWidth) {
|
||||
std::ostringstream line;
|
||||
line << std::left << std::setw(static_cast<Int>(formatNameWidth)) << "";
|
||||
for (FormatCapability capability : kReportedFormatCapabilities) {
|
||||
line << " | " << std::left << std::setw(static_cast<Int>(GetCapabilityColumnWidth(capability)))
|
||||
<< GetFormatCapabilityName(capability);
|
||||
}
|
||||
return line.str();
|
||||
}
|
||||
|
||||
String BuildFormatCapabilityRow(const FormatCapabilityCache& cache,
|
||||
SizeT targetIndex,
|
||||
SizeT formatIndex,
|
||||
SizeT formatNameWidth) {
|
||||
const auto format = static_cast<TextureInternalFormat>(formatIndex);
|
||||
std::ostringstream line;
|
||||
line << std::left << std::setw(static_cast<Int>(formatNameWidth))
|
||||
<< MG_Util::ConvertTextureInternalFormatToString(format);
|
||||
for (FormatCapability capability : kReportedFormatCapabilities) {
|
||||
line << " | " << std::left << std::setw(static_cast<Int>(GetCapabilityColumnWidth(capability)))
|
||||
<< GetFormatCapabilitySupportString(cache, targetIndex, formatIndex, capability);
|
||||
}
|
||||
return line.str();
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void FormatCapabilityCache::Clear() {
|
||||
for (auto& row : FullCaps) {
|
||||
row.fill(FormatCapabilityFlags{});
|
||||
}
|
||||
for (auto& row : CaveatCaps) {
|
||||
row.fill(FormatCapabilityFlags{});
|
||||
}
|
||||
for (auto& row : SampleCounts) {
|
||||
for (auto& counts : row) {
|
||||
counts.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Bool HasFormatCapability(FormatCapabilityFlags caps, FormatCapability capability) {
|
||||
return static_cast<Bool>(caps & capability);
|
||||
}
|
||||
|
||||
SizeT GetFormatCapabilityTargetIndex(TextureTarget target) {
|
||||
if (target == TextureTarget::Unknown || static_cast<Int>(target) < 0 ||
|
||||
static_cast<SizeT>(target) >= kFormatCapabilityTextureTargetCount) {
|
||||
return kFormatCapabilityTargetCount;
|
||||
}
|
||||
return static_cast<SizeT>(target);
|
||||
}
|
||||
|
||||
SizeT GetRenderbufferFormatCapabilityTargetIndex() {
|
||||
return kFormatCapabilityRenderbufferTargetIndex;
|
||||
}
|
||||
|
||||
const char* GetFormatCapabilityName(FormatCapability capability) {
|
||||
switch (capability) {
|
||||
case FormatCapability::Creatable:
|
||||
return "Creatable";
|
||||
case FormatCapability::Sampled:
|
||||
return "Sampled";
|
||||
case FormatCapability::LinearFilter:
|
||||
return "LinearFilter";
|
||||
case FormatCapability::GenerateMipmap:
|
||||
return "GenerateMipmap";
|
||||
case FormatCapability::TextureGather:
|
||||
return "TextureGather";
|
||||
case FormatCapability::TextureShadow:
|
||||
return "TextureShadow";
|
||||
case FormatCapability::FramebufferRenderable:
|
||||
return "FramebufferRenderable";
|
||||
case FormatCapability::FramebufferLayered:
|
||||
return "FramebufferLayered";
|
||||
case FormatCapability::MultisampleTexture:
|
||||
return "MultisampleTexture";
|
||||
case FormatCapability::MultisampleRenderbuffer:
|
||||
return "MultisampleRenderbuffer";
|
||||
case FormatCapability::ColorAttachment:
|
||||
return "ColorAttachment";
|
||||
case FormatCapability::DepthAttachment:
|
||||
return "DepthAttachment";
|
||||
case FormatCapability::StencilAttachment:
|
||||
return "StencilAttachment";
|
||||
case FormatCapability::TextureBuffer:
|
||||
return "TextureBuffer";
|
||||
}
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
String GetFormatCapabilityTargetName(SizeT targetIndex) {
|
||||
if (targetIndex == kFormatCapabilityRenderbufferTargetIndex) {
|
||||
return "Renderbuffer";
|
||||
}
|
||||
if (targetIndex >= kFormatCapabilityTextureTargetCount) {
|
||||
return "Unknown";
|
||||
}
|
||||
return MG_Util::ConvertTextureTargetToString(static_cast<TextureTarget>(targetIndex));
|
||||
}
|
||||
|
||||
void PrintFormatCapabilities(const FormatCapabilityCache& cache) {
|
||||
const SizeT formatNameWidth = GetPrintedFormatNameWidth();
|
||||
|
||||
MGLOG_D("Backend format capabilities:");
|
||||
for (SizeT targetIndex = 0; targetIndex < kFormatCapabilityTargetCount; ++targetIndex) {
|
||||
MGLOG_D("");
|
||||
const String targetName = GetFormatCapabilityTargetName(targetIndex);
|
||||
MGLOG_D("- %s", targetName.c_str());
|
||||
const String header = BuildFormatCapabilityHeader(formatNameWidth);
|
||||
MGLOG_D("%s", header.c_str());
|
||||
for (SizeT formatIndex = 0; formatIndex < kFormatCapabilityFormatCount; ++formatIndex) {
|
||||
const String row = BuildFormatCapabilityRow(cache, targetIndex, formatIndex, formatNameWidth);
|
||||
MGLOG_D("%s", row.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Bool BackendObject::InitializeEGLDisplay(EGLDisplay dpy, EGLint* major, EGLint* minor) {
|
||||
const std::lock_guard<std::recursive_mutex> lock(m_eglStateMutex);
|
||||
if (dpy == EGL_NO_DISPLAY) {
|
||||
MGLOG_E("InitializeEGLDisplay failed: invalid EGLDisplay");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_eglDisplayInitialized && m_eglDisplay != dpy) {
|
||||
MGLOG_E("InitializeEGLDisplay failed: backend already bound to a different EGLDisplay");
|
||||
return false;
|
||||
}
|
||||
|
||||
m_eglDisplay = dpy;
|
||||
m_eglDisplayInitialized = true;
|
||||
if (major) {
|
||||
*major = 1;
|
||||
}
|
||||
if (minor) {
|
||||
*minor = 5;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
Bool BackendObject::CreateEGLWindowSurface(EGLSurface surface, const WindowHandle& handle) {
|
||||
const std::lock_guard<std::recursive_mutex> lock(m_eglStateMutex);
|
||||
return RegisterEGLWindowSurface(surface, handle) && ActivateEGLSurface(surface);
|
||||
}
|
||||
|
||||
Bool BackendObject::ResizeEGLWindowSurface(EGLSurface surface, Uint32 width, Uint32 height) {
|
||||
const std::lock_guard<std::recursive_mutex> lock(m_eglStateMutex);
|
||||
auto surfaceIt = m_eglSurfaces.find(surface);
|
||||
if (surfaceIt == m_eglSurfaces.end() || surfaceIt->second.Kind != SurfaceKind::Window) {
|
||||
MGLOG_E("ResizeEGLWindowSurface failed: no window surface is initialized");
|
||||
return false;
|
||||
}
|
||||
surfaceIt->second.Window.Width = width;
|
||||
surfaceIt->second.Window.Height = height;
|
||||
if (m_eglSurface == surface) {
|
||||
m_windowHandle.Width = width;
|
||||
m_windowHandle.Height = height;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
Bool BackendObject::CreateEGLPbufferSurface(EGLSurface surface, EGLint width, EGLint height) {
|
||||
const std::lock_guard<std::recursive_mutex> lock(m_eglStateMutex);
|
||||
return RegisterEGLPbufferSurface(surface, width, height) && ActivateEGLSurface(surface);
|
||||
}
|
||||
|
||||
Bool BackendObject::RegisterEGLWindowSurface(EGLSurface surface, const WindowHandle& handle) {
|
||||
const std::lock_guard<std::recursive_mutex> lock(m_eglStateMutex);
|
||||
if (!m_eglDisplayInitialized) {
|
||||
MGLOG_E("RegisterEGLWindowSurface failed: EGL display is not initialized");
|
||||
return false;
|
||||
}
|
||||
if (surface == EGL_NO_SURFACE) {
|
||||
MGLOG_E("RegisterEGLWindowSurface failed: invalid EGLSurface");
|
||||
return false;
|
||||
}
|
||||
if (handle.Backend == WindowBackend::Unknown || !handle.Handle) {
|
||||
MGLOG_E("RegisterEGLWindowSurface failed: invalid native window handle");
|
||||
return false;
|
||||
}
|
||||
|
||||
auto& state = m_eglSurfaces[surface];
|
||||
state = EGLSurfaceState{
|
||||
.Kind = SurfaceKind::Window,
|
||||
.Window = handle,
|
||||
.Width = static_cast<EGLint>(std::max<Uint32>(handle.Width, 1)),
|
||||
.Height = static_cast<EGLint>(std::max<Uint32>(handle.Height, 1)),
|
||||
};
|
||||
return true;
|
||||
}
|
||||
|
||||
Bool BackendObject::RegisterEGLPbufferSurface(EGLSurface surface, EGLint width, EGLint height) {
|
||||
const std::lock_guard<std::recursive_mutex> lock(m_eglStateMutex);
|
||||
if (!m_eglDisplayInitialized) {
|
||||
MGLOG_E("RegisterEGLPbufferSurface failed: EGL display is not initialized");
|
||||
return false;
|
||||
}
|
||||
if (surface == EGL_NO_SURFACE) {
|
||||
MGLOG_E("RegisterEGLPbufferSurface failed: invalid EGLSurface");
|
||||
return false;
|
||||
}
|
||||
if (width <= 0 || height <= 0) {
|
||||
MGLOG_E("RegisterEGLPbufferSurface failed: invalid size %dx%d", width, height);
|
||||
return false;
|
||||
}
|
||||
|
||||
m_eglSurfaces[surface] = EGLSurfaceState{
|
||||
.Kind = SurfaceKind::Pbuffer,
|
||||
.Width = width,
|
||||
.Height = height,
|
||||
};
|
||||
return true;
|
||||
}
|
||||
|
||||
const BackendObject::EGLSurfaceState* BackendObject::GetRegisteredEGLSurface(EGLSurface surface) const {
|
||||
const std::lock_guard<std::recursive_mutex> lock(m_eglStateMutex);
|
||||
auto surfaceIt = m_eglSurfaces.find(surface);
|
||||
return surfaceIt == m_eglSurfaces.end() ? nullptr : &surfaceIt->second;
|
||||
}
|
||||
|
||||
Bool BackendObject::ActivateEGLSurface(EGLSurface surface) {
|
||||
const std::lock_guard<std::recursive_mutex> lock(m_eglStateMutex);
|
||||
const auto* surfaceState = GetRegisteredEGLSurface(surface);
|
||||
if (!surfaceState) {
|
||||
MGLOG_E("ActivateEGLSurface failed: EGL surface is not registered");
|
||||
return false;
|
||||
}
|
||||
if (m_eglSurfaceInitialized && m_eglSurface == surface) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (surfaceState->Kind == SurfaceKind::Window) {
|
||||
SetWindowHandle(surfaceState->Window);
|
||||
if (!InitWindowSurface()) {
|
||||
MGLOG_E("ActivateEGLSurface failed: backend InitWindowSurface failed");
|
||||
return false;
|
||||
}
|
||||
} else if (surfaceState->Kind == SurfaceKind::Pbuffer) {
|
||||
if (!InitPbufferSurface(surfaceState->Width, surfaceState->Height)) {
|
||||
MGLOG_E("ActivateEGLSurface failed: backend InitPbufferSurface failed");
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
MGLOG_E("ActivateEGLSurface failed: unsupported surface kind");
|
||||
return false;
|
||||
}
|
||||
|
||||
m_eglSurface = surface;
|
||||
m_eglSurfaceInitialized = true;
|
||||
m_eglSurfaceKind = surfaceState->Kind;
|
||||
m_eglCurrentThreads.clear();
|
||||
m_backendCapabilitiesInitialized = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
Bool BackendObject::MakeEGLCurrent(EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx) {
|
||||
const std::lock_guard<std::recursive_mutex> lock(m_eglStateMutex);
|
||||
const auto threadKey = CurrentThreadKey();
|
||||
if (IsReleaseCurrentRequest(dpy, draw, read, ctx)) {
|
||||
ReleaseEGLCurrentThread(threadKey);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!m_eglDisplayInitialized || m_eglDisplay != dpy) {
|
||||
MGLOG_E("MakeEGLCurrent failed: EGL display mismatch or not initialized");
|
||||
return false;
|
||||
}
|
||||
if (!m_eglSurfaceInitialized) {
|
||||
if (draw != read || !ActivateEGLSurface(draw)) {
|
||||
MGLOG_E("MakeEGLCurrent failed: EGL surface is not initialized");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!GetRegisteredEGLSurface(draw) || !GetRegisteredEGLSurface(read)) {
|
||||
MGLOG_E("MakeEGLCurrent failed: EGL surface is not registered");
|
||||
return false;
|
||||
}
|
||||
if (draw != read) {
|
||||
MGLOG_E("MakeEGLCurrent failed: separate draw/read surfaces are not supported");
|
||||
return false;
|
||||
}
|
||||
if (draw != m_eglSurface && !ActivateEGLSurface(draw)) {
|
||||
MGLOG_E("MakeEGLCurrent failed: EGL surface is not backed by this backend");
|
||||
return false;
|
||||
}
|
||||
if (draw == EGL_NO_SURFACE || read == EGL_NO_SURFACE || ctx == EGL_NO_CONTEXT) {
|
||||
MGLOG_E("MakeEGLCurrent failed: draw/read/context is invalid");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!m_backendCapabilitiesInitialized) {
|
||||
if (!InitCapabilities()) {
|
||||
MGLOG_E("MakeEGLCurrent failed: InitCapabilities failed");
|
||||
return false;
|
||||
}
|
||||
m_backendCapabilitiesInitialized = true;
|
||||
}
|
||||
|
||||
ReleaseEGLCurrentThread(threadKey);
|
||||
m_eglCurrentThreads[threadKey] = EGLCurrentState{
|
||||
.Display = dpy,
|
||||
.DrawSurface = draw,
|
||||
.ReadSurface = read,
|
||||
.Context = ctx,
|
||||
};
|
||||
return true;
|
||||
}
|
||||
|
||||
void BackendObject::ResetEGLRuntimeState() {
|
||||
const std::lock_guard<std::recursive_mutex> lock(m_eglStateMutex);
|
||||
m_eglSurfaceInitialized = false;
|
||||
m_backendCapabilitiesInitialized = false;
|
||||
m_eglSurfaceKind = SurfaceKind::None;
|
||||
m_eglSurface = EGL_NO_SURFACE;
|
||||
m_windowHandle = {};
|
||||
m_eglCurrentThreads.clear();
|
||||
}
|
||||
|
||||
Bool BackendObject::SwapEGLBuffers(EGLDisplay dpy, EGLSurface draw) {
|
||||
const std::lock_guard<std::recursive_mutex> lock(m_eglStateMutex);
|
||||
if (!m_eglDisplayInitialized || m_eglDisplay != dpy) {
|
||||
MGLOG_E("SwapEGLBuffers failed: EGL display mismatch or not initialized");
|
||||
return false;
|
||||
}
|
||||
const auto currentIt = m_eglCurrentThreads.find(CurrentThreadKey());
|
||||
if (currentIt == m_eglCurrentThreads.end()) {
|
||||
MGLOG_E("SwapEGLBuffers failed: no current context attached");
|
||||
return false;
|
||||
}
|
||||
if (currentIt->second.Display != dpy || currentIt->second.DrawSurface != draw ||
|
||||
currentIt->second.Context == EGL_NO_CONTEXT) {
|
||||
MGLOG_E("SwapEGLBuffers failed: draw surface is not current on this thread");
|
||||
return false;
|
||||
}
|
||||
if (!m_eglSurfaceInitialized || draw == EGL_NO_SURFACE || draw != m_eglSurface) {
|
||||
MGLOG_E("SwapEGLBuffers failed: invalid draw surface");
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto& backendFunctions = GetBackendFunctions();
|
||||
if (!backendFunctions.Present) {
|
||||
MGLOG_E("SwapEGLBuffers failed: backend Present function is null");
|
||||
return false;
|
||||
}
|
||||
|
||||
backendFunctions.Present();
|
||||
return true;
|
||||
}
|
||||
|
||||
void BackendObject::SetEGLSwapInterval(Int interval) {
|
||||
const auto& backendFunctions = GetBackendFunctions();
|
||||
if (backendFunctions.SetSwapInterval) {
|
||||
backendFunctions.SetSwapInterval(interval);
|
||||
}
|
||||
}
|
||||
|
||||
Bool BackendObject::IsEGLSurfaceCurrent(EGLSurface surface) const {
|
||||
if (surface == EGL_NO_SURFACE) {
|
||||
return false;
|
||||
}
|
||||
for (const auto& current : m_eglCurrentThreads) {
|
||||
if (current.second.DrawSurface == surface || current.second.ReadSurface == surface) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void BackendObject::DestroyPendingEGLSurfaceIfUnused(EGLSurface surface) {
|
||||
auto surfaceIt = m_eglSurfaces.find(surface);
|
||||
if (surfaceIt == m_eglSurfaces.end() || !surfaceIt->second.DestroyPending ||
|
||||
IsEGLSurfaceCurrent(surface)) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_eglSurfaces.erase(surfaceIt);
|
||||
if (m_eglSurface == surface) {
|
||||
OnEGLSurfaceReleased(surface);
|
||||
ResetEGLRuntimeState();
|
||||
}
|
||||
}
|
||||
|
||||
void BackendObject::ReleaseEGLCurrentThread(const std::thread::id& threadKey) {
|
||||
auto currentIt = m_eglCurrentThreads.find(threadKey);
|
||||
if (currentIt == m_eglCurrentThreads.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const EGLSurface drawSurface = currentIt->second.DrawSurface;
|
||||
const EGLSurface readSurface = currentIt->second.ReadSurface;
|
||||
m_eglCurrentThreads.erase(currentIt);
|
||||
DestroyPendingEGLSurfaceIfUnused(drawSurface);
|
||||
DestroyPendingEGLSurfaceIfUnused(readSurface);
|
||||
}
|
||||
|
||||
void BackendObject::ReleaseEGLSurface(EGLSurface surface) {
|
||||
const std::lock_guard<std::recursive_mutex> lock(m_eglStateMutex);
|
||||
auto surfaceIt = m_eglSurfaces.find(surface);
|
||||
if (surfaceIt == m_eglSurfaces.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (IsEGLSurfaceCurrent(surface)) {
|
||||
surfaceIt->second.DestroyPending = true;
|
||||
return;
|
||||
}
|
||||
|
||||
m_eglSurfaces.erase(surfaceIt);
|
||||
if (m_eglSurface == surface) {
|
||||
OnEGLSurfaceReleased(surface);
|
||||
ResetEGLRuntimeState();
|
||||
}
|
||||
}
|
||||
|
||||
void BackendObject::ReleaseEGLResources() {
|
||||
const std::lock_guard<std::recursive_mutex> lock(m_eglStateMutex);
|
||||
ResetEGLRuntimeState();
|
||||
m_eglSurfaces.clear();
|
||||
m_eglDisplay = EGL_NO_DISPLAY;
|
||||
m_eglDisplayInitialized = false;
|
||||
}
|
||||
|
||||
void BackendObject::SetWindowHandle(const WindowHandle& handle) {
|
||||
const std::lock_guard<std::recursive_mutex> lock(m_eglStateMutex);
|
||||
m_windowHandle = handle;
|
||||
}
|
||||
|
||||
const FormatCapabilityCache& BackendObject::GetFormatCapabilities() const {
|
||||
return m_formatCapabilities;
|
||||
}
|
||||
|
||||
FormatCapabilityCache& BackendObject::MutableFormatCapabilities() {
|
||||
return m_formatCapabilities;
|
||||
}
|
||||
|
||||
Bool BackendObject::InitPbufferSurface(EGLint width, EGLint height) {
|
||||
(void)width;
|
||||
(void)height;
|
||||
return false;
|
||||
}
|
||||
|
||||
void BackendObject::OnEGLSurfaceReleased(EGLSurface surface) {
|
||||
(void)surface;
|
||||
}
|
||||
} // namespace MobileGL::MG_Backend
|
||||
@@ -1,390 +0,0 @@
|
||||
// MobileGL - MobileGL/MG_Backend/BackendObject.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 "MG_State/GLState/TextureState/TextureEnum.h"
|
||||
|
||||
namespace MobileGL {
|
||||
namespace MG_State::GLState {
|
||||
class FramebufferObject;
|
||||
class ITextureObject;
|
||||
}
|
||||
|
||||
enum class BackendType {
|
||||
DirectGLES,
|
||||
DirectVulkan,
|
||||
BackendTypeCount,
|
||||
Unknown = -1
|
||||
};
|
||||
|
||||
namespace MG_Backend {
|
||||
enum class FormatCapability : Uint64 {
|
||||
Creatable = 1ull << 0,
|
||||
|
||||
Sampled = 1ull << 1,
|
||||
LinearFilter = 1ull << 2,
|
||||
GenerateMipmap = 1ull << 3,
|
||||
TextureGather = 1ull << 4,
|
||||
TextureShadow = 1ull << 5,
|
||||
|
||||
FramebufferRenderable = 1ull << 6,
|
||||
FramebufferLayered = 1ull << 7,
|
||||
MultisampleTexture = 1ull << 8,
|
||||
MultisampleRenderbuffer = 1ull << 9,
|
||||
|
||||
ColorAttachment = 1ull << 10,
|
||||
DepthAttachment = 1ull << 11,
|
||||
StencilAttachment = 1ull << 12,
|
||||
|
||||
TextureBuffer = 1ull << 13
|
||||
};
|
||||
|
||||
using FormatCapabilityFlags = Flags<FormatCapability>;
|
||||
|
||||
inline constexpr Array<FormatCapability, 14> kReportedFormatCapabilities = {
|
||||
FormatCapability::Creatable,
|
||||
FormatCapability::Sampled,
|
||||
FormatCapability::LinearFilter,
|
||||
FormatCapability::GenerateMipmap,
|
||||
FormatCapability::TextureGather,
|
||||
FormatCapability::TextureShadow,
|
||||
FormatCapability::FramebufferRenderable,
|
||||
FormatCapability::FramebufferLayered,
|
||||
FormatCapability::MultisampleTexture,
|
||||
FormatCapability::MultisampleRenderbuffer,
|
||||
FormatCapability::ColorAttachment,
|
||||
FormatCapability::DepthAttachment,
|
||||
FormatCapability::StencilAttachment,
|
||||
FormatCapability::TextureBuffer,
|
||||
};
|
||||
|
||||
inline constexpr SizeT kFormatCapabilityTextureTargetCount =
|
||||
static_cast<SizeT>(TextureTarget::TextureTargetCount);
|
||||
inline constexpr SizeT kFormatCapabilityRenderbufferTargetIndex = kFormatCapabilityTextureTargetCount;
|
||||
inline constexpr SizeT kFormatCapabilityTargetCount = kFormatCapabilityTextureTargetCount + 1;
|
||||
inline constexpr SizeT kFormatCapabilityFormatCount =
|
||||
static_cast<SizeT>(TextureInternalFormat::TextureInternalFormatCount);
|
||||
|
||||
using FormatCapabilityTable =
|
||||
Array<Array<FormatCapabilityFlags, kFormatCapabilityFormatCount>, kFormatCapabilityTargetCount>;
|
||||
using FormatSampleCountTable =
|
||||
Array<Array<Vector<Int>, kFormatCapabilityFormatCount>, kFormatCapabilityTargetCount>;
|
||||
|
||||
struct FormatCapabilityCache {
|
||||
FormatCapabilityTable FullCaps{};
|
||||
FormatCapabilityTable CaveatCaps{};
|
||||
FormatSampleCountTable SampleCounts{};
|
||||
|
||||
void Clear();
|
||||
};
|
||||
|
||||
Bool HasFormatCapability(FormatCapabilityFlags caps, FormatCapability capability);
|
||||
SizeT GetFormatCapabilityTargetIndex(TextureTarget target);
|
||||
SizeT GetRenderbufferFormatCapabilityTargetIndex();
|
||||
const char* GetFormatCapabilityName(FormatCapability capability);
|
||||
String GetFormatCapabilityTargetName(SizeT targetIndex);
|
||||
void PrintFormatCapabilities(const FormatCapabilityCache& cache);
|
||||
|
||||
// Opaque backend fence-sync handle, created by GLFunctionsTable::FenceSync
|
||||
// and released by GLFunctionsTable::DeleteSync.
|
||||
using BackendSyncHandle = void*;
|
||||
|
||||
// Opaque backend timer-query handle, created by
|
||||
// GLFunctionsTable::BeginTimeElapsedQuery / QueryCounterTimestamp and
|
||||
// released by GLFunctionsTable::DeleteBackendQuery.
|
||||
using BackendQueryHandle = void*;
|
||||
|
||||
struct GLFunctionsTable {
|
||||
void (*DrawArrays)(GLenum mode, GLint first, GLsizei count);
|
||||
void (*DrawElements)(GLenum mode, GLsizei count, GLenum type, const void* indices);
|
||||
void (*DrawElementsBaseVertex)(GLenum mode, GLsizei count, GLenum type, const void* indices,
|
||||
GLint basevertex);
|
||||
void (*MultiDrawArrays)(GLenum mode, const GLint* first, const GLsizei* count, GLsizei drawcount);
|
||||
void (*MultiDrawElements)(GLenum mode, const GLsizei* count, GLenum type, const GLvoid* const* indices,
|
||||
GLsizei drawcount);
|
||||
void (*MultiDrawElementsBaseVertex)(GLenum mode, const GLsizei* count, GLenum type,
|
||||
const GLvoid* const* indices, GLsizei drawcount,
|
||||
const GLint* basevertex);
|
||||
void (*MultiDrawElementsIndirect)(GLenum mode, GLenum type, const void* indirect, GLsizei drawcount,
|
||||
GLsizei stride);
|
||||
void (*MultiDrawArraysIndirect)(GLenum mode, const void* indirect, GLsizei drawcount, GLsizei stride);
|
||||
void (*MultiDrawElementsIndirectCount)(GLenum mode, GLenum type, const void* indirect,
|
||||
GLintptr drawcount, GLsizei maxdrawcount, GLsizei stride);
|
||||
void (*MultiDrawArraysIndirectCount)(GLenum mode, const void* indirect, GLintptr drawcount,
|
||||
GLsizei maxdrawcount, GLsizei stride);
|
||||
void (*DrawRangeElementsBaseVertex)(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type,
|
||||
const void* indices, GLint basevertex);
|
||||
void (*DrawRangeElements)(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type,
|
||||
const void* indices);
|
||||
void (*DrawElementsInstancedBaseVertexBaseInstance)(GLenum mode, GLsizei count, GLenum type,
|
||||
const void* indices, GLsizei instancecount,
|
||||
GLint basevertex, GLuint baseinstance);
|
||||
void (*DrawElementsInstancedBaseVertex)(GLenum mode, GLsizei count, GLenum type, const void* indices,
|
||||
GLsizei instancecount, GLint basevertex);
|
||||
void (*DrawElementsInstancedBaseInstance)(GLenum mode, GLsizei count, GLenum type, const void* indices,
|
||||
GLsizei instancecount, GLuint baseinstance);
|
||||
void (*DrawElementsInstanced)(GLenum mode, GLsizei count, GLenum type, const void* indices,
|
||||
GLsizei instancecount);
|
||||
void (*DrawArraysInstancedBaseInstance)(GLenum mode, GLint first, GLsizei count, GLsizei instancecount,
|
||||
GLuint baseinstance);
|
||||
void (*DrawArraysInstanced)(GLenum mode, GLint first, GLsizei count, GLsizei instancecount);
|
||||
void (*DrawElementsIndirect)(GLenum mode, GLenum type, const void* indirect);
|
||||
void (*DrawArraysIndirect)(GLenum mode, const void* indirect);
|
||||
void (*Clear)(GLbitfield mask);
|
||||
void (*ClearBufferfi)(GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil);
|
||||
void (*ClearBufferfv)(GLenum buffer, GLint drawbuffer, const GLfloat* value);
|
||||
void (*ClearBufferuiv)(GLenum buffer, GLint drawbuffer, const GLuint* value);
|
||||
void (*ClearBufferiv)(GLenum buffer, GLint drawbuffer, const GLint* value);
|
||||
void (*ClearNamedFramebufferfv)(const SharedPtr<MG_State::GLState::FramebufferObject>& framebuffer,
|
||||
GLenum buffer, GLint drawbuffer, const GLfloat* value);
|
||||
void (*ClearNamedFramebufferfi)(const SharedPtr<MG_State::GLState::FramebufferObject>& framebuffer,
|
||||
GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil);
|
||||
void (*BlitFramebuffer)(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0,
|
||||
GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter);
|
||||
void (*BlitNamedFramebuffer)(const SharedPtr<MG_State::GLState::FramebufferObject>& readFramebuffer,
|
||||
const SharedPtr<MG_State::GLState::FramebufferObject>& drawFramebuffer,
|
||||
GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1,
|
||||
GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1,
|
||||
GLbitfield mask, GLenum filter);
|
||||
void (*CopyTexImage2D)(GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width,
|
||||
GLsizei height, GLint border);
|
||||
void (*CopyTexSubImage2D)(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y,
|
||||
GLsizei width, GLsizei height);
|
||||
void (*CopyImageSubData)(const SharedPtr<MG_State::GLState::ITextureObject>& srcTexture,
|
||||
GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srcY, GLint srcZ,
|
||||
const SharedPtr<MG_State::GLState::ITextureObject>& dstTexture,
|
||||
GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ,
|
||||
GLsizei srcWidth, GLsizei srcHeight, GLsizei srcDepth);
|
||||
void (*GenerateMipmap)(GLenum target);
|
||||
void (*ReadPixels)(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type,
|
||||
void* pixels);
|
||||
void (*GetTexImage)(GLenum target, GLint level, GLenum format, GLenum type, GLvoid* pixels);
|
||||
void (*GetTextureImage)(const SharedPtr<MG_State::GLState::ITextureObject>& texture,
|
||||
TextureUploadTarget uploadTarget, GLint level, GLenum format, GLenum type,
|
||||
GLsizei bufSize, GLvoid* pixels);
|
||||
void (*DispatchCompute)(GLuint numGroupsX, GLuint numGroupsY, GLuint numGroupsZ);
|
||||
void (*DispatchComputeIndirect)(GLintptr indirect);
|
||||
void (*MemoryBarrier)(GLbitfield barriers);
|
||||
void (*MemoryBarrierByRegion)(GLbitfield barriers);
|
||||
void (*BindImageTexture)(GLuint unit, GLuint texture, GLint level, GLboolean layered, GLint layer,
|
||||
GLenum access, GLenum format);
|
||||
void (*GetIntegeri_v)(GLenum target, GLuint index, GLint* data);
|
||||
void (*GetInteger64i_v)(GLenum target, GLuint index, GLint64* data);
|
||||
void (*GetProgramiv)(GLuint program, GLenum pname, GLint* params);
|
||||
void (*GetProgramInterfaceiv)(GLuint program, GLenum programInterface, GLenum pname, GLint* params);
|
||||
GLuint (*GetProgramResourceIndex)(GLuint program, GLenum programInterface, const GLchar* name);
|
||||
void (*GetProgramResourceName)(GLuint program, GLenum programInterface, GLuint index, GLsizei bufSize,
|
||||
GLsizei* length, GLchar* name);
|
||||
void (*GetProgramResourceiv)(GLuint program, GLenum programInterface, GLuint index, GLsizei propCount,
|
||||
const GLenum* props, GLsizei bufSize, GLsizei* length, GLint* params);
|
||||
GLint (*GetProgramResourceLocation)(GLuint program, GLenum programInterface, const GLchar* name);
|
||||
GLint (*GetProgramResourceLocationIndex)(GLuint program, GLenum programInterface, const GLchar* name);
|
||||
void (*ShaderStorageBlockBinding)(GLuint program, GLuint storageBlockIndex, GLuint storageBlockBinding);
|
||||
// GL fence sync objects. All entries are optional (may be null); the
|
||||
// frontend then falls back to always-signaled sync semantics.
|
||||
// FenceSync may itself return null when the backend cannot create a
|
||||
// fence right now (e.g. the calling thread does not own the backend
|
||||
// context); the frontend treats such a sync as always signaled.
|
||||
BackendSyncHandle (*FenceSync)();
|
||||
GLenum (*ClientWaitSync)(BackendSyncHandle sync, GLbitfield flags, GLuint64 timeout);
|
||||
void (*WaitSync)(BackendSyncHandle sync, GLbitfield flags, GLuint64 timeout);
|
||||
void (*DeleteSync)(BackendSyncHandle sync);
|
||||
Bool (*GetSyncStatus)(BackendSyncHandle sync); // true = signaled
|
||||
// GL timer-query objects (GL_ARB_timer_query). All entries are
|
||||
// optional (may be null); the frontend then falls back to zero
|
||||
// results and reports GL_QUERY_COUNTER_BITS == 0.
|
||||
// BeginTimeElapsedQuery / QueryCounterTimestamp may themselves
|
||||
// return null when the backend cannot create a query right now;
|
||||
// the frontend treats such a query as immediately available with
|
||||
// a zero result.
|
||||
// Dynamic support check: true only when the live backend can
|
||||
// actually time at the moment of the call (extension / entry
|
||||
// points / timestamp valid bits are known then, not at table
|
||||
// init). Gates the advertised GL_QUERY_COUNTER_BITS.
|
||||
Bool (*IsTimerQuerySupported)();
|
||||
BackendQueryHandle (*BeginTimeElapsedQuery)(); // starts a TIME_ELAPSED span
|
||||
void (*EndTimeElapsedQuery)(BackendQueryHandle query); // ends the span
|
||||
BackendQueryHandle (*QueryCounterTimestamp)(); // glQueryCounter(GL_TIMESTAMP) one-shot
|
||||
Bool (*IsQueryResultAvailable)(BackendQueryHandle query); // non-blocking
|
||||
// Returns true when a final value was produced (*outNanoseconds
|
||||
// written; the frontend may cache it and release the handle).
|
||||
// Returns false when the result could not be obtained YET - e.g.
|
||||
// a Vulkan wait that refuses to block on a not-yet-submitted
|
||||
// frame serial - in which case the frontend must keep the handle
|
||||
// and leave the query readable later.
|
||||
Bool (*GetQueryResult64)(BackendQueryHandle query, Bool wait, Uint64* outNanoseconds);
|
||||
void (*DeleteBackendQuery)(BackendQueryHandle query);
|
||||
Int64 (*GetGpuTimestampNs)(); // glGetInteger64v(GL_TIMESTAMP); 0 if unsupported
|
||||
};
|
||||
struct GlobalBackendFunctionsTable {
|
||||
GLFunctionsTable GL;
|
||||
void (*Present)();
|
||||
// Optional: applies the app-requested eglSwapInterval to the native
|
||||
// presentation path (null = backend keeps its own pacing policy).
|
||||
void (*SetSwapInterval)(Int interval);
|
||||
};
|
||||
|
||||
struct DynamicBackendParameters {
|
||||
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 AliasedLineWidthRangeMax = 1.0f;
|
||||
Float SmoothLineWidthRangeMin = 1.0f;
|
||||
Float SmoothLineWidthRangeMax = 1.0f;
|
||||
Float SmoothLineWidthGranularity = 1.0f;
|
||||
Float PointSizeRangeMin = 1.0f;
|
||||
Float PointSizeRangeMax = 1.0f;
|
||||
Float PointSizeGranularity = 1.0f;
|
||||
Int Max3DTextureSize = 16384;
|
||||
Int MaxArrayTextureLayers = 2048;
|
||||
Int MaxCubeMapTextureSize = 16384;
|
||||
Int MaxFramebufferWidth = 16384;
|
||||
Int MaxFramebufferHeight = 16384;
|
||||
Int MaxFramebufferLayers = 2048;
|
||||
Int MaxRenderbufferSize = 16384;
|
||||
Int MaxTextureSize = 16384;
|
||||
Int MaxColorTextureSamples = 1;
|
||||
Int MaxDepthTextureSamples = 1;
|
||||
Int MaxFramebufferSamples = 1;
|
||||
Int MaxIntegerSamples = 1;
|
||||
Int MaxSamples = 1;
|
||||
Int MaxSampleMaskWords = 1;
|
||||
Int MaxTextureImageUnits = 32;
|
||||
Int MaxVertexTextureImageUnits = 32;
|
||||
Int MaxComputeTextureImageUnits = 32;
|
||||
Int MaxCombinedTextureImageUnits = 192;
|
||||
Int MaxVertexAttribs = 16;
|
||||
Int MaxComputeShaderStorageBlocks = 8;
|
||||
Int MaxCombinedShaderStorageBlocks = 32;
|
||||
Int MaxComputeUniformBlocks = 12;
|
||||
Int MaxComputeWorkGroupInvocations = 128;
|
||||
Int MaxShaderStorageBufferBindings = 8;
|
||||
Int MaxTextureBufferSize = 65536;
|
||||
Int MaxUniformBufferBindings = 24;
|
||||
Int MaxUniformBlockSize = 16384;
|
||||
Int MaxImageUnits = 8;
|
||||
Int MaxCombinedImageUniforms = 8;
|
||||
Int MaxVertexImageUniforms = 0;
|
||||
Int MaxGeometryImageUniforms = 0;
|
||||
Int MaxFragmentImageUniforms = 8;
|
||||
Int MaxComputeImageUniforms = 8;
|
||||
Int MaxDrawBuffers = 8;
|
||||
Int MaxColorAttachments = 8;
|
||||
Int MaxClipDistances = 8;
|
||||
Int MaxViewports = 16;
|
||||
Int MaxViewportWidth = 16384;
|
||||
Int MaxViewportHeight = 16384;
|
||||
Float ViewportBoundsRangeMin = 0.0f;
|
||||
Float ViewportBoundsRangeMax = 0.0f;
|
||||
Int ViewportSubpixelBits = 0;
|
||||
Bool SupportsWideLines = false;
|
||||
SizeT MaxShaderStorageBlockSize = 128 * 1024 * 1024;
|
||||
Uint32 SubgroupSize = 0;
|
||||
Uint32 SubgroupSupportedStages = 0;
|
||||
Uint32 SubgroupSupportedFeatures = 0;
|
||||
Bool SubgroupQuadOperationsInAllStages = false;
|
||||
};
|
||||
|
||||
enum class WindowBackend {
|
||||
Android,
|
||||
X11,
|
||||
MetalLayer,
|
||||
// TODO: Wayland, Windows, etc.
|
||||
WindowBackendCount,
|
||||
Unknown = -1
|
||||
};
|
||||
|
||||
struct WindowHandle {
|
||||
WindowBackend Backend = WindowBackend::Unknown;
|
||||
void* Handle = nullptr;
|
||||
Uint32 Width = 0;
|
||||
Uint32 Height = 0;
|
||||
};
|
||||
|
||||
class BackendObject {
|
||||
public:
|
||||
virtual ~BackendObject() = default;
|
||||
|
||||
virtual void Initialize() = 0;
|
||||
virtual Bool InitCapabilities() = 0;
|
||||
virtual Bool InitWindowSurface() = 0;
|
||||
|
||||
virtual Bool InitializeEGLDisplay(EGLDisplay dpy, EGLint* major, EGLint* minor);
|
||||
virtual Bool CreateEGLWindowSurface(EGLSurface surface, const WindowHandle& handle);
|
||||
virtual Bool ResizeEGLWindowSurface(EGLSurface surface, Uint32 width, Uint32 height);
|
||||
virtual Bool CreateEGLPbufferSurface(EGLSurface surface, EGLint width, EGLint height);
|
||||
virtual Bool MakeEGLCurrent(EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx);
|
||||
virtual Bool SwapEGLBuffers(EGLDisplay dpy, EGLSurface draw);
|
||||
// Forwards the app-requested eglSwapInterval to the backend's native
|
||||
// presentation path (no-op for backends without a SetSwapInterval hook).
|
||||
virtual void SetEGLSwapInterval(Int interval);
|
||||
virtual void ReleaseEGLSurface(EGLSurface surface);
|
||||
virtual void ReleaseEGLResources();
|
||||
|
||||
void SetWindowHandle(const WindowHandle& handle);
|
||||
|
||||
virtual const RendererInfo& GetRendererInfo() const = 0;
|
||||
virtual String GetBackendAPIVersionString() const = 0;
|
||||
virtual const GlobalBackendFunctionsTable& GetBackendFunctions() const = 0;
|
||||
virtual const DynamicBackendParameters& GetDynamicParameters() const = 0;
|
||||
const FormatCapabilityCache& GetFormatCapabilities() const;
|
||||
virtual BackendType GetBackendType() const = 0;
|
||||
|
||||
protected:
|
||||
enum class SurfaceKind {
|
||||
None,
|
||||
Window,
|
||||
Pbuffer
|
||||
};
|
||||
|
||||
struct EGLCurrentState {
|
||||
EGLDisplay Display = EGL_NO_DISPLAY;
|
||||
EGLSurface DrawSurface = EGL_NO_SURFACE;
|
||||
EGLSurface ReadSurface = EGL_NO_SURFACE;
|
||||
EGLContext Context = EGL_NO_CONTEXT;
|
||||
};
|
||||
|
||||
struct EGLSurfaceState {
|
||||
SurfaceKind Kind = SurfaceKind::None;
|
||||
Bool DestroyPending = false;
|
||||
WindowHandle Window;
|
||||
EGLint Width = 1;
|
||||
EGLint Height = 1;
|
||||
};
|
||||
|
||||
void ResetEGLRuntimeState();
|
||||
Bool RegisterEGLWindowSurface(EGLSurface surface, const WindowHandle& handle);
|
||||
Bool RegisterEGLPbufferSurface(EGLSurface surface, EGLint width, EGLint height);
|
||||
const EGLSurfaceState* GetRegisteredEGLSurface(EGLSurface surface) const;
|
||||
Bool ActivateEGLSurface(EGLSurface surface);
|
||||
virtual Bool InitPbufferSurface(EGLint width, EGLint height);
|
||||
virtual void OnEGLSurfaceReleased(EGLSurface surface);
|
||||
FormatCapabilityCache& MutableFormatCapabilities();
|
||||
|
||||
mutable std::recursive_mutex m_eglStateMutex;
|
||||
FormatCapabilityCache m_formatCapabilities;
|
||||
WindowHandle m_windowHandle;
|
||||
EGLDisplay m_eglDisplay = EGL_NO_DISPLAY;
|
||||
EGLSurface m_eglSurface = EGL_NO_SURFACE;
|
||||
Bool m_eglDisplayInitialized = false;
|
||||
Bool m_eglSurfaceInitialized = false;
|
||||
Bool m_backendCapabilitiesInitialized = false;
|
||||
SurfaceKind m_eglSurfaceKind = SurfaceKind::None;
|
||||
UnorderedMap<std::thread::id, EGLCurrentState> m_eglCurrentThreads;
|
||||
UnorderedMap<EGLSurface, EGLSurfaceState> m_eglSurfaces;
|
||||
|
||||
private:
|
||||
Bool IsEGLSurfaceCurrent(EGLSurface surface) const;
|
||||
void DestroyPendingEGLSurfaceIfUnused(EGLSurface surface);
|
||||
void ReleaseEGLCurrentThread(const std::thread::id& threadKey);
|
||||
};
|
||||
} // namespace MG_Backend
|
||||
} // namespace MobileGL
|
||||
@@ -1,18 +0,0 @@
|
||||
// MobileGL - MobileGL/MG_Backend/BackendObjects.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 "BackendObject.h"
|
||||
#include "DirectGLES/BackendObject_DirectGLES.h"
|
||||
#include "DirectVulkan/BackendObject_DirectVulkan.h"
|
||||
|
||||
namespace MobileGL::MG_Backend {
|
||||
extern UniquePtr<BackendObject> pActiveBackendObject;
|
||||
extern GlobalBackendFunctionsTable gBackendFunctionsTable;
|
||||
} // namespace MobileGL::MG_Backend
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,78 +0,0 @@
|
||||
// MobileGL - MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.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 "../BackendObject.h"
|
||||
#include <MG_Util/BackendLoaders/OpenGL/Loader.h>
|
||||
|
||||
namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// Populates the same format-capability cache used by backend startup. The caller
|
||||
// must keep the supplied GLES context current for the duration of this call.
|
||||
void PopulateFormatCapabilities(const MG_External::GLESFunctionsTable& gl,
|
||||
const MG_External::GLESCapabilities& capabilities,
|
||||
FormatCapabilityCache& cache);
|
||||
|
||||
class BackendObject_DirectGLES : public BackendObject {
|
||||
public:
|
||||
~BackendObject_DirectGLES() override;
|
||||
|
||||
void Initialize() override;
|
||||
Bool InitCapabilities() override;
|
||||
Bool InitWindowSurface() override;
|
||||
Bool InitializeEGLDisplay(EGLDisplay dpy, EGLint* major, EGLint* minor) override;
|
||||
Bool CreateEGLWindowSurface(EGLSurface surface, const WindowHandle& handle) override;
|
||||
Bool CreateEGLPbufferSurface(EGLSurface surface, EGLint width, EGLint height) override;
|
||||
Bool MakeEGLCurrent(EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx) override;
|
||||
Bool SwapEGLBuffers(EGLDisplay dpy, EGLSurface draw) override;
|
||||
void ReleaseEGLSurface(EGLSurface surface) override;
|
||||
void ReleaseEGLResources() override;
|
||||
|
||||
const RendererInfo& GetRendererInfo() const override;
|
||||
String GetBackendAPIVersionString() const override;
|
||||
const GlobalBackendFunctionsTable& GetBackendFunctions() const override;
|
||||
const DynamicBackendParameters& GetDynamicParameters() const override;
|
||||
BackendType GetBackendType() const override;
|
||||
|
||||
const MG_External::GLESFunctionsTable& GetGLESFunctions() const;
|
||||
const MG_External::EGLFunctionsTable& GetEGLFunctions() const;
|
||||
void ApplyGLESCapabilitiesForTesting(const MG_External::GLESCapabilities& capabilities);
|
||||
|
||||
private:
|
||||
void UpdateDynamicBackendParameters();
|
||||
Bool InitPbufferSurface(EGLint width, EGLint height) override;
|
||||
void OnEGLSurfaceReleased(EGLSurface surface) override;
|
||||
|
||||
Bool m_initialized = false;
|
||||
MG_External::EGLFunctionsTable m_EGLFunctions;
|
||||
MG_External::GLESFunctionsTable m_GLESFunctions;
|
||||
MG_External::GLESCapabilities m_GLESCapabilities;
|
||||
DynamicBackendParameters m_dynamicParameters;
|
||||
};
|
||||
|
||||
// Single-source-of-truth helpers shared with the driver POST
|
||||
// (MG_Util/SelfTest/DriverPost.cpp), so the identity strings and extension list
|
||||
// MobileGL reports to applications on this backend cannot drift from what the
|
||||
// POST screen shows.
|
||||
|
||||
// Static identity of the Espryt renderer (renderer/backend names, target GL/GLSL
|
||||
// versions, ExtraVendor). The Extensions vector inside is live backend state that
|
||||
// is reconciled after capability init; callers that need the advertised list for
|
||||
// a known capability set must use BuildAdvertisedExtensions instead.
|
||||
const RendererInfo& GetRendererIdentity();
|
||||
|
||||
// The full OpenGL extension list Espryt advertises (glGetString(GL_EXTENSIONS))
|
||||
// for a device whose timer queries / anisotropic filtering are (or are not) usable.
|
||||
// The MOBILEGL_DISABLE_TIMERQUERY escape hatch is applied inside.
|
||||
Vector<GLExtension> BuildAdvertisedExtensions(Bool timerQueriesSupported, Bool anisotropicFilteringSupported);
|
||||
|
||||
// Format: <OpenGL ES Renderer>, OpenGL ES <Major>.<Minor> — the exact string an
|
||||
// initialized backend returns from GetBackendAPIVersionString (and that ends up
|
||||
// inside the application-visible GL_RENDERER string).
|
||||
String FormatBackendAPIVersionString(const String& glesRendererString, Int glesMajor, Int glesMinor);
|
||||
} // namespace MobileGL::MG_Backend::DirectGLES
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,160 +0,0 @@
|
||||
// MobileGL - MobileGL/MG_Backend/DirectGLES/DirectGLES.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 <MG_Backend/BackendObject.h>
|
||||
#include <MG_State/GLState/FramebufferState/FramebufferObject.h>
|
||||
#include <MG_State/GLState/TextureState/TextureState.h>
|
||||
#include <MG_State/GLState/SamplerState/SamplerObject.h>
|
||||
#include <MG_Util/BackendLoaders/OpenGL/Loader.h>
|
||||
|
||||
#define CallAndCheck(operation) \
|
||||
MGLOG_D("Call GLES func: %s", #operation); \
|
||||
operation Utils::CheckGLESError();
|
||||
|
||||
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 ClearBufferfv(GLenum buffer, GLint drawbuffer, const GLfloat* value);
|
||||
void ClearBufferuiv(GLenum buffer, GLint drawbuffer, const GLuint* value);
|
||||
void ClearBufferiv(GLenum buffer, GLint drawbuffer, const GLint* value);
|
||||
void Clear(GLbitfield mask);
|
||||
void DrawElements(GLenum mode, GLsizei count, GLenum type, const void* indices);
|
||||
void DrawArrays(GLenum mode, GLint first, GLsizei count);
|
||||
void DrawElementsBaseVertex(GLenum mode, GLsizei count, GLenum type, const GLvoid* indices, GLint basevertex);
|
||||
void MultiDrawArrays(GLenum mode, const GLint* first, const GLsizei* count, GLsizei drawcount);
|
||||
void MultiDrawElements(GLenum mode, const GLsizei* count, GLenum type, const GLvoid* const* indices,
|
||||
GLsizei drawcount);
|
||||
void MultiDrawElementsBaseVertex(GLenum mode, const GLsizei* count, GLenum type, const GLvoid* const* indices,
|
||||
GLsizei drawcount, const GLint* basevertex);
|
||||
void MultiDrawElementsIndirect(GLenum mode, GLenum type, const void* indirect, GLsizei drawcount, GLsizei stride);
|
||||
void MultiDrawElementsIndirectCount(GLenum mode, GLenum type, const void* indirect, GLintptr drawcount,
|
||||
GLsizei maxdrawcount, GLsizei stride);
|
||||
void MultiDrawArraysIndirect(GLenum mode, const void* indirect, GLsizei drawcount, GLsizei stride);
|
||||
void DrawRangeElementsBaseVertex(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type,
|
||||
const void* indices, GLint basevertex);
|
||||
void DrawRangeElements(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const void* indices);
|
||||
void DrawElementsInstancedBaseVertexBaseInstance(GLenum mode, GLsizei count, GLenum type, const void* indices,
|
||||
GLsizei instancecount, GLint basevertex, GLuint baseinstance);
|
||||
void DrawElementsInstancedBaseVertex(GLenum mode, GLsizei count, GLenum type, const void* indices,
|
||||
GLsizei instancecount, GLint basevertex);
|
||||
void DrawElementsInstancedBaseInstance(GLenum mode, GLsizei count, GLenum type, const void* indices,
|
||||
GLsizei instancecount, GLuint baseinstance);
|
||||
void DrawElementsInstanced(GLenum mode, GLsizei count, GLenum type, const void* indices, GLsizei instancecount);
|
||||
void DrawElementsIndirect(GLenum mode, GLenum type, const void* indirect);
|
||||
void DrawArraysInstancedBaseInstance(GLenum mode, GLint first, GLsizei count, GLsizei instancecount,
|
||||
GLuint baseinstance);
|
||||
void DrawArraysInstanced(GLenum mode, GLint first, GLsizei count, GLsizei instancecount);
|
||||
void DrawArraysIndirect(GLenum mode, const void* indirect);
|
||||
void ClearNamedFramebufferfv(const SharedPtr<MG_State::GLState::FramebufferObject>& framebuffer,
|
||||
GLenum buffer, GLint drawbuffer, const GLfloat* value);
|
||||
void ClearNamedFramebufferfi(const SharedPtr<MG_State::GLState::FramebufferObject>& framebuffer,
|
||||
GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil);
|
||||
void BlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1,
|
||||
GLint dstY1, GLbitfield mask, GLenum filter);
|
||||
void BlitNamedFramebuffer(const SharedPtr<MG_State::GLState::FramebufferObject>& readFramebuffer,
|
||||
const SharedPtr<MG_State::GLState::FramebufferObject>& drawFramebuffer,
|
||||
GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1,
|
||||
GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1,
|
||||
GLbitfield mask, GLenum filter);
|
||||
void CopyTexImage2D(GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width,
|
||||
GLsizei height, GLint border);
|
||||
void CopyTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width,
|
||||
GLsizei height);
|
||||
void CopyImageSubData(const SharedPtr<MG_State::GLState::ITextureObject>& srcTexture,
|
||||
GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srcY, GLint srcZ,
|
||||
const SharedPtr<MG_State::GLState::ITextureObject>& dstTexture,
|
||||
GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ,
|
||||
GLsizei srcWidth, GLsizei srcHeight, GLsizei srcDepth);
|
||||
void GenerateMipmap(GLenum target);
|
||||
const GLubyte* GetString(GLenum name);
|
||||
void ReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void* pixels);
|
||||
void GetTexImage(GLenum target, GLint level, GLenum format, GLenum type, GLvoid* pixels);
|
||||
void DispatchCompute(GLuint numGroupsX, GLuint numGroupsY, GLuint numGroupsZ);
|
||||
void DispatchComputeIndirect(GLintptr indirect);
|
||||
void MemoryBarrier(GLbitfield barriers);
|
||||
void MemoryBarrierByRegion(GLbitfield barriers);
|
||||
void BindImageTexture(GLuint unit, GLuint texture, GLint level, GLboolean layered, GLint layer, GLenum access,
|
||||
GLenum format);
|
||||
void GetIntegeri_v(GLenum target, GLuint index, GLint* data);
|
||||
void GetInteger64i_v(GLenum target, GLuint index, GLint64* data);
|
||||
void GetProgramiv(GLuint program, GLenum pname, GLint* params);
|
||||
void GetProgramInterfaceiv(GLuint program, GLenum programInterface, GLenum pname, GLint* params);
|
||||
GLuint GetProgramResourceIndex(GLuint program, GLenum programInterface, const GLchar* name);
|
||||
void GetProgramResourceName(GLuint program, GLenum programInterface, GLuint index, GLsizei bufSize, GLsizei* length,
|
||||
GLchar* name);
|
||||
void GetProgramResourceiv(GLuint program, GLenum programInterface, GLuint index, GLsizei propCount,
|
||||
const GLenum* props, GLsizei bufSize, GLsizei* length, GLint* params);
|
||||
GLint GetProgramResourceLocation(GLuint program, GLenum programInterface, const GLchar* name);
|
||||
GLint GetProgramResourceLocationIndex(GLuint program, GLenum programInterface, const GLchar* name);
|
||||
void ShaderStorageBlockBinding(GLuint program, GLuint storageBlockIndex, GLuint storageBlockBinding);
|
||||
Bool InitWindowSurface(NativeWindowType window);
|
||||
Bool InitPbufferSurface(EGLint width, EGLint height);
|
||||
Bool MakeCurrent();
|
||||
Bool ReleaseCurrent();
|
||||
// True when the backend ES context is current on the calling thread, i.e.
|
||||
// immediate buffer ops may issue GL calls right now.
|
||||
Bool IsBackendContextCurrentOnThisThread();
|
||||
// GL fence sync objects, backed by native ES fences. FenceSync returns null
|
||||
// (the frontend then falls back to an always-signaled sync) when the calling
|
||||
// thread does not own the ES context. Waits/queries degrade to "signaled" in
|
||||
// the same situation, and handles created under a since-destroyed ES context
|
||||
// are always treated as signaled.
|
||||
BackendSyncHandle FenceSync();
|
||||
GLenum ClientWaitSync(BackendSyncHandle sync, GLbitfield flags, GLuint64 timeout);
|
||||
void WaitSync(BackendSyncHandle sync, GLbitfield flags, GLuint64 timeout);
|
||||
void DeleteSync(BackendSyncHandle sync);
|
||||
Bool GetSyncStatus(BackendSyncHandle sync);
|
||||
// True when GL_EXT_disjoint_timer_query and every entry point the timer
|
||||
// hooks below need are present. Also gates the E_GL_ARB_timer_query
|
||||
// advertisement in BackendObject_DirectGLES::InitCapabilities, and is
|
||||
// registered as the GLFunctionsTable::IsTimerQuerySupported hook: a pure
|
||||
// capability read needs no current ES context, and it stays false until
|
||||
// the ES capabilities have been filled in.
|
||||
Bool AreTimerQueriesSupported();
|
||||
// GL timer-query objects, backed by GL_EXT_disjoint_timer_query. The
|
||||
// creators return null (the frontend then falls back to an immediately
|
||||
// available zero result) when the calling thread does not own the ES
|
||||
// context or the extension/entry points are missing, and handles created
|
||||
// under a since-destroyed ES context are always treated as complete with
|
||||
// a zero result (mirrors the fence-sync handles above).
|
||||
BackendQueryHandle BeginTimeElapsedQuery();
|
||||
void EndTimeElapsedQuery(BackendQueryHandle query);
|
||||
BackendQueryHandle QueryCounterTimestamp();
|
||||
Bool IsQueryResultAvailable(BackendQueryHandle query);
|
||||
// Returns true when a final value landed in *outNanoseconds (a zero for
|
||||
// null or stale-generation handles IS final: the frontend may cache it
|
||||
// and release the handle). Returns false only when the calling thread
|
||||
// does not own the ES context, so the value is genuinely unobtainable
|
||||
// right now; the handle stays alive and readable later.
|
||||
Bool GetQueryResult64(BackendQueryHandle query, Bool wait, Uint64* outNanoseconds);
|
||||
void DeleteBackendQuery(BackendQueryHandle query);
|
||||
Int64 GetGpuTimestampNs();
|
||||
void Present();
|
||||
// Frame-completion watermarks for the buffer-storage pool: CurrentFrameSerial()
|
||||
// is bumped once per Present(); CompletedFrameSerial() is the newest frame whose
|
||||
// GPU work has provably finished (advanced by polling a one-fence-per-frame ring).
|
||||
// A buffer retired during frame N is safe to recycle once CompletedFrameSerial() >= N.
|
||||
Uint64 CurrentFrameSerial();
|
||||
Uint64 CompletedFrameSerial();
|
||||
// Applies (or defers until the window surface exists) the app-requested
|
||||
// eglSwapInterval on the native EGL surface.
|
||||
void SetSwapInterval(Int interval);
|
||||
void SetEGLFuncsTable(const MG_External::EGLFunctionsTable& eglFuncs);
|
||||
void SetGLESFuncsTable(const MG_External::GLESFunctionsTable& glesFuncs);
|
||||
void SetGLESCapabilities(const MG_External::GLESCapabilities& capabilities);
|
||||
void DestroyEGLContext();
|
||||
|
||||
extern MG_External::EGLFunctionsTable g_EGLFuncs;
|
||||
extern MG_External::GLESFunctionsTable g_GLESFuncs;
|
||||
extern MG_External::GLESCapabilities g_GLESCapabilities;
|
||||
} // namespace MobileGL::MG_Backend::DirectGLES
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,561 +0,0 @@
|
||||
// MobileGL - MobileGL/MG_Backend/DirectGLES/Managers.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 <atomic>
|
||||
#include <mutex>
|
||||
#include "DirectGLES.h"
|
||||
#include "MG_State/GLState/SamplerState/SamplerObject.h"
|
||||
#include "MG_State/GLState/TextureState/TextureEnum.h"
|
||||
#include <MG_State/GLState/TextureState/TextureObject.h>
|
||||
#include <MG_State/GLState/Core.h>
|
||||
#include <MG_Util/Converters/MGToGL/TextureEnumConverter.h>
|
||||
|
||||
namespace MobileGL::MG_Backend::DirectGLES {
|
||||
String EmulateBaseInstanceInVertexShader(String source, GLenum shaderType);
|
||||
String PromoteDrawParameterGlobalsToUniforms(String source, GLenum shaderType);
|
||||
|
||||
template <typename StateObject, typename BackendObject>
|
||||
class StateBackendObjectRegistry {
|
||||
public:
|
||||
using StatePtr = SharedPtr<StateObject>;
|
||||
using StateWeakPtr = std::weak_ptr<StateObject>;
|
||||
using BackendPtr = SharedPtr<BackendObject>;
|
||||
using BackendMap = UnorderedMap<StateObject*, BackendPtr>;
|
||||
using StateRefMap = UnorderedMap<StateObject*, StateWeakPtr>;
|
||||
using iterator = typename BackendMap::iterator;
|
||||
using const_iterator = typename BackendMap::const_iterator;
|
||||
|
||||
BackendPtr& GetOrCreate(const StatePtr& stateObj) {
|
||||
MOBILEGL_ASSERT(stateObj != nullptr, "State object must not be null");
|
||||
|
||||
auto* key = stateObj.get();
|
||||
auto trackedStateIt = m_stateRefs.find(key);
|
||||
if (trackedStateIt != m_stateRefs.end() && trackedStateIt->second.expired()) {
|
||||
EraseByKey(key);
|
||||
}
|
||||
m_stateRefs[key] = stateObj;
|
||||
return m_backendObjects[key];
|
||||
}
|
||||
|
||||
iterator find(StateObject* stateObj) {
|
||||
if (!IsAlive(stateObj)) {
|
||||
EraseByKey(stateObj);
|
||||
return m_backendObjects.end();
|
||||
}
|
||||
return m_backendObjects.find(stateObj);
|
||||
}
|
||||
|
||||
const_iterator find(StateObject* stateObj) const {
|
||||
return const_cast<StateBackendObjectRegistry*>(this)->find(stateObj);
|
||||
}
|
||||
|
||||
iterator begin() { return m_backendObjects.begin(); }
|
||||
const_iterator begin() const { return m_backendObjects.begin(); }
|
||||
iterator end() { return m_backendObjects.end(); }
|
||||
const_iterator end() const { return m_backendObjects.end(); }
|
||||
|
||||
void CollectGarbageIfNeeded() {
|
||||
++m_gcTick;
|
||||
if (m_gcTick < kGCInterval) {
|
||||
return;
|
||||
}
|
||||
CollectGarbage();
|
||||
m_gcTick = 0;
|
||||
}
|
||||
|
||||
void CollectGarbageNow() { CollectGarbage(); }
|
||||
|
||||
private:
|
||||
bool IsAlive(StateObject* stateObj) const {
|
||||
const auto trackedStateIt = m_stateRefs.find(stateObj);
|
||||
if (trackedStateIt == m_stateRefs.end()) {
|
||||
return false;
|
||||
}
|
||||
return !trackedStateIt->second.expired();
|
||||
}
|
||||
|
||||
void EraseByKey(StateObject* stateObj) {
|
||||
m_stateRefs.erase(stateObj);
|
||||
m_backendObjects.erase(stateObj);
|
||||
}
|
||||
|
||||
void CollectGarbage() {
|
||||
if (m_isCollecting) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_isCollecting = true;
|
||||
|
||||
Vector<StateObject*> staleKeys;
|
||||
staleKeys.reserve(m_stateRefs.size());
|
||||
for (const auto& [stateKey, stateWeakRef] : m_stateRefs) {
|
||||
if (stateWeakRef.expired()) {
|
||||
staleKeys.push_back(stateKey);
|
||||
}
|
||||
}
|
||||
|
||||
for (auto* stateKey : staleKeys) {
|
||||
m_stateRefs.erase(stateKey);
|
||||
m_backendObjects.erase(stateKey);
|
||||
}
|
||||
|
||||
m_isCollecting = false;
|
||||
}
|
||||
|
||||
private:
|
||||
static constexpr Uint32 kGCInterval = 1024;
|
||||
StateRefMap m_stateRefs;
|
||||
BackendMap m_backendObjects;
|
||||
Uint32 m_gcTick = 0;
|
||||
Bool m_isCollecting = false;
|
||||
};
|
||||
|
||||
namespace BufferImpl {
|
||||
const GLenum TempBufferTarget = GL_ARRAY_BUFFER;
|
||||
|
||||
// The DirectGLES storage behind one frontend buffer. Owned (refcounted) by
|
||||
// the frontend BufferObject; immediate BufferBackendOps keep it current, so
|
||||
// draw-time "sync" reduces to ensuring the storage exists.
|
||||
class GLESBufferResource : public MG_State::GLState::BackendBufferResource {
|
||||
public:
|
||||
~GLESBufferResource() override = default;
|
||||
|
||||
Uint id = 0;
|
||||
SizeT storageSize = 0;
|
||||
Bool storageInitialized = false;
|
||||
// ES context generation this resource's id belongs to; ids from a
|
||||
// destroyed context are invalid and must not be deleted or reused.
|
||||
Uint contextGeneration = 0;
|
||||
// Frontend change serial the backend storage reflects. When immediate
|
||||
// ops cannot run (ops unregistered, no current context), this lags and
|
||||
// EnsureBufferResource falls back to a full re-upload. Atomic: read on
|
||||
// the context-owning thread while ops on other threads may update it.
|
||||
std::atomic<Uint64> syncedChangeSerial{0};
|
||||
// Ops that arrived while no ES context was current on the calling thread
|
||||
// (or before storage existed); replayed by EnsureBufferResource. The ES
|
||||
// context migrates between app threads, so deferring ops can race with
|
||||
// the owning thread replaying them: guard both fields with pendingMutex.
|
||||
Bool pendingRespecify = false;
|
||||
VecRange1D pendingRanges;
|
||||
std::mutex pendingMutex;
|
||||
// Zero-copy coherent persistent map (EXT_buffer_storage): the GL store is
|
||||
// immutable, persistently+coherently mapped, and persistentPtr is what the app
|
||||
// (and the frontend PipeResource) write into directly. While set, draw-time
|
||||
// sync is a no-op and no per-draw glBufferSubData is issued. Cleared on ES
|
||||
// context loss.
|
||||
Bool persistentMapped = false;
|
||||
void* persistentPtr = nullptr;
|
||||
};
|
||||
|
||||
// Registered as the frontend's BufferBackendOps at backend init and on
|
||||
// every MakeCurrent (the ES context can be destroyed and recreated, e.g.
|
||||
// by the trace replayer's probe context).
|
||||
void RegisterBufferBackendOps();
|
||||
void UnregisterBufferBackendOps();
|
||||
// The ES context died: unregister ops, invalidate all outstanding GL ids
|
||||
// (they belonged to the dead context) and drop deferred deletes.
|
||||
void OnBackendContextDestroyed();
|
||||
|
||||
// Get-or-create the backend resource and bring its storage up to date
|
||||
// (creates the GL buffer, replays pending ops, pushes persistent-mapped
|
||||
// ranges). Requires the ES context to be current. Returns nullptr only
|
||||
// for null input.
|
||||
GLESBufferResource* EnsureBufferResource(const SharedPtr<MG_State::GLState::BufferObject>& bufferObject);
|
||||
// Existing resource or nullptr; performs no GL calls.
|
||||
GLESBufferResource* GetBufferResource(MG_State::GLState::BufferObject* bufferObject);
|
||||
|
||||
// Deletes GL buffers whose owning frontend objects died (possibly on a
|
||||
// thread without a current ES context). Called from draw-time sync.
|
||||
void ProcessDeferredBufferReleases();
|
||||
|
||||
// glBindBuffer with a redundant-bind cache for GL_ARRAY_BUFFER.
|
||||
void BindBufferId(GLenum target, Uint id);
|
||||
void InvalidateArrayBufferBindingCache();
|
||||
// Redundant-bind cache for INDEXED buffer bindings (glBindBufferBase/Range on
|
||||
// GL_UNIFORM_BUFFER / GL_SHADER_STORAGE_BUFFER): skips the GL call when the
|
||||
// (id, range) already at that index matches, like the array-buffer/texture/
|
||||
// sampler caches already do. Invalidated on MakeCurrent (context may reset).
|
||||
void BindBufferBaseCached(GLenum glTarget, Uint index, Uint id);
|
||||
void BindBufferRangeCached(GLenum glTarget, Uint index, Uint id, GLintptr offset, GLsizeiptr size);
|
||||
void InvalidateIndexedBufferBindingCache();
|
||||
// Buffer-storage pool maintenance. TrimBufferPool evicts over-budget entries
|
||||
// (called once per frame from Present); ClearBufferPool drops all pooled ids
|
||||
// without glDeleteBuffers (called when the ES context is going away).
|
||||
void TrimBufferPool();
|
||||
void ClearBufferPool();
|
||||
|
||||
// --- Global-UBO ring ------------------------------------------------------
|
||||
// One persistently+coherently mapped buffer (EXT_buffer_storage) shared by
|
||||
// every program's lowered default-uniform block. Each content change is
|
||||
// bump-allocated into a fresh slot and bound with glBindBufferRange, so the
|
||||
// CPU never rewrites bytes the GPU may still be reading — the per-draw
|
||||
// glBufferSubData into one static UBO forced Adreno to resolve that
|
||||
// write-after-read hazard on every uniform-dirtying draw (MC dirties
|
||||
// uniforms every draw). Reclamation rides the Present() frame-fence
|
||||
// watermark; no ring bytes are recycled before their frame's GPU work
|
||||
// completed.
|
||||
//
|
||||
// A program's cached slot, reusable within one frame while the frontend UBO
|
||||
// content version is unchanged. Cross-frame reuse is intentionally not
|
||||
// attempted: later same-frame allocations may recycle bytes of completed
|
||||
// frames, so re-referencing them would need per-bind pinning — rewriting
|
||||
// GetUBOSize() bytes once per program per frame is far cheaper.
|
||||
struct UboRingAllocation {
|
||||
Uint32 contentVersion = ~0u; // frontend UBO content version held at `offset`
|
||||
Uint32 ringGeneration = 0; // ring identity the slot lives in (0 = never valid)
|
||||
Uint64 frameSerial = ~Uint64{0}; // frame the slot was written in
|
||||
SizeT offset = 0;
|
||||
};
|
||||
// False when the feature is disabled, EXT_buffer_storage / fences are
|
||||
// missing, the ES context is not current, or ring creation already failed
|
||||
// under this context (callers then take the legacy glBufferSubData path).
|
||||
Bool UboRingAvailable();
|
||||
// Bump-allocate `size` bytes aligned to GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT.
|
||||
// Grows the ring (new GL store, generation bump) when the in-flight span
|
||||
// would be overrun. Returns false when storage (re)creation fails.
|
||||
Bool UboRingAllocate(SizeT size, SizeT& outOffset);
|
||||
void* UboRingMappedPtr();
|
||||
Uint UboRingBufferId();
|
||||
Uint32 UboRingGeneration();
|
||||
// Present()-time upkeep: records the frame's high-water mark for reclamation
|
||||
// and deletes grown-away ring stores once the GPU is done with them.
|
||||
void UboRingOnPresent();
|
||||
} // namespace BufferImpl
|
||||
|
||||
namespace VertexArrayImpl {
|
||||
class BackendVertexArrayObject {
|
||||
public:
|
||||
BackendVertexArrayObject();
|
||||
~BackendVertexArrayObject();
|
||||
void SyncToBackend(const SharedPtr<MG_State::GLState::VertexArrayObject>& stateVAOObject);
|
||||
void SyncClientSideAttributesForDrawArrays(
|
||||
const SharedPtr<MG_State::GLState::VertexArrayObject>& stateVAOObject, GLint first, GLsizei count);
|
||||
Uint GetBackendVertexArrayId() const { return m_backendVAOId; }
|
||||
void Bind() const;
|
||||
|
||||
private:
|
||||
Uint m_backendVAOId = 0;
|
||||
Array<Uint, MG_State::GLState::VertexArrayObject::MAX_VERTEX_ATTRIBS> m_clientAttributeBufferIds;
|
||||
Bool m_isInitialized = false;
|
||||
Uint16 m_syncedIndexBufferVersion = 0;
|
||||
Array<MG_State::GLState::VertexAttributeVersion, MG_State::GLState::VertexArrayObject::MAX_VERTEX_ATTRIBS>
|
||||
m_syncedAttributeVersions;
|
||||
};
|
||||
|
||||
extern StateBackendObjectRegistry<MG_State::GLState::VertexArrayObject, BackendVertexArrayObject>
|
||||
g_backendVertexArrayObjects;
|
||||
} // namespace VertexArrayImpl
|
||||
|
||||
namespace TextureImpl {
|
||||
inline Bool IsSupportedTextureTarget(TextureTarget target) {
|
||||
// Rectangle textures need non-normalized sampling ES cannot express; everything else is
|
||||
// either native or emulated (1D -> 2D with height 1, 1D array -> 2D array, see
|
||||
// MapToBackendTextureTarget). SPIRV-Cross already emits the matching ESSL samplers and
|
||||
// coordinate padding for 1D/1D-array shaders.
|
||||
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) {
|
||||
return target == TextureTarget::Texture2DMultisample ||
|
||||
target == TextureTarget::Texture2DMultisampleArray;
|
||||
}
|
||||
|
||||
inline Bool SupportsWrapR(TextureTarget target) {
|
||||
return target == TextureTarget::Texture3D || target == TextureTarget::TextureCubeMap;
|
||||
}
|
||||
|
||||
struct StateTextureBasicInfo { // Used for tracking texture state changes
|
||||
TextureInternalFormat internalFormat = TextureInternalFormat::Unknown;
|
||||
SizeT width = 0;
|
||||
SizeT height = 0;
|
||||
SizeT depth = 0;
|
||||
SizeT mipmapLevels = 0;
|
||||
Uint bufferExternalIndex = 0;
|
||||
Int samples = 0;
|
||||
Bool fixedSampleLocations = true;
|
||||
|
||||
bool operator==(const StateTextureBasicInfo& other) const {
|
||||
return internalFormat == other.internalFormat && width == other.width && height == other.height &&
|
||||
depth == other.depth && mipmapLevels == other.mipmapLevels &&
|
||||
bufferExternalIndex == other.bufferExternalIndex && samples == other.samples &&
|
||||
fixedSampleLocations == other.fixedSampleLocations;
|
||||
}
|
||||
|
||||
bool operator!=(const StateTextureBasicInfo& other) const { return !(*this == other); }
|
||||
};
|
||||
|
||||
inline const Uint TempTextureUnit = 0;
|
||||
class BackendTextureObject {
|
||||
public:
|
||||
BackendTextureObject();
|
||||
void SyncMipmapsToBackend(const SharedPtr<MG_State::GLState::ITextureObject>& stateTextureObject);
|
||||
void SyncBuiltinSamplerToBackend(const SharedPtr<MG_State::GLState::ITextureObject>& stateTextureObject);
|
||||
void SyncTextureParamsToBackend(const SharedPtr<MG_State::GLState::ITextureObject>& stateTextureObject);
|
||||
void RequireImageBindableStorage();
|
||||
void Bind(GLenum target, Uint unit = TempTextureUnit);
|
||||
Uint GetBackendTextureId() const;
|
||||
|
||||
private:
|
||||
void RecreateBackendTexture();
|
||||
|
||||
Uint m_backendTextureId = 0;
|
||||
Bool m_isInitialized = false;
|
||||
Bool m_imageBindableStorageRequired = false;
|
||||
Bool m_backendStorageImmutable = false;
|
||||
StateTextureBasicInfo m_prevTextureInfo;
|
||||
SamplerParameters m_cacheSamplerParameters;
|
||||
UintVec2 m_cacheLodRange = {0, 1000};
|
||||
FloatVec4 m_cacheBorderColor = {0.0f, 0.0f, 0.0f, 0.0f};
|
||||
Vec4<TextureSwizzleParam> m_cacheSwizzleParams = {TextureSwizzleParam::Red, TextureSwizzleParam::Green,
|
||||
TextureSwizzleParam::Blue, TextureSwizzleParam::Alpha};
|
||||
Uint16 m_syncedSamplerVersion = 0;
|
||||
Uint16 m_syncedTextureParamsVersion = 0;
|
||||
};
|
||||
|
||||
void ActivateTextureUnit(Uint unit);
|
||||
void UnbindTexture(Uint unit, GLenum target);
|
||||
extern StateBackendObjectRegistry<MG_State::GLState::ITextureObject, BackendTextureObject>
|
||||
g_backendTextureObjects;
|
||||
SharedPtr<BackendTextureObject>& SyncTextureObjectToBackend(
|
||||
const SharedPtr<MG_State::GLState::ITextureObject>& textureObject,
|
||||
Bool imageBindableStorageRequired = false);
|
||||
extern Array<Array<BackendTextureObject*, (SizeT)TextureTarget::TextureTargetCount>,
|
||||
MG_State::GLState::TextureState::MAX_TEXTURE_IMAGE_UNITS>
|
||||
g_boundTexturesCache;
|
||||
extern Uint g_activeTextureUnit;
|
||||
} // namespace TextureImpl
|
||||
|
||||
namespace FramebufferImpl {
|
||||
class BackendFramebufferObject {
|
||||
public:
|
||||
BackendFramebufferObject();
|
||||
void SyncToBackend(const SharedPtr<MG_State::GLState::FramebufferObject>& stateFBOObject,
|
||||
FramebufferTarget asTarget);
|
||||
void InvalidateSyncedState();
|
||||
Uint GetBackendFramebufferId() const { return m_backendFBOId; }
|
||||
void Bind(FramebufferTarget target) const;
|
||||
// FramebufferAttachmentType GetCompactedAttachmentTypeAtDrawBufferIndex(Int index);
|
||||
GLenum GetBackendAttachmentType(FramebufferAttachmentType frontendAtt) const;
|
||||
|
||||
private:
|
||||
Uint m_backendFBOId = 0;
|
||||
|
||||
/* this will save buffers in its original form,
|
||||
reversion, absence or not consecutive are all allowed, as long as GL spec allows it
|
||||
i.e. it could be like [COLOR_ATTACHMENT0, COLOR_ATTACHMENT5, NONE, COLOR_ATTACHMENT4]
|
||||
Probably useful to re-link shader output according to this.
|
||||
aka. realizing `glBindFragDataLocation`
|
||||
*/
|
||||
FramebufferAttachmentType m_frontendDrawBuffers[MG_State::GLState::FramebufferObject::MAX_DRAW_BUFFERS] = {
|
||||
FramebufferAttachmentType::None};
|
||||
/* this will save buffers in stricter ES rules
|
||||
reversion, absence or not consecutive are not allowed, according to ES spec
|
||||
i.e. it could be like [COLOR_ATTACHMENT0, COLOR_ATTACHMENT1, NONE, COLOR_ATTACHMENT3, ...]
|
||||
this array could be provided as data directly to ES `glDrawBuffers` function
|
||||
*/
|
||||
GLenum m_backendDrawBuffers[MG_State::GLState::FramebufferObject::MAX_DRAW_BUFFERS] = {GL_NONE};
|
||||
FramebufferAttachmentType m_frontendReadBuffer = FramebufferAttachmentType::Color0;
|
||||
GLenum m_backendReadBuffer = GL_COLOR_ATTACHMENT0;
|
||||
|
||||
using FramebufferObject = MG_State::GLState::FramebufferObject;
|
||||
FramebufferObject::FramebufferAttachmentVersionArray m_syncedFrontendAttachmentVersions = {0};
|
||||
};
|
||||
|
||||
extern StateBackendObjectRegistry<MG_State::GLState::FramebufferObject, BackendFramebufferObject>
|
||||
g_backendFramebufferObjects;
|
||||
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
|
||||
|
||||
// Image uniforms take their unit from the layout(binding=N) qualifier baked into
|
||||
// the transpiled ESSL; unlike samplers they must not (and in ES cannot) be
|
||||
// assigned through glUniform1i.
|
||||
inline Bool IsImageUniformType(GLenum type) {
|
||||
switch (type) {
|
||||
case 0x904D: /*GL_IMAGE_2D*/
|
||||
case 0x904E: /*GL_IMAGE_3D*/
|
||||
case 0x9050: /*GL_IMAGE_CUBE*/
|
||||
case 0x9051: /*GL_IMAGE_BUFFER*/
|
||||
case 0x9053: /*GL_IMAGE_2D_ARRAY*/
|
||||
case 0x9058: /*GL_INT_IMAGE_2D*/
|
||||
case 0x9059: /*GL_INT_IMAGE_3D*/
|
||||
case 0x905B: /*GL_INT_IMAGE_CUBE*/
|
||||
case 0x905C: /*GL_INT_IMAGE_BUFFER*/
|
||||
case 0x905E: /*GL_INT_IMAGE_2D_ARRAY*/
|
||||
case 0x9063: /*GL_UNSIGNED_INT_IMAGE_2D*/
|
||||
case 0x9064: /*GL_UNSIGNED_INT_IMAGE_3D*/
|
||||
case 0x9066: /*GL_UNSIGNED_INT_IMAGE_CUBE*/
|
||||
case 0x9067: /*GL_UNSIGNED_INT_IMAGE_BUFFER*/
|
||||
case 0x9069: /*GL_UNSIGNED_INT_IMAGE_2D_ARRAY*/
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
namespace PrgramImpl {
|
||||
class BackendProgramObjectImpl {
|
||||
public:
|
||||
// Per-link cache of a sampler-style uniform's backend location: built once in
|
||||
// SyncToBackend so draws stop issuing glGetUniformLocation string queries.
|
||||
// lastAssignedUnit mirrors the program-state value set through glUniform1i
|
||||
// (program state persists across binds, so caching per program is exact).
|
||||
struct SamplerUniformBinding {
|
||||
Uint frontendLocation = 0;
|
||||
Int backendLocation = -1;
|
||||
GLenum uniformType = 0;
|
||||
Int lastAssignedUnit = -1;
|
||||
};
|
||||
|
||||
BackendProgramObjectImpl();
|
||||
~BackendProgramObjectImpl();
|
||||
void SyncToBackend(const SharedPtr<MG_State::GLState::ProgramObject>& stateProgramObject);
|
||||
void Use() const;
|
||||
void SetBaseInstance(Uint32 baseInstance) const;
|
||||
void SetBaseInstanceWordIndex(Int32 wordIndex) const;
|
||||
void SetDrawID(Uint32 drawId) const;
|
||||
Int GetIndirectParamsBinding() const { return m_indirectParamsBinding; }
|
||||
Uint GetBackendProgramId() const { return m_backendProgramId; }
|
||||
Uint GetBackendGlobalUBOId() const { return m_backendGlobalUBOId; }
|
||||
Uint32 GetSnormFallbackClampOutputMask() const { return m_snormFallbackClampOutputMask; }
|
||||
Uint32 GetUnormFallbackClampOutputMask() const { return m_unormFallbackClampOutputMask; }
|
||||
|
||||
Bool HasGlobalUboBlock() const { return m_globalUboBackendBlockIndex >= 0; }
|
||||
const Vector<Int>& GetUniformBlockBackendIndices() const { return m_uniformBlockBackendIndices; }
|
||||
Vector<SamplerUniformBinding>& GetSamplerUniformBindings() { return m_samplerUniformBindings; }
|
||||
Uint32 GetLastUploadedGlobalUboVersion() const { return m_lastUploadedGlobalUboVersion; }
|
||||
void SetLastUploadedGlobalUboVersion(Uint32 version) { m_lastUploadedGlobalUboVersion = version; }
|
||||
// Backend-reported GL_UNIFORM_BLOCK_DATA_SIZE of the global block; ring
|
||||
// bindings must span at least this much (may exceed the frontend's
|
||||
// reflected size when the transpiled block pads differently).
|
||||
Int GetGlobalUboBackendBlockSize() const { return m_globalUboBackendBlockSize; }
|
||||
BufferImpl::UboRingAllocation& GetGlobalUboRingAllocation() { return m_globalUboRingAllocation; }
|
||||
// Frontend link version this backend program (and its resource caches) was
|
||||
// built from; a mismatch means every link-derived cache here is stale.
|
||||
Uint32 GetSyncedLinkVersion() const { return m_syncedLinkVersion; }
|
||||
|
||||
private:
|
||||
void CacheResourceLocations(const SharedPtr<MG_State::GLState::ProgramObject>& stateProgramObject);
|
||||
|
||||
Uint m_backendProgramId = 0;
|
||||
Uint m_backendGlobalUBOId = 0;
|
||||
Int m_baseInstanceUniformLocation = -1;
|
||||
Int m_drawIdUniformLocation = -1;
|
||||
Int m_baseInstanceWordIndexUniformLocation = -1;
|
||||
Int m_indirectParamsBinding = -1;
|
||||
Uint32 m_snormFallbackClampOutputMask = 0;
|
||||
Uint32 m_unormFallbackClampOutputMask = 0;
|
||||
Bool m_isInitialized = false;
|
||||
|
||||
Int m_globalUboBackendBlockIndex = -1;
|
||||
Int m_globalUboBackendBlockSize = 0;
|
||||
Vector<Int> m_uniformBlockBackendIndices; // frontend block index -> backend index (-1 = absent)
|
||||
Vector<SamplerUniformBinding> m_samplerUniformBindings;
|
||||
Uint32 m_lastUploadedGlobalUboVersion = ~0u;
|
||||
BufferImpl::UboRingAllocation m_globalUboRingAllocation;
|
||||
Uint32 m_syncedLinkVersion = ~0u;
|
||||
};
|
||||
|
||||
extern Uint32 g_snormFallbackClampOutputMask;
|
||||
extern Uint32 g_unormFallbackClampOutputMask;
|
||||
// Backend id of the last glUseProgram issued through this backend; lets Use()
|
||||
// skip redundant rebinds. Reset to 0 wherever glUseProgram(0) is issued or the
|
||||
// ES context is recreated.
|
||||
extern Uint g_lastUsedBackendProgramId;
|
||||
extern StateBackendObjectRegistry<MG_State::GLState::ProgramObject, BackendProgramObjectImpl>
|
||||
g_backendProgramObjects;
|
||||
} // namespace PrgramImpl
|
||||
|
||||
namespace SamplerImpl {
|
||||
class BackendSamplerObject {
|
||||
public:
|
||||
BackendSamplerObject();
|
||||
void SyncToBackend(const SharedPtr<MG_State::GLState::SamplerObject>& stateSamplerObject);
|
||||
void Bind(Uint unit);
|
||||
Uint GetBackendSamplerId() const;
|
||||
|
||||
private:
|
||||
Uint m_backendSamplerId = 0;
|
||||
Bool m_isInitialized = false;
|
||||
SamplerParameters m_cacheSamplerParameters;
|
||||
Uint16 m_syncedSamplerVersion = 0;
|
||||
};
|
||||
|
||||
void UnbindSampler(Uint unit);
|
||||
|
||||
extern Array<BackendSamplerObject*, MG_State::GLState::TextureState::MAX_TEXTURE_IMAGE_UNITS>
|
||||
g_boundSamplersCache;
|
||||
extern StateBackendObjectRegistry<MG_State::GLState::SamplerObject, BackendSamplerObject>
|
||||
g_backendSamplerObjects;
|
||||
} // namespace SamplerImpl
|
||||
|
||||
namespace RenderbufferImpl {
|
||||
class BackendRenderbufferObject {
|
||||
public:
|
||||
BackendRenderbufferObject();
|
||||
void SyncToBackend(const SharedPtr<MG_State::GLState::RenderbufferObject>& stateRBOObject);
|
||||
Uint GetBackendRenderbufferId() const { return m_backendRBOId; }
|
||||
void Bind() const;
|
||||
|
||||
private:
|
||||
Uint m_backendRBOId = 0;
|
||||
Bool m_isInitialized = false;
|
||||
TextureInternalFormat m_cacheInternalFormat = TextureInternalFormat::Unknown;
|
||||
Int m_cacheWidth = 0;
|
||||
Int m_cacheHeight = 0;
|
||||
Int m_cacheSamples = 0;
|
||||
};
|
||||
|
||||
extern StateBackendObjectRegistry<MG_State::GLState::RenderbufferObject, BackendRenderbufferObject>
|
||||
g_backendRenderbufferObjects;
|
||||
} // namespace RenderbufferImpl
|
||||
} // namespace MobileGL::MG_Backend::DirectGLES
|
||||
@@ -1,768 +0,0 @@
|
||||
// MobileGL - MobileGL/MG_Backend/DirectGLES/Utils.cpp
|
||||
// Copyright (c) 2025-2026 MobileGL-Dev
|
||||
// Licensed under the GNU Lesser General Public License v3.0:
|
||||
// https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
// https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
// End of Source File Header
|
||||
|
||||
#include "DirectGLES.h"
|
||||
#include "Utils.h"
|
||||
#include "Managers.h"
|
||||
#include "MG_Backend/BackendObjects.h"
|
||||
#include "MG_Util/Converters/GLToMG/FramebufferEnumConverter.h"
|
||||
#include "MG_Util/Texture/TextureFormatProcessor.h"
|
||||
|
||||
#include <MG_State/GLState/Core.h>
|
||||
#include <MG_Util/BackendLoaders/OpenGL/Loader.h>
|
||||
#include <MG_Util/Converters/GLToStr/GLEnumConverter.h>
|
||||
#include <MG_Util/Converters/MGToGL/TextureEnumConverter.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 {
|
||||
Flags<PixelFormatNormalizeOptionBit> GetForcedPixelFormatNormalizeOptions() {
|
||||
Flags<PixelFormatNormalizeOptionBit> options;
|
||||
if (g_GLESCapabilities.IsAngleRenderer) {
|
||||
options |= PixelFormatNormalizeOptionBit::NoRgb16;
|
||||
options |= PixelFormatNormalizeOptionBit::NoSnorm16;
|
||||
options |= PixelFormatNormalizeOptionBit::NoSnorm8;
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
Flags<PixelFormatNormalizeOptionBit> GetDriverPixelFormatNormalizeOptions() {
|
||||
Flags<PixelFormatNormalizeOptionBit> options = PixelFormatNormalizeOptionBit::NoDepthComponent32;
|
||||
options |= PixelFormatNormalizeOptionBit::NoRGBA8Snorm;
|
||||
options |= PixelFormatNormalizeOptionBit::NoRGB16Snorm;
|
||||
if (!g_GLESCapabilities.SupportsNorm16Texture) {
|
||||
options |= PixelFormatNormalizeOptionBit::NoNorm16;
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
Flags<PixelFormatNormalizeOptionBit> GetRuntimeFallbackNormalizeOptions(GLenum requestedInternalFormat) {
|
||||
using namespace MG_Util::TextureFormatProcessor;
|
||||
const Flags<PixelFormatNormalizeOptionBit> forcedOptions =
|
||||
GetApplicablePixelFormatNormalizeOptions(requestedInternalFormat, GetForcedPixelFormatNormalizeOptions());
|
||||
if (forcedOptions) {
|
||||
return forcedOptions;
|
||||
}
|
||||
return GetApplicablePixelFormatNormalizeOptions(requestedInternalFormat,
|
||||
GetDriverPixelFormatNormalizeOptions());
|
||||
}
|
||||
|
||||
Bool HasCachedFormatCapability(TextureInternalFormat internalFormat,
|
||||
SizeT targetIndex,
|
||||
Bool caveat,
|
||||
FormatCapability capability) {
|
||||
if (!pActiveBackendObject || targetIndex >= kFormatCapabilityTargetCount) {
|
||||
return false;
|
||||
}
|
||||
const SizeT formatIndex = static_cast<SizeT>(internalFormat);
|
||||
if (formatIndex >= kFormatCapabilityFormatCount) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const FormatCapabilityCache& cache = pActiveBackendObject->GetFormatCapabilities();
|
||||
const FormatCapabilityFlags caps =
|
||||
caveat ? cache.CaveatCaps[targetIndex][formatIndex] : cache.FullCaps[targetIndex][formatIndex];
|
||||
return HasFormatCapability(caps, capability);
|
||||
}
|
||||
|
||||
Bool HasAnyCachedFormatCapability(TextureInternalFormat internalFormat,
|
||||
Bool caveat,
|
||||
FormatCapability capability) {
|
||||
for (SizeT targetIndex = 0; targetIndex < kFormatCapabilityTargetCount; ++targetIndex) {
|
||||
if (HasCachedFormatCapability(internalFormat, targetIndex, caveat, capability)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Bool ShouldUseCaveatFormat(TextureInternalFormat internalFormat, SizeT targetIndex) {
|
||||
if (targetIndex < kFormatCapabilityTargetCount) {
|
||||
const Bool fullCreatable =
|
||||
HasCachedFormatCapability(internalFormat, targetIndex, false, FormatCapability::Creatable);
|
||||
const Bool caveatCreatable =
|
||||
HasCachedFormatCapability(internalFormat, targetIndex, true, FormatCapability::Creatable);
|
||||
const Bool fullRenderable =
|
||||
HasCachedFormatCapability(internalFormat, targetIndex, false, FormatCapability::FramebufferRenderable);
|
||||
const Bool caveatRenderable =
|
||||
HasCachedFormatCapability(internalFormat, targetIndex, true, FormatCapability::FramebufferRenderable);
|
||||
return (!fullCreatable && caveatCreatable) || (!fullRenderable && caveatRenderable);
|
||||
}
|
||||
|
||||
if (HasAnyCachedFormatCapability(internalFormat, false, FormatCapability::Creatable)) {
|
||||
return false;
|
||||
}
|
||||
return HasAnyCachedFormatCapability(internalFormat, true, FormatCapability::Creatable);
|
||||
}
|
||||
|
||||
void GenerateFormatInfo(TextureInternalFormat internalFormat,
|
||||
SizeT targetIndex,
|
||||
GLenum* outInternalFormat,
|
||||
GLenum* outFormat,
|
||||
GLenum* outType) {
|
||||
using namespace MobileGL::MG_Util::TextureFormatProcessor;
|
||||
const GLenum requestedInternalFormat = MG_Util::ConvertTextureInternalFormatToGLEnum(internalFormat);
|
||||
Flags<PixelFormatNormalizeOptionBit> options;
|
||||
if (!pActiveBackendObject || ShouldUseCaveatFormat(internalFormat, targetIndex)) {
|
||||
options = GetRuntimeFallbackNormalizeOptions(requestedInternalFormat);
|
||||
}
|
||||
NormalizePixelFormat(requestedInternalFormat, options, outInternalFormat, outFormat, outType);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace TextureImpl {
|
||||
void GenerateTextureFormatInfo(TextureInternalFormat internalFormat, GLenum* outInternalFormat,
|
||||
GLenum* outFormat, GLenum* outType, TextureTarget target) {
|
||||
#ifdef TRACY_ENABLE
|
||||
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
||||
#endif
|
||||
const SizeT targetIndex =
|
||||
target == TextureTarget::Unknown ? kFormatCapabilityTargetCount : GetFormatCapabilityTargetIndex(target);
|
||||
GenerateFormatInfo(internalFormat, targetIndex, outInternalFormat, outFormat, outType);
|
||||
}
|
||||
|
||||
void GenerateRenderbufferFormatInfo(TextureInternalFormat internalFormat, GLenum* outInternalFormat,
|
||||
GLenum* outFormat, GLenum* outType) {
|
||||
#ifdef TRACY_ENABLE
|
||||
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
||||
#endif
|
||||
GenerateFormatInfo(internalFormat, GetRenderbufferFormatCapabilityTargetIndex(), outInternalFormat,
|
||||
outFormat, outType);
|
||||
}
|
||||
|
||||
Bool ShouldUseCaveatTextureFormat(TextureInternalFormat internalFormat, TextureTarget target) {
|
||||
const SizeT targetIndex =
|
||||
target == TextureTarget::Unknown ? kFormatCapabilityTargetCount : GetFormatCapabilityTargetIndex(target);
|
||||
return ShouldUseCaveatFormat(internalFormat, targetIndex);
|
||||
}
|
||||
|
||||
Bool ShouldUseCaveatRenderbufferFormat(TextureInternalFormat internalFormat) {
|
||||
return ShouldUseCaveatFormat(internalFormat, GetRenderbufferFormatCapabilityTargetIndex());
|
||||
}
|
||||
} // namespace TextureImpl
|
||||
namespace PrgramImpl {
|
||||
String ProcessOutColorLocations(const String& glslCode) {
|
||||
#ifdef TRACY_ENABLE
|
||||
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
||||
#endif
|
||||
const static std::regex pattern(R"(\n(out highp vec4 outColor)(\d+);)");
|
||||
const String replacement = "\nlayout(location=$2) $1$2;";
|
||||
return std::regex_replace(glslCode, pattern, replacement);
|
||||
}
|
||||
|
||||
String ForceSupporterOutput(const String& glslCode) {
|
||||
#ifdef TRACY_ENABLE
|
||||
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
||||
#endif
|
||||
Bool hasPrecisionFloat =
|
||||
glslCode.find("precision ") != String::npos && glslCode.find("float;") != String::npos;
|
||||
Bool hasPrecisionInt = glslCode.find("precision ") != String::npos && glslCode.find("int;") != String::npos;
|
||||
|
||||
String result = glslCode;
|
||||
String precisionFloat;
|
||||
String precisionInt;
|
||||
|
||||
if (hasPrecisionFloat && hasPrecisionInt) {
|
||||
std::istringstream iss(result);
|
||||
std::vector<String> lines;
|
||||
String line;
|
||||
while (std::getline(iss, line)) {
|
||||
Bool isPrecisionLine = (line.find("precision ") != String::npos) &&
|
||||
(line.find("float;") != String::npos || line.find("int;") != String::npos);
|
||||
if (!isPrecisionLine) {
|
||||
lines.push_back(line);
|
||||
}
|
||||
}
|
||||
result.clear();
|
||||
for (SizeT i = 0; i < lines.size(); ++i) {
|
||||
if (i != 0) result += '\n';
|
||||
result += lines[i];
|
||||
}
|
||||
precisionFloat = "precision highp float;\n";
|
||||
precisionInt = "precision highp int;\n";
|
||||
} else {
|
||||
precisionFloat = hasPrecisionFloat ? "" : "precision highp float;\n";
|
||||
precisionInt = hasPrecisionInt ? "" : "precision highp int;\n";
|
||||
}
|
||||
|
||||
SizeT lastExtensionPos = result.rfind("#extension");
|
||||
SizeT insertionPos = 0;
|
||||
|
||||
if (lastExtensionPos != String::npos) {
|
||||
SizeT nextNewline = result.find('\n', lastExtensionPos);
|
||||
if (nextNewline != String::npos) {
|
||||
insertionPos = nextNewline + 1;
|
||||
} else {
|
||||
insertionPos = result.length();
|
||||
}
|
||||
} else {
|
||||
SizeT firstNewline = result.find('\n');
|
||||
if (firstNewline != String::npos) {
|
||||
insertionPos = firstNewline + 1;
|
||||
} else {
|
||||
result = precisionFloat + precisionInt + result;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
result.insert(insertionPos, precisionFloat + precisionInt);
|
||||
return result;
|
||||
}
|
||||
|
||||
String ClampNormFallbackOutputs(String glslCode, GLenum shaderType, Uint32 snormOutputMask,
|
||||
Uint32 unormOutputMask) {
|
||||
#ifdef TRACY_ENABLE
|
||||
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
||||
#endif
|
||||
const Uint32 outputMask = snormOutputMask | unormOutputMask;
|
||||
if (shaderType != GL_FRAGMENT_SHADER || outputMask == 0) {
|
||||
return glslCode;
|
||||
}
|
||||
|
||||
const std::regex outputPattern(
|
||||
R"(layout\s*\(\s*location\s*=\s*([0-9]+)\s*\)\s*out\s+(?:(?:lowp|mediump|highp)\s+)?vec4\s+([A-Za-z_][A-Za-z0-9_]*)\s*;)");
|
||||
std::sregex_iterator outputIt(glslCode.begin(), glslCode.end(), outputPattern);
|
||||
std::sregex_iterator outputEnd;
|
||||
struct OutputClamp {
|
||||
String Name;
|
||||
Bool Signed;
|
||||
};
|
||||
Vector<OutputClamp> outputClamps;
|
||||
for (; outputIt != outputEnd; ++outputIt) {
|
||||
const Uint location = static_cast<Uint>(std::stoul((*outputIt)[1].str()));
|
||||
if (location < 32 && (outputMask & (1u << location))) {
|
||||
outputClamps.push_back({(*outputIt)[2].str(), static_cast<Bool>(snormOutputMask & (1u << location))});
|
||||
}
|
||||
}
|
||||
if (outputClamps.empty()) {
|
||||
return glslCode;
|
||||
}
|
||||
|
||||
const std::regex mainPattern(R"(void\s+main\s*\([^)]*\)\s*\{)");
|
||||
std::smatch mainMatch;
|
||||
if (!std::regex_search(glslCode, mainMatch, mainPattern)) {
|
||||
return glslCode;
|
||||
}
|
||||
|
||||
SizeT bracePos = static_cast<SizeT>(mainMatch.position(0) + mainMatch.length(0) - 1);
|
||||
Int depth = 0;
|
||||
for (SizeT pos = bracePos; pos < glslCode.size(); ++pos) {
|
||||
if (glslCode[pos] == '{') {
|
||||
++depth;
|
||||
} else if (glslCode[pos] == '}') {
|
||||
--depth;
|
||||
if (depth == 0) {
|
||||
String clampLine;
|
||||
for (const OutputClamp& outputClamp : outputClamps) {
|
||||
const String minValue = outputClamp.Signed ? "-1.0" : "0.0";
|
||||
clampLine += "\n " + outputClamp.Name + " = clamp(" + outputClamp.Name +
|
||||
", vec4(" + minValue + "), vec4(1.0));";
|
||||
}
|
||||
clampLine += "\n";
|
||||
glslCode.insert(pos, clampLine);
|
||||
return glslCode;
|
||||
}
|
||||
}
|
||||
}
|
||||
return glslCode;
|
||||
}
|
||||
|
||||
String ForceFlatIntegerVaryings(const String& glslCode, GLenum shaderType) {
|
||||
#ifdef TRACY_ENABLE
|
||||
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
||||
#endif
|
||||
String result = glslCode;
|
||||
const String integerType = R"((?:(?:lowp|mediump|highp)\s+)?(?:u?int|[iu]vec[234])\b)";
|
||||
|
||||
auto addFlatQualifier = [&result, &integerType](const String& qualifier) {
|
||||
const std::regex pattern("(layout\\s*\\([^)]*\\)\\s*)(?!(?:flat|smooth|noperspective)\\s)(" +
|
||||
qualifier + "\\s+" + integerType + ")");
|
||||
result = std::regex_replace(result, pattern, "$1flat $2");
|
||||
};
|
||||
|
||||
switch (shaderType) {
|
||||
case GL_VERTEX_SHADER:
|
||||
addFlatQualifier("out");
|
||||
break;
|
||||
case GL_GEOMETRY_SHADER:
|
||||
addFlatQualifier("in");
|
||||
addFlatQualifier("out");
|
||||
break;
|
||||
case GL_FRAGMENT_SHADER:
|
||||
addFlatQualifier("in");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
String RemoveLayoutBinding(const String& glslCode) {
|
||||
#ifdef TRACY_ENABLE
|
||||
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
||||
#endif
|
||||
// Sampler and uniform-block bindings are re-established at draw time through the
|
||||
// API, so their layout qualifiers are stripped (they may exceed ES limits). SSBO
|
||||
// blocks and image uniforms are different: ES has no glShaderStorageBlockBinding,
|
||||
// and image units cannot be set with glUniform1i, so for those declarations the
|
||||
// binding qualifier is the only binding mechanism and must be preserved.
|
||||
static std::regex bindingRegex(R"(layout\s*\(\s*binding\s*=\s*\d+\s*\)\s*)");
|
||||
static std::regex bindingRegex2(R"(layout\s*\(\s*binding\s*=\s*\d+\s*,)");
|
||||
static std::regex keepBindingRegex(R"(\b(buffer|[iu]?image[A-Za-z0-9]*)\b)");
|
||||
|
||||
String result;
|
||||
result.reserve(glslCode.size());
|
||||
SizeT lineStart = 0;
|
||||
while (lineStart <= glslCode.size()) {
|
||||
SizeT lineEnd = glslCode.find('\n', lineStart);
|
||||
const Bool lastLine = lineEnd == String::npos;
|
||||
String line = glslCode.substr(lineStart, lastLine ? String::npos : lineEnd - lineStart);
|
||||
|
||||
if (!std::regex_search(line, keepBindingRegex)) {
|
||||
line = std::regex_replace(line, bindingRegex, "");
|
||||
line = std::regex_replace(line, bindingRegex2, "layout(");
|
||||
}
|
||||
|
||||
result += line;
|
||||
if (lastLine) {
|
||||
break;
|
||||
}
|
||||
result += '\n';
|
||||
lineStart = lineEnd + 1;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
} // namespace PrgramImpl
|
||||
|
||||
namespace Utils {
|
||||
void CheckGLESError() {
|
||||
#ifdef TRACY_ENABLE
|
||||
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
||||
#endif
|
||||
for (GLenum err = g_GLESFuncs.glGetError(); err != GL_NO_ERROR; err = g_GLESFuncs.glGetError()) {
|
||||
MGLOG_E("-> GLES Error: %s", MG_Util::ConvertGLEnumToString(err).c_str());
|
||||
}
|
||||
}
|
||||
|
||||
GLenum GetBindingQuery(GLenum target, bool isTexture) {
|
||||
#ifdef TRACY_ENABLE
|
||||
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
||||
#endif
|
||||
switch (target) {
|
||||
case GL_TEXTURE_BUFFER:
|
||||
return isTexture ? GL_TEXTURE_BINDING_BUFFER : GL_TEXTURE_BUFFER_BINDING;
|
||||
|
||||
case GL_ARRAY_BUFFER:
|
||||
return GL_ARRAY_BUFFER_BINDING;
|
||||
case GL_ATOMIC_COUNTER_BUFFER:
|
||||
return GL_ATOMIC_COUNTER_BUFFER_BINDING;
|
||||
case GL_COPY_READ_BUFFER:
|
||||
return GL_COPY_READ_BUFFER_BINDING;
|
||||
case GL_COPY_WRITE_BUFFER:
|
||||
return GL_COPY_WRITE_BUFFER_BINDING;
|
||||
case GL_DISPATCH_INDIRECT_BUFFER:
|
||||
return GL_DISPATCH_INDIRECT_BUFFER_BINDING;
|
||||
case GL_DRAW_INDIRECT_BUFFER:
|
||||
return GL_DRAW_INDIRECT_BUFFER_BINDING;
|
||||
case GL_ELEMENT_ARRAY_BUFFER:
|
||||
return GL_ELEMENT_ARRAY_BUFFER_BINDING;
|
||||
case GL_PIXEL_PACK_BUFFER:
|
||||
return GL_PIXEL_PACK_BUFFER_BINDING;
|
||||
case GL_PIXEL_UNPACK_BUFFER:
|
||||
return GL_PIXEL_UNPACK_BUFFER_BINDING;
|
||||
case GL_QUERY_BUFFER:
|
||||
return GL_QUERY_BUFFER_BINDING;
|
||||
case GL_SHADER_STORAGE_BUFFER:
|
||||
return GL_SHADER_STORAGE_BUFFER_BINDING;
|
||||
case GL_TRANSFORM_FEEDBACK_BUFFER:
|
||||
return GL_TRANSFORM_FEEDBACK_BUFFER_BINDING;
|
||||
case GL_UNIFORM_BUFFER:
|
||||
return GL_UNIFORM_BUFFER_BINDING;
|
||||
|
||||
case GL_FRAMEBUFFER:
|
||||
case GL_DRAW_FRAMEBUFFER:
|
||||
return GL_DRAW_FRAMEBUFFER_BINDING;
|
||||
case GL_READ_FRAMEBUFFER:
|
||||
return GL_READ_FRAMEBUFFER_BINDING;
|
||||
|
||||
case GL_RENDERBUFFER:
|
||||
return GL_RENDERBUFFER_BINDING;
|
||||
|
||||
case GL_VERTEX_ARRAY:
|
||||
case GL_VERTEX_ARRAY_BINDING:
|
||||
return GL_VERTEX_ARRAY_BINDING;
|
||||
|
||||
case GL_PROGRAM_PIPELINE:
|
||||
return GL_PROGRAM_PIPELINE_BINDING;
|
||||
|
||||
case GL_PROGRAM:
|
||||
return GL_CURRENT_PROGRAM;
|
||||
|
||||
case GL_SAMPLER:
|
||||
return GL_SAMPLER_BINDING;
|
||||
|
||||
case GL_TEXTURE:
|
||||
return GL_TEXTURE_BINDING_2D;
|
||||
case GL_TEXTURE_1D:
|
||||
return GL_TEXTURE_BINDING_1D;
|
||||
case GL_TEXTURE_1D_ARRAY:
|
||||
return GL_TEXTURE_BINDING_1D_ARRAY;
|
||||
case GL_TEXTURE_2D:
|
||||
return GL_TEXTURE_BINDING_2D;
|
||||
case GL_TEXTURE_2D_ARRAY:
|
||||
return GL_TEXTURE_BINDING_2D_ARRAY;
|
||||
case GL_TEXTURE_2D_MULTISAMPLE:
|
||||
return GL_TEXTURE_BINDING_2D_MULTISAMPLE;
|
||||
case GL_TEXTURE_2D_MULTISAMPLE_ARRAY:
|
||||
return GL_TEXTURE_BINDING_2D_MULTISAMPLE_ARRAY;
|
||||
case GL_TEXTURE_3D:
|
||||
return GL_TEXTURE_BINDING_3D;
|
||||
case GL_TEXTURE_CUBE_MAP:
|
||||
return GL_TEXTURE_BINDING_CUBE_MAP;
|
||||
case GL_TEXTURE_CUBE_MAP_ARRAY:
|
||||
return GL_TEXTURE_BINDING_CUBE_MAP_ARRAY;
|
||||
case GL_TEXTURE_RECTANGLE:
|
||||
return GL_TEXTURE_BINDING_RECTANGLE;
|
||||
|
||||
case GL_TRANSFORM_FEEDBACK:
|
||||
return GL_TRANSFORM_FEEDBACK_BINDING;
|
||||
|
||||
case GL_SAMPLES_PASSED:
|
||||
return GL_SAMPLES_PASSED;
|
||||
case GL_PRIMITIVES_GENERATED:
|
||||
return GL_PRIMITIVES_GENERATED;
|
||||
|
||||
case GL_DEBUG_OUTPUT:
|
||||
return GL_DEBUG_OUTPUT;
|
||||
case GL_DEBUG_OUTPUT_SYNCHRONOUS:
|
||||
return GL_DEBUG_OUTPUT_SYNCHRONOUS;
|
||||
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
} // 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
|
||||
@@ -1,106 +0,0 @@
|
||||
// MobileGL - MobileGL/MG_Backend/DirectGLES/Utils.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 <MG_State/GLState/Core.h>
|
||||
|
||||
namespace MobileGL::MG_Backend::DirectGLES {
|
||||
namespace DebugImpl {
|
||||
class ErrorLopper {
|
||||
public:
|
||||
static void Loop(const std::function<void(GLenum)>&);
|
||||
static void Clear();
|
||||
ErrorLopper();
|
||||
~ErrorLopper();
|
||||
};
|
||||
|
||||
class OpenGLScopeMarker {
|
||||
public:
|
||||
explicit OpenGLScopeMarker(const String& scopeName);
|
||||
~OpenGLScopeMarker();
|
||||
};
|
||||
} // namespace DebugImpl
|
||||
|
||||
namespace BufferImpl {} // namespace BufferImpl
|
||||
|
||||
namespace VertexArrayImpl {
|
||||
GLenum GetBindingQuery(GLenum target, bool isTexture);
|
||||
} // namespace VertexArrayImpl
|
||||
|
||||
namespace TextureImpl {
|
||||
void GenerateTextureFormatInfo(TextureInternalFormat internalFormat, GLenum* outInternalFormat,
|
||||
GLenum* outFormat, GLenum* outType,
|
||||
TextureTarget target = TextureTarget::Unknown);
|
||||
void GenerateRenderbufferFormatInfo(TextureInternalFormat internalFormat, GLenum* outInternalFormat,
|
||||
GLenum* outFormat, GLenum* outType);
|
||||
Bool ShouldUseCaveatTextureFormat(TextureInternalFormat internalFormat, TextureTarget target);
|
||||
Bool ShouldUseCaveatRenderbufferFormat(TextureInternalFormat internalFormat);
|
||||
} // namespace TextureImpl
|
||||
|
||||
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 {
|
||||
String ProcessOutColorLocations(const String& glslCode);
|
||||
String ForceSupporterOutput(const String& glslCode);
|
||||
String ClampNormFallbackOutputs(String glslCode, GLenum shaderType, Uint32 snormOutputMask,
|
||||
Uint32 unormOutputMask);
|
||||
String ForceFlatIntegerVaryings(const String& glslCode, GLenum shaderType);
|
||||
String RemoveLayoutBinding(const String& glslCode);
|
||||
} // namespace PrgramImpl
|
||||
|
||||
namespace Utils {
|
||||
void CheckGLESError();
|
||||
GLenum GetBindingQuery(GLenum target, bool isTexture);
|
||||
} // namespace Utils
|
||||
} // namespace MobileGL::MG_Backend::DirectGLES
|
||||
@@ -1,798 +0,0 @@
|
||||
// MobileGL - MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.cpp
|
||||
// Copyright (c) 2025-2026 MobileGL-Dev
|
||||
// Licensed under the GNU Lesser General Public License v3.0:
|
||||
// https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
// https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
// End of Source File Header
|
||||
|
||||
#include "BackendObject_DirectVulkan.h"
|
||||
#include "MG_Backend/BackendObject.h"
|
||||
#include "DirectVulkan.h"
|
||||
#include "MG_State/GLState/FramebufferState/FramebufferObject.h"
|
||||
#include "MG_State/GLState/TextureState/TextureState.h"
|
||||
#include "MG_Util/Classifiers/TextureEnumClassifier.h"
|
||||
#include "MG_Util/Converters/MGToGL/TextureEnumConverter.h"
|
||||
#include "MG_Util/Converters/MGToStr/TextureEnumConverter.h"
|
||||
#include "MG_Util/Converters/MGToVk/TextureEnumConverter.h"
|
||||
#include "MG_Util/Texture/TextureFormatProcessor.h"
|
||||
|
||||
#include <Config.h>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
namespace {
|
||||
Bool IsR11G11B10FFallbackEnabled() {
|
||||
return MG_Config::Features.MagmaR11G11B10FFallback;
|
||||
}
|
||||
|
||||
Bool IsReleaseCurrentRequest(EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx) {
|
||||
(void)dpy;
|
||||
return draw == EGL_NO_SURFACE && read == EGL_NO_SURFACE && ctx == EGL_NO_CONTEXT;
|
||||
}
|
||||
|
||||
Bool IsFormatIndexValid(TextureInternalFormat format) {
|
||||
return format != TextureInternalFormat::Unknown && static_cast<Int>(format) >= 0 &&
|
||||
static_cast<SizeT>(format) < kFormatCapabilityFormatCount;
|
||||
}
|
||||
|
||||
Bool IsLayeredTarget(TextureTarget target) {
|
||||
return target == TextureTarget::Texture3D || target == TextureTarget::Texture1DArray ||
|
||||
target == TextureTarget::Texture2DArray || target == TextureTarget::TextureCubeMap ||
|
||||
target == TextureTarget::TextureCubeMapArray ||
|
||||
target == TextureTarget::Texture2DMultisampleArray;
|
||||
}
|
||||
|
||||
Bool IsMultisampleTarget(TextureTarget target) {
|
||||
return target == TextureTarget::Texture2DMultisample ||
|
||||
target == TextureTarget::Texture2DMultisampleArray;
|
||||
}
|
||||
|
||||
Bool IsTextureBufferTarget(TextureTarget target) {
|
||||
return target == TextureTarget::TextureBuffer;
|
||||
}
|
||||
|
||||
Bool IsIntegerInternalFormat(TextureInternalFormat format) {
|
||||
const GLenum glFormat = MG_Util::ConvertTextureInternalFormatToGLEnum(format);
|
||||
GLenum normalizedInternalFormat = glFormat;
|
||||
GLenum imageFormat = GL_RGBA;
|
||||
GLenum imageType = GL_UNSIGNED_BYTE;
|
||||
MG_Util::TextureFormatProcessor::NormalizePixelFormat(
|
||||
glFormat, PixelFormatNormalizeOptionBit::None, &normalizedInternalFormat, &imageFormat, &imageType);
|
||||
return imageFormat == GL_RED_INTEGER || imageFormat == GL_RG_INTEGER || imageFormat == GL_RGB_INTEGER ||
|
||||
imageFormat == GL_RGBA_INTEGER;
|
||||
}
|
||||
|
||||
FormatCapabilityFlags GetAttachmentCaps(TextureInternalFormat format) {
|
||||
FormatCapabilityFlags caps = FormatCapability::FramebufferRenderable;
|
||||
const Bool isDepth = MG_Util::IsDepthFormatInternalFormat(format);
|
||||
const Bool isStencil = MG_Util::IsStencilFormatInternalFormat(format);
|
||||
if (!isDepth && !isStencil) {
|
||||
caps |= FormatCapability::ColorAttachment;
|
||||
}
|
||||
if (isDepth) {
|
||||
caps |= FormatCapability::DepthAttachment;
|
||||
}
|
||||
if (isStencil) {
|
||||
caps |= FormatCapability::StencilAttachment;
|
||||
}
|
||||
return caps;
|
||||
}
|
||||
|
||||
FormatCapabilityFlags BuildVulkanCaps(TextureInternalFormat logicalFormat,
|
||||
TextureTarget target,
|
||||
VkFormatFeatureFlags features) {
|
||||
FormatCapabilityFlags caps;
|
||||
const Bool isDepth = MG_Util::IsDepthFormatInternalFormat(logicalFormat);
|
||||
const Bool isStencil = MG_Util::IsStencilFormatInternalFormat(logicalFormat);
|
||||
const Bool isInteger = IsIntegerInternalFormat(logicalFormat);
|
||||
|
||||
if (IsTextureBufferTarget(target)) {
|
||||
if ((features & VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT) != 0) {
|
||||
caps |= FormatCapability::Creatable;
|
||||
caps |= FormatCapability::Sampled;
|
||||
caps |= FormatCapability::TextureBuffer;
|
||||
}
|
||||
return caps;
|
||||
}
|
||||
|
||||
const Bool sampled = (features & VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT) != 0;
|
||||
const Bool linearFilter = (features & VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT) != 0;
|
||||
const Bool colorRenderable = (features & VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT) != 0;
|
||||
const Bool depthStencilRenderable =
|
||||
(features & VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT) != 0;
|
||||
const Bool renderable = (isDepth || isStencil) ? depthStencilRenderable : colorRenderable;
|
||||
|
||||
if (sampled || renderable) {
|
||||
caps |= FormatCapability::Creatable;
|
||||
}
|
||||
if (sampled) {
|
||||
caps |= FormatCapability::Sampled;
|
||||
if (linearFilter && !isInteger && !isStencil) {
|
||||
caps |= FormatCapability::LinearFilter;
|
||||
}
|
||||
if (!isStencil && (features & VK_FORMAT_FEATURE_BLIT_SRC_BIT) != 0 &&
|
||||
(features & VK_FORMAT_FEATURE_BLIT_DST_BIT) != 0) {
|
||||
caps |= FormatCapability::GenerateMipmap;
|
||||
}
|
||||
if (!isInteger && !isDepth && !isStencil) {
|
||||
caps |= FormatCapability::TextureGather;
|
||||
}
|
||||
if (isDepth && !isStencil) {
|
||||
caps |= FormatCapability::TextureShadow;
|
||||
}
|
||||
}
|
||||
if (renderable) {
|
||||
caps |= GetAttachmentCaps(logicalFormat);
|
||||
if (IsLayeredTarget(target)) {
|
||||
caps |= FormatCapability::FramebufferLayered;
|
||||
}
|
||||
}
|
||||
if (IsMultisampleTarget(target)) {
|
||||
caps |= FormatCapability::MultisampleTexture;
|
||||
}
|
||||
return caps;
|
||||
}
|
||||
|
||||
Optional<TextureInternalFormat> ResolveVulkanFallbackLogicalFormat(TextureInternalFormat format) {
|
||||
switch (format) {
|
||||
case TextureInternalFormat::RGB:
|
||||
case TextureInternalFormat::RGB8:
|
||||
return TextureInternalFormat::RGBA8;
|
||||
case TextureInternalFormat::SRGB8:
|
||||
return TextureInternalFormat::SRGB8Alpha8;
|
||||
case TextureInternalFormat::RGB8Snorm:
|
||||
return TextureInternalFormat::RGBA8Snorm;
|
||||
case TextureInternalFormat::RGB16:
|
||||
return TextureInternalFormat::RGBA16;
|
||||
case TextureInternalFormat::RGB16Snorm:
|
||||
return TextureInternalFormat::RGBA16Snorm;
|
||||
case TextureInternalFormat::RGB16F:
|
||||
return TextureInternalFormat::RGBA16F;
|
||||
case TextureInternalFormat::R11FG11FB10F:
|
||||
if (IsR11G11B10FFallbackEnabled()) {
|
||||
return TextureInternalFormat::RGBA16F;
|
||||
}
|
||||
return Nullopt;
|
||||
case TextureInternalFormat::RGB32F:
|
||||
return TextureInternalFormat::RGBA32F;
|
||||
case TextureInternalFormat::RGB8I:
|
||||
return TextureInternalFormat::RGBA8I;
|
||||
case TextureInternalFormat::RGB8UI:
|
||||
return TextureInternalFormat::RGBA8UI;
|
||||
case TextureInternalFormat::RGB16I:
|
||||
return TextureInternalFormat::RGBA16I;
|
||||
case TextureInternalFormat::RGB16UI:
|
||||
return TextureInternalFormat::RGBA16UI;
|
||||
case TextureInternalFormat::RGB32I:
|
||||
return TextureInternalFormat::RGBA32I;
|
||||
case TextureInternalFormat::RGB32UI:
|
||||
return TextureInternalFormat::RGBA32UI;
|
||||
default:
|
||||
return Nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
Optional<VkFormat> ResolveVulkanFallbackFormat(TextureInternalFormat format) {
|
||||
const Optional<TextureInternalFormat> fallbackLogicalFormat = ResolveVulkanFallbackLogicalFormat(format);
|
||||
if (!fallbackLogicalFormat) {
|
||||
return Nullopt;
|
||||
}
|
||||
return MG_Util::ConvertTextureInternalFormatToVkEnum(*fallbackLogicalFormat);
|
||||
}
|
||||
|
||||
Bool HasNewCaveatFormatCaps(FormatCapabilityFlags nativeCaps, FormatCapabilityFlags fallbackCaps) {
|
||||
for (FormatCapability capability : kReportedFormatCapabilities) {
|
||||
if (HasFormatCapability(fallbackCaps, capability) &&
|
||||
!HasFormatCapability(nativeCaps, capability)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void LogVulkanFormatCaveat(TextureInternalFormat logicalFormat,
|
||||
SizeT targetIndex,
|
||||
TextureInternalFormat fallbackFormat) {
|
||||
MGLOG_D("Caveat: %s %s not fully supported. Reason: native Vulkan format is not fully supported. Fallback: %s",
|
||||
GetFormatCapabilityTargetName(targetIndex).c_str(),
|
||||
MG_Util::ConvertTextureInternalFormatToString(logicalFormat).c_str(),
|
||||
MG_Util::ConvertTextureInternalFormatToString(fallbackFormat).c_str());
|
||||
}
|
||||
|
||||
Vector<Int> BuildSampleCounts(Int maxSamples) {
|
||||
Vector<Int> counts;
|
||||
for (Int samples = std::max(maxSamples, 1); samples > 1; samples >>= 1) {
|
||||
counts.push_back(samples);
|
||||
}
|
||||
counts.push_back(1);
|
||||
return counts;
|
||||
}
|
||||
|
||||
void PopulateFormatCapabilitiesImpl(VkPhysicalDevice physicalDevice,
|
||||
PFN_vkGetPhysicalDeviceFormatProperties getFormatProperties,
|
||||
const MG_External::VulkanCapabilities& capabilities,
|
||||
FormatCapabilityCache& cache) {
|
||||
cache.Clear();
|
||||
if (physicalDevice == VK_NULL_HANDLE || getFormatProperties == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (SizeT formatIndex = 0; formatIndex < kFormatCapabilityFormatCount; ++formatIndex) {
|
||||
const auto logicalFormat = static_cast<TextureInternalFormat>(formatIndex);
|
||||
if (!IsFormatIndexValid(logicalFormat)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
VkFormat nativeFormat = MG_Util::ConvertTextureInternalFormatToVkEnum(logicalFormat);
|
||||
const Optional<TextureInternalFormat> fallbackLogicalFormat =
|
||||
ResolveVulkanFallbackLogicalFormat(logicalFormat);
|
||||
VkFormat fallbackFormat = ResolveVulkanFallbackFormat(logicalFormat).value_or(VK_FORMAT_UNDEFINED);
|
||||
|
||||
VkFormatProperties nativeProperties{};
|
||||
if (nativeFormat != VK_FORMAT_UNDEFINED) {
|
||||
getFormatProperties(physicalDevice, nativeFormat, &nativeProperties);
|
||||
}
|
||||
|
||||
VkFormatProperties fallbackProperties{};
|
||||
if (fallbackFormat != VK_FORMAT_UNDEFINED && fallbackFormat != nativeFormat) {
|
||||
getFormatProperties(physicalDevice, fallbackFormat, &fallbackProperties);
|
||||
}
|
||||
|
||||
for (SizeT targetIndex = 0; targetIndex < kFormatCapabilityTextureTargetCount; ++targetIndex) {
|
||||
const auto target = static_cast<TextureTarget>(targetIndex);
|
||||
const VkFormatFeatureFlags nativeFeatures =
|
||||
IsTextureBufferTarget(target) ? nativeProperties.bufferFeatures
|
||||
: nativeProperties.optimalTilingFeatures;
|
||||
FormatCapabilityFlags nativeCaps = BuildVulkanCaps(logicalFormat, target, nativeFeatures);
|
||||
cache.FullCaps[targetIndex][formatIndex] |= nativeCaps;
|
||||
|
||||
const VkFormatFeatureFlags fallbackFeatures =
|
||||
IsTextureBufferTarget(target) ? fallbackProperties.bufferFeatures
|
||||
: fallbackProperties.optimalTilingFeatures;
|
||||
FormatCapabilityFlags fallbackCaps = BuildVulkanCaps(logicalFormat, target, fallbackFeatures);
|
||||
if (fallbackFormat != VK_FORMAT_UNDEFINED && fallbackFormat != nativeFormat) {
|
||||
cache.CaveatCaps[targetIndex][formatIndex] |= fallbackCaps;
|
||||
if (fallbackLogicalFormat && HasNewCaveatFormatCaps(nativeCaps, fallbackCaps)) {
|
||||
LogVulkanFormatCaveat(logicalFormat, targetIndex, *fallbackLogicalFormat);
|
||||
}
|
||||
}
|
||||
|
||||
if (HasFormatCapability(nativeCaps | fallbackCaps, FormatCapability::MultisampleTexture)) {
|
||||
const Bool isDepth = MG_Util::IsDepthFormatInternalFormat(logicalFormat);
|
||||
const Bool isStencil = MG_Util::IsStencilFormatInternalFormat(logicalFormat);
|
||||
const Bool isInteger = IsIntegerInternalFormat(logicalFormat);
|
||||
Int maxSamples = capabilities.MaxColorTextureSamples;
|
||||
if (isDepth || isStencil) {
|
||||
maxSamples = capabilities.MaxDepthTextureSamples;
|
||||
} else if (isInteger) {
|
||||
maxSamples = capabilities.MaxIntegerSamples;
|
||||
}
|
||||
cache.SampleCounts[targetIndex][formatIndex] = BuildSampleCounts(maxSamples);
|
||||
}
|
||||
}
|
||||
|
||||
const SizeT renderbufferTargetIndex = GetRenderbufferFormatCapabilityTargetIndex();
|
||||
FormatCapabilityFlags renderbufferCaps =
|
||||
BuildVulkanCaps(logicalFormat, TextureTarget::Texture2D, nativeProperties.optimalTilingFeatures);
|
||||
renderbufferCaps &= FormatCapability::Creatable;
|
||||
if ((nativeProperties.optimalTilingFeatures &
|
||||
(VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT)) != 0) {
|
||||
renderbufferCaps |= GetAttachmentCaps(logicalFormat);
|
||||
renderbufferCaps |= FormatCapability::MultisampleRenderbuffer;
|
||||
}
|
||||
cache.FullCaps[renderbufferTargetIndex][formatIndex] |= renderbufferCaps;
|
||||
|
||||
if (fallbackFormat != VK_FORMAT_UNDEFINED && fallbackFormat != nativeFormat) {
|
||||
FormatCapabilityFlags fallbackRenderbufferCaps =
|
||||
BuildVulkanCaps(logicalFormat, TextureTarget::Texture2D,
|
||||
fallbackProperties.optimalTilingFeatures);
|
||||
fallbackRenderbufferCaps &= FormatCapability::Creatable;
|
||||
if ((fallbackProperties.optimalTilingFeatures &
|
||||
(VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT)) !=
|
||||
0) {
|
||||
fallbackRenderbufferCaps |= GetAttachmentCaps(logicalFormat);
|
||||
fallbackRenderbufferCaps |= FormatCapability::MultisampleRenderbuffer;
|
||||
}
|
||||
cache.CaveatCaps[renderbufferTargetIndex][formatIndex] |= fallbackRenderbufferCaps;
|
||||
if (fallbackLogicalFormat &&
|
||||
HasNewCaveatFormatCaps(renderbufferCaps, fallbackRenderbufferCaps)) {
|
||||
LogVulkanFormatCaveat(logicalFormat, renderbufferTargetIndex, *fallbackLogicalFormat);
|
||||
}
|
||||
}
|
||||
|
||||
const FormatCapabilityFlags rbCaps = cache.FullCaps[renderbufferTargetIndex][formatIndex] |
|
||||
cache.CaveatCaps[renderbufferTargetIndex][formatIndex];
|
||||
if (HasFormatCapability(rbCaps, FormatCapability::MultisampleRenderbuffer)) {
|
||||
cache.SampleCounts[renderbufferTargetIndex][formatIndex] =
|
||||
BuildSampleCounts(capabilities.MaxFramebufferSamples);
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void PopulateFormatCapabilities(VkPhysicalDevice physicalDevice,
|
||||
PFN_vkGetPhysicalDeviceFormatProperties getFormatProperties,
|
||||
const MG_External::VulkanCapabilities& capabilities,
|
||||
FormatCapabilityCache& cache) {
|
||||
PopulateFormatCapabilitiesImpl(physicalDevice, getFormatProperties, capabilities, cache);
|
||||
}
|
||||
|
||||
BackendObject_DirectVulkan::~BackendObject_DirectVulkan() = default;
|
||||
|
||||
BackendObject_DirectVulkan::BackendObject_DirectVulkan(): m_rendererInfo{GetRendererIdentity()} {}
|
||||
|
||||
Bool BackendObject_DirectVulkan::InitWindowSurface() {
|
||||
if (!m_windowHandle.Handle) {
|
||||
MGLOG_E("Cannot initialize DirectVulkan window surface: native window handle is null");
|
||||
return false;
|
||||
}
|
||||
|
||||
auto nativeWindow = reinterpret_cast<NativeWindowType>(m_windowHandle.Handle);
|
||||
|
||||
// Any renderer instance this assignment replaces is destroyed here;
|
||||
// fence/timer-query handles stamped with the old generation go stale.
|
||||
BumpRendererGeneration();
|
||||
pVulkanRenderer = MakeUnique<MG_Backend::DirectVulkan::VulkanRenderer>(nativeWindow);
|
||||
MOBILEGL_ASSERT(pVulkanRenderer != nullptr, "InitWindowSurface: VulkanRenderer creation failed");
|
||||
pVulkanRenderer->Initialize();
|
||||
return true;
|
||||
}
|
||||
|
||||
Bool BackendObject_DirectVulkan::InitPbufferSurface(EGLint width, EGLint height) {
|
||||
VulkanRendererConfig config;
|
||||
config.SurfaceWidth = static_cast<Uint32>(std::max<EGLint>(width, 1));
|
||||
config.SurfaceHeight = static_cast<Uint32>(std::max<EGLint>(height, 1));
|
||||
// Any renderer instance this assignment replaces is destroyed here;
|
||||
// fence/timer-query handles stamped with the old generation go stale.
|
||||
BumpRendererGeneration();
|
||||
pVulkanRenderer = MakeUnique<MG_Backend::DirectVulkan::VulkanRenderer>(NativeWindowType{}, config);
|
||||
MOBILEGL_ASSERT(pVulkanRenderer != nullptr, "InitPbufferSurface: VulkanRenderer creation failed");
|
||||
pVulkanRenderer->Initialize();
|
||||
return true;
|
||||
}
|
||||
|
||||
void BackendObject_DirectVulkan::Initialize() {
|
||||
m_initialized = true;
|
||||
}
|
||||
|
||||
Bool BackendObject_DirectVulkan::InitCapabilities() {
|
||||
if (!m_initialized) {
|
||||
MGLOG_E("Cannot initialize capabilities before backend is initialized");
|
||||
return false;
|
||||
}
|
||||
if (!pVulkanRenderer) {
|
||||
MGLOG_E("Cannot initialize capabilities: Vulkan renderer has not been created");
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto& physicalDevice = pVulkanRenderer->GetPhysicalDevice();
|
||||
if (!MG_Util::BackendLoader::QueryVulkanCapabilities(m_vulkanCaps, pVulkanRenderer->GetInstance(),
|
||||
physicalDevice.handle)) {
|
||||
MGLOG_W("DirectVulkan: failed to query extended Vulkan capabilities, using basic properties");
|
||||
MG_Util::BackendLoader::FillInVulkanCapabilities(m_vulkanCaps, physicalDevice.properties);
|
||||
}
|
||||
UpdateDynamicBackendParameters();
|
||||
UpdateAdvertisedExtensions();
|
||||
PopulateFormatCapabilities(physicalDevice.handle, vkGetPhysicalDeviceFormatProperties, m_vulkanCaps,
|
||||
MutableFormatCapabilities());
|
||||
PrintFormatCapabilities(GetFormatCapabilities());
|
||||
return true;
|
||||
}
|
||||
|
||||
Bool BackendObject_DirectVulkan::InitializeEGLDisplay(EGLDisplay dpy, EGLint* major, EGLint* minor) {
|
||||
if (!m_initialized) {
|
||||
MGLOG_E("DirectVulkan backend not initialized");
|
||||
return false;
|
||||
}
|
||||
return BackendObject::InitializeEGLDisplay(dpy, major, minor);
|
||||
}
|
||||
|
||||
Bool BackendObject_DirectVulkan::CreateEGLWindowSurface(EGLSurface surface, const WindowHandle& handle) {
|
||||
const std::lock_guard<std::recursive_mutex> lock(m_eglStateMutex);
|
||||
if (!m_initialized) {
|
||||
MGLOG_E("DirectVulkan backend not initialized");
|
||||
return false;
|
||||
}
|
||||
if (!handle.Handle || (handle.Backend != WindowBackend::Android &&
|
||||
handle.Backend != WindowBackend::X11 &&
|
||||
handle.Backend != WindowBackend::MetalLayer)) {
|
||||
MGLOG_E("DirectVulkan backend only supports Android, X11, and CAMetalLayer native windows");
|
||||
return false;
|
||||
}
|
||||
|
||||
return RegisterEGLWindowSurface(surface, handle);
|
||||
}
|
||||
|
||||
Bool BackendObject_DirectVulkan::ResizeEGLWindowSurface(EGLSurface surface, Uint32 width, Uint32 height) {
|
||||
const std::lock_guard<std::recursive_mutex> lock(m_eglStateMutex);
|
||||
if (!m_initialized) {
|
||||
MGLOG_E("DirectVulkan backend not initialized");
|
||||
return false;
|
||||
}
|
||||
if (!BackendObject::ResizeEGLWindowSurface(surface, width, height)) {
|
||||
return false;
|
||||
}
|
||||
if (pVulkanRenderer && m_eglSurface == surface) {
|
||||
pVulkanRenderer->RequestSwapchainResize(width, height);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
Bool BackendObject_DirectVulkan::CreateEGLPbufferSurface(EGLSurface surface, EGLint width, EGLint height) {
|
||||
const std::lock_guard<std::recursive_mutex> lock(m_eglStateMutex);
|
||||
if (!m_initialized) {
|
||||
MGLOG_E("DirectVulkan backend not initialized");
|
||||
return false;
|
||||
}
|
||||
return RegisterEGLPbufferSurface(surface, width, height);
|
||||
}
|
||||
|
||||
Bool BackendObject_DirectVulkan::MakeEGLCurrent(EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx) {
|
||||
const std::lock_guard<std::recursive_mutex> lock(m_eglStateMutex);
|
||||
return BackendObject::MakeEGLCurrent(dpy, draw, read, ctx);
|
||||
}
|
||||
|
||||
Bool BackendObject_DirectVulkan::SwapEGLBuffers(EGLDisplay dpy, EGLSurface draw) {
|
||||
const std::lock_guard<std::recursive_mutex> lock(m_eglStateMutex);
|
||||
if (!pVulkanRenderer) {
|
||||
MGLOG_E("DirectVulkan renderer is not initialized");
|
||||
return false;
|
||||
}
|
||||
return BackendObject::SwapEGLBuffers(dpy, draw);
|
||||
}
|
||||
|
||||
void BackendObject_DirectVulkan::ReleaseEGLSurface(EGLSurface surface) {
|
||||
const std::lock_guard<std::recursive_mutex> lock(m_eglStateMutex);
|
||||
BackendObject::ReleaseEGLSurface(surface);
|
||||
}
|
||||
|
||||
void BackendObject_DirectVulkan::ReleaseEGLResources() {
|
||||
const std::lock_guard<std::recursive_mutex> lock(m_eglStateMutex);
|
||||
// Outstanding fence/timer-query handles now refer to a dead renderer;
|
||||
// treat them as signaled/available with zero results from here on.
|
||||
BumpRendererGeneration();
|
||||
pVulkanRenderer.reset();
|
||||
BackendObject::ReleaseEGLResources();
|
||||
}
|
||||
|
||||
void BackendObject_DirectVulkan::OnEGLSurfaceReleased(EGLSurface surface) {
|
||||
(void)surface;
|
||||
// Outstanding fence/timer-query handles now refer to a dead renderer;
|
||||
// treat them as signaled/available with zero results from here on.
|
||||
BumpRendererGeneration();
|
||||
pVulkanRenderer.reset();
|
||||
}
|
||||
|
||||
const RendererInfo& BackendObject_DirectVulkan::GetRendererInfo() const {
|
||||
return m_rendererInfo;
|
||||
}
|
||||
|
||||
String BackendObject_DirectVulkan::GetBackendAPIVersionString() const {
|
||||
if (!m_initialized) {
|
||||
return "<uninitialized DirectVulkan backend>";
|
||||
}
|
||||
return FormatBackendAPIVersionString(m_vulkanCaps.DeviceName, m_vulkanCaps.VulkanAPIVersion.toString(),
|
||||
m_vulkanCaps.DriverVersionString);
|
||||
}
|
||||
|
||||
const RendererInfo& GetRendererIdentity() {
|
||||
static const RendererInfo rendererInfo = {
|
||||
.RendererName = "Magma",
|
||||
.BackendName = "Direct (Vulkan)",
|
||||
.ExtraVendor = Nullopt,
|
||||
.RendererGLInfo =
|
||||
{
|
||||
.TargetGLVersion = {3, 3, 0},
|
||||
.TargetGLSLVersion = {4, 6, 0},
|
||||
// Baseline advertisement (no shader subgroup, no timer queries); a
|
||||
// live backend reconciles its copy in UpdateAdvertisedExtensions.
|
||||
.Extensions = BuildAdvertisedExtensions(false, false, false),
|
||||
.IsCompatibilityProfile = false
|
||||
},
|
||||
.StaticBackendCapability = {.AllowVSOnlyPrograms = false}};
|
||||
return rendererInfo;
|
||||
}
|
||||
|
||||
Vector<GLExtension> BuildAdvertisedExtensions(Bool shaderSubgroupSupported, Bool timerQueriesSupported,
|
||||
Bool anisotropicFilteringSupported) {
|
||||
Vector<GLExtension> extensions = {V_OpenGL30, V_OpenGL31, V_OpenGL32,
|
||||
V_OpenGL33, E_GL_ARB_draw_buffers_blend, E_GL_ARB_compute_shader,
|
||||
E_GL_ARB_shader_storage_buffer_object, E_GL_ARB_shader_image_load_store,
|
||||
E_GL_ARB_program_interface_query, E_GL_ARB_framebuffer_object,
|
||||
E_GL_ARB_multi_draw_indirect, E_GL_ARB_indirect_parameters,
|
||||
E_GL_EXT_framebuffer_object, E_GL_ARB_depth_texture, E_GL_ARB_buffer_storage,
|
||||
E_GL_ARB_texture_storage, E_GL_ARB_texture_storage_multisample,
|
||||
E_GL_ARB_clear_texture, E_GL_ARB_direct_state_access,
|
||||
E_GL_ARB_shader_draw_parameters, E_GL_ARB_gpu_shader_int64, E_GL_KHR_debug,
|
||||
E_GL_ARB_gpu_shader5, E_GL_ARB_multi_bind, E_GL_ARB_shading_language_420pack,
|
||||
E_GL_ARB_vertex_attrib_binding, E_GL_ARB_shader_image_size};
|
||||
if (shaderSubgroupSupported && !MG_Config::Features.DisableSubgroup) {
|
||||
extensions.push_back(E_GL_KHR_shader_subgroup);
|
||||
}
|
||||
// GL_ARB_timer_query gates MC's F3 GPU% (LWJGL checks the extension string);
|
||||
// only advertised when the device actually supports timestamp queries and the
|
||||
// MOBILEGL_DISABLE_TIMERQUERY escape hatch is off.
|
||||
if (timerQueriesSupported && !MG_Config::Features.DisableTimerQuery) {
|
||||
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;
|
||||
}
|
||||
|
||||
String FormatBackendAPIVersionString(const String& deviceName, const String& vulkanApiVersionString,
|
||||
const String& driverVersionString) {
|
||||
// Format:
|
||||
// <GPU Name>, Vulkan <Vulkan Version>, Driver <Driver Version>
|
||||
return deviceName + ", Vulkan " + vulkanApiVersionString + ", Driver " + driverVersionString;
|
||||
}
|
||||
|
||||
BackendType BackendObject_DirectVulkan::GetBackendType() const {
|
||||
return BackendType::DirectVulkan;
|
||||
}
|
||||
|
||||
const GlobalBackendFunctionsTable& BackendObject_DirectVulkan::GetBackendFunctions() const {
|
||||
static GlobalBackendFunctionsTable funcsTable;
|
||||
static Bool funcsTableInitialized = false;
|
||||
if (!funcsTableInitialized) {
|
||||
funcsTable.Present = Present;
|
||||
funcsTable.GL.DrawArrays = DrawArrays;
|
||||
funcsTable.GL.DrawElements = DrawElements;
|
||||
funcsTable.GL.DrawElementsBaseVertex = DrawElementsBaseVertex;
|
||||
funcsTable.GL.MultiDrawArrays = MultiDrawArrays;
|
||||
funcsTable.GL.MultiDrawElements = MultiDrawElements;
|
||||
funcsTable.GL.MultiDrawElementsBaseVertex = MultiDrawElementsBaseVertex;
|
||||
funcsTable.GL.MultiDrawElementsIndirect = MultiDrawElementsIndirect;
|
||||
funcsTable.GL.MultiDrawArraysIndirect = MultiDrawArraysIndirect;
|
||||
funcsTable.GL.MultiDrawElementsIndirectCount = MultiDrawElementsIndirectCount;
|
||||
funcsTable.GL.MultiDrawArraysIndirectCount = MultiDrawArraysIndirectCount;
|
||||
funcsTable.GL.DrawRangeElementsBaseVertex = DrawRangeElementsBaseVertex;
|
||||
funcsTable.GL.DrawRangeElements = DrawRangeElements;
|
||||
funcsTable.GL.DrawElementsInstancedBaseVertexBaseInstance = DrawElementsInstancedBaseVertexBaseInstance;
|
||||
funcsTable.GL.DrawElementsInstancedBaseVertex = DrawElementsInstancedBaseVertex;
|
||||
funcsTable.GL.DrawElementsInstancedBaseInstance = DrawElementsInstancedBaseInstance;
|
||||
funcsTable.GL.DrawElementsInstanced = DrawElementsInstanced;
|
||||
funcsTable.GL.DrawArraysInstancedBaseInstance = DrawArraysInstancedBaseInstance;
|
||||
funcsTable.GL.DrawArraysInstanced = DrawArraysInstanced;
|
||||
funcsTable.GL.DrawElementsIndirect = DrawElementsIndirect;
|
||||
funcsTable.GL.DrawArraysIndirect = DrawArraysIndirect;
|
||||
funcsTable.GL.Clear = Clear;
|
||||
funcsTable.GL.ClearBufferfi = ClearBufferfi;
|
||||
funcsTable.GL.ClearBufferfv = ClearBufferfv;
|
||||
funcsTable.GL.ClearBufferuiv = ClearBufferuiv;
|
||||
funcsTable.GL.ClearBufferiv = ClearBufferiv;
|
||||
funcsTable.GL.ClearNamedFramebufferfv = ClearNamedFramebufferfv;
|
||||
funcsTable.GL.ClearNamedFramebufferfi = ClearNamedFramebufferfi;
|
||||
funcsTable.GL.BlitFramebuffer = BlitFramebuffer;
|
||||
funcsTable.GL.BlitNamedFramebuffer = BlitNamedFramebuffer;
|
||||
funcsTable.GL.CopyTexImage2D = CopyTexImage2D;
|
||||
funcsTable.GL.CopyTexSubImage2D = CopyTexSubImage2D;
|
||||
funcsTable.GL.CopyImageSubData = CopyImageSubData;
|
||||
funcsTable.GL.GenerateMipmap = GenerateMipmap;
|
||||
funcsTable.GL.ReadPixels = ReadPixels;
|
||||
funcsTable.GL.GetTexImage = GetTexImage;
|
||||
funcsTable.GL.GetTextureImage = GetTextureImage;
|
||||
funcsTable.GL.DispatchCompute = DispatchCompute;
|
||||
funcsTable.GL.DispatchComputeIndirect = DispatchComputeIndirect;
|
||||
funcsTable.GL.MemoryBarrier = MemoryBarrier;
|
||||
funcsTable.GL.MemoryBarrierByRegion = MemoryBarrierByRegion;
|
||||
funcsTable.GL.BindImageTexture = BindImageTexture;
|
||||
funcsTable.GL.GetIntegeri_v = GetIntegeri_v;
|
||||
funcsTable.GL.GetInteger64i_v = GetInteger64i_v;
|
||||
funcsTable.GL.GetProgramiv = GetProgramiv;
|
||||
funcsTable.GL.GetProgramInterfaceiv = GetProgramInterfaceiv;
|
||||
funcsTable.GL.GetProgramResourceIndex = GetProgramResourceIndex;
|
||||
funcsTable.GL.GetProgramResourceName = GetProgramResourceName;
|
||||
funcsTable.GL.GetProgramResourceiv = GetProgramResourceiv;
|
||||
funcsTable.GL.GetProgramResourceLocation = GetProgramResourceLocation;
|
||||
funcsTable.GL.GetProgramResourceLocationIndex = GetProgramResourceLocationIndex;
|
||||
funcsTable.GL.ShaderStorageBlockBinding = ShaderStorageBlockBinding;
|
||||
funcsTable.GL.FenceSync = FenceSync;
|
||||
funcsTable.GL.ClientWaitSync = ClientWaitSync;
|
||||
funcsTable.GL.WaitSync = WaitSync;
|
||||
funcsTable.GL.DeleteSync = DeleteSync;
|
||||
funcsTable.GL.GetSyncStatus = GetSyncStatus;
|
||||
// Optional timer-query group: left null (the frontend then falls
|
||||
// back) when disabled via MOBILEGL_DISABLE_TIMERQUERY. The hooks
|
||||
// themselves additionally degrade to null handles when the device
|
||||
// lacks timestamp support.
|
||||
if (!MG_Config::Features.DisableTimerQuery) {
|
||||
funcsTable.GL.IsTimerQuerySupported = IsTimerQuerySupported;
|
||||
funcsTable.GL.BeginTimeElapsedQuery = BeginTimeElapsedQuery;
|
||||
funcsTable.GL.EndTimeElapsedQuery = EndTimeElapsedQuery;
|
||||
funcsTable.GL.QueryCounterTimestamp = QueryCounterTimestamp;
|
||||
funcsTable.GL.IsQueryResultAvailable = IsQueryResultAvailable;
|
||||
funcsTable.GL.GetQueryResult64 = GetQueryResult64;
|
||||
funcsTable.GL.DeleteBackendQuery = DeleteBackendQuery;
|
||||
funcsTable.GL.GetGpuTimestampNs = GetGpuTimestampNs;
|
||||
}
|
||||
funcsTableInitialized = true;
|
||||
}
|
||||
return funcsTable;
|
||||
}
|
||||
|
||||
const DynamicBackendParameters& BackendObject_DirectVulkan::GetDynamicParameters() const {
|
||||
return m_dynamicParameters;
|
||||
}
|
||||
|
||||
void BackendObject_DirectVulkan::ApplyVulkanCapabilitiesForTesting(
|
||||
const MG_External::VulkanCapabilities& capabilities) {
|
||||
m_vulkanCaps = capabilities;
|
||||
UpdateDynamicBackendParameters();
|
||||
UpdateAdvertisedExtensions();
|
||||
MutableFormatCapabilities().Clear();
|
||||
}
|
||||
|
||||
void BackendObject_DirectVulkan::UpdateAdvertisedExtensions() {
|
||||
// GL_ARB_timer_query gates MC's F3 GPU% (LWJGL checks the extension
|
||||
// string). InitCapabilities runs after InitWindowSurface has created
|
||||
// and initialized the renderer, so the advertisement can be gated on
|
||||
// real device timestamp support. ApplyVulkanCapabilitiesForTesting may
|
||||
// run without a renderer; no timer query is advertised then. Rebuilding
|
||||
// the whole list keeps re-runs idempotent.
|
||||
m_rendererInfo.RendererGLInfo.Extensions = BuildAdvertisedExtensions(
|
||||
m_vulkanCaps.SupportsShaderSubgroup, pVulkanRenderer && pVulkanRenderer->IsTimerQuerySupported(),
|
||||
pVulkanRenderer && pVulkanRenderer->IsSamplerAnisotropySupported());
|
||||
}
|
||||
|
||||
void BackendObject_DirectVulkan::UpdateDynamicBackendParameters() {
|
||||
const auto mapShaderStages = [](Uint32 vkStages) {
|
||||
Uint32 glStages = 0;
|
||||
if ((vkStages & VK_SHADER_STAGE_VERTEX_BIT) != 0) glStages |= GL_VERTEX_SHADER_BIT;
|
||||
if ((vkStages & VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT) != 0) glStages |= GL_TESS_CONTROL_SHADER_BIT;
|
||||
if ((vkStages & VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT) != 0) {
|
||||
glStages |= GL_TESS_EVALUATION_SHADER_BIT;
|
||||
}
|
||||
if ((vkStages & VK_SHADER_STAGE_GEOMETRY_BIT) != 0) glStages |= GL_GEOMETRY_SHADER_BIT;
|
||||
if ((vkStages & VK_SHADER_STAGE_FRAGMENT_BIT) != 0) glStages |= GL_FRAGMENT_SHADER_BIT;
|
||||
if ((vkStages & VK_SHADER_STAGE_COMPUTE_BIT) != 0) glStages |= GL_COMPUTE_SHADER_BIT;
|
||||
return glStages;
|
||||
};
|
||||
|
||||
const auto mapSubgroupFeatures = [](Uint32 vkFeatures) {
|
||||
Uint32 glFeatures = 0;
|
||||
if ((vkFeatures & VK_SUBGROUP_FEATURE_BASIC_BIT) != 0) {
|
||||
glFeatures |= GL_SUBGROUP_FEATURE_BASIC_BIT_KHR;
|
||||
}
|
||||
if ((vkFeatures & VK_SUBGROUP_FEATURE_VOTE_BIT) != 0) {
|
||||
glFeatures |= GL_SUBGROUP_FEATURE_VOTE_BIT_KHR;
|
||||
}
|
||||
if ((vkFeatures & VK_SUBGROUP_FEATURE_ARITHMETIC_BIT) != 0) {
|
||||
glFeatures |= GL_SUBGROUP_FEATURE_ARITHMETIC_BIT_KHR;
|
||||
}
|
||||
if ((vkFeatures & VK_SUBGROUP_FEATURE_BALLOT_BIT) != 0) {
|
||||
glFeatures |= GL_SUBGROUP_FEATURE_BALLOT_BIT_KHR;
|
||||
}
|
||||
if ((vkFeatures & VK_SUBGROUP_FEATURE_SHUFFLE_BIT) != 0) {
|
||||
glFeatures |= GL_SUBGROUP_FEATURE_SHUFFLE_BIT_KHR;
|
||||
}
|
||||
if ((vkFeatures & VK_SUBGROUP_FEATURE_SHUFFLE_RELATIVE_BIT) != 0) {
|
||||
glFeatures |= GL_SUBGROUP_FEATURE_SHUFFLE_RELATIVE_BIT_KHR;
|
||||
}
|
||||
if ((vkFeatures & VK_SUBGROUP_FEATURE_CLUSTERED_BIT) != 0) {
|
||||
glFeatures |= GL_SUBGROUP_FEATURE_CLUSTERED_BIT_KHR;
|
||||
}
|
||||
if ((vkFeatures & VK_SUBGROUP_FEATURE_QUAD_BIT) != 0) {
|
||||
glFeatures |= GL_SUBGROUP_FEATURE_QUAD_BIT_KHR;
|
||||
}
|
||||
return glFeatures;
|
||||
};
|
||||
|
||||
static constexpr SizeT kMaxAdvertisedShaderStorageBlockSize = 512ull * 1024ull * 1024ull;
|
||||
m_dynamicParameters.UniformBufferOffsetAlignment = m_vulkanCaps.UniformBufferOffsetAlignment;
|
||||
m_dynamicParameters.AliasedLineWidthRangeMin = m_vulkanCaps.AliasedLineWidthRangeMin;
|
||||
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.SmoothLineWidthRangeMax = m_vulkanCaps.SmoothLineWidthRangeMax;
|
||||
m_dynamicParameters.SmoothLineWidthGranularity = m_vulkanCaps.SmoothLineWidthGranularity;
|
||||
m_dynamicParameters.PointSizeRangeMin = m_vulkanCaps.PointSizeRangeMin;
|
||||
m_dynamicParameters.PointSizeRangeMax = m_vulkanCaps.PointSizeRangeMax;
|
||||
m_dynamicParameters.PointSizeGranularity = m_vulkanCaps.PointSizeGranularity;
|
||||
m_dynamicParameters.Max3DTextureSize = m_vulkanCaps.Max3DTextureSize;
|
||||
m_dynamicParameters.MaxArrayTextureLayers = m_vulkanCaps.MaxArrayTextureLayers;
|
||||
m_dynamicParameters.MaxCubeMapTextureSize = m_vulkanCaps.MaxCubeMapTextureSize;
|
||||
m_dynamicParameters.MaxFramebufferWidth = m_vulkanCaps.MaxFramebufferWidth;
|
||||
m_dynamicParameters.MaxFramebufferHeight = m_vulkanCaps.MaxFramebufferHeight;
|
||||
m_dynamicParameters.MaxFramebufferLayers = m_vulkanCaps.MaxFramebufferLayers;
|
||||
m_dynamicParameters.MaxRenderbufferSize = m_vulkanCaps.MaxRenderbufferSize;
|
||||
m_dynamicParameters.MaxTextureSize = m_vulkanCaps.MaxTextureSize;
|
||||
m_dynamicParameters.MaxColorTextureSamples = m_vulkanCaps.MaxColorTextureSamples;
|
||||
m_dynamicParameters.MaxDepthTextureSamples = m_vulkanCaps.MaxDepthTextureSamples;
|
||||
m_dynamicParameters.MaxFramebufferSamples = m_vulkanCaps.MaxFramebufferSamples;
|
||||
m_dynamicParameters.MaxIntegerSamples = m_vulkanCaps.MaxIntegerSamples;
|
||||
m_dynamicParameters.MaxSamples = m_vulkanCaps.MaxSamples;
|
||||
m_dynamicParameters.MaxSampleMaskWords = m_vulkanCaps.MaxSampleMaskWords;
|
||||
const Int maxSupportedTextureUnits =
|
||||
static_cast<Int>(MG_State::GLState::TextureState::MAX_TEXTURE_IMAGE_UNITS);
|
||||
// GL_MAX_TEXTURE_IMAGE_UNITS is a *per-stage* sampler limit. Adreno/Qualcomm report a huge
|
||||
// maxPerStageDescriptorSampledImages (descriptor-indexing scale), so clamping it only to our
|
||||
// combined array capacity (192) still advertises 192 per stage. Host code treats this value as
|
||||
// an array bound: Minecraft's Blaze3D GlStateManager.TEXTURES[] holds 128 entries and Iris
|
||||
// iterates [0, GL_MAX_TEXTURE_IMAGE_UNITS) over it (CompositeRenderer.renderAll), so any value
|
||||
// > 128 throws ArrayIndexOutOfBoundsException. Match desktop drivers (32) for the per-stage
|
||||
// limits while keeping the combined limit at our texture-unit array capacity.
|
||||
constexpr Int maxPerStageTextureUnits =
|
||||
static_cast<Int>(MG_State::GLState::TextureState::MAX_PER_STAGE_TEXTURE_IMAGE_UNITS);
|
||||
m_dynamicParameters.MaxTextureImageUnits =
|
||||
std::min(m_vulkanCaps.MaxTextureImageUnits, maxPerStageTextureUnits);
|
||||
m_dynamicParameters.MaxVertexTextureImageUnits =
|
||||
std::min(m_vulkanCaps.MaxVertexTextureImageUnits, maxPerStageTextureUnits);
|
||||
m_dynamicParameters.MaxComputeTextureImageUnits =
|
||||
std::min(m_vulkanCaps.MaxComputeTextureImageUnits, maxPerStageTextureUnits);
|
||||
m_dynamicParameters.MaxCombinedTextureImageUnits =
|
||||
std::min(m_vulkanCaps.MaxCombinedTextureImageUnits, maxSupportedTextureUnits);
|
||||
// Never advertise more attributes than the state layer can store: the current-value array and
|
||||
// the Uint32 attribute masks the draw path passes around are both bounded by MAX_VERTEX_ATTRIBS.
|
||||
m_dynamicParameters.MaxVertexAttribs =
|
||||
std::min(m_vulkanCaps.MaxVertexAttribs,
|
||||
static_cast<Int>(MG_State::GLState::VertexArrayObject::MAX_VERTEX_ATTRIBS));
|
||||
m_dynamicParameters.MaxComputeShaderStorageBlocks = m_vulkanCaps.MaxComputeShaderStorageBlocks;
|
||||
m_dynamicParameters.MaxCombinedShaderStorageBlocks = m_vulkanCaps.MaxCombinedShaderStorageBlocks;
|
||||
m_dynamicParameters.MaxComputeUniformBlocks = m_vulkanCaps.MaxComputeUniformBlocks;
|
||||
m_dynamicParameters.MaxComputeWorkGroupInvocations = m_vulkanCaps.MaxComputeWorkGroupInvocations;
|
||||
m_dynamicParameters.MaxShaderStorageBufferBindings = m_vulkanCaps.MaxShaderStorageBufferBindings;
|
||||
m_dynamicParameters.MaxTextureBufferSize = m_vulkanCaps.MaxTextureBufferSize;
|
||||
m_dynamicParameters.MaxUniformBufferBindings = m_vulkanCaps.MaxUniformBufferBindings;
|
||||
m_dynamicParameters.MaxUniformBlockSize = m_vulkanCaps.MaxUniformBlockSize;
|
||||
m_dynamicParameters.MaxImageUnits =
|
||||
std::max(std::min(m_vulkanCaps.MaxImageUnits, maxSupportedTextureUnits), 0);
|
||||
m_dynamicParameters.MaxCombinedImageUniforms = std::max(m_vulkanCaps.MaxCombinedImageUniforms, 0);
|
||||
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 =
|
||||
static_cast<Int>(MG_State::GLState::FramebufferObject::MAX_DRAW_BUFFERS);
|
||||
m_dynamicParameters.MaxDrawBuffers = std::min(m_vulkanCaps.MaxDrawBuffers, maxSupportedDrawBuffers);
|
||||
m_dynamicParameters.MaxColorAttachments = std::min(m_vulkanCaps.MaxColorAttachments, maxSupportedDrawBuffers);
|
||||
m_dynamicParameters.MaxClipDistances = m_vulkanCaps.MaxClipDistances;
|
||||
m_dynamicParameters.MaxViewports = m_vulkanCaps.MaxViewports;
|
||||
m_dynamicParameters.MaxViewportWidth = m_vulkanCaps.MaxViewportWidth;
|
||||
m_dynamicParameters.MaxViewportHeight = m_vulkanCaps.MaxViewportHeight;
|
||||
m_dynamicParameters.ViewportBoundsRangeMin = m_vulkanCaps.ViewportBoundsRangeMin;
|
||||
m_dynamicParameters.ViewportBoundsRangeMax = m_vulkanCaps.ViewportBoundsRangeMax;
|
||||
m_dynamicParameters.ViewportSubpixelBits = m_vulkanCaps.ViewportSubpixelBits;
|
||||
m_dynamicParameters.SupportsWideLines = m_vulkanCaps.SupportsWideLines;
|
||||
m_dynamicParameters.MaxShaderStorageBlockSize =
|
||||
std::min(m_vulkanCaps.MaxShaderStorageBlockSize, kMaxAdvertisedShaderStorageBlockSize);
|
||||
if (m_vulkanCaps.SupportsShaderSubgroup) {
|
||||
m_dynamicParameters.SubgroupSize = m_vulkanCaps.SubgroupSize;
|
||||
m_dynamicParameters.SubgroupSupportedStages = mapShaderStages(m_vulkanCaps.SubgroupSupportedStages);
|
||||
m_dynamicParameters.SubgroupSupportedFeatures = mapSubgroupFeatures(m_vulkanCaps.SubgroupSupportedOperations);
|
||||
m_dynamicParameters.SubgroupQuadOperationsInAllStages = m_vulkanCaps.SubgroupQuadOperationsInAllStages;
|
||||
} else {
|
||||
m_dynamicParameters.SubgroupSize = 0;
|
||||
m_dynamicParameters.SubgroupSupportedStages = 0;
|
||||
m_dynamicParameters.SubgroupSupportedFeatures = 0;
|
||||
m_dynamicParameters.SubgroupQuadOperationsInAllStages = false;
|
||||
}
|
||||
if (m_dynamicParameters.MaxShaderStorageBlockSize != m_vulkanCaps.MaxShaderStorageBlockSize) {
|
||||
MGLOG_I("DirectVulkan: clamped GL_MAX_SHADER_STORAGE_BLOCK_SIZE from %zu to %zu",
|
||||
m_vulkanCaps.MaxShaderStorageBlockSize,
|
||||
m_dynamicParameters.MaxShaderStorageBlockSize);
|
||||
}
|
||||
}
|
||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||
@@ -1,84 +0,0 @@
|
||||
// MobileGL - MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.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 "../BackendObject.h"
|
||||
#include <MG_Util/BackendLoaders/Vulkan/Loader.h>
|
||||
|
||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
// Populates the same format-capability cache used by backend startup. Passing the
|
||||
// instance-resolved function keeps standalone callers independent of global loader
|
||||
// initialization; the physical device must remain valid for the duration of the call.
|
||||
void PopulateFormatCapabilities(VkPhysicalDevice physicalDevice,
|
||||
PFN_vkGetPhysicalDeviceFormatProperties getFormatProperties,
|
||||
const MG_External::VulkanCapabilities& capabilities,
|
||||
FormatCapabilityCache& cache);
|
||||
|
||||
class BackendObject_DirectVulkan : public BackendObject {
|
||||
public:
|
||||
BackendObject_DirectVulkan();
|
||||
~BackendObject_DirectVulkan() override;
|
||||
|
||||
void Initialize() override;
|
||||
Bool InitWindowSurface() override;
|
||||
Bool InitCapabilities() override;
|
||||
Bool InitializeEGLDisplay(EGLDisplay dpy, EGLint* major, EGLint* minor) override;
|
||||
Bool CreateEGLWindowSurface(EGLSurface surface, const WindowHandle& handle) override;
|
||||
Bool ResizeEGLWindowSurface(EGLSurface surface, Uint32 width, Uint32 height) override;
|
||||
Bool CreateEGLPbufferSurface(EGLSurface surface, EGLint width, EGLint height) override;
|
||||
Bool MakeEGLCurrent(EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx) override;
|
||||
Bool SwapEGLBuffers(EGLDisplay dpy, EGLSurface draw) override;
|
||||
void ReleaseEGLSurface(EGLSurface surface) override;
|
||||
void ReleaseEGLResources() override;
|
||||
|
||||
const RendererInfo& GetRendererInfo() const override;
|
||||
String GetBackendAPIVersionString() const override;
|
||||
const GlobalBackendFunctionsTable& GetBackendFunctions() const override;
|
||||
const DynamicBackendParameters& GetDynamicParameters() const override;
|
||||
BackendType GetBackendType() const override;
|
||||
void ApplyVulkanCapabilitiesForTesting(const MG_External::VulkanCapabilities& capabilities);
|
||||
|
||||
private:
|
||||
Bool InitPbufferSurface(EGLint width, EGLint height) override;
|
||||
void OnEGLSurfaceReleased(EGLSurface surface) override;
|
||||
void UpdateAdvertisedExtensions();
|
||||
void UpdateDynamicBackendParameters();
|
||||
|
||||
Bool m_initialized = false;
|
||||
DynamicBackendParameters m_dynamicParameters;
|
||||
MG_External::VulkanCapabilities m_vulkanCaps;
|
||||
RendererInfo m_rendererInfo;
|
||||
};
|
||||
|
||||
// Single-source-of-truth helpers shared with the driver POST
|
||||
// (MG_Util/SelfTest/DriverPost.cpp), so the identity strings and extension list
|
||||
// MobileGL reports to applications on this backend cannot drift from what the
|
||||
// POST screen shows.
|
||||
|
||||
// Static identity of the Magma renderer (renderer/backend names, target GL/GLSL
|
||||
// versions, ExtraVendor) with the baseline extension advertisement (no shader
|
||||
// subgroup, no timer queries). A live backend copies this in its constructor and
|
||||
// reconciles the Extensions in UpdateAdvertisedExtensions once real capabilities
|
||||
// exist; callers that need the advertised list for a known capability set must
|
||||
// use BuildAdvertisedExtensions instead.
|
||||
const RendererInfo& GetRendererIdentity();
|
||||
|
||||
// The full OpenGL extension list Magma advertises (glGetString(GL_EXTENSIONS)) for
|
||||
// a device with the given raw capabilities. The MOBILEGL_DISABLE_SUBGROUP and
|
||||
// MOBILEGL_DISABLE_TIMERQUERY escape hatches are applied inside, so callers pass
|
||||
// the detected device support (passing an already-gated value is harmless).
|
||||
Vector<GLExtension> BuildAdvertisedExtensions(Bool shaderSubgroupSupported, Bool timerQueriesSupported,
|
||||
Bool anisotropicFilteringSupported);
|
||||
|
||||
// Format: <GPU Name>, Vulkan <Vulkan Version>, Driver <Driver Version> — the exact
|
||||
// string an initialized backend returns from GetBackendAPIVersionString (and that
|
||||
// ends up inside the application-visible GL_RENDERER string).
|
||||
String FormatBackendAPIVersionString(const String& deviceName, const String& vulkanApiVersionString,
|
||||
const String& driverVersionString);
|
||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user