diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml index c6f444cd..6c4a39f5 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.yml +++ b/.github/ISSUE_TEMPLATE/bug-report.yml @@ -23,7 +23,7 @@ body: options: - MetalSharp - Release package in MetalSharp's Wine - - My own Wine build with patches/wine + - Wine module installed into my own Wine (install-wine-module.sh) validations: required: true - type: input diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 6f3699cd..b09fa138 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -17,4 +17,4 @@ - [ ] No code that recognises Khronos CTS tests or shaders - [ ] Generated docs regenerated with `scripts/docs/generate.py`, not edited by hand - [ ] `scripts/update-checksums.sh` run after adding, removing or changing files -- [ ] `patches/wine` still applies to `wine-11.17`, if it changed +- [ ] The Wine fixtures pass with the module, if `src/wine` changed diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 42e67b26..6eb71d08 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -82,3 +82,50 @@ jobs: - name: Check the architecture run: test "$(/usr/bin/lipo -archs build/host/libwinemetalgl.dylib)" = arm64 + + wine-module: + name: Wine module (x86_64 + i386 PE, x86_64 unix) + runs-on: macos-15 + env: + LLVM_MINGW: llvm-mingw-20260922-ucrt-macos-universal + LLVM_MINGW_SHA256: 52e5f5a7b131021d0c39a37a38fa380a1da7885cd04bd61afd0cd4ecfb8bc1f3 + WINE_SHA256: c07a6857933c1fc60dff5448d79f39c92481c1e9db5aa628db9d0358446e0701 + steps: + - name: Check out source + uses: actions/checkout@v4 + + - name: Fetch llvm-mingw and the Wine 11.0 headers + run: | + mkdir -p "$RUNNER_TEMP/deps" + cd "$RUNNER_TEMP/deps" + curl -fsSL -o llvm-mingw.tar.xz "https://github.com/mstorsjo/llvm-mingw/releases/download/20260922/$LLVM_MINGW.tar.xz" + echo "$LLVM_MINGW_SHA256 llvm-mingw.tar.xz" | shasum -a 256 -c - + tar -xJf llvm-mingw.tar.xz + curl -fsSL -o wine.tar.xz https://dl.winehq.org/wine/source/11.0/wine-11.0.tar.xz + echo "$WINE_SHA256 wine.tar.xz" | shasum -a 256 -c - + tar -xJf wine.tar.xz wine-11.0/include + echo "$RUNNER_TEMP/deps/$LLVM_MINGW/bin" >> "$GITHUB_PATH" + + - name: Build the module + run: sh scripts/build-wine-module.sh "$RUNNER_TEMP/deps/wine-11.0" + + - name: Check the module + run: | + m=build/wine-module + test "$(/usr/bin/lipo -archs $m/x86_64-unix/winemetalgl.so)" = x86_64 + nm -gU $m/x86_64-unix/winemetalgl.so | grep -q ' ___wine_unix_call_funcs$' + nm -gU $m/x86_64-unix/winemetalgl.so | grep -q ' ___wine_unix_call_wow64_funcs$' + for arch in x86_64 i386; do + dd if=$m/$arch-windows/winemetalgl.dll bs=1 skip=64 count=16 2>/dev/null | grep -q 'Wine builtin DLL' + test -s $m/$arch-windows/opengl32.dll + done + + - name: Upload the module + uses: actions/upload-artifact@v4 + with: + name: winemetalgl-wine-module + path: | + build/wine-module/x86_64-windows/*.dll + build/wine-module/i386-windows/*.dll + build/wine-module/x86_64-unix/winemetalgl.so + if-no-files-found: error diff --git a/.github/workflows/gl33-conformance.yml b/.github/workflows/gl33-conformance.yml index 94045158..acf94a64 100644 --- a/.github/workflows/gl33-conformance.yml +++ b/.github/workflows/gl33-conformance.yml @@ -4,7 +4,7 @@ on: workflow_dispatch: inputs: wine_runtime: - description: Absolute path to the Wine installation on the self-hosted runner + description: Absolute path to a Wine 11.x installation on the self-hosted runner (llvm-mingw on PATH) required: true type: string cts_executable: @@ -41,26 +41,34 @@ jobs: - name: Check out source uses: actions/checkout@v4 - - name: Build the core and stage it into the Wine runtime + - name: Build the core and the Wine module and install them env: WINE_RUNTIME: ${{ inputs.wine_runtime }} + TEMPLATE_PREFIX: ${{ inputs.template_prefix }} run: | set -eu ./scripts/build.sh - ./scripts/stage-wine.sh "$WINE_RUNTIME/lib/wine/x86_64-unix" + ./scripts/build-wine-module.sh "$WINE_RUNTIME" + # The template prefix gets the forwarding opengl32.dll and its override. + template=${TEMPLATE_PREFIX:-$RUNNER_TEMP/prefix-template} + if test ! -d "$template"; then + WINEPREFIX="$template" WINEARCH=wow64 WINEDLLOVERRIDES="mscoree,mshtml=" "$WINE_RUNTIME/bin/wine" wineboot -u + WINEPREFIX="$template" "$WINE_RUNTIME/bin/wineserver" -w + fi + ./scripts/install-wine-module.sh "$WINE_RUNTIME" "$template" + echo "TEMPLATE=$template" >> "$GITHUB_ENV" - name: Run KHR-GL33 env: WINE_RUNTIME: ${{ inputs.wine_runtime }} CTS_EXECUTABLE: ${{ inputs.cts_executable }} - TEMPLATE_PREFIX: ${{ inputs.template_prefix }} CONFIG: ${{ inputs.config }} FILTER: ${{ inputs.filter }} SHARDS: ${{ inputs.shards }} run: | set -eu args="--wine $WINE_RUNTIME --cts $CTS_EXECUTABLE --config $CONFIG --shards $SHARDS --out $RUNNER_TEMP/results.txt" - test -z "$TEMPLATE_PREFIX" || args="$args --template $TEMPLATE_PREFIX" + args="$args --template $TEMPLATE" test -z "$FILTER" || args="$args --filter $FILTER" # --strict: any Fail, Crash, Timeout, InternalError or Missing case fails the job. status=0 diff --git a/CMakeLists.txt b/CMakeLists.txt index e34f0654..40adf284 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -52,8 +52,8 @@ find_library(QUARTZCORE_FRAMEWORK QuartzCore REQUIRED) # --------------------------------------------------------------------------- # WineMetalGL core: the OpenGL implementation on Metal. Built as -# libwinemetalgl.dylib, which winemac.drv loads from its own directory -# (patches/wine). +# libwinemetalgl.dylib, which the Wine module's winemetalgl.so loads from its +# own directory (src/wine, scripts/build-wine-module.sh). find_package(Python3 REQUIRED COMPONENTS Interpreter) set(WMGL_GEN_DIR "${CMAKE_CURRENT_BINARY_DIR}/generated") @@ -84,6 +84,7 @@ add_library(wmgl_portable STATIC src/core/format/formats.cpp src/core/format/pixel_transfer.cpp src/core/format/vertex_decode.cpp + src/core/shader/border.cpp src/core/shader/compiler.cpp src/core/shader/fp64.cpp src/core/shader/geometry.cpp @@ -91,6 +92,7 @@ add_library(wmgl_portable STATIC src/core/shader/glsl_upgrade.cpp src/core/shader/tessellation.cpp src/core/tess/tessellator.cpp + src/core/shader/spirv_input.cpp src/core/shader/spirv_passes.cpp src/core/shader/subroutines.cpp src/core/util/log.cpp diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 25276458..3bdf5515 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -27,8 +27,8 @@ build/host/wmgl_host_tests && build/host/wmgl_gpu_tests ./scripts/check-forbidden-identifiers.sh ``` -Changes that reach Wine (WGL, contexts, surfaces, `patches/wine`) also need -the Wine fixtures (`tests/wine/probe-opengl-wow64.sh`). Changes to core +Changes that reach Wine (WGL, contexts, surfaces, the Wine module in +`src/wine`) also need the Wine fixtures (`tests/wine/probe-opengl-wow64.sh`). Changes to core rendering or GL state that could move conformance results also need the affected Khronos CTS lists, in `mustpass.xml` configurations 1 to 4. @@ -53,9 +53,9 @@ affected Khronos CTS lists, in `mustpass.xml` configurations 1 to 4. `scripts/update-checksums.sh` so `MANIFEST.txt` and `SHA256SUMS` match. - **No Apple OpenGL.** The core must not link OpenGL.framework, and it exports only the driver ABI in `include/winemetalgl/driver.h`. -- **Wine patches apply to Wine 11.17.** `patches/wine/wine-11.17` must keep - applying cleanly to the `wine-11.17` release; regenerate them with - `git diff` from a patched tree rather than editing them. +- **No Wine changes.** The Wine module (`src/wine`) builds against the headers + of an unmodified Wine 11.x and installs next to Wine's own modules; it must + not need a patched or rebuilt Wine. ## Code @@ -78,6 +78,7 @@ affected Khronos CTS lists, in `mustpass.xml` configurations 1 to 4. ## License -WineMetalGL is LGPL-3.0-or-later; the Wine patches in `patches/wine` are +WineMetalGL is LGPL-3.0-or-later; the files of the Wine module adapted from +Wine (`src/wine/pe/wgl_formats.c`, `src/wine/pe/wgl_fonts.c`) are LGPL-2.1-or-later, like Wine. By contributing you agree that your changes are licensed under the same terms. diff --git a/DEPENDENCIES.md b/DEPENDENCIES.md index 25b06c4b..19b121b5 100644 --- a/DEPENDENCIES.md +++ b/DEPENDENCIES.md @@ -14,8 +14,11 @@ Local changes to vendored sources (each marked `WineMetalGL:` in the code): - glslang `glslang/MachineIndependent/linkValidate.cpp`, `checkStageIO`: the Vulkan "every input has a matching output" check skips built-in blocks (`gl_PerVertex`) and redeclared built-in inputs (`gl_ClipDistance`), which - GL provides implicitly, and inputs the stage never reads, which GL does - not require to be written. + GL provides implicitly, and inputs (and input blocks) the stage never + reads, which GL does not require to be written. +- glslang `glslang/MachineIndependent/linkValidate.cpp`, `addUsedLocation`: + vertex shader inputs may alias locations under the relaxed Vulkan rules + WineMetalGL's OpenGL code generation uses, as they may in OpenGL. - glslang `glslang/MachineIndependent/linkValidate.cpp`, `isIoResizeArray`: per-patch tessellation evaluation inputs (`patch in`) are not arrayed per vertex, so `patch` arrays match their control-stage outputs at link time. @@ -77,9 +80,10 @@ Local changes to vendored sources (each marked `WineMetalGL:` in the code): locations it spans (`getExplicitUniformLocations`), for the linker's GL 4.3 uniform location assignment; the Vulkan rules drop the qualifier. - glslang `glslang/MachineIndependent/ParseHelper.cpp`, - `vkRelaxedRemapUniformVariable`: an unsized uniform array takes its size - from its initializer before the Vulkan rules drop the initializer (the - values are applied from the default block, `applyUniformInitializers`). + `vkRelaxedRemapUniformVariable`: an unsized uniform array (any dimension + of an array of arrays) takes its sizes from its initializer before the + Vulkan rules drop the initializer (the values are applied from the default + block, `applyUniformInitializers`). - SPIRV-Cross `spirv_glsl.cpp`, `bitcast_expression`, and `spirv_msl.cpp`, `emit_atomic_func_op`: atomic operands are read unpacked, so an element of a scalar array in a std140 block (padded to a vector) is passed as a scalar. @@ -109,8 +113,8 @@ The built dylib dynamically links only Apple system frameworks and libraries libobjc); it never links OpenGL.framework (`scripts/verify-source.sh` and CI check this). -Wine integration requires the Wine patch series in `patches/wine/`, applied to -a Wine 11.17 source tree (`patches/wine/README.md`). +The Wine module (`src/wine`) builds against the headers of an unmodified +Wine 11.x (`scripts/build-wine-module.sh`); its PE side needs llvm-mingw. Test tooling that is not vendored: the Khronos VK-GL-CTS (commit `1942832`, built with `tests/cts/windows/build.sh` and `tests/cts/native/build.sh`), diff --git a/MANIFEST.txt b/MANIFEST.txt index f720afe4..cce8ddab 100644 --- a/MANIFEST.txt +++ b/MANIFEST.txt @@ -18,6 +18,7 @@ docs/api-coverage.json docs/building.md docs/conformance.md docs/decisions/0001-gl-implementation-strategy.md +docs/decisions/0002-gl4-architecture.md docs/games.md docs/performance.md docs/release.md @@ -32,10 +33,8 @@ LICENSES/SPIRV-Cross-Khronos-Free-Use.txt LICENSES/tessellator-MIT.txt LICENSES/Wine-LGPL-2.1.txt MANIFEST.txt -patches/wine/README.md -patches/wine/wine-11.17/0001-opengl-wgl-fixes.patch -patches/wine/wine-11.17/0002-winemac-winemetalgl-core.patch README.md +scripts/build-wine-module.sh scripts/build.sh scripts/check-forbidden-identifiers.sh scripts/cts/diff.py @@ -43,10 +42,10 @@ scripts/cts/results.py scripts/cts/run.py scripts/cts/summary.py scripts/docs/generate.py +scripts/install-wine-module.sh scripts/stage-arm64-core.sh scripts/stage-host-libs.sh scripts/stage-release.sh -scripts/stage-wine.sh scripts/traces/benchmark.py scripts/traces/replay.py scripts/update-checksums.sh @@ -60,6 +59,7 @@ src/core/api/compute.mm src/core/api/copy_image.mm src/core/api/debug.mm src/core/api/draw.mm +src/core/api/dsa.mm src/core/api/fixed_attrib.mm src/core/api/fixed_get.mm src/core/api/fixed_raster.mm @@ -123,6 +123,8 @@ src/core/proc_table.h src/core/program.h src/core/program.mm src/core/query.h +src/core/shader/border.cpp +src/core/shader/border.h src/core/shader/compiler.cpp src/core/shader/compiler.h src/core/shader/fp64_library.inc @@ -133,6 +135,8 @@ src/core/shader/geometry.cpp src/core/shader/geometry.h src/core/shader/glsl_upgrade.cpp src/core/shader/glsl_upgrade.h +src/core/shader/spirv_input.cpp +src/core/shader/spirv_input.h src/core/shader/spirv_passes.cpp src/core/shader/spirv_passes.h src/core/shader/subroutines.cpp @@ -158,6 +162,18 @@ src/core/util/name_table.h src/core/util/ref.h src/core/util/version.h src/core/version.mm +src/wine/include/wmgl_gltypes.h +src/wine/include/wmgl_wine.h +src/wine/pe/wgl_fonts.c +src/wine/pe/wgl_formats.c +src/wine/pe/winemetalgl.c +src/wine/pe/wmgl_pe.h +src/wine/unix/unix_main.c +src/wine/unix/unix_wgl.c +src/wine/unix/unix_window.m +src/wine/unix/unix_wow64.c +src/wine/unix/wmgl_unix.h +src/wine/unix/wmgl_window.h tests/cts/baseline/gl30-cfg1-native.txt tests/cts/baseline/gl30-cfg1-wine.txt tests/cts/baseline/gl30-cfg2-native.txt @@ -285,6 +301,8 @@ tests/gpu/framebuffer_tests.cpp tests/gpu/geometry_tests.cpp tests/gpu/gl4_tests.cpp tests/gpu/gl43_tests.cpp +tests/gpu/gl44_tests.cpp +tests/gpu/gl46_tests.cpp tests/gpu/gpu.h tests/gpu/main.cpp tests/gpu/pipeline_tests.cpp @@ -304,9 +322,11 @@ tests/host/shader_tests.cpp tests/host/test.h tests/wine/core_triangle.c tests/wine/cts_wgl_sequence.c +tests/wine/fixture_version.h tests/wine/fixtures.txt tests/wine/opengl_1d.c tests/wine/opengl_blend.c +tests/wine/opengl_buffer_storage.c tests/wine/opengl_clear_metal.c tests/wine/opengl_compute.c tests/wine/opengl_contexts.c @@ -372,6 +392,7 @@ third_party/khronos/REVISION third_party/khronos/wgl.xml tools/gen/extensions.txt tools/gen/gl_gen.py +tools/gen/wine_gen.py vendor/glslang/_config.yml vendor/glslang/.clang-format vendor/glslang/.gitattributes diff --git a/README.md b/README.md index 3fa2f273..1d1585bd 100644 --- a/README.md +++ b/README.md @@ -1,22 +1,27 @@ # WineMetalGL WineMetalGL is an OpenGL implementation on Metal for Wine on macOS 15 or newer. -Wine's macOS driver (`winemac.drv`) loads it instead of Apple's OpenGL, so -Windows programs get OpenGL 4.3 contexts, core or compatibility profile: -geometry and tessellation shaders, double precision, transform feedback with -vertex streams, separate shader objects, viewport arrays, image load/store, -atomic counters, compute shaders, shader storage buffers and texture views. -Contexts created without a core-profile request (which Apple's OpenGL caps at -2.1) get the full 4.3 compatibility profile: -fixed-function vertex and fragment processing, immediate mode, display lists, -client arrays, legacy GLSL built-ins and the legacy pixel paths, together with -every 3.x and 4.x feature. It runs x86_64 and i386 +It installs into an unmodified Wine 11.x as a Wine module of its own, and +gives Windows programs OpenGL 4.6 contexts, core or compatibility profile, +instead of Apple's OpenGL: geometry and tessellation shaders, double +precision, transform feedback with vertex streams, separate shader objects, +viewport arrays, image load/store, atomic counters, compute shaders, shader +storage buffers, texture views, buffer storage, multi-bind, direct state +access, clip control, cull distances, SPIR-V shaders, indirect draw counts +and pipeline statistics queries. Contexts created without a core-profile +request (which Apple's OpenGL caps at 2.1) get the full 4.6 compatibility +profile: fixed-function vertex and fragment processing, immediate mode, +display lists, client arrays, legacy GLSL built-ins and the legacy pixel +paths, together with every 3.x and 4.x feature. It runs x86_64 and i386 Windows programs together in a WoW64 prefix. -The library, `libwinemetalgl.dylib`, has no dependency on Apple's OpenGL. It +The core, `libwinemetalgl.dylib`, has no dependency on Apple's OpenGL. It compiles GLSL with glslang and SPIRV-Cross to the Metal Shading Language and -implements the GL state machine, resources and draws on Metal. Wine reaches -it through the small driver ABI in `include/winemetalgl/driver.h`. +implements the GL state machine, resources and draws on Metal. The Wine +module (`winemetalgl.dll` and `winemetalgl.so`, with a forwarding +`opengl32.dll` in the prefix) carries WGL and every GL call to it through the +driver ABI in `include/winemetalgl/driver.h`; Wine itself is not patched or +rebuilt ([Build it yourself](docs/building.md#the-wine-module)). ## Proof Of Concept @@ -56,13 +61,14 @@ OpenGL 1.0 to 2.1 are implemented by WineMetalGL itself, as its compatibility pr OpenGL version, configuration and runner, with the reason for each failing case. - [Unsupported](docs/unsupported.md): what WineMetalGL does not support and - why: OpenGL 4.3 and later, the gaps in the compatibility profile, what the + why: the gaps in the compatibility profile, what the Unsupported % counts, the failing CTS cases, and every case the CTS reports NotSupported. - [Test suite](docs/testing.md): each test suite, how to run it, and roughly how long it takes. -- [Build it yourself](docs/building.md): building the core and a Wine 11.17 - that loads it, and checking that a program uses it. +- [Build it yourself](docs/building.md): building the core and the Wine + module, installing them into a Wine 11.x, and checking that a program uses + them. - [Performance](docs/performance.md): frame times against Apple's OpenGL. Also: the [game corpus](docs/games.md) results, [releasing](docs/release.md), @@ -79,6 +85,7 @@ General Public License, version 3 or (at your option) any later version Public License version 3 in [`LICENSES/GPL-3.0.txt`](LICENSES/GPL-3.0.txt). Releases up to 2.1.0 were published under the MIT License. -The Wine patches in `patches/wine` are LGPL-2.1-or-later, like Wine +The files of the Wine module adapted from Wine (`src/wine/pe/wgl_formats.c`, +`src/wine/pe/wgl_fonts.c`) are LGPL-2.1-or-later, like Wine (`LICENSES/Wine-LGPL-2.1.txt`). The vendored glslang and SPIRV-Cross keep their own licenses (`LICENSES/`). diff --git a/SECURITY.md b/SECURITY.md index 095f7ae0..215ddcb6 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -39,7 +39,7 @@ GL and WGL call. In scope: writes, use-after-free, integer overflows in sizes and offsets); - input that crashes or hangs the host process or the GPU beyond the program that sent it; -- problems in the Wine patches in `patches/wine` or in the release packages. +- problems in the Wine module in `src/wine` or in the release packages. Out of scope, and better reported where they belong: diff --git a/SHA256SUMS b/SHA256SUMS index 23882f1c..377f2fd9 100644 --- a/SHA256SUMS +++ b/SHA256SUMS @@ -1,29 +1,30 @@ 1d3abfa5d212943512da6bd4741b2665b7605b012748576278178844adeffc20 .github/CODEOWNERS e49b0bad89c4caf2a07bb3fa625ff431977115a81a39b455061e96534be291a0 .github/dependabot.yml -57d1c051dfe61e89af1b76d30f89861dac2cabfee8905018111c5b2513e7b964 .github/ISSUE_TEMPLATE/bug-report.yml +f019ad714644b60cfcbd219ae6bccc432f6a9ec557af2e4038889baf97652893 .github/ISSUE_TEMPLATE/bug-report.yml 3116ae3ff6169a040fecaabf665eb17ff8a1a18550337c2524ab34547bab3691 .github/ISSUE_TEMPLATE/config.yml f5721e1869b3955e4af862947003e6ebac08450b9cad532b76c9bdd1004e02c8 .github/ISSUE_TEMPLATE/feature-request.yml f909877aa656960852496df676f2e1ef3b02eb384c564a23ad7b4faede2343c0 .github/ISSUE_TEMPLATE/game-compatibility.yml -57558ef266bee3d81e5e97cbb9f651b95f4691c7df109631831a076144a6b84d .github/PULL_REQUEST_TEMPLATE.md -75b509b7523585443ca8c61fdbaca26e309f4facfa61967e72194b5bd6c3b0c8 .github/workflows/build.yml +7bb422acabf65fc035420ac2c3891cda37572c2cbf2ecd69b49754aaebb62346 .github/PULL_REQUEST_TEMPLATE.md +ab0fbb39824b25294bb4b13d8406358e86424e2544de09faba779bc9a82141f4 .github/workflows/build.yml 4180360465ed6f8e3880f7d3a2590eaad919d6599dcf38ad0e1df0cff83adc8c .github/workflows/codeql.yml -42d0127e6ed2f1dcd548b4ff4daeb4ce944132eae28164dff8d60993554bc592 .github/workflows/gl33-conformance.yml +d7522376906756a55cc650729fe3ac8d580782da0d9c4f3e691ca238e3e7fb5d .github/workflows/gl33-conformance.yml 88af1416678f84d2eec850f894db307643e812ec851d1811b207ff83f7b00932 .gitignore -2d2df73fa98cf99f37ad77800745674d14f94eebab721239c10025e2b247708f CMakeLists.txt +65d1df08c8c7ab8a76f0d95939bf7e8a2d4ea48847fb8a72b82f5c712ca72658 CMakeLists.txt 835b45e441b772dad742f0636a55e1eda61ded5db3742fcfb40d7ef9efeabbcd CMakePresets.json ff306fafcc6be6314847f7024bd0d286a3ef5625cfb09b64268b743d69a0810c CODE_OF_CONDUCT.md -3c223a2ff998b943db6ff94441ddd0f5de7261ed69f4762ea6b22c535de57ea7 CONTRIBUTING.md -c53fcda2e6c47bd90199d0bea90b196cccbb26f23aad11136ffeaeba251dde23 DEPENDENCIES.md -abf579f3705ba1c1b98061fd5f25983615726f6ed3e9ea00f78f8a2e6e1402d0 docs/api-coverage.json -2c8d0fcc6bd139ce8a148251f63cc33f9ee05a79898a5ba0f85d16e21c81a8d7 docs/building.md -95974fd899b6ff79a37cff6b376f6290434ee59e95db074c50c33a58d3df3a32 docs/conformance.md +932fd61da5393f8826781187555b30709a50dfaf403b04927163fde611921495 CONTRIBUTING.md +d001020847ceca4a9affd8e5f917de0be0d6cccfade0cd128516668e72ef9650 DEPENDENCIES.md +6b6592ae4a277602a76062d92de802ea9d1d96a07cca23b8915428417c85e44d docs/api-coverage.json +544e92153bda1990552a3e1eeb9c69e54044780ec515fc04f1e7128a717e6154 docs/building.md +7dc0d1e44705f42765da9e5fd394d81c8b877740829f7fc409d82d59fd9a70ad docs/conformance.md 04ac213790c398f562eb56b80df5f29ec98ce5a33ff1c53b50cf746f0835a237 docs/decisions/0001-gl-implementation-strategy.md +1f5fc4f6155d77bfe6b0cdfa9118bad39fd2017438e82313d69916039c54f02d docs/decisions/0002-gl4-architecture.md be80f937e20c9ece7c672c43d254535728bfb90479c7027d2c4eda25ab745d70 docs/games.md 4b7c359d6124a089f5b06bc59dcc4fb6e8f12e7030e9f6adf10e4f67627a2e29 docs/performance.md -0cf0c13b9b64aa536827f4b85e0bb12830152f07a0e2efcebfb9692e0782cec4 docs/release.md -ca118fd31a89143b0057c0e73e0345da1203ca5853a154369e134242f419e40c docs/testing.md -2c56ba7e5c9be18af52746e00e9b144cbfdfd2e361776135b445bc3d8cda1c12 docs/unsupported.md -9406d3f4ca2743990831877b786f4b02721e3c9ef7e864a1c4d145e38b4aff61 include/winemetalgl/driver.h +31e16a110ff5df631d15e86e071fe27a589e8589ac3b9cf79f361a7fc1e4305a docs/release.md +4a0a77f41f1c9d57bfe17ded11eb13d488f157e4b9f6a45681c02776b339b513 docs/testing.md +46610eed0f6853326376e3300faa543a706bf3d124722cebaf4cb0cf47735c03 docs/unsupported.md +d1a5728f829c5c8c5d778f916700e851657a26c348170638e6448ebee3ff81d1 include/winemetalgl/driver.h e3a994d82e644b03a792a930f574002658412f62407f5fee083f2555c5f23118 LICENSE adb783e734e906d1f46db5df29991dbde84bdb0ceab502ac2febb44fe3c2b5f4 LICENSES/glslang.txt 3972dc9744f6499f0f9b2dbf76696f2ae7ad8af9b23dde66d6af86c9dfb36986 LICENSES/GPL-3.0.txt @@ -31,54 +32,53 @@ cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30 LICENSES/SPIRV fbeaca472f4f70e276dd1106ca5097435967a22ad6c1d8200aef7ad9f70aaf3f LICENSES/SPIRV-Cross-Khronos-Free-Use.txt 5c622abfa675949701a011e120ee04564f1ecd53ea94488621d341c3efa25b36 LICENSES/tessellator-MIT.txt e237fa56668030e928551ddd60f05df5fe957f75eab874bbd017e085ed722e7c LICENSES/Wine-LGPL-2.1.txt -99507131095e7d56fbbcbcd645309cfa52e9a7c4534a248af7a4da515b0c3642 MANIFEST.txt -72dd0235d7a35057df4ec17813a5ba6a0136ec98cd74e63b119f6a27324a5820 patches/wine/README.md -2646bc10ed0e75206f9570eae7d9a69beb59e68c117b39db815ae658b618c39e patches/wine/wine-11.17/0001-opengl-wgl-fixes.patch -27ea4506f74db1934f54e7e0e30171d3ddf0faeed1148ce0532093b3fb6140f5 patches/wine/wine-11.17/0002-winemac-winemetalgl-core.patch -0e4b15aea69e58055842f1787518b0752fcbdc962745845f60464bd1259f3d3a README.md +cf870b9dec62ebc10baf4dabab20707d6ef63c0e0dca78a1cdd93c4fa5c27804 MANIFEST.txt +4fa6b996ae78d570d30793d4c006be7fe9460d15201361df21a11211a799f741 README.md +913db3990303599bbeb0b9a67c9237c33c154eae95c042f064d58036b78f4303 scripts/build-wine-module.sh 94921d09a375c9e6bb042722fc91701313fb4631ed88b5469b3a95b6432fc374 scripts/build.sh 595eba100b052e88649657ef43baf1fdf497ae89b1be4da83b1a2c9268f74be7 scripts/check-forbidden-identifiers.sh e39c7d93f8c44b38cfcfe262dc1969cb6de518332bb0237d8f7a5feb01ab173c scripts/cts/diff.py 40042a0551254a33e094ba6cf2657722ebef0058fade54174fc2e231096f795a scripts/cts/results.py -dbbd760738dfd4020165ce8cce23dad6acbbd325aa2f7f7dd9eca4fda41bf5d7 scripts/cts/run.py +9872c74f905be9564194a2480b8dbfff553016645003fbb87136bbbfffa33917 scripts/cts/run.py 767fe6e18939cf74cd79d01f1d27320c15221e2a63b350d472e36efb6ccb119a scripts/cts/summary.py 52e4145de6d011ee8e4576a6385fdb495bc96bb3c8d69b7ee1d75e0247c2c4d3 scripts/docs/generate.py -b7c5962c25c1f9e6882354a8de278c3a669b8cdd4563041fbd038d69608eb525 scripts/stage-arm64-core.sh +e8aacf89c694cbbf7363b034e3b7f93eb4eba61418dfc412ff61d84e354acdb4 scripts/install-wine-module.sh +be54f5d9b461f1c84d1a04d8cf1822cc99a6690e7254e42f58991757931846c7 scripts/stage-arm64-core.sh 093156b86267a491bed571f3940980b020298bf7d036c79c3715d7b1a4fde614 scripts/stage-host-libs.sh -0cae76b0ad0a2406a64db79a7f4676cae8393715f515af947e5197e91b3cfe6b scripts/stage-release.sh -565f8ffb4cd337e3268d121ae8e0cfeace8818cb982b36a0615ddb22dde556ee scripts/stage-wine.sh -84253f24319c8487886953e30daabe2dba5f12703258adbbdaa6bdc9c7c14b56 scripts/traces/benchmark.py -817aefd47453af37a4c2fa0c59ac0506671e1c8684749bec6bd94bb7edd22d0d scripts/traces/replay.py +504fc7197af34e93515c186eb7d4126f10c3a32a6ce683d725fbb2a89fcf29ca scripts/stage-release.sh +15fb5b227a7ccf7b7925f3639c56aaf2c1948758574325272ef123cfe8450495 scripts/traces/benchmark.py +355e85ffb56f47c3a880bdfcaf117146874eb8d6dc04dff5ed4e0c947b8b1bab scripts/traces/replay.py d4122875503aff9ddc8507e0b70b39eea16b648a7808445500474d585d9f872b scripts/update-checksums.sh -883e20b499a54157d34317ea97fd742e81a6e7a773f41012f4433ef2fafee86e scripts/verify-release.sh -c99ff1755a03a423109349b0b4c3e7a25993e87c5c664a5995312750d122dfe2 scripts/verify-source.sh -be500f7b5df5266aa7ce48d2b269f6de149245409afc72c2d319e16ca7fcf821 SECURITY.md -12a5177489fa2912905e4dcdb478ca81823a9feafec8be55023daf52fb27466a src/core/api/blit.mm -918edc836482eaeb362f33cda3a2a005f6268e92ff181e4dee479f43877b5a43 src/core/api/buffer.mm +29a9d84a15441d0aa92245e3cc7c2398a2acc469592c3c72b7b171d1164dd3ba scripts/verify-release.sh +8af04883afb541abb8cc83f108386bdbf0906df3b4bdc56316aaa8d56e86b6bf scripts/verify-source.sh +025bcdebea1505700766eeb87c8f5af722b48224e68868e590caeab991101dd2 SECURITY.md +bd623c6e46634bfc0428d7e6bf0f9fde1db1d9770ccd7f5169d6a7b78ae36eb3 src/core/api/blit.mm +ad0aed0cbdfd062f1a933e93ad5e39f96f249977b7d23df1bc2f8d97a228be78 src/core/api/buffer.mm 1250f7dffaa5d948e36c15f484be2ba735480fe28bf768ffd72326c746248f3d src/core/api/compute.mm 9fad0ff3e979320e148d3f131aefb70d8b7c0f22bc886183a5beb8531c728439 src/core/api/copy_image.mm d8bbf9e1912d351772aaa540f7d63a0378fcb40cfc4a5193a2aab59fa7edf748 src/core/api/debug.mm -f57cd21e852aaee901540ba9a09102016465d99548fb16adebf6007c8d960b1d src/core/api/draw.mm +999c27f68138d89231436c6e055bc45816227aa1b3d50427d13f9d1034a08652 src/core/api/draw.mm +bc93b17d70a63f0b4f49b5308371ea57d16547470f2a368a30ea4cf063bb95b6 src/core/api/dsa.mm b857990c15240cdb258cc17620b74927d0759f84038aac3918862d4d0800c9a9 src/core/api/fixed_attrib.mm c2864feefca469061d8badb218aac474a19c426490f3b986c64e06f7de3d361f src/core/api/fixed_get.mm d2deb4109f78ba43585bd0628ee0c7e2cc70041ba43856ae6fdcbc9192226931 src/core/api/fixed_raster.mm 8e6c4e08ec6b7f7e9bb7394d8cc33623b944498552508c3bdff61dabbf5bd6b8 src/core/api/fixed_select.mm 81504e5642224088788e6f31930835c46039fea39630cb9b911b3ed08785ce26 src/core/api/fixed_state.mm 05113953e422700980611ac3179c400b5b64299deeca21b24cdeba0f5b82d34c src/core/api/fixed_vertex.mm -34d40f0e10e3b2ed7c31371fdb47c5678fa6eea5fe4b16d4b79231fff3826b17 src/core/api/framebuffer.mm -845f288137f06f110d97280452edf73e93e05a757de2ff33d171f7535de7c3a1 src/core/api/get.mm -cb70e13a19128d12dd56e047158605b8570ed890380ef03049d0ee0d665ea51f src/core/api/image.mm +508641638ba03072f203c0fce7aee342f33921fd99e142e5b96f50b2f77e1fa6 src/core/api/framebuffer.mm +20da9a94346db250697536d49485227e36da33f4161dc93c9465fb35196be231 src/core/api/get.mm +3569ef48e672976865add94e892518f9022d02273290ecfd8002e3cfede9a813 src/core/api/image.mm d9906e39e5b5681f30f97fc9bd29924fda73fa535ba4dd03b7e56115d8b9e316 src/core/api/pipeline.mm -8c1f9e8a849b3218b8debe43488fd24400116149846b0fa912eabf624c8065ef src/core/api/program_interface.mm -a3ba2595a223de8805d7a466cd1e67a4958ec690bf93eb696d757f692f5ed9f8 src/core/api/program.mm -d2f1109c30f1af608fe4ae40b7f06a00de514418d64f51a0ed45bf7684044955 src/core/api/query.mm +757b4e20e66078edad8b6acc2ed3a5f27c3076ea64ff8679eadbbe791425fd00 src/core/api/program_interface.mm +6eee4af97b25f494721f0ad33f046db93790bdb87086143729f0a6a532f56538 src/core/api/program.mm +d465a8552f1418e464cd168c9be9452b3e6a3c245f8eb9837ffcfd6a6fe80c8e src/core/api/query.mm 3aa311e05cde81dc3e48a5306156830122505e1cfe2b7049def1d068efa437fd src/core/api/robustness.mm -c0be61e9b36a138e9ba27b1d6ee33a8daa70ccc65e2cf45060ca4b4b9a882946 src/core/api/state.mm +a121f892e0b390bda2a5997c59a0288cf12d431ba8ab62c0cd6c702bfd5799e6 src/core/api/state.mm c8924a3ae1b533b11527145d2d69b9535d8a9bd53ff49dde3792cef52245ab47 src/core/api/subroutine.mm 443345df8f82bb22f4ff9d06b4dddc3ebb5f97cd6dc18c5936645e35188fb3f1 src/core/api/tessellation.mm -b6ef706c697621d44ec649bfd05670d7aa34be5ddfeee9288c640de74999b999 src/core/api/texture.mm -92d9fe03ef8aa188fec67eeea9ae91dbead8c6fed149a2a672ec97585ade8b6b src/core/api/transform_feedback.mm -5100d6c383020ba3a7826596ba7d11b96af20c935bc7437e44705f1c6fec898a src/core/api/vertex.mm +b10a4a918a3322d747aefa3c46b5b4cc5c4e758309d4c74b73039fa28d42d710 src/core/api/texture.mm +f1237cf8ce6c7f20de2a7352449ddb0e132a062869a4130a2a408030513f1b16 src/core/api/transform_feedback.mm +885b1871dcce5f0f2b79df15fbd865c1964ed41646cceeaa039364f9c9c59073 src/core/api/vertex.mm b5bf470fa94b447761b8df2b6e4f2bd38bed92dd70e639aa58a1efb00a243649 src/core/compat/compat_api.h 6114a2a93b7c68b5f1d897e48aeb9f2db9bf8663703b9d2e3536e42cbb0ff239 src/core/compat/display_list.h 3fecd8d085dbd2969f8207563609343a493f49d3e28e32008f2b8cbdcc44a1f2 src/core/compat/display_list.mm @@ -86,29 +86,29 @@ a319a785e06cccc8cdba703e4c94a95ed0ea5b3dbe6262f97d796a7058d4bded src/core/compa 381d908b862fe8dfb4f5cef528ad306ea44285a3b84eaf961f8311d3ddd9629c src/core/compat/fixed_function.mm 4a8a3a8a2ec368be434f9f57f15b91bb66d1a00d003c8f71af5f934dff6565f6 src/core/compat/fixed_state.h 66df8f9a8d323030d2b88ffc850c89717736caa099b2c766123dafbb576f1fab src/core/compat/immediate.h -b8f5723341c43187ef793ba19bf0b4e5ffc3a266ab4204247e92b2cedf3dfef7 src/core/context.h -034239a865bde2e2f0e8613c5858de2290577ae3efa5d63814995e5c84bf0fed src/core/context.mm -b5bd0614c9538fc3dc1f56007d4da8dbd32afcf21da4f7689a30601f66ed1550 src/core/draw.h -355486ed444b6dd81b74db0352f7156ca5db2dcd53b16c2728e6fb5858ff6c3c src/core/draw.mm -6a132f07e7ab549716131b9454940ede63dd28d4f95285d43acb5e265d41c74e src/core/driver.mm +f4a4db23deedf7ca682dc7fa8e37e1e81114f38237433e73340d61d92fb910c1 src/core/context.h +edb7d50d1b3f8881d2d3ad72e52413c269c69cdc11417158105a5427f424e35a src/core/context.mm +cdf77bd905eadd9fe3c5c1007e4ec0fab5e0f5e418fbf0bfa6adffc6f1326cc3 src/core/draw.h +6d61e3a792d36101dc6046f4c9086d30d287b4d1bcbc333cd392f40bfdce0e2c src/core/draw.mm +b04fdd73f968b3a219b8f8a47f582cd5e2f434356ed50162642766dafb6f006b src/core/driver.mm a1b999a5561f4cd06b1764c083a9ee6fc0f9266e3cd360d50180d3c7e1175a91 src/core/format/block_encode.cpp 214ed2cbd3f2c51db9e77b3934bd8957baf43914e1f419f3b4231104af7ffbff src/core/format/block_encode.h -cd9d0e4ef68e5060a5057741a9bc7fa0046d1515f9baa1789b543e06557ef467 src/core/format/formats.cpp -77143ffe1d53cca5a7b419263dcc87fe5e58a765b431b312a18e741e388f12df src/core/format/formats.h +b993acdb223a561fe157252ede8c67978bc0a3a30d58181320cb9d408529efa7 src/core/format/formats.cpp +059344000eaf9ce26be83152f1d6a7795b861c007b61aee15cf3d8087bc2e052 src/core/format/formats.h 4daea90e2987e2baa7e03c6d35ee27e55b0f435e1fdec80c6b91d604837a7dee src/core/format/pixel_transfer.cpp 6c94aac84dbce1c7fa1a62ae13c9e8b178c82da37971fe9bc684c08d0c80e47a src/core/format/pixel_transfer.h b89c589eec66864698cf268f2c0e76a91d562c5a63e9ab8a0a7eb5c0876fa5f8 src/core/format/vertex_decode.cpp bdfa56d77febb267667f37b3edf5122588727484e39039146e0e94e4a4290389 src/core/format/vertex_decode.h e75f6ae6d3d9e2cc40ca488526d68bbfdad0a7e98ce7dfd163f5aa468bfda593 src/core/gl/types.h -73bf22f7a10a96211bb3ae2e54feb72ff67c14807de0c8de7783859eb81a713a src/core/limits.h +0bd0aceab4ba105544b9dbbbb1887707f6906f868a98bfdb298acb06252c1fd8 src/core/limits.h cc0d4f842871c70d7d6bba4d7e42bd0d2d596b1e1efe10709dd8cb562a57eca3 src/core/metal/command_stream.h 45f7f28b32c752c6a0182759851eb28a9ee29d72421c682d2c92825712b3179c src/core/metal/command_stream.mm 4b6c1f7a3a3c8a1472a543a1a58a237f223e7dc22566ae2db2d2760857acea8c src/core/metal/device.h 4e9e4299a14274a312a02bc5873867a7f96bfc858e7df8e1004fa0f10f40a07c src/core/metal/device.mm 48207bb43aa38d0fd39685324fd0407ac8d10436770cbdc14133d5541f932add src/core/metal/enums.h 13ef0ee6e2c7106ac35bd65ca5c2035a6dd8872b8de2b2d9b1336404970f0c35 src/core/metal/enums.mm -7aafdcc46be044e6869fc2abfef78c352d2d91be06be40642c6745a97ad3e706 src/core/metal/pipeline_cache.h -7772cd453d05bd78fa5c12e0a4efcfc0d920d93c2e5c3549429a8d1118344d99 src/core/metal/pipeline_cache.mm +67ff86c4cb12a0afb98f20e79c1f8120b874a1c278828579307343ef91d38544 src/core/metal/pipeline_cache.h +78558ca157662bba2cb6cf2f73089d8077d4cdee8f21747e0b78e382c3c948a1 src/core/metal/pipeline_cache.mm b4bfb73513cc082f16bbdc8816db8547b209134e8acbefe9d65e69d638914bb2 src/core/metal/pixel_format.h 80f3414f9898a6b540be0528520ca04f80b0e478894ac640840baa48497347dd src/core/metal/pixel_format.mm 98afdd9f75c9a569dbe18cd6a283e10820dc577c06c7ed47fbfc5a3bd88e6bc9 src/core/metal/queue.h @@ -116,47 +116,63 @@ bc13e7134ffb89c55a7564e581d59320be9f5e4087ac806756767c4dc0545929 src/core/metal 9bc230c9961a69e52639f2a48d5f1888aca01db5ed09863568a02dc8c5f6cbd0 src/core/metal/transient.h ff9135b0443af02762a0c96e82309a20602231727bfe6152c415becc9dda0e8c src/core/metal/transient.mm b3bacced498ac0c0d8543238ced0cac98cdc37dba5f34efc8ab53953970bd8be src/core/metal/utility_shaders.h -267773b2a72a3f535cb7009923d87bf0ea1b881435900d2f4f5cf630b11e0146 src/core/objects.h +ee780be32a3bb23fbccaa9f969f8bfc7ea6b749702a694e7b2ed5b6348dd16ef src/core/objects.h fadd564dceacc7d981585ca50c0616613f74fe6e96b7b7f937ded0cdfdc21749 src/core/proc_table.cpp 1267bf9f5437340f4610f077f9c061b5c04fd9df60a384182304055bce18af72 src/core/proc_table.h -dac8cae87737281dc152fa4c7dad3f2a1da6f31058f066438ea4d5a66c875834 src/core/program.h -4236b3f4fd3b93415824a9978b6533fb5c9fe68c7c3c193a0adde7e29b0a9fd2 src/core/program.mm -53cb375b77d57f042c05d9f03af260719628c55d63e3437d9deb3750048c9a86 src/core/query.h -96a28ee4adfa959e844aa0eedc3ce29f574b645241e90bc3825033d4da7d03b6 src/core/shader/compiler.cpp -48eeb95c3f175aa0e4dae200bd40177c3a5a67b8939fb365a0cde7531ea66370 src/core/shader/compiler.h +2979c04cc4298c468e2fe91578723da667509a66bc6723f757aee16752ee1572 src/core/program.h +89fdcdcbd9803a0515c47119cdb05f2f10b49bf0e8bcd86aa0c51817624ae1fa src/core/program.mm +d8e18bffe25aeacc38a42061ffdb5e8a7fe64c4fb60ba1a609cc7e85dec6a3c7 src/core/query.h +f5c08932f70c442f4d31e76b2803d737f8b67bca6b0587531b6e7dc31b61f532 src/core/shader/border.cpp +20c09f414bc1a9a74b3a52a1110cf01f6c448c9ab39c725a2e3b5ab66649b562 src/core/shader/border.h +319f9100650e202d4254b0aa3f730249edc641c865cc28d10937901cc34b5a20 src/core/shader/compiler.cpp +56d7d55c67f77fa82667b3420d308335bc389e2e7db37bd86c551cf1fef57a93 src/core/shader/compiler.h 6dc697235eb6afa4e189cc9d6481375fe9ebe535a3c3a569c15b70e8cf5778ab src/core/shader/fp64_library.inc 771664218f820fb8859d363850ed08d4bb642e2a5b3dda319b197ed798bbd0c9 src/core/shader/fp64_soft.h -5f0f07ce2aa58aab53fad3041db84be2be7a97e9b3c3ce7e74625b73547725f1 src/core/shader/fp64.cpp +655c1b5cb7cef7396322c25522a0f94d97a50bd49a4b8a68c78aec1765fb557a src/core/shader/fp64.cpp 576193b54a139ccf8160ae93bf1811d92eb8c2a85d2f7205db28d32f578b5a06 src/core/shader/fp64.h -e1d5e39a60a6c3d25a977183dd6a547877c37922f4cf99572bee5538fe400d31 src/core/shader/geometry.cpp -a87bfbb99c8d6f0542c826448e3951051482597997445b06dfa03adbba9a3a32 src/core/shader/geometry.h -ae8f537c0ee7c78a04418e41d4aa610bb54494103e0f7c3b9804c612902f2f13 src/core/shader/glsl_upgrade.cpp -028bdfde76b11c05f6d0aaddafa0221207fbb1c091ae0c518841854da49c113e src/core/shader/glsl_upgrade.h -1fee123c3cd15245d4aadf158834725c768cfc1e9690d27ca3a42faa65ab70ae src/core/shader/spirv_passes.cpp -27f06c82040889711eb9e7ac27f42dc06ce2bd64bf93688eaf63c11c3bb998c2 src/core/shader/spirv_passes.h +d31dd601bf6798a8895c4df34b9c871089343d3cfe89744a8396d9d1f04d4824 src/core/shader/geometry.cpp +8ead45c7933736356b83711639e4e34e89f0809540beaa4cc0d48821c9b15023 src/core/shader/geometry.h +601f267a2184761f7185e374c4b37f2850f3aacf8888501f5aaf10b24428709d src/core/shader/glsl_upgrade.cpp +fd3f8262d42cb758ca2655e6a324a51e2ac18c42e93ed9a373f432fc0cf7727c src/core/shader/glsl_upgrade.h +6f4a526eb75e8c82104ef0ee46c24816ea7a013ffe061edcfeefde2691f0c102 src/core/shader/spirv_input.cpp +305a2504f0a325a1d7308494f2206ac37a544b4c0ecef6cd937db5afb544083b src/core/shader/spirv_input.h +e3b0e3efa7803601cbb83226d7949bdfab90800945102ed6071907af6f903706 src/core/shader/spirv_passes.cpp +98e55b7d071bb3a63b39a8b8715b65761af1aff79ee67dcaad6bf94e740ab3df src/core/shader/spirv_passes.h 14e5cf5832965422506a9171ff5bf631a8dca25a63ebe5cffd05670fafa68c4d src/core/shader/subroutines.cpp 5e4f3355b4a5c6280e6ae8b15dbd27e2db20ec97a31b74c8364bd7e41cdfb209 src/core/shader/subroutines.h -b82746eaa6d031bec7e4ebdb6b2e033878c32dbda30f26ac499656e569ff5844 src/core/shader/tessellation.cpp -d5210af09108dcc56889baba3df9d459173d47dee0be3e2623aab19bf1d99809 src/core/shader/tessellation.h -59f200bd0a124421b92ae32caca48d73987fadb1004c7aebf22090e6130abc09 src/core/shader/transform_feedback.cpp +8ed48895172ec9845a1d2478a3dbead839bf191dfeb8650df0472cd9233c0ef0 src/core/shader/tessellation.cpp +14e3255c1d66003fafe0cdc661e9ab8221d652a216f6222620b62e8fea2bae3b src/core/shader/tessellation.h +d4dbd5d23f21eba275e9fba12ca84112790920f45e1b50df0044e1a109e5f5a4 src/core/shader/transform_feedback.cpp 72dbda34c93ce5977c565519fbd961f36c3885fb2b362f6da19d5a89b18c36dd src/core/shader/transform_feedback.h c0f21efe5b88ec80474f8ea8cfa37a622b51146369e9738f9994d74336fe3568 src/core/share_group.h b91fe6570935c0b520f976e8da71c1219300a4911b60e15cc5bddf58947cca0f src/core/share_group.mm -e0a64f0dc8dd6bee12672be849f7fce3a11954f07bc45bfaf0f2f8fe55999237 src/core/state.h +54f87f8d144295762b47bfac549f583daf25b60d918c1c32dff177faa956396f src/core/state.h c7074dd1ccb3fa9423b21e24e48172d4c9415be854e77946664c5f0ae03711bf src/core/stats.h 8209c1b0ce756d2563d4ae3e6f5e2f7a161cf3ced042decffee7f779eee79d8f src/core/stats.mm cb0a592f655cfa49d65068f9298fc05f111a89e55447ba2ae19b4189970e2e05 src/core/surface.h aab54a9fcec0d104f4f91eb359f87e86f64ddf6ab49ef55bca0e88eaf277bc48 src/core/surface.mm aeca5590d1a43a00bf85eb26234e0e9bac360a0cc0c1644ca4cb8ecdef73076a src/core/tess/tessellator.cpp e6aaaa842f2e0eb4fa9964ae92e20461976fb3cc855889438a44eaaeec86a761 src/core/tess/tessellator.h -e4dc0a7810b2e7e7cde6138ec924f9ed7c673e4d7266da1fdfc41f737c295161 src/core/texture_storage.h -7d0bb0f7b4ff0850226c6efe367d0a974cdb999057877c4135199c2364838083 src/core/texture_storage.mm +b34f422f5df7ef53791fc7318295a8990a46a9ea12760a3e05399dfd034e0e01 src/core/texture_storage.h +33c498421f55050e69dc0192e7658123503eaa0e365bd9faaa2f72e80ddd6740 src/core/texture_storage.mm 06e258833e77590c89033c91288b82b33e976dd7679392cfa2490b9fbf035802 src/core/util/log.cpp 738b363a32ce59f8ebf0d42005d568d8601be8248e2c715b3a4ab0667925d9e1 src/core/util/log.h 12a77213202f21ca5023bba9739d44803fb9e0e4e578894bee070a9098af59f8 src/core/util/name_table.h 921f784d47f49214fb73e5411f8959f95d7829abcaf4db62903b445dbdabaaca src/core/util/ref.h 9e85153d438bf3ea07a17145b5db6bef45beec17bced3929a3efa6eb6201fb0d src/core/util/version.h cb3659bffb2fc60ee4907a85166631abe8c08b6c071496a00a738fd553921438 src/core/version.mm +d27230e7e0e3e89ea31b4d280fa85fd223a6d411088a67a23223326843d0fa6c src/wine/include/wmgl_gltypes.h +639cac9ebd3f422124cf4d81c730540431defd4cad5f82287bf06bb7e5ca208b src/wine/include/wmgl_wine.h +0497448149511e35fc589b0d37b1fbc601a622c874fe075eb01fe9d02057b390 src/wine/pe/wgl_fonts.c +9b3f25275d3d0ccc7121606a04997ef3398ce7f1fd7c0019e0ef41e13850c7d2 src/wine/pe/wgl_formats.c +8ee7129a8736cca9aa816ac0fd59af414c72f2bbc4973cc6929f978afb94c94f src/wine/pe/winemetalgl.c +9eff7c2212cb58eabc4a936d2a6694d6be5c00994a6150e224042dc57f871d5a src/wine/pe/wmgl_pe.h +451fff6855a50d96a535624c3a569e9155969ebe7487766f400467b6e4eeef34 src/wine/unix/unix_main.c +01a0fb6e9130cdcafa1dbb06728679102a1d746586a2f1daf417e9cb7e1671fb src/wine/unix/unix_wgl.c +5f51a104c65c74719d869aea5d5c96526cd33de7c8302cf15295e659729ccc7f src/wine/unix/unix_window.m +c5273c60575ce170006983189a76432fdab21df23694740cabcbfed8f5d4fadc src/wine/unix/unix_wow64.c +87f89962661099f1b53849c0a59812d99514977cb3b6c41d9f50e0589c6b7a22 src/wine/unix/wmgl_unix.h +ae74bb85b7daec0984c8e823c42cac2b674fcf31915549ea277532a8dd418369 src/wine/unix/wmgl_window.h 5f5532afeb9e726ecd85af69e4a762da44109897ef08451420219970775f7a14 tests/cts/baseline/gl30-cfg1-native.txt 071faa73a3040134110c59f7c4fb7ebff277337d6ed797c25886d1d740277baa tests/cts/baseline/gl30-cfg1-wine.txt fade4f9aec9a898c8c951810f362f40155caa49478fa758d9a71d72c5ccae82d tests/cts/baseline/gl30-cfg2-native.txt @@ -224,7 +240,7 @@ b424d237c5b6ae7cdf94e42483d2b769a2b69b30264088567ce8839dfd708629 tests/cts/base a34d6b3b841bef98ccc0b5fe11811bb6603ed73f18bb4de859d035da43dfce4e tests/cts/baseline/gl43-cfg4-wine.txt 945ba1079440613ad5c880ab1d9a7fdf288dd35a11d603100c5208b2fc9d81dd tests/cts/known-failures.json c633d83da7aba071bc142e672c182d437fa33e0ce591b2bc3c8b53447595801f tests/cts/native/build.sh -dbe3aee99d2dd953b260fc8310dcb2a1bdf1c33bf7fee4dbb8840d592887133a tests/cts/native/README.md +6bb8b297b9c9817542e69476d65adaf7fdc1c3f929e4d1ba959669af3f0ae146 tests/cts/native/README.md 565d10eb2637493518f26b82a5a6279d0c2fc4f35b93f3067122e90cb594fa15 tests/cts/native/tcuWmglPlatform.cpp 5b52f84e7990e0ebce9f63280a5650e2878527c7d6ec0678bab86dee79860062 tests/cts/native/wmgl.cmake 059d2f59213c63a56f66c39722558d5b77c47fa10e9daf46b36f6f259d87a6c4 tests/cts/notsupported.json @@ -270,9 +286,9 @@ cfafb34aa8d04ae13d2ed4d31dfeffec0b16665fdcb75b687860f44c077ec112 tests/cts/wind 4084ff106611faed07b5b3097f0acd94b8575edadc4429f0fbb103fb9798b47e tests/games/demos/scene33.c 0c9cd51bca8d782b67d7b683572bf2b9775933894897cfff78998989d850bb6b tests/games/demos/scene4x.c e884d802688295bd008ea5916aeda44bf34b4fd928bc9e669a698a1d7fa0df18 tests/games/perf.json -cb3290a85dd22366d1f0e1108c4505cb2ecb3fa9ea0576f8586790ad8a0a36ca tests/games/README.md +0b74b607784dda1b31ef9f94e7573cbadc9d3a2f1edd965d8cc8e30728d69334 tests/games/README.md a8b2d20c011aade91fcb45b090244c7c8cb567364d74a9cb01165d9bff0295c6 tests/games/results.json -d6336cbd9999a538409596ea2ff0875475fc2eb500e6345f3d20d9c741ba52ba tests/gpu/basic_tests.cpp +364200026e88293b7a73bac3a66d48986afaeddeeb9700c8d3fef621ef2efb09 tests/gpu/basic_tests.cpp 8990524c4cf8cac11dde44ea40ec209d6c5f9fe0c7cd9f2f31baf29ef6143618 tests/gpu/buffer_storage_tests.cpp 738df1c70e2336aa379dfbb9feb53123e5cd5992f6a6955253b394974dc7f53f tests/gpu/code_review_tests.cpp a2884f3213d566ea41749b82099523a43fe7ef96c6434e6a07c3b577b3197b8d tests/gpu/compat_tests.cpp @@ -284,8 +300,10 @@ ed701012fa8fee6398abe3f9142f4084f7aec515b0bc9ee177b4e358ed4d487a tests/gpu/fp64 5e00f9a3047f0298c16603ed6c18045841e663a3c8a5557434c9d5e724261e15 tests/gpu/geometry_tests.cpp 1fc7e5478a869e2bffbe33bd76f5e979b819b210d0099c023a385323269011c2 tests/gpu/gl4_tests.cpp 08f24fd0c0756c0c64b2702a8eabaf821e3e79cd42759822c779219238082ae3 tests/gpu/gl43_tests.cpp -20da96fb4fdfd0d21de15a59f5dba267826dc18b334526ac5c423767a2167c36 tests/gpu/gpu.h -11a41bbe9f6527c75f764f9fded65895dbe9b3e00b72598141aa91e72fc7a789 tests/gpu/main.cpp +901998306d0531e283266ce315844c5e36c6ad4ec32eb9704af2f7872d5f1ca2 tests/gpu/gl44_tests.cpp +cf24e499d5d1ddeb4d14feb31340701d49a24354802f0b9b3a0d81ca41866577 tests/gpu/gl46_tests.cpp +373db2f9563c11f091cb25b3a231806babff56bbfe8b1f3e9d54aaf73a0c846d tests/gpu/gpu.h +91a809f7e334044869d2405db056d0f47708d23a7bf6c349121d1cf87b741873 tests/gpu/main.cpp 879e9c7a2d5461981030e1691ae1a138e8f5d5d3c4b63c0694ce55f373b34ca1 tests/gpu/pipeline_tests.cpp 16e8c0b27101560c6658b6ddc903342556d38540d79ab4141627807e75309bad tests/gpu/query_tests.cpp a52076c17d772231794c179ecd3724257c0e33f3f71338a8151c7b78897fd5f1 tests/gpu/raster_tests.cpp @@ -299,13 +317,15 @@ c3a661dc50182ff1e6c0008a15b5a69a120f09ced859bb1d58b833c747664a1d tests/gpu/stre da60a56b67148510066cb787107971b20da46752952d3bdc2793b468e33cc1ec tests/host/format_tests.cpp 9a0d06b736cd2bb7619d19010edeaeac406f9a768112637d45aa14c67b6b7eb8 tests/host/fp64_tests.cpp 9814e864c74703cf2ff3dc3946e6bb3f2d5e8753c3ef7122a04494d76d3ff0c9 tests/host/main.cpp -b0bc1c38b3cc84c754131b48e846e9456955b5498c14af160d2fbcfdeab62c5d tests/host/shader_tests.cpp +3e62f6fea67f72c2a60b64478c0d1df882ab8552b13885426078a8b4a46c39be tests/host/shader_tests.cpp a2eaeb860acd730d5bf7fec7bd19c822aeeb9509bc551fa26cba4806f1243e72 tests/host/test.h 01abf574c5d789d579b6c8a2502f759bd5716b850100d751597803127bf8f046 tests/wine/core_triangle.c b76eacfe644c0a16ad3871f07c1d422f259fca98dae3bb50356aa5b72f9f5224 tests/wine/cts_wgl_sequence.c -0e076bd3eec49875bd9c5a17d9cff331077d6c5000848a7e7ba9e5feb867e421 tests/wine/fixtures.txt +f0fa86f2726da9ce244a8e3217537d5d029af6d1c42b26eb2c5542fac40b5c39 tests/wine/fixture_version.h +1cfe4651e5043b41acc9a63c418432b3b98ba2cd3fcc208866358208acc4b7e4 tests/wine/fixtures.txt 0d05ccc8cff52e20effd478ad485643d322d1538c7469e2dd48b229b37a064a5 tests/wine/opengl_1d.c fc4554dcaa157382c1ac952c7d0980a9e29051ea47c0cce769d8f1d5475b4651 tests/wine/opengl_blend.c +4219d20da07e5687520dd1eabd733fb0ba7904bb85e7abca66429f300c12eafc tests/wine/opengl_buffer_storage.c b69bd6556f727661bd8c710d0965abb5f1a77d796802f2836aea245fc3eea06f tests/wine/opengl_clear_metal.c d91b7004cf0048777ff1d94ec90de97b1755c323f2f000fc897a1c0988a1eb7a tests/wine/opengl_compute.c 02960e8e3959c6f75bb0492d1c67efd8b80628579ee9a04e833c8f4bcbfc5c57 tests/wine/opengl_contexts.c @@ -320,11 +340,11 @@ fa194e91e8b4db062f2fe208735689cdd75324e9a3ac83ed4899adf196976722 tests/wine/ope 01726610f03a2f76d1a5220c256340513055dbcdaaa725d0b466955f37d9ce4a tests/wine/opengl_geometry.c bd6ddd1eda93dd606eb2aadade8321f026a3ef6447acc2c55ce843bf7fd4cd7a tests/wine/opengl_gl33_fbo.c 8d79467aa0bcfd42108197c3ec707b76905ff197c8ad1bfcbbf1881128cc330e tests/wine/opengl_gl33_instanced.c -5055b7c275892441e3ac72fd9f0e943f5487076de400d95576f94f7ff34bfc5d tests/wine/opengl_gl33_metal.c -71af5b88f0132369c65593a77e6f104c160835e3fc45cc6c9f0d374fbeed74f5 tests/wine/opengl_gl33_resources.c -8c765f256cc6a9813cab902a135ae1f9646d4a8ea7c683e3c8a352461466a452 tests/wine/opengl_gl33_texture.c -6c322f0fab9d951313c9bd039e3b43268fe874e4a3415c78a0b7ac65a5ba811c tests/wine/opengl_gl42.c -ae93f2519771c8fa0f75d963de39d61e4bd78c7cf4d517f7f17ffa86e3806568 tests/wine/opengl_gl43.c +de1f8c60181f0efcd7d311e7090b707c7e547dc09e3763974931e0b694a10440 tests/wine/opengl_gl33_metal.c +160436f34c70d08c9a1ff786b4ae2136ec907a0dd93f62d67d63f17ddae2ef53 tests/wine/opengl_gl33_resources.c +11f23e03b91e7ed21133504a0825359f77881e2ac4935e67cd15b1ebe542a7a2 tests/wine/opengl_gl33_texture.c +6e13cda436fcc33e67b032daf19c23f9afc175f16b74536d33cb20f15c49c2e8 tests/wine/opengl_gl42.c +b3e3eb9a73be99f7e231feed822696f23d72dc126b6d44457944d7fd6c4c93ce tests/wine/opengl_gl43.c 9fe4401e1b36ca0c40f204502a094570cf80909cef0b80fb76e3a29f7f8aaa24 tests/wine/opengl_glsl110.c 47852754bfb8cb30fe00e23e28cc9504f456bfd03f1451c6b0f224d340ab4554 tests/wine/opengl_glsl120.c 00505085bc76ea3154ca33f2763a2b9cb4d6c064fb1dcfc558ee3c9f28f28a51 tests/wine/opengl_image_formats.c @@ -335,7 +355,7 @@ adb9d972ca7f902c6ec3b327026ea7ee526f6e42b86edfef1e174572cb1b79bd tests/wine/ope 97a64ed160cb6774aed7076343dbbbf6a767cbc00728b910c435cd917ca34f91 tests/wine/opengl_indirect_elements.c 555e628d40a5a5cd2dab98e04d9aa56f94285ccf3665ac2444fa5dc1cdb02fbf tests/wine/opengl_indirect.c d42b83c33c94fc443b60baea42ad3222712a298b95763dfaf3e4bee48a210d52 tests/wine/opengl_interface.c -3c4185d9a6d7355e011de3877c834bfee5a1d2a02afcc122566be344cefabc08 tests/wine/opengl_later_entry_points.c +ad919b602b3582edaf4228580735f2aa449dbcba152e051dba2a9c1bc854f778 tests/wine/opengl_later_entry_points.c 50b4dba25316cf8027cba64478182a5d7cefa4192823f47780f8d0116ff12c2f tests/wine/opengl_load_probe.c 2343c7273c09e07632321cfa3a4ede57f0b17ea3ea8bba2ccd7bcb139abac673 tests/wine/opengl_multi_draw.c 1820a086886bf66f015e3a978a60ae2bf69e46c4ed4079a8ee17b0c0276cb709 tests/wine/opengl_multisample.c @@ -364,10 +384,11 @@ db7ee43f6acebe6e98ca366769e58cc29e9f2ebd7b986321f60de834f06b820e tests/wine/ope 956071449047602507897e303d12212edb7ab5bae92ea0bbff256e2e422e7c05 tests/wine/opengl_transform.c a59e2548b370c6e5acd31439c0976e3b8e6fef51f3189a17389d274f8dc178a5 tests/wine/opengl_ubo.c 78f5cb472c40e15a2c37c94c6143bd78a88fe2036c572b11ee9dfc49916f4dec tests/wine/opengl_uniform_matrix.c -25cfa04482be85c97ff385f46efea9978623b02f3e4dbbe0abd9687c0da5dbdd tests/wine/opengl_wgl_modern.c -cd214f6019047b7ed65038934cd79dc74ba129501988c4afcb169d8d9cadaf42 tests/wine/probe-opengl-wow64.sh +9a5e58f07cf6b1b59ce5cd829d0ca2a6ffa58446a4cb3c70e8b52dbe9e186bec tests/wine/opengl_wgl_modern.c +33c15d4202ae90e94f499dd567cd1dd2709f8868921bd406df3f280e857de1ed tests/wine/probe-opengl-wow64.sh b9ca2cfa5c676e901c20d34af3407f1687cde0f1336a5ff7a8974d04c7494ad3 third_party/khronos/gl.xml b3706c0ea5100229abc6e185cc20d916b74e863eaad289331d9c43f2fcaeb7d1 third_party/khronos/REVISION 9172b2f34cff8eb19ee5c1651b040019c0290a7075679df3a6da2a5f6ffd1f73 third_party/khronos/wgl.xml 314af3147c14ad82494584ddd1adf4c629389e51b39f959ed36f76aff376ebd7 tools/gen/extensions.txt 92a3ef305e786c4e22a64f3ed2b0b66df1b037b3d96dd95ab65ea70f48bc16aa tools/gen/gl_gen.py +49a2b3ccc40c37d815e3c344e1f75f6f937a6c6e08ff796513d902c736e07315 tools/gen/wine_gen.py diff --git a/docs/api-coverage.json b/docs/api-coverage.json index 60a7b258..20771173 100644 --- a/docs/api-coverage.json +++ b/docs/api-coverage.json @@ -124,6 +124,7 @@ "GL_ARB_clip_control", "GL_ARB_get_program_binary", "GL_ARB_buffer_storage", + "GL_AMD_pinned_memory", "GL_KHR_debug", "GL_ARB_debug_output", "GL_ARB_separate_shader_objects", @@ -174,8 +175,36 @@ "GL_ARB_shader_image_size", "GL_ARB_stencil_texturing", "GL_ARB_texture_barrier", + "GL_ARB_clear_texture", + "GL_ARB_enhanced_layouts", + "GL_ARB_multi_bind", + "GL_ARB_query_buffer_object", + "GL_ARB_texture_mirror_clamp_to_edge", + "GL_ARB_texture_stencil8", + "GL_ARB_vertex_type_10f_11f_11f_rev", + "GL_ARB_conditional_render_inverted", + "GL_ARB_cull_distance", + "GL_ARB_derivative_control", + "GL_ARB_direct_state_access", + "GL_ARB_ES3_1_compatibility", + "GL_ARB_get_texture_sub_image", + "GL_ARB_shader_texture_image_samples", + "GL_KHR_context_flush_control", + "GL_ARB_gl_spirv", + "GL_ARB_spirv_extensions", + "GL_ARB_indirect_parameters", + "GL_ARB_pipeline_statistics_query", + "GL_ARB_shader_atomic_counter_ops", + "GL_ARB_shader_draw_parameters", + "GL_ARB_shader_group_vote", + "GL_ARB_transform_feedback_overflow_query", + "GL_ARB_parallel_shader_compile", + "GL_KHR_parallel_shader_compile", + "GL_KHR_no_error", "GL_EXT_shader_integer_mix", "GL_ARB_polygon_offset_clamp", - "GL_EXT_polygon_offset_clamp" + "GL_EXT_polygon_offset_clamp", + "GL_ARB_shader_viewport_layer_array", + "GL_EXT_texture_shadow_lod" ] } diff --git a/docs/building.md b/docs/building.md index bf0c0efe..c398d8f4 100644 --- a/docs/building.md +++ b/docs/building.md @@ -1,16 +1,19 @@ # Build it yourself -How to build the core, build a Wine 11.17 that loads it, and check that -programs use it. The test suites are in [Test suite](testing.md); release -packages are in [Releasing](release.md). +How to build the core and the Wine module, install them into a Wine, and +check that programs use them. The test suites are in [Test suite](testing.md); +release packages are in [Releasing](release.md). ## Requirements - macOS 15 or later on Apple silicon, Xcode 16 or later, CMake 3.24 or later, Ninja and Python 3. - Rosetta 2, to run an x86_64 Wine. -- For Wine and the guest test programs: Homebrew `bison`, `flex` and - `mingw-w64` (`x86_64-w64-mingw32-gcc`, `i686-w64-mingw32-gcc`). +- For the Wine module: [llvm-mingw](https://github.com/mstorsjo/llvm-mingw) + (`x86_64-w64-mingw32-clang`, `i686-w64-mingw32-clang` on `PATH`) and the + headers of a Wine 11.x (an installation or its source tree). +- For the guest test programs: Homebrew `mingw-w64` + (`x86_64-w64-mingw32-gcc`, `i686-w64-mingw32-gcc`). glslang and SPIRV-Cross are vendored at pinned revisions ([`DEPENDENCIES.md`](../DEPENDENCIES.md)), so the build fetches nothing. The @@ -22,10 +25,10 @@ core links only Apple system frameworks and never links OpenGL.framework. ./scripts/build.sh # build/release/libwinemetalgl.dylib (x86_64, macOS 15) ``` -That is the core for an x86_64 Wine under Rosetta. For a native arm64 Wine, -`scripts/stage-arm64-core.sh /path/to/output` builds the arm64 core and stages -it with the driver ABI header and the Wine patches; that combination has not -yet been run inside an arm64 Wine. +That is the core for an x86_64 Wine under Rosetta. `scripts/stage-arm64-core.sh +/path/to/output` builds the arm64 core and stages it with the driver ABI +header; the Wine module is built for x86_64 Wine only, so the arm64 core has +not yet run inside an arm64 Wine. For development, the core and its tests also build for the machine itself, without Rosetta: @@ -36,52 +39,43 @@ cmake --build build/host build/host/wmgl_host_tests && build/host/wmgl_gpu_tests ``` -## Wine 11.17 with the patches +## The Wine module -The patches in [`patches/wine/wine-11.17`](../patches/wine/README.md) add the -core to `winemac.drv` and fix four Wine OpenGL bugs. They apply to the -`wine-11.17` release; other Wine versions may need a small rebase when Wine's -OpenGL driver interface changes. These steps build an x86_64 Wine (run under -Rosetta) with x86_64 and i386 guests, and cover only what WineMetalGL needs; -any other configure options are up to you. +WineMetalGL reaches Windows programs through a Wine module of its own, built +outside Wine and installed next to Wine's modules: no Wine source is patched +or rebuilt. It works with any x86_64 Wine 11.x on macOS 15 or later, such as +the WineHQ release or MetalSharp's Wine 11.17; it is tested on stock Wine 11.0 +and on MetalSharp's Wine 11.17. + +| File | Where | What | +|---|---|---| +| `winemetalgl.dll` | `lib/wine/x86_64-windows`, `lib/wine/i386-windows` | WGL and the GL entry points, for 64- and 32-bit programs | +| `winemetalgl.so` | `lib/wine/x86_64-unix` | its unix side, which runs both (32-bit calls through Wine's WoW64 thunks) | +| `libwinemetalgl.dylib` | `lib/wine/x86_64-unix` | the core | +| `opengl32.dll` | the prefix's `system32` and `syswow64` | forwards every export to `winemetalgl.dll` | ```sh -# 1. Wine 11.17 source, with the patches applied from the top of the tree -tar -xf wine-11.17.tar.xz -cd wine-11.17 -git apply /path/to/WineMetalGL/patches/wine/wine-11.17/*.patch -cd .. - -# 2. Configure an x86_64 macOS 15 build with i386 and x86_64 guests -export PATH="/opt/homebrew/opt/bison/bin:/opt/homebrew/opt/flex/bin:$PATH" -mkdir wine-build && cd wine-build -../wine-11.17/configure \ - --build=x86_64-apple-darwin --host=x86_64-apple-darwin \ - --enable-archs=i386,x86_64 --with-mingw \ - CC='clang -arch x86_64 -mmacosx-version-min=15.0' \ - --prefix=/path/to/wine - -# 3. Build and install Wine -make -j"$(sysctl -n hw.ncpu)" -make install - -# 4. Build the x86_64 core and install it next to winemac.so -cd /path/to/WineMetalGL -./scripts/build.sh -./scripts/stage-wine.sh /path/to/wine/lib/wine/x86_64-unix +./scripts/build.sh # the core +./scripts/build-wine-module.sh /path/to/wine # build/wine-module +./scripts/install-wine-module.sh /path/to/wine /path/to/prefix ``` -What matters for the core: - -- `--host=x86_64-apple-darwin` with `-arch x86_64` builds the x86_64 Wine - that the x86_64 core is built for. `winemac.so` only loads a core of its own - architecture and otherwise falls back to Apple's OpenGL. -- `--enable-archs=i386,x86_64` gives one WoW64 prefix for 32- and 64-bit - programs. -- `-mmacosx-version-min=15.0` matches the core's minimum macOS. -- Both patches must be applied. They change `opengl32` (the DLL and its Unix - side), `win32u` and `winemac.drv`, so use the modules from this build, not - modules from another Wine. +`install-wine-module.sh` adds the module and the core to the Wine installation +without replacing any of Wine's files. Given a prefix, it also puts the +forwarding `opengl32.dll` into it and sets the DLL override +`opengl32=native`; run it once per prefix. Removing the override (or running +a program with `WINEDLLOVERRIDES=opengl32=b`) gives that prefix Wine's own +OpenGL back. + +What matters: + +- The Wine must be an x86_64 build (run under Rosetta) with i386 and x86_64 + guests (`--enable-archs=i386,x86_64`), so one WoW64 prefix runs 32- and + 64-bit programs. +- The module loads the core from its own directory; it reports an error on + stderr and offers no pixel formats when the core cannot be loaded. +- Windows appear through Wine's macOS driver: the module puts a Metal layer + over the window's client area, in Wine's Cocoa window. - Wine's optional features need x86_64 builds of their libraries, such as FreeType for fonts. On Apple silicon, Homebrew installs arm64 libraries, which an x86_64 Wine cannot load; @@ -90,33 +84,19 @@ What matters for the core: ## Checking that a program uses the core -With the patches applied, the core is Wine's OpenGL. Look at a program's -`GL_RENDERER` string: it ends in `(WineMetalGL)`, for example -`Apple M4 (WineMetalGL)`. With `WINEMETALGL_LOG=2` the core prints +Look at a program's `GL_RENDERER` string: it ends in `(WineMetalGL)`, for +example `Apple M4 (WineMetalGL)`. With `WINEMETALGL_LOG=2` the core prints `winemetalgl:info: WineMetalGL core on ` to stderr when it loads. If the -renderer is Apple's, `winemac.so` did not find or could not load -`libwinemetalgl.dylib` in its own directory, and Wine fell back to Apple's -OpenGL. +renderer is anything else, the prefix has no `opengl32.dll` from the module or +no `opengl32=native` override, and the program got Wine's own OpenGL. ## Environment | Variable | Effect | |---|---| -| `WINEMETALGL=0` | Use Apple's OpenGL instead of the core (how reference frames are recorded) | +| `WINEDLLOVERRIDES=opengl32=b` | Use Wine's own OpenGL instead of WineMetalGL (how reference frames are recorded) | | `WINEMETALGL_LOG=1..3` | Log warnings, information or traces (errors are always logged) | | `WINEMETALGL_TRACE=1` | Log every GL call | | `WINEMETALGL_STATS=1` | Log per-frame statistics | +| `WINEMETALGL_WINE_DEBUG=1` | Log what the Wine module does (contexts, surfaces, windows) | | `WINEMETALGL_VENDOR`, `WINEMETALGL_RENDERER` | Replace the `GL_VENDOR` and `GL_RENDERER` strings, for games that refuse unknown vendors | - -Some programs' OpenGL loaders need entry points of GL versions above 4.3, -which Windows drivers return whatever the context's version. Wine returns them -to programs that opt in through the registry of their prefix: - -```text -HKCU\Software\Wine\AppDefaults\\OpenGL LaterEntryPoints = "Y" -``` - -Calling one of these functions raises `GL_INVALID_OPERATION`, since 4.3 -contexts do not offer them. Dead Cells (`deadcells_gl.exe`) needed it with -earlier versions; the GL 4.2 and 4.3 entry points its loader requires are -part of 4.3 contexts. diff --git a/docs/conformance.md b/docs/conformance.md index 5bc040a0..41163ac7 100644 --- a/docs/conformance.md +++ b/docs/conformance.md @@ -176,6 +176,6 @@ NotSupported cases, and the reason for each: [Unsupported](unsupported.md#cts-ca GL 4.3 core profile commands in the Khronos registry: 533. Implemented: 533. Missing: none. -Advertised extensions: `GL_ARB_compatibility`, `GL_ARB_multitexture`, `GL_ARB_transpose_matrix`, `GL_ARB_texture_env_add`, `GL_EXT_texture_env_add`, `GL_ARB_texture_env_combine`, `GL_EXT_texture_env_combine`, `GL_ARB_texture_env_dot3`, `GL_ARB_texture_env_crossbar`, `GL_ARB_texture_cube_map`, `GL_EXT_texture_cube_map`, `GL_ARB_texture_border_clamp`, `GL_ARB_texture_compression`, `GL_ARB_texture_mirrored_repeat`, `GL_ARB_depth_texture`, `GL_ARB_shadow`, `GL_ARB_window_pos`, `GL_ARB_point_parameters`, `GL_ARB_point_sprite`, `GL_EXT_secondary_color`, `GL_EXT_fog_coord`, `GL_EXT_blend_func_separate`, `GL_EXT_blend_minmax`, `GL_EXT_blend_subtract`, `GL_EXT_blend_color`, `GL_EXT_stencil_wrap`, `GL_EXT_texture_lod_bias`, `GL_EXT_bgra`, `GL_EXT_draw_range_elements`, `GL_EXT_compiled_vertex_array`, `GL_EXT_rescale_normal`, `GL_EXT_separate_specular_color`, `GL_ARB_vertex_buffer_object`, `GL_ARB_texture_non_power_of_two`, `GL_ARB_occlusion_query`, `GL_ARB_draw_buffers`, `GL_ARB_texture_float`, `GL_ARB_half_float_pixel`, `GL_ARB_pixel_buffer_object`, `GL_EXT_pixel_buffer_object`, `GL_SGIS_generate_mipmap`, `GL_ARB_texture_rectangle`, `GL_NV_texture_rectangle`, `GL_NV_blend_square`, `GL_EXT_packed_pixels`, `GL_SGIS_texture_edge_clamp`, `GL_ARB_multisample`, `GL_EXT_texture3D`, `GL_EXT_blend_equation_separate`, `GL_ARB_shading_language_100`, `GL_ARB_shader_objects`, `GL_ARB_vertex_shader`, `GL_ARB_fragment_shader`, `GL_ARB_vertex_type_2_10_10_10_rev`, `GL_ARB_ES2_compatibility`, `GL_ARB_texture_storage`, `GL_EXT_texture_storage`, `GL_ARB_compressed_texture_pixel_storage`, `GL_ARB_texture_storage_multisample`, `GL_EXT_texture_compression_s3tc`, `GL_EXT_texture_filter_anisotropic`, `GL_ARB_texture_filter_anisotropic`, `GL_ARB_framebuffer_object`, `GL_EXT_framebuffer_object`, `GL_EXT_framebuffer_blit`, `GL_EXT_framebuffer_multisample`, `GL_EXT_packed_depth_stencil`, `GL_ARB_vertex_array_object`, `GL_ARB_map_buffer_range`, `GL_ARB_texture_rg`, `GL_ARB_texture_compression_rgtc`, `GL_ARB_instanced_arrays`, `GL_ARB_draw_instanced`, `GL_ARB_draw_elements_base_vertex`, `GL_ARB_uniform_buffer_object`, `GL_ARB_copy_buffer`, `GL_ARB_sync`, `GL_ARB_timer_query`, `GL_EXT_timer_query`, `GL_ARB_occlusion_query2`, `GL_ARB_sampler_objects`, `GL_ARB_texture_swizzle`, `GL_ARB_depth_clamp`, `GL_ARB_seamless_cube_map`, `GL_ARB_provoking_vertex`, `GL_ARB_texture_multisample`, `GL_ARB_blend_func_extended`, `GL_ARB_explicit_attrib_location`, `GL_ARB_fragment_coord_conventions`, `GL_ARB_shader_bit_encoding`, `GL_ARB_texture_rgb10_a2ui`, `GL_ARB_half_float_vertex`, `GL_ARB_texture_buffer_object`, `GL_ARB_framebuffer_sRGB`, `GL_EXT_framebuffer_sRGB`, `GL_EXT_texture_sRGB`, `GL_ARB_map_buffer_alignment`, `GL_ARB_depth_texture`, `GL_ARB_texture_float`, `GL_EXT_texture_shared_exponent`, `GL_EXT_texture_integer`, `GL_ARB_draw_buffers_blend`, `GL_ARB_base_instance`, `GL_ARB_clip_control`, `GL_ARB_get_program_binary`, `GL_ARB_buffer_storage`, `GL_KHR_debug`, `GL_ARB_debug_output`, `GL_ARB_separate_shader_objects`, `GL_KHR_robustness`, `GL_ARB_robustness`, `GL_ARB_internalformat_query`, `GL_ARB_invalidate_subdata`, `GL_ARB_copy_image`, `GL_NVX_gpu_memory_info`, `GL_ATI_meminfo`, `GL_ARB_draw_indirect`, `GL_ARB_gpu_shader5`, `GL_ARB_gpu_shader_fp64`, `GL_ARB_sample_shading`, `GL_ARB_shader_subroutine`, `GL_ARB_tessellation_shader`, `GL_ARB_texture_buffer_object_rgb32`, `GL_ARB_texture_cube_map_array`, `GL_ARB_texture_gather`, `GL_ARB_texture_query_lod`, `GL_ARB_transform_feedback2`, `GL_ARB_transform_feedback3`, `GL_ARB_shader_precision`, `GL_ARB_vertex_attrib_64bit`, `GL_ARB_viewport_array`, `GL_ARB_conservative_depth`, `GL_ARB_shader_atomic_counters`, `GL_ARB_shader_image_load_store`, `GL_ARB_shading_language_420pack`, `GL_ARB_shading_language_packing`, `GL_ARB_texture_compression_bptc`, `GL_ARB_transform_feedback_instanced`, `GL_ARB_compute_shader`, `GL_ARB_explicit_uniform_location`, `GL_ARB_program_interface_query`, `GL_ARB_shader_storage_buffer_object`, `GL_ARB_texture_buffer_range`, `GL_ARB_texture_view`, `GL_ARB_texture_query_levels`, `GL_ARB_vertex_attrib_binding`, `GL_ARB_arrays_of_arrays`, `GL_ARB_clear_buffer_object`, `GL_ARB_ES3_compatibility`, `GL_ARB_fragment_layer_viewport`, `GL_ARB_framebuffer_no_attachments`, `GL_ARB_internalformat_query2`, `GL_ARB_multi_draw_indirect`, `GL_ARB_shader_image_size`, `GL_ARB_stencil_texturing`, `GL_ARB_texture_barrier`, `GL_EXT_shader_integer_mix`, `GL_ARB_polygon_offset_clamp`, `GL_EXT_polygon_offset_clamp`. +Advertised extensions: `GL_ARB_compatibility`, `GL_ARB_multitexture`, `GL_ARB_transpose_matrix`, `GL_ARB_texture_env_add`, `GL_EXT_texture_env_add`, `GL_ARB_texture_env_combine`, `GL_EXT_texture_env_combine`, `GL_ARB_texture_env_dot3`, `GL_ARB_texture_env_crossbar`, `GL_ARB_texture_cube_map`, `GL_EXT_texture_cube_map`, `GL_ARB_texture_border_clamp`, `GL_ARB_texture_compression`, `GL_ARB_texture_mirrored_repeat`, `GL_ARB_depth_texture`, `GL_ARB_shadow`, `GL_ARB_window_pos`, `GL_ARB_point_parameters`, `GL_ARB_point_sprite`, `GL_EXT_secondary_color`, `GL_EXT_fog_coord`, `GL_EXT_blend_func_separate`, `GL_EXT_blend_minmax`, `GL_EXT_blend_subtract`, `GL_EXT_blend_color`, `GL_EXT_stencil_wrap`, `GL_EXT_texture_lod_bias`, `GL_EXT_bgra`, `GL_EXT_draw_range_elements`, `GL_EXT_compiled_vertex_array`, `GL_EXT_rescale_normal`, `GL_EXT_separate_specular_color`, `GL_ARB_vertex_buffer_object`, `GL_ARB_texture_non_power_of_two`, `GL_ARB_occlusion_query`, `GL_ARB_draw_buffers`, `GL_ARB_texture_float`, `GL_ARB_half_float_pixel`, `GL_ARB_pixel_buffer_object`, `GL_EXT_pixel_buffer_object`, `GL_SGIS_generate_mipmap`, `GL_ARB_texture_rectangle`, `GL_NV_texture_rectangle`, `GL_NV_blend_square`, `GL_EXT_packed_pixels`, `GL_SGIS_texture_edge_clamp`, `GL_ARB_multisample`, `GL_EXT_texture3D`, `GL_EXT_blend_equation_separate`, `GL_ARB_shading_language_100`, `GL_ARB_shader_objects`, `GL_ARB_vertex_shader`, `GL_ARB_fragment_shader`, `GL_ARB_vertex_type_2_10_10_10_rev`, `GL_ARB_ES2_compatibility`, `GL_ARB_texture_storage`, `GL_EXT_texture_storage`, `GL_ARB_compressed_texture_pixel_storage`, `GL_ARB_texture_storage_multisample`, `GL_EXT_texture_compression_s3tc`, `GL_EXT_texture_filter_anisotropic`, `GL_ARB_texture_filter_anisotropic`, `GL_ARB_framebuffer_object`, `GL_EXT_framebuffer_object`, `GL_EXT_framebuffer_blit`, `GL_EXT_framebuffer_multisample`, `GL_EXT_packed_depth_stencil`, `GL_ARB_vertex_array_object`, `GL_ARB_map_buffer_range`, `GL_ARB_texture_rg`, `GL_ARB_texture_compression_rgtc`, `GL_ARB_instanced_arrays`, `GL_ARB_draw_instanced`, `GL_ARB_draw_elements_base_vertex`, `GL_ARB_uniform_buffer_object`, `GL_ARB_copy_buffer`, `GL_ARB_sync`, `GL_ARB_timer_query`, `GL_EXT_timer_query`, `GL_ARB_occlusion_query2`, `GL_ARB_sampler_objects`, `GL_ARB_texture_swizzle`, `GL_ARB_depth_clamp`, `GL_ARB_seamless_cube_map`, `GL_ARB_provoking_vertex`, `GL_ARB_texture_multisample`, `GL_ARB_blend_func_extended`, `GL_ARB_explicit_attrib_location`, `GL_ARB_fragment_coord_conventions`, `GL_ARB_shader_bit_encoding`, `GL_ARB_texture_rgb10_a2ui`, `GL_ARB_half_float_vertex`, `GL_ARB_texture_buffer_object`, `GL_ARB_framebuffer_sRGB`, `GL_EXT_framebuffer_sRGB`, `GL_EXT_texture_sRGB`, `GL_ARB_map_buffer_alignment`, `GL_ARB_depth_texture`, `GL_ARB_texture_float`, `GL_EXT_texture_shared_exponent`, `GL_EXT_texture_integer`, `GL_ARB_draw_buffers_blend`, `GL_ARB_base_instance`, `GL_ARB_clip_control`, `GL_ARB_get_program_binary`, `GL_ARB_buffer_storage`, `GL_AMD_pinned_memory`, `GL_KHR_debug`, `GL_ARB_debug_output`, `GL_ARB_separate_shader_objects`, `GL_KHR_robustness`, `GL_ARB_robustness`, `GL_ARB_internalformat_query`, `GL_ARB_invalidate_subdata`, `GL_ARB_copy_image`, `GL_NVX_gpu_memory_info`, `GL_ATI_meminfo`, `GL_ARB_draw_indirect`, `GL_ARB_gpu_shader5`, `GL_ARB_gpu_shader_fp64`, `GL_ARB_sample_shading`, `GL_ARB_shader_subroutine`, `GL_ARB_tessellation_shader`, `GL_ARB_texture_buffer_object_rgb32`, `GL_ARB_texture_cube_map_array`, `GL_ARB_texture_gather`, `GL_ARB_texture_query_lod`, `GL_ARB_transform_feedback2`, `GL_ARB_transform_feedback3`, `GL_ARB_shader_precision`, `GL_ARB_vertex_attrib_64bit`, `GL_ARB_viewport_array`, `GL_ARB_conservative_depth`, `GL_ARB_shader_atomic_counters`, `GL_ARB_shader_image_load_store`, `GL_ARB_shading_language_420pack`, `GL_ARB_shading_language_packing`, `GL_ARB_texture_compression_bptc`, `GL_ARB_transform_feedback_instanced`, `GL_ARB_compute_shader`, `GL_ARB_explicit_uniform_location`, `GL_ARB_program_interface_query`, `GL_ARB_shader_storage_buffer_object`, `GL_ARB_texture_buffer_range`, `GL_ARB_texture_view`, `GL_ARB_texture_query_levels`, `GL_ARB_vertex_attrib_binding`, `GL_ARB_arrays_of_arrays`, `GL_ARB_clear_buffer_object`, `GL_ARB_ES3_compatibility`, `GL_ARB_fragment_layer_viewport`, `GL_ARB_framebuffer_no_attachments`, `GL_ARB_internalformat_query2`, `GL_ARB_multi_draw_indirect`, `GL_ARB_shader_image_size`, `GL_ARB_stencil_texturing`, `GL_ARB_texture_barrier`, `GL_ARB_clear_texture`, `GL_ARB_enhanced_layouts`, `GL_ARB_multi_bind`, `GL_ARB_query_buffer_object`, `GL_ARB_texture_mirror_clamp_to_edge`, `GL_ARB_texture_stencil8`, `GL_ARB_vertex_type_10f_11f_11f_rev`, `GL_ARB_conditional_render_inverted`, `GL_ARB_cull_distance`, `GL_ARB_derivative_control`, `GL_ARB_direct_state_access`, `GL_ARB_ES3_1_compatibility`, `GL_ARB_get_texture_sub_image`, `GL_ARB_shader_texture_image_samples`, `GL_KHR_context_flush_control`, `GL_ARB_gl_spirv`, `GL_ARB_spirv_extensions`, `GL_ARB_indirect_parameters`, `GL_ARB_pipeline_statistics_query`, `GL_ARB_shader_atomic_counter_ops`, `GL_ARB_shader_draw_parameters`, `GL_ARB_shader_group_vote`, `GL_ARB_transform_feedback_overflow_query`, `GL_ARB_parallel_shader_compile`, `GL_KHR_parallel_shader_compile`, `GL_KHR_no_error`, `GL_EXT_shader_integer_mix`, `GL_ARB_polygon_offset_clamp`, `GL_EXT_polygon_offset_clamp`, `GL_ARB_shader_viewport_layer_array`, `GL_EXT_texture_shadow_lod`. The machine-readable form is `docs/api-coverage.json`. diff --git a/docs/decisions/0002-gl4-architecture.md b/docs/decisions/0002-gl4-architecture.md new file mode 100644 index 00000000..f2fff5bd --- /dev/null +++ b/docs/decisions/0002-gl4-architecture.md @@ -0,0 +1,142 @@ +# 0002: GL 4.4–4.6 architecture + +- **Status:** accepted, extended as the 3.0.0 work lands +- **Date:** 2026-10-11 +- **Context:** milestone M3 (`feat/gl46`): OpenGL 4.4, 4.5 and 4.6 on Metal, released as 3.0.0 + +Each section is one decision: what Metal lacks or offers, what WineMetalGL does, and what that costs. + +## Direct state access (GL 4.5) + +**Question.** Should the 97 GL 4.5 DSA commands get their own implementation per object type, or reuse the bind-to-edit code? + +**Decision.** They reuse it. `src/core/api/dsa.mm`: +1. swaps the named object into a binding with `BindingSwap` (the copy-write buffer, the active unit's texture target, the draw/read framebuffer, the renderbuffer, the vertex array, the transform feedback object, the query buffer); +2. calls the existing command; +3. restores the binding. + +The errors only the named commands have (the GL 4.5 rules on names, targets and default objects) are checked first, so the older CTS lists keep their behaviour. + +**Cost.** Two binding writes per call. No draw-time cost. + +## Cull distances (GL 4.5, `ARB_cull_distance`) + +**Question.** Metal has clip distances but no cull distances. A primitive must be dropped only when one distance is negative at all of its vertices. A per-fragment test of the interpolated distance would clip instead. + +**Options.** +- MSL 4's per-vertex fragment inputs (`vertex_value`) are exact, but need macOS 26; the floor is macOS 15. +- Routing every cull-distance draw through the compute geometry emulation is exact but slow. + +**Decision.** An interpolated indicator. +- SPIRV-Cross masks the `gl_CullDistance` output into a local array (`mask_stage_output_by_builtin`). +- `addCullDistances` writes each element as the `[[user(cullN)]]` varying a fragment stage reads. +- It also writes a second varying, `[[user(wmgl_cullN)]]`, which is 0 where the distance is negative and 1e30 elsewhere. +- Interpolated (no perspective), that second varying is exactly 0 only where every vertex of the primitive is negative. +- The fragment variant `fragmentWithCullDistances` (pipeline key `cull`) discards those fragments. +- The geometry and tessellation emulations carry `gl_CullDistance` like `gl_ClipDistance`. +- `#extension GL_ARB_cull_distance` raises a shader to GLSL 4.50 (glslang has the built-in there only); a glslang preamble defines the macro. + +**Cost and limits.** +- One extra varying per distance, and early fragment tests are lost for such draws. +- A fragment exactly on an edge or vertex whose vertices all have the distance negative is dropped with the primitive. +- `layout(early_fragment_tests)` shaders still update depth and stencil for culled primitives. + +## Arbitrary sampler border colours + +**Question.** Metal's samplers have three border colours: transparent black, opaque black and opaque white. GL takes any colour, and integer textures take integer border colours. This is a GL 1.x feature, but the CTS counts the gap against 4.4: `texture_border_clamp` (integer formats) and the `gpu_shader5` clamp-to-border gathers. + +**Decision.** Clamp-to-border is emulated in the shader, only for draws that need it. + +1. **Wrapping (`src/core/shader/border.cpp`).** Every stage is translated by `BorderMsl`, a SPIRV-Cross subclass. It wraps each sample and gather of a non-arrayed 1D, 2D, 3D or rectangle texture in a macro named after the texture's Metal index: `wmgl_bcs_N` for samples, `wmgl_bcg_N` for gathers. +2. **Default.** The default definition keeps only the sampled value, so the common path costs nothing. +3. **Per draw.** The draw looks at the bound textures and samplers (`borderEmulation`). If an axis wraps `CLAMP_TO_BORDER` with a border Metal cannot give, the draw uses a function variant: the pipeline key's `borders`, a number from `borderVariantId`. +4. **What the variant does.** Its macros keep the hardware's value while the filter footprint stays inside the image. Otherwise: + - nearest filtering returns the border; + - linear filtering filters by hand from texel reads, using the border for texels outside the image and the texture's other wrap modes on the other axes; + - gathers substitute the border texel by texel; + - shadow lookups compare against the border's depth. +5. **Inputs to the variant.** The level of detail, the filters, the LOD clamps, the base-format mapping and the swizzle are baked into it. +6. **Coverage.** Vertex functions (including the transform feedback capture) and fragment functions, compute dispatches, and the geometry and tessellation control kernels all take variants. Rectangle textures, whose Metal samplers can only clamp to the edge, always use the emulation when they wrap to the border. + +**Cost and limits.** +- One function variant per distinct set of borders, compiled on first use. +- Arrays of samplers, `textureGatherOffsets` and cube maps are not emulated. Cube maps filter seamlessly, so they have no border. +- Hand filtering ignores anisotropy at the image's edge. + +## Draw parameters and indirect counts (GL 4.6) + +**Question.** `gl_BaseVertex`, `gl_BaseInstance` and `gl_DrawID` have no Metal equivalent that holds GL's values. Metal's `[[base_vertex]]` is not GL's after WineMetalGL rebases or rewrites indices, and Metal draws have no index within a multi-draw. + +**Decision.** +- The vertex shader's draw parameters (the GL 4.60 names, and the ARB names with `GL_ARB_shader_draw_parameters`) become hidden default-block uniforms, like `gl_DepthRange` (`lowerBuiltinUniforms`). +- The draw path fills them from the GL command: + - the base vertex of an elements draw (0 for array draws); + - the base instance; + - the index within the multi-draw, set by `DrawState::DrawIdScope` around each draw of `glMultiDraw*`. +- `glMultiDraw*IndirectCount` reads the count from `PARAMETER_BUFFER` on the CPU, after any GPU write to it, as the other indirect draws read their commands. The count is clamped to `maxdrawcount`, and `maxdrawcount` commands must fit in the indirect buffer. + +**Cost.** One default-uniform upload per draw, which every draw already makes. A read-back wait when the GPU wrote the parameter buffer. + +## Pipeline statistics and transform feedback overflow (GL 4.6) + +**Question.** Apple GPUs expose no pipeline statistics counters to Metal. + +**Decision.** Count on the CPU from what the draw paths already know, and in the shader only where nothing else can count: +- **Vertices and primitives submitted**: per restart-delimited run of the command. +- **Vertex shader invocations**: one per submitted vertex. Metal can reuse transformed vertices of indexed draws, so the true count can be lower. +- **Tessellation control patches and evaluation invocations**: from the CPU tessellator's domain points. +- **Geometry shader invocations and emitted primitives**: from the emulation's kernel size and its read-back output. +- **Compute invocations**: from the dispatch size, read from the indirect buffer for indirect dispatches. +- **Clipping input**: the primitives reaching the rasterizer (submitted, tessellated, or emitted on stream 0). +- **Clipping output**: counts the clipping input, because Metal does not report what clipping does. +- **Fragment shader invocations**: a fragment function variant (pipeline key `countFragments`) increments a 32-bit counter at buffer 16. The counter is fresh for each command buffer the query spans, and the query sums them. +- **Overflow queries**: set when the capture clamps a stream's primitives to the space left in its buffers. + +**Cost.** A pipeline variant only while a `FRAGMENT_SHADER_INVOCATIONS` query is active. + +## Vertex-stage atomic counters and side effects with transform feedback + +**Question.** +- A vertex function had one free buffer slot for atomic counter buffers, and GL 4.6 tests use two. +- Transform feedback runs the vertex stage in a capture pass before the draw, so a vertex shader that writes memory ran twice. + +**Decision.** +- **Counter buffers.** A vertex function's counter buffers are entries of its storage block argument buffer (buffer 28), so `MAX_VERTEX_ATOMIC_COUNTER_BUFFERS` is 8. +- **Discarding draws.** With rasterizer discard, the capture pass is the only vertex-stage run, and the draw is skipped. +- **Rasterizing draws.** When the vertex stage writes memory (storage blocks, counters or images) and rasterizes, the capture pass rasterizes the assembled list of independent primitives itself, with the current state. +- **Fallback.** That capture-while-drawing path is skipped when something else rewrites the list: provoking-vertex rotation for flat inputs, polygon-mode points, wide lines, tessellation. Those draws keep two passes. + +**Cost and limits.** Those remaining two-pass draws still run a memory-writing vertex shader twice. + +## SPIR-V shaders (GL 4.6, `ARB_gl_spirv`) + +**Question.** The compiler and every emulation (geometry, tessellation, transform feedback capture, fp64, border colours) start from GLSL. Should SPIR-V get its own path into Metal? + +**Decision.** No. `glSpecializeShader` turns the module into GLSL, and the existing path takes it as application source (`src/core/shader/spirv_input.cpp`). The translation: +1. checks the entry point and specialization constant IDs (`INVALID_VALUE` otherwise); +2. writes the constants into the module; +3. emits GLSL 4.60 with SPIRV-Cross. A subclass declares GL atomic counters (`uniform atomic_uint`, the `atomicCounter*` functions) and `gl_PatchVerticesIn`, which SPIRV-Cross's GLSL lacks. + +Naming: +- SPIR-V matches stage interfaces by location; GLSL by name. +- Every inter-stage variable is named after its location (`wmgl_spv__l`). +- At link time, a stage's inputs are renamed after the closest earlier stage's outputs. +- Unnamed resources are named after their location or binding. +- Program queries report those names as empty, and every name of a module without debug names (`spirvDisplayName`). + +The program binary keeps the translated GLSL, as for GLSL programs. + +**Cost and limits.** +- A translation per `glSpecializeShader`. +- Transform feedback varyings of a SPIR-V program report empty names even when the module names them. +- Modules are not validated beyond what the translation and the GLSL compiler reject. `GL_SPIR_V_EXTENSIONS` lists the extensions the translation handles; others are refused. + +## Image uniforms past Metal's read_write limit + +**Question.** A Metal function takes at most 8 `read_write` textures, and images were all declared `read_write`. Programs (and the CTS) use more images per stage than that, mostly ones they only read or only write. Should images move into an argument buffer? + +**Decision.** No. A SPIR-V pass (`inferStorageImageAccess`) decorates every image a stage only reads `NonWritable`, and every image it only writes `NonReadable`, so they become `access::read` and `access::write` textures. Atomics, images passed to functions and any other use keep both accesses. A stage binds up to 16 images (textures 16..31), at most 8 of them `read_write`. Image units are binding points only, so `GL_MAX_IMAGE_UNITS` is 16. The per-stage image uniform limits stay 8, since 8 images that are both read and written are all a stage can be guaranteed. + +**Cost and limits.** +- A stage with more than 8 images that are each read and written fails to link. +- An argument buffer of images was tried. Format views of one texture then lost writes (`shader_image_load_store.multiple-uniforms`), so it was dropped. diff --git a/docs/release.md b/docs/release.md index 7057ccb1..681718e6 100644 --- a/docs/release.md +++ b/docs/release.md @@ -2,19 +2,19 @@ ## Artifacts -`scripts/stage-release.sh /path/to/wine-build OUTPUT` builds the core from the -checkout (`scripts/build.sh`), ad-hoc signs the Mach-O libraries and stages a -release from a Wine 11.17 build with `patches/wine` applied. `STAGED-FROM.txt` +`scripts/stage-release.sh /path/to/wine-headers OUTPUT` builds the core +(`scripts/build.sh`) and the Wine module (`scripts/build-wine-module.sh`, with +the headers of a Wine 11.x installation or source tree) from the checkout, +ad-hoc signs the Mach-O libraries and stages a release. `STAGED-FROM.txt` records the commit, and says so when the tree had uncommitted changes: ```text OUTPUT/ - artifacts/ - wine-driver/ libwinemetalgl.dylib the core (x86_64) - winemac.so loads the core from its own directory - win32u.so, opengl32.so - guest/i386/ opengl32.dll - guest/x86_64/ opengl32.dll + install-wine-module.sh + module/ + x86_64-unix/ winemetalgl.so, libwinemetalgl.dylib (the core) + x86_64-windows/ winemetalgl.dll, opengl32.dll + i386-windows/ winemetalgl.dll, opengl32.dll evidence/ CTS baselines and their rechecks (cts/recheck), corpus results and frame times of the commit docs/ conformance.md, unsupported.md, testing.md, building.md, @@ -23,35 +23,33 @@ OUTPUT/ README.md, STAGED-FROM.txt, SHA256SUMS ``` -The `wine-driver` files go to `lib/wine/x86_64-unix`, the guest DLLs to -`lib/wine/i386-windows` and `lib/wine/x86_64-windows`. They are ABI-coupled to -the Wine build they were staged from; a different Wine tree applies -`patches/wine` and takes only `libwinemetalgl.dylib`. `WINEMETALGL=0` selects -Apple's OpenGL, and Wine falls back to it when the core cannot be loaded. +`./install-wine-module.sh /path/to/wine [/path/to/prefix]` installs the module +into any x86_64 Wine 11.x and, given a prefix, the forwarding `opengl32.dll` +with the `opengl32=native` override ([Build it yourself](building.md#the-wine-module)). +No Wine file is replaced, and the module does not depend on the Wine build it +was staged with. `scripts/verify-release.sh OUTPUT` checks the layout, the architectures and code signatures, that the core links no OpenGL.framework and exports only the -driver ABI, and the SHA256 manifest. +driver ABI, that `winemetalgl.dll` is marked as a Wine builtin and +`winemetalgl.so` has its WoW64 call table, and the SHA256 manifest. ## The release After the development branch merges, `main` is tagged `winemetalgl-` (the `project()` version in `CMakeLists.txt`) and -released with two archives: +released with two archives and `SHA256SUMS.txt`: - `winemetalgl--x86_64.tar.gz`: the staged release above, with the - evidence directory. Its Wine modules are staged from MetalSharp's x86_64 - Wine 11.17 (under Rosetta) and are a drop-in for that Wine; any other - Wine 11.17 is built with `patches/wine` ([Build it yourself](building.md)) - and takes only `libwinemetalgl.dylib`. + evidence directory, for x86_64 Wine 11.x (under Rosetta). Its Wine evidence + ran on stock Wine 11.0 with the module installed. - `winemetalgl--arm64-core.tar.gz`: `scripts/stage-arm64-core.sh - OUTPUT`, the arm64 core with the driver ABI header and `patches/wine`, for a - native arm64 Wine built with the patches. Its evidence is the native - KHR-GL30 to KHR-GL43 runs (the core built for the build machine); it has not - yet run inside an arm64 Wine. + OUTPUT`, the arm64 core with the driver ABI header. Its evidence is the + native runs (the core built for the build machine); the Wine module is + built for x86_64 Wine only, so it has not yet run inside an arm64 Wine. -The two cores are not interchangeable: `winemac.so` loads the core of its own -architecture, and Wine falls back to Apple's OpenGL when it cannot. +The two cores are not interchangeable: a library of the other architecture +does not load. ## MetalSharp @@ -68,3 +66,14 @@ installs them into its Wine runtime. Checked against the 0.5.0 consumer | `tools/bundles/wine-runtime-hashes.tsv` | | regenerate | | `steam_actions.c` sets `WINEMETALGL=0` | still honoured (Apple's OpenGL) | remove to enable the core | | `VKMT_OPENGL_METAL_EXPERIMENTAL` | no longer read | drop | + +From 3.0.0 the release is the Wine module, which MetalSharp's Wine 11.17 +loads without the 2.x patches: + +| 2.x layout | 3.0.0 layout | MetalSharp change | +|---|---|---| +| `artifacts/wine-driver/libwinemetalgl.dylib` | `module/x86_64-unix/libwinemetalgl.dylib` | same destination, `lib/wine/x86_64-unix`; the driver ABI is unchanged, so a patched `winemac.so` still loads it | +| `artifacts/wine-driver/winemac.so`, `win32u.so`, `opengl32.so`; patches applied to the runtime | none | the runtime can return to unpatched `winemac.drv`, `win32u` and `opengl32` | +| | `module/x86_64-unix/winemetalgl.so`, `module/{x86_64,i386}-windows/winemetalgl.dll` | install into `lib/wine/x86_64-unix`, `lib/wine/{x86_64,i386}-windows` | +| `artifacts/guest/{i386,x86_64}/opengl32.dll` (Wine's, patched) | `module/{x86_64,i386}-windows/opengl32.dll` (a forwarder) | install into each prefix's `system32` and `syswow64` with `opengl32=native` (`install-wine-module.sh`), instead of into `lib/wine` | +| `WINEMETALGL=0` | `WINEDLLOVERRIDES=opengl32=b` | Wine's own OpenGL for one program | diff --git a/docs/testing.md b/docs/testing.md index aef3ceb4..bcc2b7d3 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -30,15 +30,21 @@ build/host/wmgl_gpu_tests # or one test: build/host/wmgl_gpu_tests gp ## Wine fixtures Small Windows programs (`tests/wine/fixtures.txt`), each run as an x86_64 and -an i386 guest in one fresh WoW64 prefix. Each prints markers that the script -checks. +an i386 guest in one fresh WoW64 prefix, through the Wine module. Each prints +markers that the script checks. ```sh +./scripts/build.sh && ./scripts/build-wine-module.sh /path/to/wine +./scripts/install-wine-module.sh /path/to/wine WINEMETALGL_WINE_RUNTIME=/path/to/wine tests/wine/probe-opengl-wow64.sh ``` -The 59 gate fixtures run by default; `--all` adds the 10 fixtures kept for -the later 4.x milestones, which need features of GL 4.4 or later. +The script puts the module's `opengl32.dll` into its prefix +(`WINEMETALGL_MODULE_DIR`, default `build/wine-module`). The gate runs on +stock Wine 11.0 and on MetalSharp's Wine 11.17. + +All 70 fixtures are in the gate (the 10 that need GL 4.4 to 4.6 features +joined it with GL 4.6), each run as an x86_64 and an i386 guest. ## Khronos CTS @@ -116,7 +122,6 @@ Results: [Game corpus](games.md) and [Performance](performance.md). (`scripts/update-checksums.sh` refreshes both), that the generated docs match the committed evidence, that no evidence was recorded from a tree with uncommitted changes, that nothing uses Apple's OpenGL, and, once built, that -the core targets macOS 15 and does not link OpenGL.framework. When -`WINEMETALGL_WINE_SOURCE` names a Wine 11.17 tree, it also checks that the -Wine patches apply. `check-forbidden-identifiers.sh` keeps code that +the core targets macOS 15 and does not link OpenGL.framework. +`check-forbidden-identifiers.sh` keeps code that recognises Khronos CTS shaders out of the source. CI runs both. diff --git a/docs/unsupported.md b/docs/unsupported.md index 15526a3f..bfa43e83 100644 --- a/docs/unsupported.md +++ b/docs/unsupported.md @@ -18,9 +18,9 @@ version: | 4.1 | Separate shader objects, program binaries, viewport arrays, 64-bit attributes | `GL_ARB_ES2_compatibility`, `GL_ARB_get_program_binary`, `GL_ARB_separate_shader_objects`, `GL_ARB_shader_precision`, `GL_ARB_vertex_attrib_64bit`, `GL_ARB_viewport_array` (6 of 6) | none | | 4.2 | Image load/store, atomic counters, immutable texture storage | `GL_ARB_base_instance`, `GL_ARB_compressed_texture_pixel_storage`, `GL_ARB_conservative_depth`, `GL_ARB_internalformat_query`, `GL_ARB_map_buffer_alignment`, `GL_ARB_shader_atomic_counters`, `GL_ARB_shader_image_load_store`, `GL_ARB_shading_language_420pack`, `GL_ARB_shading_language_packing`, `GL_ARB_texture_compression_bptc`, `GL_ARB_texture_storage`, `GL_ARB_transform_feedback_instanced` (12 of 12) | none | | 4.3 | Compute shaders, shader storage buffers, debug output, texture views | `GL_ARB_arrays_of_arrays`, `GL_ARB_clear_buffer_object`, `GL_ARB_compute_shader`, `GL_ARB_copy_image`, `GL_ARB_ES3_compatibility`, `GL_ARB_explicit_uniform_location`, `GL_ARB_fragment_layer_viewport`, `GL_ARB_framebuffer_no_attachments`, `GL_ARB_internalformat_query2`, `GL_ARB_invalidate_subdata`, `GL_ARB_multi_draw_indirect`, `GL_ARB_program_interface_query`, `GL_ARB_shader_image_size`, `GL_ARB_shader_storage_buffer_object`, `GL_ARB_stencil_texturing`, `GL_ARB_texture_buffer_range`, `GL_ARB_texture_query_levels`, `GL_ARB_texture_storage_multisample`, `GL_ARB_texture_view`, `GL_ARB_vertex_attrib_binding`, `GL_KHR_debug` (21 of 22) | `GL_ARB_robust_buffer_access_behavior` | -| 4.4 | Buffer storage, multi-bind, texture clears, enhanced layouts | `GL_ARB_buffer_storage` (1 of 8) | `GL_ARB_clear_texture`, `GL_ARB_enhanced_layouts`, `GL_ARB_multi_bind`, `GL_ARB_query_buffer_object`, `GL_ARB_texture_mirror_clamp_to_edge`, `GL_ARB_texture_stencil8`, `GL_ARB_vertex_type_10f_11f_11f_rev` | -| 4.5 | Direct state access, clip control, robustness, cull distances | `GL_ARB_clip_control`, `GL_ARB_texture_barrier`, `GL_KHR_robustness` (3 of 11) | `GL_ARB_conditional_render_inverted`, `GL_ARB_cull_distance`, `GL_ARB_derivative_control`, `GL_ARB_direct_state_access`, `GL_ARB_ES3_1_compatibility`, `GL_ARB_get_texture_sub_image`, `GL_ARB_shader_texture_image_samples`, `GL_KHR_context_flush_control` | -| 4.6 | SPIR-V shaders, anisotropic filtering, indirect draw counts | `GL_ARB_polygon_offset_clamp`, `GL_ARB_texture_filter_anisotropic` (2 of 11) | `GL_ARB_gl_spirv`, `GL_ARB_indirect_parameters`, `GL_ARB_pipeline_statistics_query`, `GL_ARB_shader_atomic_counter_ops`, `GL_ARB_shader_draw_parameters`, `GL_ARB_shader_group_vote`, `GL_ARB_spirv_extensions`, `GL_ARB_transform_feedback_overflow_query`, `GL_KHR_no_error` | +| 4.4 | Buffer storage, multi-bind, texture clears, enhanced layouts | `GL_ARB_buffer_storage`, `GL_ARB_clear_texture`, `GL_ARB_enhanced_layouts`, `GL_ARB_multi_bind`, `GL_ARB_query_buffer_object`, `GL_ARB_texture_mirror_clamp_to_edge`, `GL_ARB_texture_stencil8`, `GL_ARB_vertex_type_10f_11f_11f_rev` (8 of 8) | none | +| 4.5 | Direct state access, clip control, robustness, cull distances | `GL_ARB_clip_control`, `GL_ARB_conditional_render_inverted`, `GL_ARB_cull_distance`, `GL_ARB_derivative_control`, `GL_ARB_direct_state_access`, `GL_ARB_ES3_1_compatibility`, `GL_ARB_get_texture_sub_image`, `GL_ARB_shader_texture_image_samples`, `GL_ARB_texture_barrier`, `GL_KHR_context_flush_control`, `GL_KHR_robustness` (11 of 11) | none | +| 4.6 | SPIR-V shaders, anisotropic filtering, indirect draw counts | `GL_ARB_gl_spirv`, `GL_ARB_indirect_parameters`, `GL_ARB_pipeline_statistics_query`, `GL_ARB_polygon_offset_clamp`, `GL_ARB_shader_atomic_counter_ops`, `GL_ARB_shader_draw_parameters`, `GL_ARB_shader_group_vote`, `GL_ARB_spirv_extensions`, `GL_ARB_texture_filter_anisotropic`, `GL_ARB_transform_feedback_overflow_query`, `GL_KHR_no_error` (11 of 11) | none | `GL_ARB_compute_shader` is not advertised even though the core runs compute diff --git a/include/winemetalgl/driver.h b/include/winemetalgl/driver.h index 171dc183..45ce40e6 100644 --- a/include/winemetalgl/driver.h +++ b/include/winemetalgl/driver.h @@ -1,6 +1,6 @@ -/* WineMetalGL driver ABI: the C interface winemac.drv uses to drive the - * WineMetalGL core (winemetalgl.dylib). winemac loads the library with - * dlopen and resolves these symbols with dlsym; GL entry points are obtained +/* WineMetalGL driver ABI: the C interface the Wine module (src/wine/unix) + * uses to drive the WineMetalGL core (libwinemetalgl.dylib). The module loads + * the library with dlopen and resolves these symbols with dlsym; GL entry points are obtained * through wmgl_get_proc_address, never as exported symbols. * * Every function is called on the Unix side of Wine. Pointers are host @@ -12,6 +12,8 @@ #ifndef WINEMETALGL_DRIVER_H #define WINEMETALGL_DRIVER_H +#include + #ifdef __cplusplus extern "C" { #endif @@ -84,6 +86,13 @@ void wmgl_max_versions(int *core_major, int *core_minor, int *compat_major, int /* WineMetalGL's own version (WGL_RENDERER_VERSION_WINE). Optional: cores * before 2.0.0 do not export it. */ void wmgl_version(int *major, int *minor, int *patch); +/* Memory for buffer objects' storage, for hosts whose clients see part of + * the address space only (32-bit Windows applications under Wine's wow64, + * which must be able to address mapped buffers): `allocate` returns memory + * of at least `size` bytes aligned to 64 KiB (NULL on failure), `release` + * frees it. Call before creating contexts. Optional: cores before 3.0.0 do + * not export it. */ +void wmgl_set_buffer_memory(void *(*allocate)(size_t size), void (*release)(void *memory, size_t size)); #ifdef __cplusplus } diff --git a/patches/wine/README.md b/patches/wine/README.md deleted file mode 100644 index df9eb60b..00000000 --- a/patches/wine/README.md +++ /dev/null @@ -1,49 +0,0 @@ -# Wine integration - -The patches in `wine-11.17/` integrate WineMetalGL with Wine 11.17 for x86_64 -Unix Wine with x86_64 and i386 guests in one WoW64 prefix. They apply with -`git apply` from the top of a Wine 11.17 source tree (checked against the -`wine-11.17` release), and any Wine 11.17 built with them loads the core. -The results in `docs/` were recorded with MetalSharp's Wine 11.17, which the -x86_64 release's prebuilt Wine modules are built from. Other Wine versions may -need a small rebase when Wine's OpenGL driver interface changes. - -- `0001-opengl-wgl-fixes.patch`: six Wine OpenGL fixes. - - `win32u` indexes the parsed extension list correctly for drivers without - `GL_NUM_EXTENSIONS`. - - `wglChoosePixelFormatARB` keeps window requests within the on-screen - pixel formats. - - `glDeleteSync(0)` is ignored instead of raising `GL_INVALID_VALUE`. - - Deleting object 0 (`glDeleteProgram(0)` and the other deletes that go - through Wine's object names) passes 0 to the driver instead of -1, so it - stays silently ignored. - - `glGetBooleanv`, `glGetBooleani_v` and `glGetBooleanIndexedvEXT` of a - binding (`GL_IMAGE_BINDING_NAME`, ...) return the driver's `GL_TRUE` - instead of translating it as an object name. - - `wglGetProcAddress` can return entry points of GL versions above the - context's, as Windows drivers do, for applications that opt in with - `HKCU\Software\Wine\AppDefaults\\OpenGL` - `LaterEntryPoints = "Y"` (or `HKCU\Software\Wine\OpenGL` for every - application). HashLink's GL loader (Dead Cells) needs - `glDispatchCompute`, `glMemoryBarrier`, `glBindImageTexture` and - `glMultiDrawElementsIndirect` to start on a 3.3 context. It is opt-in - because programs that take a non-NULL entry point as support would - misbehave; calling one raises `GL_INVALID_OPERATION`. -- `0002-winemac-winemetalgl-core.patch`: `winemac.drv` loads - `libwinemetalgl.dylib` from its own directory and hands it a `CAMetalLayer` - per window; contexts, surfaces, pbuffers and swaps go through the driver ABI - in `include/winemetalgl/driver.h`. No CGL context is created. - `WGL_WINE_query_renderer` reports the core's renderer (vendor Apple, video - memory, GL versions), which wined3d uses to describe the GPU. - -The core is Wine's OpenGL once the patches are applied. `WINEMETALGL=0` -selects Apple's OpenGL (reference frames), and `winemac.drv` falls back to -Apple's OpenGL when the core is missing or refuses to initialise. - -`scripts/verify-source.sh` checks that the patches apply when -`WINEMETALGL_WINE_SOURCE` names a Wine checkout. - -## Building Wine with the patches - -The steps, what matters in them for the core, and how to check that a program -uses the core: [Build it yourself](../../docs/building.md#wine-1117-with-the-patches). diff --git a/patches/wine/wine-11.17/0001-opengl-wgl-fixes.patch b/patches/wine/wine-11.17/0001-opengl-wgl-fixes.patch deleted file mode 100644 index 2c5c7e6a..00000000 --- a/patches/wine/wine-11.17/0001-opengl-wgl-fixes.patch +++ /dev/null @@ -1,193 +0,0 @@ -diff --git a/dlls/opengl32/make_opengl b/dlls/opengl32/make_opengl -index 0304cea..5088378 100755 ---- a/dlls/opengl32/make_opengl -+++ b/dlls/opengl32/make_opengl -@@ -1304,7 +1304,8 @@ sub get_integer_call($) - return 0 if !$pname; - return 0 if $name !~ /^glGet/; - -- my $hook = "if (get_integer( $pname, $index, $to_int*$params, &integer )) *$params = $to_val" . "integer;"; -+ my $getter = $name =~ /^glGetBoolean/ ? "get_boolean" : "get_integer"; -+ my $hook = "if ($getter( $pname, $index, $to_int*$params, &integer )) *$params = $to_val" . "integer;"; - return $hook if $name =~ /^glGet(Boolean|Double|Fixed|Float|Integer|Pointer|UnsignedByte)/; - return $hook if $name =~ /^glGet(Multi)?Tex(ture)?LevelParameter/; - return $hook if $name =~ /^glGet(Named)?FramebufferAttachmentParameter/; -diff --git a/dlls/opengl32/private.h b/dlls/opengl32/private.h -index ca7132d..dc20a2e 100644 ---- a/dlls/opengl32/private.h -+++ b/dlls/opengl32/private.h -@@ -45,6 +45,7 @@ extern BOOL get_sync_from_handle( GLsync handle, GLsync *obj ); - extern void set_gl_error( GLenum error ); - extern struct registry_entry *get_function_entry( const char *name ); - extern BOOL get_integer( GLenum pname, GLuint index, GLint value, GLint *data ); -+extern BOOL get_boolean( GLenum pname, GLuint index, GLint value, GLint *data ); - extern void set_context_attribute( GLenum name, const void *value, size_t size ); - - enum object_type -diff --git a/dlls/opengl32/thunks.c b/dlls/opengl32/thunks.c -index 66d6281..f569a76 100644 ---- a/dlls/opengl32/thunks.c -+++ b/dlls/opengl32/thunks.c -@@ -941,7 +941,7 @@ void WINAPI glGetBooleanv( GLenum pname, GLboolean *data ) - int integer; - TRACE( "pname %d, data %p\n", pname, data ); - if ((status = UNIX_CALL( glGetBooleanv, &args ))) WARN( "glGetBooleanv returned %#lx\n", status ); -- else if (get_integer( pname, 0, *data, &integer )) *data = integer; -+ else if (get_boolean( pname, 0, *data, &integer )) *data = integer; - } - - void WINAPI glGetClipPlane( GLenum plane, GLdouble *equation ) -@@ -9181,7 +9181,7 @@ static void WINAPI glGetBooleanIndexedvEXT( GLenum target, GLuint index, GLboole - int integer; - TRACE( "target %d, index %d, data %p\n", target, index, data ); - if ((status = UNIX_CALL( glGetBooleanIndexedvEXT, &args ))) WARN( "glGetBooleanIndexedvEXT returned %#lx\n", status ); -- else if (get_integer( target, index, *data, &integer )) *data = integer; -+ else if (get_boolean( target, index, *data, &integer )) *data = integer; - } - - static void WINAPI glGetBooleani_v( GLenum target, GLuint index, GLboolean *data ) -@@ -9191,7 +9191,7 @@ static void WINAPI glGetBooleani_v( GLenum target, GLuint index, GLboolean *data - int integer; - TRACE( "target %d, index %d, data %p\n", target, index, data ); - if ((status = UNIX_CALL( glGetBooleani_v, &args ))) WARN( "glGetBooleani_v returned %#lx\n", status ); -- else if (get_integer( target, index, *data, &integer )) *data = integer; -+ else if (get_boolean( target, index, *data, &integer )) *data = integer; - } - - static void WINAPI glGetBufferParameteri64v( GLenum target, GLenum pname, GLint64 *params ) -diff --git a/dlls/opengl32/wgl.c b/dlls/opengl32/wgl.c -index 9ff082d..227eaa9 100644 ---- a/dlls/opengl32/wgl.c -+++ b/dlls/opengl32/wgl.c -@@ -387,7 +387,8 @@ static GLuint del_object( struct object_table *table, GLuint client_id ) - { - GLuint *object, host_id = 0; - -- if (!client_id || !(object = find_object_id( table->host_ids, client_id ))) return -1; -+ if (!client_id) return 0; /* deleting object 0 is silently ignored */ -+ if (!(object = find_object_id( table->host_ids, client_id ))) return -1; - table->min_free = min( table->min_free, client_id - 1 ); - host_id = *object; - if (table->type != OBJ_TYPE_SHADER) *object = 0; /* shader objects may outlive their deletion */ -@@ -2000,6 +2001,7 @@ BOOL WINAPI wglChoosePixelFormatARB( HDC hdc, const int *attribs_int, const FLOA - UINT i, num_wgl_formats, num_wgl_onscreen_formats; - const struct wgl_pixel_format **format_array; - struct compare_formats_ctx ctx = { 0 }; -+ BOOL wants_window = FALSE, wants_pbuffer = FALSE; - DWORD is_memdc; - - TRACE( "hdc %p, attribs_int %p, attribs_float %p, max_formats %u, formats %p, num_formats %p\n", -@@ -2021,13 +2023,23 @@ BOOL WINAPI wglChoosePixelFormatARB( HDC hdc, const int *attribs_int, const FLOA - compare_formats_ctx_set_attrib( &ctx, attribs_float[i], attribs_float[i + 1] ); - } - -- /* Initialize the format_array with (pointers to) all wgl formats */ -+ for (i = 0; i < ctx.num_attribs; ++i) -+ { -+ if (ctx.attribs[2 * i] == WGL_DRAW_TO_WINDOW_ARB && ctx.attribs[2 * i + 1]) wants_window = TRUE; -+ if (ctx.attribs[2 * i] == WGL_DRAW_TO_PBUFFER_ARB && ctx.attribs[2 * i + 1]) wants_pbuffer = TRUE; -+ } -+ -+ /* Initialize the format_array with (pointers to) all wgl formats. A -+ * window DC cannot later accept a pbuffer-only format in SetPixelFormat; -+ * keep the WGL chooser's result within the on-screen portion when the -+ * caller explicitly asks for a window drawable. */ - format_array = malloc( num_wgl_formats * sizeof(*format_array) ); - if (!format_array) return FALSE; - for (i = 0; i < num_wgl_formats; ++i) - { - struct wgl_pixel_format *format = wgl_formats + i; -- if (is_memdc && format->pfd.cColorBits == 16 && format->pfd.cAlphaBits != 1) format_array[i] = NULL; -+ if ((wants_window && !wants_pbuffer && i >= num_wgl_onscreen_formats) || -+ (is_memdc && format->pfd.cColorBits == 16 && format->pfd.cAlphaBits != 1)) format_array[i] = NULL; - else format_array[i] = &wgl_formats[i]; - } - -@@ -2238,6 +2250,38 @@ int WINAPI wglGetLayerPaletteEntries( HDC hdc, int plane, int start, int count, - /*********************************************************************** - * wglGetProcAddress (OPENGL32.@) - */ -+/* Windows drivers return the entry points they export whatever the current -+ * context's version, and some loaders require entry points of later GL -+ * versions that they only call on newer contexts: HashLink's GL loader (Dead -+ * Cells) refuses to start on a 3.3 context without glDispatchCompute. Programs -+ * that take a non-NULL entry point as support would misbehave, so this is an -+ * opt-in per application: -+ * HKCU\Software\Wine\AppDefaults\\OpenGL LaterEntryPoints = "Y" -+ * or for every application in HKCU\Software\Wine\OpenGL. Calls the driver -+ * cannot serve are ignored. */ -+static BOOL later_entry_points(void) -+{ -+ WCHAR module[MAX_PATH], key[MAX_PATH + 64], value[8]; -+ const WCHAR *app = module, *p; -+ DWORD size = sizeof(value); -+ -+ if (!GetModuleFileNameW( NULL, module, ARRAY_SIZE(module) )) module[0] = 0; -+ for (p = module; *p; p++) if (*p == '\\' || *p == '/') app = p + 1; -+ if (*app && lstrlenW( app ) < MAX_PATH) -+ { -+ lstrcpyW( key, L"Software\\Wine\\AppDefaults\\" ); -+ lstrcatW( key, app ); -+ lstrcatW( key, L"\\OpenGL" ); -+ if (!RegGetValueW( HKEY_CURRENT_USER, key, L"LaterEntryPoints", RRF_RT_REG_SZ, NULL, value, &size )) -+ return value[0] == 'y' || value[0] == 'Y' || value[0] == '1'; -+ size = sizeof(value); -+ } -+ if (!RegGetValueW( HKEY_CURRENT_USER, L"Software\\Wine\\OpenGL", L"LaterEntryPoints", RRF_RT_REG_SZ, NULL, -+ value, &size )) -+ return value[0] == 'y' || value[0] == 'Y' || value[0] == '1'; -+ return FALSE; -+} -+ - PROC WINAPI wglGetProcAddress( LPCSTR name ) - { - const struct registry_entry *func; -@@ -2268,6 +2312,8 @@ PROC WINAPI wglGetProcAddress( LPCSTR name ) - if (ctx->base.extensions[*ext]) return func->func; - } - -+ if (func->major && later_entry_points()) return func->func; -+ - WARN( "Extensions required for %s not supported\n", name ); - return NULL; - } -@@ -2935,6 +2981,7 @@ void WINAPI glDeleteSync( GLsync sync ) - TRACE( "sync %p\n", sync ); - - if (!(ctx = context_from_handle( teb->glCurrentRC ))) return; -+ if (!sync) return; /* DeleteSync silently ignores a sync value of zero. */ - if (!(ptr = get_handle_ptr( &ctx->lists->syncs, sync ))) return set_gl_error( GL_INVALID_VALUE ); - args.sync = ptr->user_data; - -@@ -2986,6 +3033,14 @@ GLsync WINAPI glImportSyncEXT( GLenum external_sync_type, GLintptr external_sync - return NULL; - } - -+/* A boolean query of an object name returns whether one is bound: the host -+ * value is GL_TRUE, not a name to translate to the client's. */ -+BOOL get_boolean( GLenum name, GLuint index, GLint value, GLint *data ) -+{ -+ if (get_pname_object_type( name ) != OBJ_TYPE_COUNT) return FALSE; -+ return get_integer( name, index, value, data ); -+} -+ - BOOL get_integer( GLenum name, GLuint index, GLint value, GLint *data ) - { - struct context *ctx; -diff --git a/dlls/win32u/opengl.c b/dlls/win32u/opengl.c -index 6ab3a03..5679212 100644 ---- a/dlls/win32u/opengl.c -+++ b/dlls/win32u/opengl.c -@@ -275,9 +275,9 @@ static void parse_current_extensions( BOOLEAN extensions[GL_EXTENSION_COUNT] ) - } - else - { -- enum opengl_extension extensions[GL_EXTENSION_COUNT]; -- size_t extension_count = parse_extensions( (const char *)funcs->p_glGetString( GL_EXTENSIONS ), extensions ); -- for (size_t i = 0; i < extension_count; i++) extensions[i] = TRUE; -+ enum opengl_extension parsed_extensions[GL_EXTENSION_COUNT]; -+ size_t extension_count = parse_extensions( (const char *)funcs->p_glGetString( GL_EXTENSIONS ), parsed_extensions ); -+ for (size_t i = 0; i < extension_count; i++) extensions[parsed_extensions[i]] = TRUE; - } - } - diff --git a/patches/wine/wine-11.17/0002-winemac-winemetalgl-core.patch b/patches/wine/wine-11.17/0002-winemac-winemetalgl-core.patch deleted file mode 100644 index 867a7d40..00000000 --- a/patches/wine/wine-11.17/0002-winemac-winemetalgl-core.patch +++ /dev/null @@ -1,449 +0,0 @@ -diff --git a/dlls/winemac.drv/opengl.c b/dlls/winemac.drv/opengl.c -index 696734f..0ee7f73 100644 ---- a/dlls/winemac.drv/opengl.c -+++ b/dlls/winemac.drv/opengl.c -@@ -2518,6 +2518,435 @@ static void macdrv_init_extensions(struct opengl_funcs *funcs, BOOLEAN extension - funcs->p_wglQueryRendererStringWINE = macdrv_wglQueryRendererStringWINE; - } - -+ -+/********************************************************************** -+ * WineMetalGL core -+ * -+ * The whole OpenGL implementation lives in libwinemetalgl.dylib on Metal. -+ * It is the default; WINEMETALGL=0 selects Apple's OpenGL instead, and so -+ * does a missing or incompatible libwinemetalgl.dylib. -+ * No CGL context is created: winemac only hands the core a CAMetalLayer per -+ * window and forwards context, surface and swap operations. The interface -+ * is include/winemetalgl/driver.h in the WineMetalGL repository. -+ */ -+ -+#define WMGL_DRIVER_ABI_VERSION 1 -+ -+struct wmgl_pixel_format -+{ -+ int draw_to_window, draw_to_pbuffer, double_buffer, stereo; -+ int color_bits, red_bits, green_bits, blue_bits, alpha_bits; -+ int red_shift, green_shift, blue_shift, alpha_shift; -+ int depth_bits, stencil_bits, accum_bits, aux_buffers; -+ int sample_buffers, samples, srgb_capable, float_components; -+ int max_pbuffer_width, max_pbuffer_height, max_pbuffer_pixels; -+}; -+ -+static void *wmgl_handle; -+static int (*p_wmgl_initialize)(unsigned int); -+static unsigned int (*p_wmgl_get_pixel_formats)(struct wmgl_pixel_format *, unsigned int, unsigned int *); -+static void *(*p_wmgl_surface_create_window)(void *, int, int, int); -+static void *(*p_wmgl_surface_create_pbuffer)(int, int, int); -+static void (*p_wmgl_surface_destroy)(void *); -+static void (*p_wmgl_surface_flush)(void *, unsigned int, int, int, int); -+static int (*p_wmgl_surface_swap)(void *); -+static void *(*p_wmgl_context_create)(int, void *, const int *, int *); -+static void (*p_wmgl_context_destroy)(void *); -+static int (*p_wmgl_make_current)(void *, void *, void *); -+static void *(*p_wmgl_get_proc_address)(const char *); -+static const char *(*p_wmgl_renderer_string)(void); -+static unsigned int (*p_wmgl_video_memory_mb)(void); -+static void (*p_wmgl_max_versions)(int *, int *, int *, int *); -+static void (*p_wmgl_version)(int *, int *, int *); /* optional */ -+ -+static struct wmgl_pixel_format *wmgl_formats; -+static unsigned int wmgl_format_count, wmgl_onscreen_count; -+ -+struct wmgl_drawable -+{ -+ struct opengl_drawable base; -+ void *surface; -+}; -+ -+static struct wmgl_drawable *wmgl_impl(struct opengl_drawable *base) -+{ -+ return base ? CONTAINING_RECORD(base, struct wmgl_drawable, base) : NULL; -+} -+ -+static BOOL wmgl_core_requested(void) -+{ -+ const char *value = getenv("WINEMETALGL"); -+ return !value || strcmp(value, "0"); -+} -+ -+static UINT wmgl_init_pixel_formats(UINT *onscreen) -+{ -+ *onscreen = wmgl_onscreen_count; -+ return wmgl_format_count; -+} -+ -+static BOOL wmgl_describe_pixel_format(int format, struct wgl_pixel_format *descr) -+{ -+ const struct wmgl_pixel_format *f; -+ -+ if (format <= 0 || format > (int)wmgl_format_count) return FALSE; -+ f = &wmgl_formats[format - 1]; -+ memset(descr, 0, sizeof(*descr)); -+ descr->pfd.nSize = sizeof(descr->pfd); -+ descr->pfd.nVersion = 1; -+ /* Fully accelerated: neither PFD_GENERIC_FORMAT nor PFD_GENERIC_ACCELERATED. */ -+ descr->pfd.dwFlags = PFD_SUPPORT_OPENGL | PFD_SUPPORT_COMPOSITION; -+ if (f->draw_to_window) descr->pfd.dwFlags |= PFD_DRAW_TO_WINDOW; -+ if (f->double_buffer) descr->pfd.dwFlags |= PFD_DOUBLEBUFFER; -+ if (f->stereo) descr->pfd.dwFlags |= PFD_STEREO; -+ descr->pfd.iPixelType = PFD_TYPE_RGBA; -+ descr->pfd.cColorBits = f->color_bits; -+ descr->pfd.cRedBits = f->red_bits; -+ descr->pfd.cRedShift = f->red_shift; -+ descr->pfd.cGreenBits = f->green_bits; -+ descr->pfd.cGreenShift = f->green_shift; -+ descr->pfd.cBlueBits = f->blue_bits; -+ descr->pfd.cBlueShift = f->blue_shift; -+ descr->pfd.cAlphaBits = f->alpha_bits; -+ descr->pfd.cAlphaShift = f->alpha_shift; -+ descr->pfd.cAccumBits = f->accum_bits; -+ descr->pfd.cDepthBits = f->depth_bits; -+ descr->pfd.cStencilBits = f->stencil_bits; -+ descr->pfd.cAuxBuffers = f->aux_buffers; -+ descr->pfd.iLayerType = PFD_MAIN_PLANE; -+ descr->swap_method = WGL_SWAP_UNDEFINED_ARB; -+ descr->transparent = FALSE; -+ descr->pixel_type = f->float_components ? WGL_TYPE_RGBA_FLOAT_ARB : WGL_TYPE_RGBA_ARB; -+ descr->draw_to_pbuffer = f->draw_to_pbuffer; -+ descr->max_pbuffer_pixels = f->max_pbuffer_pixels; -+ descr->max_pbuffer_width = f->max_pbuffer_width; -+ descr->max_pbuffer_height = f->max_pbuffer_height; -+ descr->sample_buffers = f->sample_buffers; -+ descr->samples = f->samples; -+ descr->framebuffer_srgb_capable = f->srgb_capable; -+ descr->float_components = f->float_components; -+ return TRUE; -+} -+ -+/* WGL_WINE_query_renderer for the core: one renderer, the Metal device. -+ * wined3d reads the vendor, device and video memory from it. */ -+static BOOL wmgl_query_renderer_integer(GLenum attribute, GLuint *value) -+{ -+ int core_major = 0, core_minor = 0, compat_major = 0, compat_minor = 0; -+ -+ switch (attribute) -+ { -+ case WGL_RENDERER_ACCELERATED_WINE: -+ *value = 1; -+ return TRUE; -+ case WGL_RENDERER_UNIFIED_MEMORY_ARCHITECTURE_WINE: -+ *value = 1; /* Apple silicon */ -+ return TRUE; -+ case WGL_RENDERER_VENDOR_ID_WINE: -+ *value = 0x106b; /* Apple */ -+ return TRUE; -+ case WGL_RENDERER_DEVICE_ID_WINE: -+ *value = 0xffffffff; -+ return TRUE; -+ case WGL_RENDERER_OPENGL_CORE_PROFILE_VERSION_WINE: -+ case WGL_RENDERER_OPENGL_COMPATIBILITY_PROFILE_VERSION_WINE: -+ p_wmgl_max_versions(&core_major, &core_minor, &compat_major, &compat_minor); -+ if (attribute == WGL_RENDERER_OPENGL_CORE_PROFILE_VERSION_WINE) -+ { -+ value[0] = core_major; -+ value[1] = core_minor; -+ } -+ else -+ { -+ value[0] = compat_major; -+ value[1] = compat_minor; -+ } -+ return TRUE; -+ case WGL_RENDERER_PREFERRED_PROFILE_WINE: -+ *value = WGL_CONTEXT_CORE_PROFILE_BIT_ARB; -+ return TRUE; -+ case WGL_RENDERER_VERSION_WINE: -+ if (p_wmgl_version) -+ { -+ int major, minor, patch; -+ p_wmgl_version(&major, &minor, &patch); -+ value[0] = major; -+ value[1] = minor; -+ value[2] = patch; -+ } -+ else -+ { -+ value[0] = value[1] = value[2] = 0; -+ get_fallback_renderer_version(value); -+ } -+ return TRUE; -+ case WGL_RENDERER_VIDEO_MEMORY_WINE: -+ *value = p_wmgl_video_memory_mb(); -+ return TRUE; -+ } -+ FIXME("unrecognized attribute 0x%04x\n", attribute); -+ return FALSE; -+} -+ -+static const char *wmgl_query_renderer_string(GLenum attribute) -+{ -+ const char *vendor; -+ -+ switch (attribute) -+ { -+ case WGL_RENDERER_DEVICE_ID_WINE: -+ return p_wmgl_renderer_string(); -+ case WGL_RENDERER_VENDOR_ID_WINE: -+ /* The core's GL_VENDOR (WINEMETALGL_VENDOR overrides it). */ -+ vendor = getenv("WINEMETALGL_VENDOR"); -+ return vendor && *vendor ? vendor : "WineMetalGL"; -+ } -+ FIXME("unrecognized attribute 0x%04x\n", attribute); -+ return NULL; -+} -+ -+static BOOL wmgl_wglQueryCurrentRendererIntegerWINE(GLenum attribute, GLuint *value) -+{ -+ return wmgl_query_renderer_integer(attribute, value); -+} -+ -+static const char *wmgl_wglQueryCurrentRendererStringWINE(GLenum attribute) -+{ -+ return wmgl_query_renderer_string(attribute); -+} -+ -+static BOOL wmgl_wglQueryRendererIntegerWINE(HDC dc, GLint renderer, GLenum attribute, GLuint *value) -+{ -+ if (renderer) return FALSE; -+ return wmgl_query_renderer_integer(attribute, value); -+} -+ -+static const char *wmgl_wglQueryRendererStringWINE(HDC dc, GLint renderer, GLenum attribute) -+{ -+ if (renderer) return NULL; -+ return wmgl_query_renderer_string(attribute); -+} -+ -+static void wmgl_init_extensions(struct opengl_funcs *funcs, BOOLEAN extensions[GL_EXTENSION_COUNT]) -+{ -+ extensions[WGL_WINE_query_renderer] = 1; -+ funcs->p_wglQueryCurrentRendererIntegerWINE = wmgl_wglQueryCurrentRendererIntegerWINE; -+ funcs->p_wglQueryCurrentRendererStringWINE = wmgl_wglQueryCurrentRendererStringWINE; -+ funcs->p_wglQueryRendererIntegerWINE = wmgl_wglQueryRendererIntegerWINE; -+ funcs->p_wglQueryRendererStringWINE = wmgl_wglQueryRendererStringWINE; -+} -+ -+static void wmgl_surface_destroy(struct opengl_drawable *base) -+{ -+ struct wmgl_drawable *gl = wmgl_impl(base); -+ TRACE("drawable %s\n", debugstr_opengl_drawable(base)); -+ if (gl->surface) p_wmgl_surface_destroy(gl->surface); -+ gl->surface = NULL; -+} -+ -+static void wmgl_surface_flush(struct opengl_drawable *base, UINT flags) -+{ -+ struct wmgl_drawable *gl = wmgl_impl(base); -+ TRACE("drawable %s, flags %#x\n", debugstr_opengl_drawable(base), flags); -+ if (!gl->surface) return; -+ p_wmgl_surface_flush(gl->surface, flags, base->monitor_size.cx, base->monitor_size.cy, base->interval); -+ if (flags & GL_FLUSH_PRESENT) client_surface_present(base->client); -+} -+ -+static BOOL wmgl_surface_swap(struct opengl_drawable *base) -+{ -+ struct wmgl_drawable *gl = wmgl_impl(base); -+ TRACE("drawable %s\n", debugstr_opengl_drawable(base)); -+ if (!gl->surface) return FALSE; -+ /* A frame the core could not present (no drawable available) is -+ * dropped, as a compositor would; SwapBuffers itself succeeds. */ -+ if (!p_wmgl_surface_swap(gl->surface)) TRACE("frame not presented\n"); -+ client_surface_present(base->client); -+ return TRUE; -+} -+ -+static const struct opengl_drawable_funcs wmgl_surface_funcs = -+{ -+ .destroy = wmgl_surface_destroy, -+ .flush = wmgl_surface_flush, -+ .swap = wmgl_surface_swap, -+}; -+ -+static const struct opengl_drawable_funcs wmgl_pbuffer_funcs = -+{ -+ .destroy = wmgl_surface_destroy, -+}; -+ -+static BOOL wmgl_surface_create(struct client_surface *client, int format, struct opengl_drawable **drawable) -+{ -+ struct macdrv_client_surface *surface = impl_from_client_surface(client); -+ struct wmgl_drawable *gl; -+ macdrv_metal_layer layer; -+ RECT rect; -+ -+ TRACE("client %s, format %d\n", debugstr_client_surface(client), format); -+ if (!macdrv_client_surface_acquire_metal_swapchain(surface)) return FALSE; -+ if (!(layer = macdrv_swapchain_get_layer(surface->metal_swapchain))) return FALSE; -+ if (!NtUserGetClientRect(client->hwnd, &rect, NtUserGetWinMonitorDpi(client->hwnd, MDT_RAW_DPI))) -+ SetRectEmpty(&rect); -+ if (!(gl = opengl_drawable_create(sizeof(*gl), &wmgl_surface_funcs, format, client))) return FALSE; -+ gl->surface = p_wmgl_surface_create_window(layer, format, rect.right - rect.left, rect.bottom - rect.top); -+ if (!gl->surface) -+ { -+ opengl_drawable_release(&gl->base); -+ return FALSE; -+ } -+ *drawable = &gl->base; -+ return TRUE; -+} -+ -+static BOOL wmgl_pbuffer_create(HDC hdc, int format, BOOL largest, GLenum texture_format, GLenum texture_target, -+ GLint max_level, GLsizei *width, GLsizei *height, struct opengl_drawable **drawable) -+{ -+ struct wmgl_drawable *gl; -+ -+ TRACE("hdc %p, format %d, %dx%d\n", hdc, format, *width, *height); -+ if (!(gl = opengl_drawable_create(sizeof(*gl), &wmgl_pbuffer_funcs, format, NULL))) return FALSE; -+ gl->surface = p_wmgl_surface_create_pbuffer(format, *width, *height); -+ if (!gl->surface) -+ { -+ opengl_drawable_release(&gl->base); -+ return FALSE; -+ } -+ *drawable = &gl->base; -+ return TRUE; -+} -+ -+/* win32u creates every client context without a share context and lets -+ * opengl32 handle wglShareLists, which needs all contexts in the process to -+ * share one object namespace (upstream Wine shares them all with its internal -+ * context). Every WineMetalGL context therefore shares with a hidden root -+ * context that lives for the rest of the process. */ -+static void *wmgl_root_context; -+static pthread_mutex_t wmgl_root_mutex = PTHREAD_MUTEX_INITIALIZER; -+ -+static void *wmgl_share_root(int format) -+{ -+ int error = 0; -+ pthread_mutex_lock(&wmgl_root_mutex); -+ if (!wmgl_root_context) wmgl_root_context = p_wmgl_context_create(format, NULL, NULL, &error); -+ pthread_mutex_unlock(&wmgl_root_mutex); -+ return wmgl_root_context; -+} -+ -+static BOOL wmgl_context_create(int format, void *share, const int *attribs, void **private, BOOL *shared) -+{ -+ int error = 0; -+ void *context; -+ -+ TRACE("format %d, share %p, attribs %p\n", format, share, attribs); -+ if (!share && !(share = wmgl_share_root(format))) -+ { -+ RtlSetLastWin32Error(ERROR_INVALID_PARAMETER); -+ return FALSE; -+ } -+ if (!(context = p_wmgl_context_create(format, share, attribs, &error))) -+ { -+ switch (error) -+ { -+ case 1: RtlSetLastWin32Error(ERROR_INVALID_VERSION_ARB); break; -+ case 2: RtlSetLastWin32Error(ERROR_INVALID_PROFILE_ARB); break; -+ default: RtlSetLastWin32Error(ERROR_INVALID_PARAMETER); break; -+ } -+ return FALSE; -+ } -+ *private = context; -+ *shared = TRUE; -+ return TRUE; -+} -+ -+static BOOL wmgl_context_destroy(void *private) -+{ -+ TRACE("context %p\n", private); -+ p_wmgl_context_destroy(private); -+ return TRUE; -+} -+ -+static BOOL wmgl_make_current(struct opengl_drawable *draw, struct opengl_drawable *read, void *private) -+{ -+ struct wmgl_drawable *d = wmgl_impl(draw), *r = wmgl_impl(read); -+ TRACE("draw %s, read %s, context %p\n", debugstr_opengl_drawable(draw), debugstr_opengl_drawable(read), private); -+ return p_wmgl_make_current(private, d ? d->surface : NULL, r ? r->surface : NULL); -+} -+ -+static void *wmgl_driver_get_proc_address(const char *name) -+{ -+ return p_wmgl_get_proc_address(name); -+} -+ -+static BOOL wmgl_pbuffer_updated(HDC hdc, struct opengl_drawable *base, GLenum cube_face, GLint mipmap_level) -+{ -+ return GL_TRUE; -+} -+ -+static UINT wmgl_pbuffer_bind(HDC hdc, struct opengl_drawable *base, GLenum buffer) -+{ -+ return -1; /* win32u copies the pbuffer into the bound texture */ -+} -+ -+static const struct opengl_driver_funcs wmgl_driver_funcs = -+{ -+ .p_get_proc_address = wmgl_driver_get_proc_address, -+ .p_init_pixel_formats = wmgl_init_pixel_formats, -+ .p_describe_pixel_format = wmgl_describe_pixel_format, -+ .p_init_extensions = wmgl_init_extensions, -+ .p_surface_create = wmgl_surface_create, -+ .p_context_create = wmgl_context_create, -+ .p_context_destroy = wmgl_context_destroy, -+ .p_make_current = wmgl_make_current, -+ .p_pbuffer_create = wmgl_pbuffer_create, -+ .p_pbuffer_updated = wmgl_pbuffer_updated, -+ .p_pbuffer_bind = wmgl_pbuffer_bind, -+}; -+ -+static UINT wmgl_core_init(const struct opengl_driver_funcs **driver_funcs) -+{ -+ if (!(wmgl_handle = dlopen("@loader_path/libwinemetalgl.dylib", RTLD_NOW | RTLD_LOCAL))) -+ { -+ WARN("libwinemetalgl.dylib failed to load, using Apple's OpenGL: %s\n", dlerror()); -+ return STATUS_NOT_SUPPORTED; -+ } -+#define LOAD(name) if (!(p_##name = dlsym(wmgl_handle, #name))) { ERR("%s missing\n", #name); goto failed; } -+ LOAD(wmgl_initialize); -+ LOAD(wmgl_get_pixel_formats); -+ LOAD(wmgl_surface_create_window); -+ LOAD(wmgl_surface_create_pbuffer); -+ LOAD(wmgl_surface_destroy); -+ LOAD(wmgl_surface_flush); -+ LOAD(wmgl_surface_swap); -+ LOAD(wmgl_context_create); -+ LOAD(wmgl_context_destroy); -+ LOAD(wmgl_make_current); -+ LOAD(wmgl_get_proc_address); -+ LOAD(wmgl_renderer_string); -+ LOAD(wmgl_video_memory_mb); -+ LOAD(wmgl_max_versions); -+#undef LOAD -+ p_wmgl_version = dlsym(wmgl_handle, "wmgl_version"); -+ if (p_wmgl_initialize(WMGL_DRIVER_ABI_VERSION)) -+ { -+ ERR("WineMetalGL core refused to initialize\n"); -+ goto failed; -+ } -+ wmgl_format_count = p_wmgl_get_pixel_formats(NULL, 0, &wmgl_onscreen_count); -+ if (!wmgl_format_count || !(wmgl_formats = calloc(wmgl_format_count, sizeof(*wmgl_formats)))) goto failed; -+ p_wmgl_get_pixel_formats(wmgl_formats, wmgl_format_count, &wmgl_onscreen_count); -+ TRACE("WineMetalGL core: %u pixel formats (%u on screen), renderer %s\n", wmgl_format_count, wmgl_onscreen_count, -+ p_wmgl_renderer_string()); -+ *driver_funcs = &wmgl_driver_funcs; -+ return STATUS_SUCCESS; -+ -+failed: -+ dlclose(wmgl_handle); -+ wmgl_handle = NULL; -+ return STATUS_NOT_SUPPORTED; -+} -+ - /********************************************************************** - * macdrv_OpenGLInit - */ -@@ -2532,6 +2961,8 @@ UINT macdrv_OpenGLInit(UINT version, const struct opengl_funcs *opengl_funcs, co - } - funcs = opengl_funcs; - -+ if (wmgl_core_requested() && wmgl_core_init(driver_funcs) == STATUS_SUCCESS) return STATUS_SUCCESS; -+ - opengl_handle = dlopen("/System/Library/Frameworks/OpenGL.framework/OpenGL", RTLD_LAZY|RTLD_LOCAL|RTLD_NOLOAD); - if (!opengl_handle) - { diff --git a/scripts/build-wine-module.sh b/scripts/build-wine-module.sh new file mode 100755 index 00000000..4082273c --- /dev/null +++ b/scripts/build-wine-module.sh @@ -0,0 +1,76 @@ +#!/bin/sh +# Builds the WineMetalGL Wine module (src/wine): winemetalgl.dll and +# opengl32.dll for x86_64 and i386 Windows programs, and winemetalgl.so, the +# unix library the 64-bit Wine process runs for both. +# +# scripts/build-wine-module.sh WINE_INSTALL [OUTDIR] +# +# WINE_INSTALL is any Wine 11.x installation (its include/wine headers) or a +# Wine 11.x source tree (its include directory); Wine itself is not rebuilt +# or changed. The PE side needs llvm-mingw +# (x86_64-w64-mingw32-clang, i686-w64-mingw32-clang) on PATH. The core +# (libwinemetalgl.dylib) is built by the CMake release preset. +set -eu + +ROOT=$(cd "$(dirname "$0")/.." && pwd) +WINE=${1:?usage: build-wine-module.sh WINE_INSTALL [OUTDIR]} +OUT=${2:-$ROOT/build/wine-module} +GEN=$OUT/gen +OBJ=$OUT/obj +mkdir -p "$GEN" "$OBJ/x86_64" "$OBJ/i386" "$OBJ/unix" "$OUT/x86_64-windows" "$OUT/i386-windows" "$OUT/x86_64-unix" + +python3 "$ROOT/tools/gen/wine_gen.py" --registry "$ROOT/third_party/khronos/gl.xml" \ + --extensions "$ROOT/tools/gen/extensions.txt" --out "$GEN" >/dev/null + +# ---------------------------------------------------------------- PE side +for arch in x86_64 i386; do + case $arch in x86_64) cc=x86_64-w64-mingw32-clang ;; i386) cc=i686-w64-mingw32-clang ;; esac + objs= + for src in "$ROOT"/src/wine/pe/*.c "$GEN/wine_gl_pe.gen.c"; do + obj="$OBJ/$arch/$(basename "${src%.c}").o" + "$cc" -c -O2 -Wall -Werror -I"$ROOT/src/wine/include" -I"$ROOT/src/wine/pe" -I"$GEN" "$src" -o "$obj" + objs="$objs $obj" + done + # shellcheck disable=SC2086 + "$cc" -shared -o "$OUT/$arch-windows/winemetalgl.dll" $objs "$GEN/winemetalgl.def" \ + -lgdi32 -luser32 -lkernel32 -lntdll -Wl,--kill-at + printf 'BOOL __stdcall DllMain(void *i, unsigned int r, void *p) { return 1; }\n' | + sed 's/BOOL/int/' > "$OBJ/$arch/forwarder.c" + "$cc" -c -O2 "$OBJ/$arch/forwarder.c" -o "$OBJ/$arch/forwarder.o" + "$cc" -shared -nostdlib -o "$OUT/$arch-windows/opengl32.dll" "$OBJ/$arch/forwarder.o" "$GEN/opengl32.def" \ + -Wl,--entry,DllMain$( [ $arch = i386 ] && echo @12 ) -lkernel32 + # Wine loads winemetalgl.so for a builtin winemetalgl.dll only (winebuild --builtin). + python3 - "$OUT/$arch-windows/winemetalgl.dll" <<'PY' +import struct, sys +path = sys.argv[1] +with open(path, "r+b") as f: + header = f.read(64) + assert header[:2] == b"MZ" + e_lfanew = struct.unpack_from("= 64 + 32, "no room for the Wine builtin signature" + f.seek(64) + f.write(b"Wine builtin DLL".ljust(32, b"\0")) +PY +done + +# -------------------------------------------------------------- unix side +# Apple's clang: llvm-mingw's plain clang may come first on PATH. +UNIX_CC=${UNIX_CC:-/usr/bin/clang} +CFLAGS="-arch x86_64 -mmacosx-version-min=15.0 -O2 -fPIC -Wall -DWINE_UNIX_LIB -D__WINESRC__ -D_WIN64" +if test -d "$WINE/include/wine/windows"; then WINEINC="-I$WINE/include/wine/windows -I$WINE/include" +elif test -f "$WINE/include/windef.h"; then WINEINC="-I$WINE/include" +else echo "no Wine headers in $WINE" >&2; exit 1; fi +INCS="-I$ROOT/src/wine/include -I$ROOT/src/wine/unix -I$GEN $WINEINC" +uobjs= +for src in "$ROOT"/src/wine/unix/*.c "$GEN/wine_gl_unix.gen.c"; do + obj="$OBJ/unix/$(basename "${src%.c}").o" + # shellcheck disable=SC2086 + "$UNIX_CC" $CFLAGS $INCS -c "$src" -o "$obj" + uobjs="$uobjs $obj" +done +"$UNIX_CC" -arch x86_64 -mmacosx-version-min=15.0 -O2 -fPIC -fobjc-arc -Wall -c "$ROOT/src/wine/unix/unix_window.m" \ + -o "$OBJ/unix/unix_window.o" +# shellcheck disable=SC2086 +"$UNIX_CC" -arch x86_64 -mmacosx-version-min=15.0 -shared -o "$OUT/x86_64-unix/winemetalgl.so" $uobjs "$OBJ/unix/unix_window.o" \ + -framework AppKit -framework Metal -framework QuartzCore -Wl,-undefined,dynamic_lookup +echo "Built: $OUT/{x86_64,i386}-windows/{winemetalgl,opengl32}.dll, $OUT/x86_64-unix/winemetalgl.so" diff --git a/scripts/cts/run.py b/scripts/cts/run.py index 70a6ca89..425f8fe1 100755 --- a/scripts/cts/run.py +++ b/scripts/cts/run.py @@ -36,6 +36,14 @@ MAX_ABORTS = 20 # platform aborts tolerated per shard before cases are marked Crash MUSTPASS_REL = "external/openglcts/data/gl_cts/data/mustpass/gl/khronos_mustpass/main" +# The single-configuration lists (KHR-Single-GL4x: enhanced layouts, arrays of arrays). +SINGLE_MUSTPASS_REL = "external/openglcts/data/gl_cts/data/mustpass/gl/khronos_mustpass_single/main" + + +def mustpass_dir(caselist_name): + return SINGLE_MUSTPASS_REL if caselist_name.endswith("-khr-single.txt") else MUSTPASS_REL + + CASE_BEGIN = re.compile(r"^#beginTestCaseResult (\S+)") CASE_END = re.compile(r"^#(endTestCaseResult|terminateTestCaseResult)") STATUS = re.compile(r'&2; exit 1; } +done + +cp "$MODULE/x86_64-windows/winemetalgl.dll" "$LIB/x86_64-windows/winemetalgl.dll" +cp "$MODULE/i386-windows/winemetalgl.dll" "$LIB/i386-windows/winemetalgl.dll" +cp "$MODULE/x86_64-unix/winemetalgl.so" "$LIB/x86_64-unix/winemetalgl.so" +if test "$CORE" != "$LIB/x86_64-unix/libwinemetalgl.dylib"; then cp "$CORE" "$LIB/x86_64-unix/libwinemetalgl.dylib"; fi +codesign -f -s - "$LIB/x86_64-unix/winemetalgl.so" "$LIB/x86_64-unix/libwinemetalgl.dylib" 2>/dev/null +echo "Installed winemetalgl into $LIB" + +if test -n "$PREFIX"; then + test -d "$PREFIX/drive_c/windows/system32" || { echo "not a Wine prefix: $PREFIX" >&2; exit 1; } + cp "$MODULE/x86_64-windows/opengl32.dll" "$PREFIX/drive_c/windows/system32/opengl32.dll" + if test -d "$PREFIX/drive_c/windows/syswow64"; then + cp "$MODULE/i386-windows/opengl32.dll" "$PREFIX/drive_c/windows/syswow64/opengl32.dll" + fi + WINEPREFIX="$PREFIX" WINEDEBUG=-all "$WINE/bin/wine" reg add 'HKCU\Software\Wine\DllOverrides' \ + /v opengl32 /d native /f >/dev/null + echo "Installed opengl32.dll (opengl32=native) into $PREFIX" +fi diff --git a/scripts/stage-arm64-core.sh b/scripts/stage-arm64-core.sh index 6a5c2635..23b714c2 100755 --- a/scripts/stage-arm64-core.sh +++ b/scripts/stage-arm64-core.sh @@ -1,9 +1,9 @@ #!/bin/sh -# Stages the arm64 core for a native arm64 Wine: the core, the driver ABI -# header and the Wine patches (docs/release.md). The x86_64 release -# (scripts/stage-release.sh) is the drop-in package for x86_64 Wine under -# Rosetta; the two cores are not interchangeable (winemac.so loads the core of -# its own architecture and falls back to Apple's OpenGL otherwise). +# Stages the arm64 core for a native arm64 Wine: the core and the driver ABI +# header (docs/release.md). The x86_64 release (scripts/stage-release.sh) is the +# package for x86_64 Wine under Rosetta, with the Wine module; the module is +# built for x86_64 Wine only, and the two cores are not interchangeable (a +# library of the other architecture does not load). # # scripts/stage-arm64-core.sh /absolute/path/to/output set -eu @@ -42,31 +42,30 @@ if nm -gU "$core" | awk '{print $3}' | grep -v '^_wmgl_' | grep -q .; then exit 1 fi -mkdir -p "$output/lib" "$output/include/winemetalgl" "$output/patches" "$output/docs" "$output/evidence/cts" +mkdir -p "$output/lib" "$output/include/winemetalgl" "$output/docs" "$output/evidence/cts" cp "$core" "$output/lib/libwinemetalgl.dylib" codesign --force --sign - "$output/lib/libwinemetalgl.dylib" cp "$project_root/include/winemetalgl/driver.h" "$output/include/winemetalgl/driver.h" -cp -R "$project_root/patches/wine" "$output/patches/wine" cp "$project_root/README.md" "$output/README.md" # LGPL-3.0-or-later: LICENSE, the GPL-3.0 it builds on and the third-party licenses. cp "$project_root/LICENSE" "$output/LICENSE" cp -R "$project_root/LICENSES" "$output/LICENSES" cp "$project_root/docs/conformance.md" "$project_root/docs/unsupported.md" "$project_root/docs/building.md" \ "$project_root/docs/release.md" "$output/docs/" -# The native KHR-GL30 to KHR-GL33 runs exercise this build of the core (tests/cts/native). -cp "$project_root"/tests/cts/baseline/gl3[0-3]-cfg*-native.txt "$output/evidence/cts/" +# The native runs exercise this build of the core (tests/cts/native). +cp "$project_root"/tests/cts/baseline/gl*-cfg*-native.txt "$output/evidence/cts/" cat > "$output/ARM64.txt" <<'EOF' WineMetalGL core for arm64 (native) Wine on macOS 15 or later. -lib/libwinemetalgl.dylib goes next to an arm64 winemac.so built from a Wine -11.17 tree with patches/wine applied (patches/wine/README.md); winemac.so -loads it from its own directory. Do not install it into an x86_64 Wine (use -the x86_64 package there): a core of the wrong architecture does not load, and -Wine silently falls back to Apple's OpenGL. +lib/libwinemetalgl.dylib implements the driver ABI in +include/winemetalgl/driver.h. The Wine module that loads it +(scripts/build-wine-module.sh) is built for x86_64 Wine only, so this core is +for those porting the module to an arm64 Wine. Do not install it into an +x86_64 Wine (use the x86_64 package there): a library of the wrong +architecture does not load. -This core is the build the native KHR-GL30 to KHR-GL33 runs in evidence/cts -exercised (mustpass configurations 1 to 4). It has not yet run inside an arm64 -Wine. +This core is the build the native runs in evidence/cts exercised. It has not +yet run inside an arm64 Wine. EOF commit=$(git -C "$project_root" rev-parse HEAD) diff --git a/scripts/stage-release.sh b/scripts/stage-release.sh index 0c47b993..08bf6cc9 100755 --- a/scripts/stage-release.sh +++ b/scripts/stage-release.sh @@ -1,38 +1,29 @@ #!/bin/sh -# Stages a release: the core next to the patched Wine modules that load it, -# the generated documentation and the evidence behind it (docs/release.md). +# Stages a release: the Wine module with the core, the generated documentation +# and the evidence behind it (docs/release.md). # -# artifacts/ -# wine-driver/ libwinemetalgl.dylib, winemac.so, win32u.so, opengl32.so -# (install into lib/wine/x86_64-unix; winemac.so loads the -# core from its own directory) -# guest/i386/ opengl32.dll (lib/wine/i386-windows) -# guest/x86_64/ opengl32.dll (lib/wine/x86_64-windows) +# scripts/stage-release.sh WINE_HEADERS OUTPUT +# +# WINE_HEADERS is a Wine 11.x installation or source tree, for the module's +# build (scripts/build-wine-module.sh). +# +# install-wine-module.sh +# module/ +# x86_64-unix/ winemetalgl.so, libwinemetalgl.dylib (lib/wine/x86_64-unix) +# x86_64-windows/ winemetalgl.dll (lib/wine/x86_64-windows), opengl32.dll (prefix system32) +# i386-windows/ winemetalgl.dll (lib/wine/i386-windows), opengl32.dll (prefix syswow64) set -eu if test "$#" -ne 2; then - printf '%s\n' "usage: $0 /absolute/path/to/wine-build /absolute/path/to/output" >&2 + printf '%s\n' "usage: $0 /absolute/path/to/wine-headers /absolute/path/to/output" >&2 exit 2 fi project_root=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) -wine_build=$1 +wine_headers=$1 output=$2 core="$project_root/build/release/libwinemetalgl.dylib" - -# Build the core from this checkout, so the staged library is the commit -# recorded below. -"$project_root/scripts/build.sh" >/dev/null - -for path in \ - "$core" \ - "$wine_build/dlls/opengl32/i386-windows/opengl32.dll" \ - "$wine_build/dlls/opengl32/x86_64-windows/opengl32.dll" \ - "$wine_build/dlls/opengl32/opengl32.so" \ - "$wine_build/dlls/winemac.drv/winemac.so" \ - "$wine_build/dlls/win32u/win32u.so"; do - test -f "$path" || { printf 'missing build artifact: %s\n' "$path" >&2; exit 1; } -done +module="$project_root/build/wine-module" if test "$output" = /; then printf '%s\n' 'refusing to stage over /' >&2 @@ -42,20 +33,25 @@ if test -e "$output"; then printf 'output already exists; refusing to replace it: %s\n' "$output" >&2 exit 1 fi -mkdir -p "$output/artifacts/guest/i386" "$output/artifacts/guest/x86_64" "$output/artifacts/wine-driver" "$output/docs" \ - "$output/evidence/cts" "$output/evidence/games" -cp "$core" "$output/artifacts/wine-driver/libwinemetalgl.dylib" -cp "$wine_build/dlls/winemac.drv/winemac.so" "$output/artifacts/wine-driver/winemac.so" -cp "$wine_build/dlls/win32u/win32u.so" "$output/artifacts/wine-driver/win32u.so" -cp "$wine_build/dlls/opengl32/opengl32.so" "$output/artifacts/wine-driver/opengl32.so" -cp "$wine_build/dlls/opengl32/i386-windows/opengl32.dll" "$output/artifacts/guest/i386/opengl32.dll" -cp "$wine_build/dlls/opengl32/x86_64-windows/opengl32.dll" "$output/artifacts/guest/x86_64/opengl32.dll" +# Build the core and the module from this checkout, so the staged libraries +# are the commit recorded below. +"$project_root/scripts/build.sh" >/dev/null +"$project_root/scripts/build-wine-module.sh" "$wine_headers" "$module" >/dev/null + +mkdir -p "$output/module/x86_64-unix" "$output/module/x86_64-windows" "$output/module/i386-windows" "$output/docs" \ + "$output/evidence/cts" "$output/evidence/games" +cp "$core" "$output/module/x86_64-unix/libwinemetalgl.dylib" +cp "$module/x86_64-unix/winemetalgl.so" "$output/module/x86_64-unix/winemetalgl.so" +for arch in x86_64 i386; do + cp "$module/$arch-windows/winemetalgl.dll" "$module/$arch-windows/opengl32.dll" "$output/module/$arch-windows/" +done # Ad-hoc signatures: a runtime that requires signed code refuses unsigned -# libraries, and winemac.so would quietly fall back to Apple's OpenGL. -for library in "$output"/artifacts/wine-driver/*; do +# libraries. +for library in "$output"/module/x86_64-unix/*; do codesign --force --sign - "$library" done +cp "$project_root/scripts/install-wine-module.sh" "$output/install-wine-module.sh" cp "$project_root/README.md" "$output/README.md" # LGPL-3.0-or-later: LICENSE, the GPL-3.0 it builds on and the third-party licenses. cp "$project_root/LICENSE" "$output/LICENSE" @@ -81,8 +77,8 @@ if test -f "$project_root/tests/games/perf.json"; then cp "$project_root/tests/g commit=$(git -C "$project_root" rev-parse HEAD) if ! git -C "$project_root" diff --quiet HEAD; then commit="$commit (uncommitted changes)"; fi -printf 'WineMetalGL staged from %s\nWine build: %s\nCommit: %s\n' \ - "$project_root" "$wine_build" "$commit" > "$output/STAGED-FROM.txt" +printf 'WineMetalGL staged from %s\nWine headers: %s\nCommit: %s\n' \ + "$project_root" "$wine_headers" "$commit" > "$output/STAGED-FROM.txt" ( cd "$output" diff --git a/scripts/stage-wine.sh b/scripts/stage-wine.sh deleted file mode 100755 index 1be8220e..00000000 --- a/scripts/stage-wine.sh +++ /dev/null @@ -1,28 +0,0 @@ -#!/bin/sh -# Installs the core next to a Wine build's winemac.so, which loads it from -# its own directory. -set -eu - -if test "$#" -ne 1; then - printf '%s\n' "usage: $0 /absolute/path/to/wine-build-or-install-unix-dir" >&2 - exit 2 -fi - -project_root=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) -target=$1 -source_library="$project_root/build/release/libwinemetalgl.dylib" -if test -d "$target/dlls/winemac.drv"; then - destination_dir="$target/dlls/winemac.drv" -else - destination_dir="$target" -fi - -test -f "$destination_dir/winemac.so" -test -f "$source_library" -test "$(lipo -archs "$source_library")" = "x86_64" -test "$(otool -l "$source_library" | awk '/LC_BUILD_VERSION/{found=1} found && /minos/{print $2; exit}')" = "15.0" - -install -m 755 "$source_library" "$destination_dir/libwinemetalgl.dylib" -codesign --force --sign - "$destination_dir/libwinemetalgl.dylib" - -printf '%s\n' "Staged: $destination_dir/libwinemetalgl.dylib" diff --git a/scripts/traces/benchmark.py b/scripts/traces/benchmark.py index 8346794c..245a5068 100644 --- a/scripts/traces/benchmark.py +++ b/scripts/traces/benchmark.py @@ -46,7 +46,10 @@ def impl_env(base, impl): env = dict(base) env.pop("WINEMETALGL", None) if impl == "apple": - env["WINEMETALGL"] = "0" # Apple's OpenGL (winemac's fallback path) + # Wine's own opengl32, Apple's OpenGL (WINEMETALGL=0 for a Wine with + # the 2.x patches) instead of the Wine module. + env["WINEDLLOVERRIDES"] = "opengl32=b" + env["WINEMETALGL"] = "0" return env diff --git a/scripts/traces/replay.py b/scripts/traces/replay.py index 5c1cd1e0..6807e0b1 100755 --- a/scripts/traces/replay.py +++ b/scripts/traces/replay.py @@ -9,7 +9,7 @@ are snapshotted as PNM on stdout. Frames are compared with a reference image (by default produced by the -Apple OpenGL path, WINEMETALGL=0) using two numbers: the fraction of pixels +Apple OpenGL path, Wine's own opengl32) using two numbers: the fraction of pixels whose largest channel difference exceeds --threshold, and PSNR. An entry passes when the fraction is at most its "tolerance" (default 0.005). @@ -96,8 +96,9 @@ def compare(reference, actual, threshold): def path_env(path): - # The core is Wine's default OpenGL; WINEMETALGL=0 selects Apple's. - return {"WINEMETALGL": "0"} if path == "apple" else {} + # WineMetalGL is the prefix's OpenGL (the Wine module); Wine's own + # opengl32 is Apple's (WINEMETALGL=0 for a Wine with the 2.x patches). + return {"WINEDLLOVERRIDES": "opengl32=b", "WINEMETALGL": "0"} if path == "apple" else {} def run_entry(entry, args, env, workdir): diff --git a/scripts/verify-release.sh b/scripts/verify-release.sh index 85ea49b6..083e0b72 100755 --- a/scripts/verify-release.sh +++ b/scripts/verify-release.sh @@ -8,15 +8,17 @@ if test "$#" -ne 1; then fi stage=$1 -core="$stage/artifacts/wine-driver/libwinemetalgl.dylib" +core="$stage/module/x86_64-unix/libwinemetalgl.dylib" +unix="$stage/module/x86_64-unix/winemetalgl.so" for path in \ "$core" \ - "$stage/artifacts/guest/i386/opengl32.dll" \ - "$stage/artifacts/guest/x86_64/opengl32.dll" \ - "$stage/artifacts/wine-driver/winemac.so" \ - "$stage/artifacts/wine-driver/win32u.so" \ - "$stage/artifacts/wine-driver/opengl32.so" \ + "$unix" \ + "$stage/module/x86_64-windows/winemetalgl.dll" \ + "$stage/module/x86_64-windows/opengl32.dll" \ + "$stage/module/i386-windows/winemetalgl.dll" \ + "$stage/module/i386-windows/opengl32.dll" \ + "$stage/install-wine-module.sh" \ "$stage/LICENSE" \ "$stage/LICENSES/GPL-3.0.txt" \ "$stage/docs/api-coverage.json" \ @@ -27,18 +29,14 @@ for path in \ "$stage/docs/testing.md" \ "$stage/docs/building.md" \ "$stage/docs/performance.md" \ - "$stage/evidence/cts/gl33-cfg1-native.txt" \ - "$stage/evidence/cts/gl33-cfg1-wine.txt" \ + "$stage/evidence/cts/gl46-cfg1-native.txt" \ + "$stage/evidence/cts/gl46-cfg1-wine.txt" \ "$stage/evidence/games/results.json" \ "$stage/SHA256SUMS"; do test -f "$path" || { printf 'missing staged artifact: %s\n' "$path" >&2; exit 1; } done -for path in \ - "$core" \ - "$stage/artifacts/wine-driver/winemac.so" \ - "$stage/artifacts/wine-driver/win32u.so" \ - "$stage/artifacts/wine-driver/opengl32.so"; do +for path in "$core" "$unix"; do arch=$(lipo -archs "$path") test "$arch" = x86_64 || { printf 'unexpected Mach-O architecture %s: %s\n' "$arch" "$path" >&2; exit 1; } codesign --verify "$path" 2>/dev/null || { printf 'not validly signed: %s\n' "$path" >&2; exit 1; } @@ -55,8 +53,17 @@ nm -gU "$core" | awk '{print $3}' | grep -v '^_wmgl_' | grep -q . && { exit 1 } -file "$stage/artifacts/guest/i386/opengl32.dll" | grep -q 'Intel 80386' -file "$stage/artifacts/guest/x86_64/opengl32.dll" | grep -q 'x86-64' +for dll in winemetalgl opengl32; do + file "$stage/module/i386-windows/$dll.dll" | grep -q 'Intel 80386' + file "$stage/module/x86_64-windows/$dll.dll" | grep -q 'x86-64' +done +# Wine loads winemetalgl.so only for a builtin winemetalgl.dll. +for arch in x86_64 i386; do + dd if="$stage/module/$arch-windows/winemetalgl.dll" bs=1 skip=64 count=16 2>/dev/null | grep -q 'Wine builtin DLL' || + { printf 'not marked as a Wine builtin: %s\n' "$arch-windows/winemetalgl.dll" >&2; exit 1; } +done +nm -gU "$unix" | grep -q ' ___wine_unix_call_wow64_funcs$' || + { printf '%s\n' 'winemetalgl.so has no WoW64 call table' >&2; exit 1; } python3 -m json.tool "$stage/docs/api-coverage.json" >/dev/null python3 -m json.tool "$stage/evidence/games/results.json" >/dev/null diff --git a/scripts/verify-source.sh b/scripts/verify-source.sh index 6e170ae3..e7750eb6 100755 --- a/scripts/verify-source.sh +++ b/scripts/verify-source.sh @@ -17,7 +17,7 @@ PY python3 -m json.tool docs/api-coverage.json >/dev/null python3 -I scripts/docs/generate.py --check --check-provenance shasum -a 256 -c SHA256SUMS >/dev/null -if grep -i -n 'VKMT' scripts/build.sh scripts/stage-host-libs.sh scripts/stage-release.sh scripts/stage-wine.sh README.md CMakeLists.txt || grep -R -i -n 'VKMT' tests; then +if grep -i -n 'VKMT' scripts/build.sh scripts/stage-host-libs.sh scripts/stage-release.sh scripts/build-wine-module.sh scripts/install-wine-module.sh README.md CMakeLists.txt || grep -R -i -n 'VKMT' tests; then printf '%s\n' 'standalone source contains VKMT-specific naming' >&2 exit 1 fi @@ -37,21 +37,4 @@ if test -f build/release/libwinemetalgl.dylib; then fi fi -wine_source=${WINEMETALGL_WINE_SOURCE:-} -if test -n "$wine_source"; then - patch_file=${WINEMETALGL_WINE_PATCH:-} - patch_dir=$project_root/patches/wine/wine-11.17 - check_dir=$(mktemp -d "${TMPDIR:-/tmp}/winemetalgl-patch-check.XXXXXX") - cleanup() { git -C "$wine_source" worktree remove --force "$check_dir" >/dev/null 2>&1 || true; } - trap cleanup EXIT INT TERM - git -C "$wine_source" worktree add --detach "$check_dir" HEAD >/dev/null 2>&1 - if test -n "$patch_file"; then - git -C "$check_dir" apply --check "$patch_file" - else - for patch in "$patch_dir"/*.patch; do - git -C "$check_dir" apply "$patch" - done - fi -fi - printf '%s\n' 'WINEMETALGL_SOURCE_AUDIT_OK' diff --git a/src/core/api/blit.mm b/src/core/api/blit.mm index db05102c..fd1b17f0 100644 --- a/src/core/api/blit.mm +++ b/src/core/api/blit.mm @@ -422,7 +422,11 @@ void BlitFramebuffer(Context& ctx, GLint srcX0, GLint srcY0, GLint srcX1, GLint if (pipeline) drawBlit(ctx, target, pipeline, view, paramsFor(depthW, depthH), scissor, true, false); } } - if (doStencil) + // Stencil-only images of one format and sample count copy directly, every + // sample included (KHR-GL46.texture_stencil8.multisample); otherwise the + // CPU path, which resolves a multisample source. + if (doStencil && !(srcStencil.texture.pixelFormat == MTLPixelFormatStencil8 && + tryCopy(ctx, srcStencil, stencilW, stencilH, dst.stencil, dst.width, dst.height, srcRect, dstRect))) blitStencil(ctx, srcStencil, stencilW, stencilH, dst.stencil, dstFormats.depth, dst.samples, dst.width, dst.height, srcRect, dstRect); // Draws after the blit start a fresh pass with their own state. diff --git a/src/core/api/buffer.mm b/src/core/api/buffer.mm index ecdf60e8..e9314451 100644 --- a/src/core/api/buffer.mm +++ b/src/core/api/buffer.mm @@ -2,6 +2,7 @@ #include #include +#include #include "core/context.h" #include "core/draw.h" @@ -22,12 +23,32 @@ bool validUsage(GLenum usage) { } } +} // namespace + +// The host's memory for buffer storage (wmgl_set_buffer_memory), or none. +void* (*g_bufferAllocate)(size_t) = nullptr; +void (*g_bufferRelease)(void*, size_t) = nullptr; + +namespace { + id newStorage(GLsizeiptr size) { if (size <= 0) return nil; // Padded to 256 bytes, so texture views of the buffer (texture buffers, // buffer images) have room for Metal's aligned rows at any size. const NSUInteger length = (static_cast(size) + 255) & ~NSUInteger(255); - return [Device::get().mtl() newBufferWithLength:length options:MTLResourceStorageModeShared]; + if (!g_bufferAllocate) return [Device::get().mtl() newBufferWithLength:length options:MTLResourceStorageModeShared]; + // In the host's memory (64 KiB pages), zero-filled like Metal's. + const NSUInteger pages = (length + 0xffff) & ~NSUInteger(0xffff); + void* memory = g_bufferAllocate(pages); + if (!memory) return nil; + std::memset(memory, 0, pages); + void (*release)(void*, size_t) = g_bufferRelease; + return [Device::get().mtl() newBufferWithBytesNoCopy:memory + length:pages + options:MTLResourceStorageModeShared + deallocator:^(void* pointer, NSUInteger bytes) { + if (release) release(pointer, bytes); + }]; } bool gpuBusy(Context& ctx, const Buffer& buffer) { @@ -119,7 +140,7 @@ void DeleteBuffers(Context& ctx, GLsizei n, const GLuint* buffers) { for (RefPtr* b : {&s.arrayBuffer, &s.copyReadBuffer, &s.copyWriteBuffer, &s.pixelPackBuffer, &s.pixelUnpackBuffer, &s.textureBuffer, &s.uniformBuffer, &s.transformFeedbackBuffer, &s.drawIndirectBuffer, &s.dispatchIndirectBuffer, &s.shaderStorageBuffer, - &s.atomicCounterBuffer, &s.queryBuffer}) + &s.atomicCounterBuffer, &s.queryBuffer, &s.parameterBuffer, &s.externalVirtualMemoryBuffer}) if (*b == buffer) b->reset(); if (s.vertexArray->elementBuffer == buffer) s.vertexArray->elementBuffer.reset(); for (VertexAttrib& a : s.vertexArray->attribs) @@ -229,6 +250,60 @@ void BindBufferBase(Context& ctx, GLenum target, GLuint index, GLuint name) { } } +// ------------------------------------------------------------ multi-bind (GL 4.4) + +namespace { + +GLuint indexedBindingCount(GLenum target) { + switch (target) { + case GL_SHADER_STORAGE_BUFFER: return ContextState::kMaxShaderStorageBufferBindings; + case GL_ATOMIC_COUNTER_BUFFER: return ContextState::kMaxAtomicCounterBufferBindings; + case GL_UNIFORM_BUFFER: return ContextState::kMaxUniformBufferBindings; + default: return TransformFeedback::kMaxBuffers; + } +} + +RefPtr& genericBinding(Context& ctx, GLenum target) { + switch (target) { + case GL_SHADER_STORAGE_BUFFER: return ctx.state.shaderStorageBuffer; + case GL_ATOMIC_COUNTER_BUFFER: return ctx.state.atomicCounterBuffer; + case GL_UNIFORM_BUFFER: return ctx.state.uniformBuffer; + default: return ctx.state.transformFeedbackBuffer; + } +} + +// glBindBuffersBase/Range (GL 4.4 section 6.1.1): a range of indexed bindings +// at once. A range past the last binding changes nothing; otherwise an error +// in one binding leaves that binding alone and the others are still made. The +// generic binding point is not changed. +void bindBuffers(Context& ctx, const char* command, GLenum target, GLuint first, GLsizei count, const GLuint* buffers, + const GLintptr* offsets, const GLsizeiptr* sizes) { + if (ctx.version() < 44 && !ctx.hasExtension("GL_ARB_multi_bind")) return ctx.unimplemented(command); + if (!indexedTarget(ctx, target)) return ctx.setError(GL_INVALID_ENUM); + if (count < 0) return ctx.setError(GL_INVALID_VALUE); + if (static_cast(first) + static_cast(count) > indexedBindingCount(target)) + return ctx.setError(GL_INVALID_OPERATION); + if (target == GL_TRANSFORM_FEEDBACK_BUFFER && ctx.state.transformFeedback->active) return ctx.setError(GL_INVALID_OPERATION); + const RefPtr generic = genericBinding(ctx, target); + for (GLsizei i = 0; i < count; ++i) { + const GLuint name = buffers ? buffers[i] : 0; + if (offsets && sizes && name) BindBufferRange(ctx, target, first + static_cast(i), name, offsets[i], sizes[i]); + else BindBufferBase(ctx, target, first + static_cast(i), name); + } + genericBinding(ctx, target) = generic; +} + +} // namespace + +void BindBuffersBase(Context& ctx, GLenum target, GLuint first, GLsizei count, const GLuint* buffers) { + bindBuffers(ctx, "glBindBuffersBase", target, first, count, buffers, nullptr, nullptr); +} + +void BindBuffersRange(Context& ctx, GLenum target, GLuint first, GLsizei count, const GLuint* buffers, const GLintptr* offsets, + const GLsizeiptr* sizes) { + bindBuffers(ctx, "glBindBuffersRange", target, first, count, buffers, buffers ? offsets : nullptr, buffers ? sizes : nullptr); +} + void BufferData(Context& ctx, GLenum target, GLsizeiptr size, const void* data, GLenum usage) { if (size < 0) return ctx.setError(GL_INVALID_VALUE); if (!validUsage(usage)) return ctx.setError(GL_INVALID_ENUM); @@ -236,6 +311,28 @@ void BufferData(Context& ctx, GLenum target, GLsizeiptr size, const void* data, if (!buffer) return; if (buffer->immutable) return ctx.setError(GL_INVALID_OPERATION); if (buffer->mapped) unmapBuffer(*buffer); + if (target == GL_EXTERNAL_VIRTUAL_MEMORY_BUFFER_AMD) { + // GL_AMD_pinned_memory: `data` (page aligned, owned by the + // application while the buffer uses it) is the storage itself. + const uintptr_t page = static_cast(getpagesize()); + if (!data || reinterpret_cast(data) % page) return ctx.setError(GL_INVALID_OPERATION); + const NSUInteger length = (static_cast(size) + page - 1) & ~static_cast(page - 1); + id storage = size ? [Device::get().mtl() newBufferWithBytesNoCopy:const_cast(data) + length:length + options:MTLResourceStorageModeShared + deallocator:nil] + : nil; + if (size && !storage) return ctx.setError(GL_INVALID_OPERATION); + buffer->mtl = storage; + buffer->size = size; + buffer->usage = usage; + buffer->pinned = true; + buffer->lastUseSerial = buffer->lastWriteSerial = 0; + ++buffer->version; + ctx.markDirty(kDirtyVertexInput | kDirtyUniformBuffers); + return; + } + buffer->pinned = false; // Reuse the storage only when no queued work references it (orphaning). if (buffer->mtl && buffer->size == size && !gpuBusy(ctx, *buffer)) { ++buffer->version; @@ -270,6 +367,7 @@ void BufferStorage(Context& ctx, GLenum target, GLsizeiptr size, const void* dat if (!storage) return ctx.setError(GL_OUT_OF_MEMORY); if (data) std::memcpy(storage.contents, data, static_cast(size)); buffer->mtl = storage; + buffer->pinned = false; buffer->size = size; buffer->usage = GL_DYNAMIC_DRAW; buffer->immutable = true; @@ -284,12 +382,13 @@ void BufferSubData(Context& ctx, GLenum target, GLintptr offset, GLsizeiptr size Buffer* buffer = boundBuffer(ctx, target); if (!buffer) return; if (offset + size > buffer->size) return ctx.setError(GL_INVALID_VALUE); - if (buffer->mapped && !(buffer->mapAccess & GL_MAP_PERSISTENT_BIT)) return ctx.setError(GL_INVALID_OPERATION); + if (buffer->rangeMapped(offset, size)) return ctx.setError(GL_INVALID_OPERATION); if (buffer->immutable && !(buffer->storageFlags & GL_DYNAMIC_STORAGE_BIT)) return ctx.setError(GL_INVALID_OPERATION); if (!size || !data || !buffer->mtl) return; if (gpuBusy(ctx, *buffer)) { - // Immutable storage keeps its memory (it may be persistently mapped). - if (!buffer->immutable && (size == buffer->size || buffer->size <= (256 << 10))) { + // Immutable storage keeps its memory (it may be persistently mapped), + // and so does a mapped buffer. + if (!buffer->immutable && !buffer->pinned && !buffer->mapped && (size == buffer->size || buffer->size <= (256 << 10))) { // Small or whole-buffer updates: copy-on-write. rename(ctx, *buffer, size != buffer->size); } else { @@ -333,7 +432,7 @@ void ClearBufferSubData(Context& ctx, GLenum target, GLenum internalformat, GLin if (!transferCompatible(format, type, *info)) return ctx.setError(GL_INVALID_OPERATION); if (offset < 0 || size < 0 || offset + size > buffer->size) return ctx.setError(GL_INVALID_VALUE); if (offset % static_cast(element) || size % static_cast(element)) return ctx.setError(GL_INVALID_VALUE); - if (buffer->mapped && !(buffer->mapAccess & GL_MAP_PERSISTENT_BIT)) return ctx.setError(GL_INVALID_OPERATION); + if (buffer->rangeMapped(offset, size)) return ctx.setError(GL_INVALID_OPERATION); if (!size || !buffer->mtl) return; // The element, as stored (RGB32 texels are stored with a fourth component). uint8_t texel[16] = {}; @@ -363,7 +462,7 @@ void GetBufferSubData(Context& ctx, GLenum target, GLintptr offset, GLsizeiptr s Buffer* buffer = boundBuffer(ctx, target); if (!buffer) return; if (offset + size > buffer->size) return ctx.setError(GL_INVALID_VALUE); - if (buffer->mapped && !(buffer->mapAccess & GL_MAP_PERSISTENT_BIT)) return ctx.setError(GL_INVALID_OPERATION); + if (buffer->rangeMapped(offset, size)) return ctx.setError(GL_INVALID_OPERATION); if (!size || !data || !buffer->mtl) return; waitForGpuUnlocked(ctx, buffer->lastUseSerial); // Another context may have respecified the buffer during the wait. @@ -409,8 +508,8 @@ void GetBufferSubData(Context& ctx, GLenum target, GLintptr offset, GLsizeiptr s } if (!(access & GL_MAP_UNSYNCHRONIZED_BIT) && gpuBusy(ctx, *buffer)) { // Immutable storage is never renamed: an invalidating map just waits. - if ((access & GL_MAP_INVALIDATE_BUFFER_BIT) && !buffer->immutable) rename(ctx, *buffer, false); - else if ((access & GL_MAP_INVALIDATE_RANGE_BIT) && !read && !buffer->immutable) rename(ctx, *buffer, true); + if ((access & GL_MAP_INVALIDATE_BUFFER_BIT) && !buffer->immutable && !buffer->pinned) rename(ctx, *buffer, false); + else if ((access & GL_MAP_INVALIDATE_RANGE_BIT) && !read && !buffer->immutable && !buffer->pinned) rename(ctx, *buffer, true); else waitForGpuUnlocked(ctx, buffer->lastUseSerial); } // Another context may have respecified or mapped the buffer during a wait. @@ -476,7 +575,7 @@ void CopyBufferSubData(Context& ctx, GLenum readTarget, GLenum writeTarget, GLin return ctx.setError(GL_INVALID_VALUE); if (src == dst && std::max(readOffset, writeOffset) < std::min(readOffset, writeOffset) + size) return ctx.setError(GL_INVALID_VALUE); - if (src->mappedNonPersistent() || dst->mappedNonPersistent()) return ctx.setError(GL_INVALID_OPERATION); + if (src->rangeMapped(readOffset, size) || dst->rangeMapped(writeOffset, size)) return ctx.setError(GL_INVALID_OPERATION); if (!size) return; [ctx.stream().blit() copyFromBuffer:src->mtl sourceOffset:static_cast(readOffset) toBuffer:dst->mtl destinationOffset:static_cast(writeOffset) size:static_cast(size)]; diff --git a/src/core/api/draw.mm b/src/core/api/draw.mm index 04f83a57..13478a30 100644 --- a/src/core/api/draw.mm +++ b/src/core/api/draw.mm @@ -1,5 +1,6 @@ // Drawing commands (GL 3.3 section 2.8.3). +#include #include #include @@ -174,35 +175,102 @@ void DrawElementsIndirect(Context& ctx, GLenum mode, GLenum type, const void* in } // GL 4.3 / GL_ARB_multi_draw_indirect: each command as its own indirect -// draw (commands are read on the CPU, as for the single-draw forms). +// draw (commands are read on the CPU, as for the single-draw forms), with +// gl_DrawID its index. +namespace { + +// INVALID_OPERATION, before anything draws, when `drawcount` commands of +// `size` bytes `step` apart from `indirect` don't fit in DRAW_INDIRECT_BUFFER. +bool indirectCommandsFit(Context& ctx, const void* indirect, GLsizei drawcount, uintptr_t step, uintptr_t size) { + Buffer* buffer = ctx.state.drawIndirectBuffer.get(); + if (!buffer || drawcount <= 0) return true; // client memory (compatibility profile), or nothing to read + const uint64_t end = reinterpret_cast(indirect) + step * static_cast(drawcount - 1) + size; + if (end > static_cast(buffer->size)) return ctx.setError(GL_INVALID_OPERATION), false; + return true; +} + +void multiDrawArraysIndirect(Context& ctx, GLenum mode, const void* indirect, GLsizei drawcount, uintptr_t step) { + for (GLsizei i = 0; i < drawcount; ++i) { + DrawState::DrawIdScope id(ctx.draw(), i); + DrawArraysIndirect(ctx, mode, reinterpret_cast(reinterpret_cast(indirect) + step * static_cast(i))); + } +} + +void multiDrawElementsIndirect(Context& ctx, GLenum mode, GLenum type, const void* indirect, GLsizei drawcount, uintptr_t step) { + for (GLsizei i = 0; i < drawcount; ++i) { + DrawState::DrawIdScope id(ctx.draw(), i); + DrawElementsIndirect(ctx, mode, type, + reinterpret_cast(reinterpret_cast(indirect) + step * static_cast(i))); + } +} + +// GL 4.6 / GL_ARB_indirect_parameters: the draw count is the uint at +// `offset` in PARAMETER_BUFFER (read on the CPU after any GPU write to it), +// at most `maxdrawcount`. -1 with the GL error set. +GLsizei parameterDrawCount(Context& ctx, GLintptr offset, GLsizei maxdrawcount) { + if (offset < 0 || offset % 4 || maxdrawcount < 0) return ctx.setError(GL_INVALID_VALUE), -1; + Buffer* buffer = ctx.state.parameterBuffer.get(); + if (!buffer || !buffer->mtl || offset + 4 > buffer->size || buffer->mappedNonPersistent()) + return ctx.setError(GL_INVALID_OPERATION), -1; + if (buffer->lastWriteSerial) waitForGpuUnlocked(ctx, buffer->lastWriteSerial); + uint32_t count = 0; + std::memcpy(&count, buffer->contents() + offset, 4); + return static_cast(std::min(count, static_cast(maxdrawcount))); +} + +} // namespace + void MultiDrawArraysIndirect(Context& ctx, GLenum mode, const void* indirect, GLsizei drawcount, GLsizei stride) { if (ctx.version() < 43 && !ctx.hasExtension("GL_ARB_multi_draw_indirect")) return ctx.unimplemented("glMultiDrawArraysIndirect"); if (drawcount < 0 || stride < 0 || stride % 4) return ctx.setError(GL_INVALID_VALUE); const uintptr_t step = stride ? static_cast(stride) : 16; - for (GLsizei i = 0; i < drawcount; ++i) - DrawArraysIndirect(ctx, mode, reinterpret_cast(reinterpret_cast(indirect) + step * static_cast(i))); + if (!indirectCommandsFit(ctx, indirect, drawcount, step, 16)) return; + multiDrawArraysIndirect(ctx, mode, indirect, drawcount, step); } void MultiDrawElementsIndirect(Context& ctx, GLenum mode, GLenum type, const void* indirect, GLsizei drawcount, GLsizei stride) { if (ctx.version() < 43 && !ctx.hasExtension("GL_ARB_multi_draw_indirect")) return ctx.unimplemented("glMultiDrawElementsIndirect"); if (drawcount < 0 || stride < 0 || stride % 4) return ctx.setError(GL_INVALID_VALUE); const uintptr_t step = stride ? static_cast(stride) : 20; - for (GLsizei i = 0; i < drawcount; ++i) - DrawElementsIndirect(ctx, mode, type, - reinterpret_cast(reinterpret_cast(indirect) + step * static_cast(i))); + if (!indirectCommandsFit(ctx, indirect, drawcount, step, 20)) return; + multiDrawElementsIndirect(ctx, mode, type, indirect, drawcount, step); +} + +void MultiDrawArraysIndirectCount(Context& ctx, GLenum mode, const void* indirect, GLintptr drawcount, GLsizei maxdrawcount, + GLsizei stride) { + if (!ctx.indirectParameters()) return ctx.unimplemented("glMultiDrawArraysIndirectCount"); + if (stride < 0 || stride % 4) return ctx.setError(GL_INVALID_VALUE); + const uintptr_t step = stride ? static_cast(stride) : 16; + const GLsizei count = parameterDrawCount(ctx, drawcount, maxdrawcount); + if (count < 0 || !indirectCommandsFit(ctx, indirect, maxdrawcount, step, 16)) return; + multiDrawArraysIndirect(ctx, mode, indirect, count, step); +} + +void MultiDrawElementsIndirectCount(Context& ctx, GLenum mode, GLenum type, const void* indirect, GLintptr drawcount, + GLsizei maxdrawcount, GLsizei stride) { + if (!ctx.indirectParameters()) return ctx.unimplemented("glMultiDrawElementsIndirectCount"); + if (stride < 0 || stride % 4) return ctx.setError(GL_INVALID_VALUE); + const uintptr_t step = stride ? static_cast(stride) : 20; + const GLsizei count = parameterDrawCount(ctx, drawcount, maxdrawcount); + if (count < 0 || !indirectCommandsFit(ctx, indirect, maxdrawcount, step, 20)) return; + multiDrawElementsIndirect(ctx, mode, type, indirect, count, step); } void MultiDrawArrays(Context& ctx, GLenum mode, const GLint* first, const GLsizei* count, GLsizei drawcount) { if (drawcount < 0) return ctx.setError(GL_INVALID_VALUE); - for (GLsizei i = 0; i < drawcount; ++i) + for (GLsizei i = 0; i < drawcount; ++i) { + DrawState::DrawIdScope id(ctx.draw(), i); if (compileArrays(ctx, mode, first[i], count[i])) ctx.draw().drawArrays(mode, first[i], count[i], 1); + } } void MultiDrawElements(Context& ctx, GLenum mode, const GLsizei* count, GLenum type, const void* const* indices, GLsizei drawcount) { if (drawcount < 0) return ctx.setError(GL_INVALID_VALUE); - for (GLsizei i = 0; i < drawcount; ++i) + for (GLsizei i = 0; i < drawcount; ++i) { + DrawState::DrawIdScope id(ctx.draw(), i); if (compileElements(ctx, mode, count[i], type, indices[i], 0)) ctx.draw().drawElements(mode, count[i], type, indices[i], 1, 0); + } } void MultiDrawElementsBaseVertex(Context& ctx, GLenum mode, const GLsizei* count, GLenum type, const void* const* indices, @@ -213,8 +281,10 @@ void MultiDrawElementsBaseVertex(Context& ctx, GLenum mode, const GLsizei* count for (GLsizei i = 0; i < drawcount; ++i) if (count[i] < 0) return ctx.setError(GL_INVALID_VALUE); if (type != GL_UNSIGNED_BYTE && type != GL_UNSIGNED_SHORT && type != GL_UNSIGNED_INT) return ctx.setError(GL_INVALID_ENUM); - for (GLsizei i = 0; i < drawcount; ++i) + for (GLsizei i = 0; i < drawcount; ++i) { + DrawState::DrawIdScope id(ctx.draw(), i); ctx.draw().drawElements(mode, count[i], type, indices[i], 1, basevertex ? basevertex[i] : 0); + } } void Clear(Context& ctx, GLbitfield mask) { ctx.draw().clear(mask); } diff --git a/src/core/api/dsa.mm b/src/core/api/dsa.mm new file mode 100644 index 00000000..7b39f7e6 --- /dev/null +++ b/src/core/api/dsa.mm @@ -0,0 +1,958 @@ +// Direct state access (GL 4.5, GL_ARB_direct_state_access): glCreate* and +// the commands that name their object instead of using a binding. +// +// Each named command runs the bind-to-edit implementation with the object +// swapped into a binding for the duration of the call (BindingSwap), so the +// two share their validation and behaviour; the binding is restored before +// returning and no binding visible to the application changes. A name that +// is not an existing object of the kind is INVALID_OPERATION. +#include + +#include "core/context.h" +#include "core/query.h" +#include "core/draw.h" +#include "core/format/formats.h" +#include "core/format/pixel_transfer.h" +#include "core/limits.h" +#include "gl_api.gen.h" + +namespace wmgl::api { +namespace { + +// Puts `value` in `slot` until the end of the scope. +template +class BindingSwap { +public: + BindingSwap(T& slot, T value) : slot_(slot), saved_(slot) { slot_ = std::move(value); } + ~BindingSwap() { slot_ = std::move(saved_); } + BindingSwap(const BindingSwap&) = delete; + BindingSwap& operator=(const BindingSwap&) = delete; + +private: + T& slot_; + T saved_; +}; + +bool dsa(Context& ctx, const char* command) { + if (ctx.version() >= 45 || ctx.hasExtension("GL_ARB_direct_state_access")) return true; + ctx.unimplemented(command); + return false; +} + +RefPtr namedBuffer(Context& ctx, GLuint name) { + if (!name) return nullptr; + ShareLock lock(ctx); + return ctx.share().buffers.getRef(name); +} + +RefPtr namedTexture(Context& ctx, GLuint name) { + if (!name) return nullptr; + ShareLock lock(ctx); + return ctx.share().textures.getRef(name); +} + +RefPtr namedRenderbuffer(Context& ctx, GLuint name) { + if (!name) return nullptr; + ShareLock lock(ctx); + return ctx.share().renderbuffers.getRef(name); +} + +// A texture's binding on the active unit, swapped for the call. +struct TextureScope { + TextureScope(Context& ctx, const RefPtr& tex) + : swap(ctx.state.textureUnits[ctx.state.activeTexture].bound[static_cast(textureTargetIndex(tex->target))], tex) {} + BindingSwap> swap; +}; + +} // namespace + +// ------------------------------------------------------------ creation + +void CreateBuffers(Context& ctx, GLsizei n, GLuint* buffers) { + if (!dsa(ctx, "glCreateBuffers")) return; + if (n < 0) return ctx.setError(GL_INVALID_VALUE); + if (!buffers) return; + ShareLock lock(ctx); + ctx.share().buffers.generate(n, buffers); + for (GLsizei i = 0; i < n; ++i) ctx.share().buffers.set(buffers[i], makeRef(buffers[i])); +} + +void CreateTextures(Context& ctx, GLenum target, GLsizei n, GLuint* textures) { + if (!dsa(ctx, "glCreateTextures")) return; + if (textureTargetIndex(target) < 0) return ctx.setError(GL_INVALID_ENUM); + if (n < 0) return ctx.setError(GL_INVALID_VALUE); + if (!textures) return; + ShareLock lock(ctx); + ctx.share().textures.generate(n, textures); + for (GLsizei i = 0; i < n; ++i) { + RefPtr tex = makeRef(textures[i], target); + // Rectangle textures default to linear filtering without mipmaps and edge clamping. + if (target == GL_TEXTURE_RECTANGLE) { + tex->sampler.minFilter = GL_LINEAR; + tex->sampler.wrapS = tex->sampler.wrapT = tex->sampler.wrapR = GL_CLAMP_TO_EDGE; + } + ctx.share().textures.set(textures[i], tex); + } +} + +void CreateFramebuffers(Context& ctx, GLsizei n, GLuint* framebuffers) { + if (!dsa(ctx, "glCreateFramebuffers")) return; + if (n < 0) return ctx.setError(GL_INVALID_VALUE); + if (!framebuffers) return; + ctx.framebuffers.generate(n, framebuffers); + for (GLsizei i = 0; i < n; ++i) ctx.framebuffers.set(framebuffers[i], makeRef(framebuffers[i])); +} + +void CreateRenderbuffers(Context& ctx, GLsizei n, GLuint* renderbuffers) { + if (!dsa(ctx, "glCreateRenderbuffers")) return; + if (n < 0) return ctx.setError(GL_INVALID_VALUE); + if (!renderbuffers) return; + ShareLock lock(ctx); + ctx.share().renderbuffers.generate(n, renderbuffers); + for (GLsizei i = 0; i < n; ++i) ctx.share().renderbuffers.set(renderbuffers[i], makeRef(renderbuffers[i])); +} + +void CreateVertexArrays(Context& ctx, GLsizei n, GLuint* arrays) { + if (!dsa(ctx, "glCreateVertexArrays")) return; + if (n < 0) return ctx.setError(GL_INVALID_VALUE); + if (!arrays) return; + ctx.vertexArrays.generate(n, arrays); + for (GLsizei i = 0; i < n; ++i) ctx.vertexArrays.set(arrays[i], makeRef(arrays[i])); +} + +void CreateSamplers(Context& ctx, GLsizei n, GLuint* samplers) { + if (!dsa(ctx, "glCreateSamplers")) return; + GenSamplers(ctx, n, samplers); // sampler objects exist from glGenSamplers +} + +void CreateProgramPipelines(Context& ctx, GLsizei n, GLuint* pipelines) { + if (!dsa(ctx, "glCreateProgramPipelines")) return; + if (n < 0) return ctx.setError(GL_INVALID_VALUE); + if (!pipelines) return; + ctx.programPipelines.generate(n, pipelines); + for (GLsizei i = 0; i < n; ++i) ctx.programPipelines.set(pipelines[i], makeRef(pipelines[i])); +} + +void CreateTransformFeedbacks(Context& ctx, GLsizei n, GLuint* ids) { + if (!dsa(ctx, "glCreateTransformFeedbacks")) return; + if (n < 0) return ctx.setError(GL_INVALID_VALUE); + if (!ids) return; + ctx.transformFeedbacks.generate(n, ids); + for (GLsizei i = 0; i < n; ++i) ctx.transformFeedbacks.set(ids[i], makeRef(ids[i])); +} + +void CreateQueries(Context& ctx, GLenum target, GLsizei n, GLuint* ids) { + if (!dsa(ctx, "glCreateQueries")) return; + switch (target) { + case GL_SAMPLES_PASSED: case GL_ANY_SAMPLES_PASSED: case GL_ANY_SAMPLES_PASSED_CONSERVATIVE: case GL_TIME_ELAPSED: + case GL_TIMESTAMP: case GL_PRIMITIVES_GENERATED: case GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN: + break; + case GL_TRANSFORM_FEEDBACK_OVERFLOW: case GL_TRANSFORM_FEEDBACK_STREAM_OVERFLOW: case GL_VERTICES_SUBMITTED: + case GL_PRIMITIVES_SUBMITTED: case GL_VERTEX_SHADER_INVOCATIONS: case GL_TESS_CONTROL_SHADER_PATCHES: + case GL_TESS_EVALUATION_SHADER_INVOCATIONS: case GL_GEOMETRY_SHADER_INVOCATIONS: + case GL_GEOMETRY_SHADER_PRIMITIVES_EMITTED: case GL_FRAGMENT_SHADER_INVOCATIONS: case GL_COMPUTE_SHADER_INVOCATIONS: + case GL_CLIPPING_INPUT_PRIMITIVES: case GL_CLIPPING_OUTPUT_PRIMITIVES: + if (queryTargetIndex(ctx, target) < 0) return ctx.setError(GL_INVALID_ENUM); + break; + default: return ctx.setError(GL_INVALID_ENUM); + } + if (n < 0) return ctx.setError(GL_INVALID_VALUE); + if (!ids) return; + ctx.queries.generate(n, ids); + for (GLsizei i = 0; i < n; ++i) { + RefPtr q = makeRef(ids[i]); + q->target = target; // a created query only begins with its target + ctx.queries.set(ids[i], q); + } +} + +// ------------------------------------------------------------ buffers + +#define WMGL_NAMED_BUFFER(command, name) \ + if (!dsa(ctx, command)) return; \ + RefPtr buffer_ = namedBuffer(ctx, name); \ + if (!buffer_) return ctx.setError(GL_INVALID_OPERATION); \ + BindingSwap> bound_(ctx.state.copyWriteBuffer, buffer_) + +void NamedBufferStorage(Context& ctx, GLuint buffer, GLsizeiptr size, const void* data, GLbitfield flags) { + WMGL_NAMED_BUFFER("glNamedBufferStorage", buffer); + BufferStorage(ctx, GL_COPY_WRITE_BUFFER, size, data, flags); +} + +void NamedBufferData(Context& ctx, GLuint buffer, GLsizeiptr size, const void* data, GLenum usage) { + WMGL_NAMED_BUFFER("glNamedBufferData", buffer); + BufferData(ctx, GL_COPY_WRITE_BUFFER, size, data, usage); +} + +void NamedBufferSubData(Context& ctx, GLuint buffer, GLintptr offset, GLsizeiptr size, const void* data) { + WMGL_NAMED_BUFFER("glNamedBufferSubData", buffer); + BufferSubData(ctx, GL_COPY_WRITE_BUFFER, offset, size, data); +} + +void CopyNamedBufferSubData(Context& ctx, GLuint readBuffer, GLuint writeBuffer, GLintptr readOffset, GLintptr writeOffset, + GLsizeiptr size) { + if (!dsa(ctx, "glCopyNamedBufferSubData")) return; + RefPtr read = namedBuffer(ctx, readBuffer), write = namedBuffer(ctx, writeBuffer); + if (!read || !write) return ctx.setError(GL_INVALID_OPERATION); + BindingSwap> r(ctx.state.copyReadBuffer, read), w(ctx.state.copyWriteBuffer, write); + CopyBufferSubData(ctx, GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER, readOffset, writeOffset, size); +} + +void ClearNamedBufferData(Context& ctx, GLuint buffer, GLenum internalformat, GLenum format, GLenum type, const void* data) { + WMGL_NAMED_BUFFER("glClearNamedBufferData", buffer); + ClearBufferData(ctx, GL_COPY_WRITE_BUFFER, internalformat, format, type, data); +} + +void ClearNamedBufferSubData(Context& ctx, GLuint buffer, GLenum internalformat, GLintptr offset, GLsizeiptr size, GLenum format, + GLenum type, const void* data) { + WMGL_NAMED_BUFFER("glClearNamedBufferSubData", buffer); + ClearBufferSubData(ctx, GL_COPY_WRITE_BUFFER, internalformat, offset, size, format, type, data); +} + +void* MapNamedBuffer(Context& ctx, GLuint buffer, GLenum access) { + if (!dsa(ctx, "glMapNamedBuffer")) return nullptr; + RefPtr object = namedBuffer(ctx, buffer); + if (!object) return ctx.setError(GL_INVALID_OPERATION), nullptr; + BindingSwap> bound(ctx.state.copyWriteBuffer, object); + return MapBuffer(ctx, GL_COPY_WRITE_BUFFER, access); +} + +void* MapNamedBufferRange(Context& ctx, GLuint buffer, GLintptr offset, GLsizeiptr length, GLbitfield access) { + if (!dsa(ctx, "glMapNamedBufferRange")) return nullptr; + RefPtr object = namedBuffer(ctx, buffer); + if (!object) return ctx.setError(GL_INVALID_OPERATION), nullptr; + if (length == 0) return ctx.setError(GL_INVALID_OPERATION), nullptr; // GL 4.5 section 6.3 + BindingSwap> bound(ctx.state.copyWriteBuffer, object); + return MapBufferRange(ctx, GL_COPY_WRITE_BUFFER, offset, length, access); +} + +GLboolean UnmapNamedBuffer(Context& ctx, GLuint buffer) { + if (!dsa(ctx, "glUnmapNamedBuffer")) return GL_FALSE; + RefPtr object = namedBuffer(ctx, buffer); + if (!object) return ctx.setError(GL_INVALID_OPERATION), GL_FALSE; + BindingSwap> bound(ctx.state.copyWriteBuffer, object); + return UnmapBuffer(ctx, GL_COPY_WRITE_BUFFER); +} + +void FlushMappedNamedBufferRange(Context& ctx, GLuint buffer, GLintptr offset, GLsizeiptr length) { + WMGL_NAMED_BUFFER("glFlushMappedNamedBufferRange", buffer); + FlushMappedBufferRange(ctx, GL_COPY_WRITE_BUFFER, offset, length); +} + +void GetNamedBufferParameteriv(Context& ctx, GLuint buffer, GLenum pname, GLint* params) { + WMGL_NAMED_BUFFER("glGetNamedBufferParameteriv", buffer); + GetBufferParameteriv(ctx, GL_COPY_WRITE_BUFFER, pname, params); +} + +void GetNamedBufferParameteri64v(Context& ctx, GLuint buffer, GLenum pname, GLint64* params) { + WMGL_NAMED_BUFFER("glGetNamedBufferParameteri64v", buffer); + GetBufferParameteri64v(ctx, GL_COPY_WRITE_BUFFER, pname, params); +} + +void GetNamedBufferPointerv(Context& ctx, GLuint buffer, GLenum pname, void** params) { + WMGL_NAMED_BUFFER("glGetNamedBufferPointerv", buffer); + GetBufferPointerv(ctx, GL_COPY_WRITE_BUFFER, pname, params); +} + +void GetNamedBufferSubData(Context& ctx, GLuint buffer, GLintptr offset, GLsizeiptr size, void* data) { + WMGL_NAMED_BUFFER("glGetNamedBufferSubData", buffer); + GetBufferSubData(ctx, GL_COPY_WRITE_BUFFER, offset, size, data); +} + +#undef WMGL_NAMED_BUFFER + +// ------------------------------------------------------------ textures + +namespace { + +bool oneOf(GLenum target, std::initializer_list targets) { + for (GLenum t : targets) + if (t == target) return true; + return false; +} + +// The texture for a named texture command whose target must be one of +// `targets`: INVALID_OPERATION for a missing texture or another target. +RefPtr textureFor(Context& ctx, const char* command, GLuint name, std::initializer_list targets) { + if (!dsa(ctx, command)) return nullptr; + RefPtr tex = namedTexture(ctx, name); + if (!tex || (targets.size() && !oneOf(tex->target, targets))) { + ctx.setError(GL_INVALID_OPERATION); + return nullptr; + } + return tex; +} + +// Bytes between consecutive images of client pixels (TextureSubImage3D of a +// cube map: one face each). +size_t imageStride(Context& ctx, GLenum format, GLenum type, GLsizei w, GLsizei h) { + PixelStoreParams ps = pixelStoreParams(ctx.state.unpack, true); + return transferLayout(format, type, w, h, 2, ps).imageStride; +} + +} // namespace + +void BindTextureUnit(Context& ctx, GLuint unit, GLuint texture) { + if (!dsa(ctx, "glBindTextureUnit")) return; + if (unit >= static_cast(limits::kMaxCombinedTextureUnits)) return ctx.setError(GL_INVALID_OPERATION); + TextureUnit& u = ctx.state.textureUnits[unit]; + if (!texture) { + for (size_t t = 0; t < u.bound.size(); ++t) u.bound[t] = ctx.defaultTextures[t]; + } else { + RefPtr tex = namedTexture(ctx, texture); + if (!tex) return ctx.setError(GL_INVALID_OPERATION); + u.bound[static_cast(textureTargetIndex(tex->target))] = tex; + } + ctx.markDirty(kDirtyTextures); +} + +void TextureStorage1D(Context& ctx, GLuint texture, GLsizei levels, GLenum internalformat, GLsizei width) { + RefPtr tex = textureFor(ctx, "glTextureStorage1D", texture, {GL_TEXTURE_1D}); + if (!tex) return; + TextureScope scope(ctx, tex); + TexStorage1D(ctx, tex->target, levels, internalformat, width); +} + +void TextureStorage2D(Context& ctx, GLuint texture, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height) { + RefPtr tex = textureFor(ctx, "glTextureStorage2D", texture, + {GL_TEXTURE_2D, GL_TEXTURE_1D_ARRAY, GL_TEXTURE_RECTANGLE, GL_TEXTURE_CUBE_MAP}); + if (!tex) return; + TextureScope scope(ctx, tex); + TexStorage2D(ctx, tex->target, levels, internalformat, width, height); +} + +void TextureStorage3D(Context& ctx, GLuint texture, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height, + GLsizei depth) { + RefPtr tex = + textureFor(ctx, "glTextureStorage3D", texture, {GL_TEXTURE_3D, GL_TEXTURE_2D_ARRAY, GL_TEXTURE_CUBE_MAP_ARRAY}); + if (!tex) return; + TextureScope scope(ctx, tex); + TexStorage3D(ctx, tex->target, levels, internalformat, width, height, depth); +} + +void TextureStorage2DMultisample(Context& ctx, GLuint texture, GLsizei samples, GLenum internalformat, GLsizei width, + GLsizei height, GLboolean fixedsamplelocations) { + RefPtr tex = textureFor(ctx, "glTextureStorage2DMultisample", texture, {GL_TEXTURE_2D_MULTISAMPLE}); + if (!tex) return; + TextureScope scope(ctx, tex); + TexStorage2DMultisample(ctx, tex->target, samples, internalformat, width, height, fixedsamplelocations); +} + +void TextureStorage3DMultisample(Context& ctx, GLuint texture, GLsizei samples, GLenum internalformat, GLsizei width, + GLsizei height, GLsizei depth, GLboolean fixedsamplelocations) { + RefPtr tex = textureFor(ctx, "glTextureStorage3DMultisample", texture, {GL_TEXTURE_2D_MULTISAMPLE_ARRAY}); + if (!tex) return; + TextureScope scope(ctx, tex); + TexStorage3DMultisample(ctx, tex->target, samples, internalformat, width, height, depth, fixedsamplelocations); +} + +void TextureSubImage1D(Context& ctx, GLuint texture, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, + const void* pixels) { + RefPtr tex = textureFor(ctx, "glTextureSubImage1D", texture, {GL_TEXTURE_1D}); + if (!tex) return; + TextureScope scope(ctx, tex); + TexSubImage1D(ctx, tex->target, level, xoffset, width, format, type, pixels); +} + +void TextureSubImage2D(Context& ctx, GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, + GLenum format, GLenum type, const void* pixels) { + RefPtr tex = + textureFor(ctx, "glTextureSubImage2D", texture, {GL_TEXTURE_2D, GL_TEXTURE_1D_ARRAY, GL_TEXTURE_RECTANGLE}); + if (!tex) return; + TextureScope scope(ctx, tex); + TexSubImage2D(ctx, tex->target, level, xoffset, yoffset, width, height, format, type, pixels); +} + +void TextureSubImage3D(Context& ctx, GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, + GLsizei height, GLsizei depth, GLenum format, GLenum type, const void* pixels) { + RefPtr tex = textureFor(ctx, "glTextureSubImage3D", texture, + {GL_TEXTURE_3D, GL_TEXTURE_2D_ARRAY, GL_TEXTURE_CUBE_MAP_ARRAY, GL_TEXTURE_CUBE_MAP}); + if (!tex) return; + TextureScope scope(ctx, tex); + if (tex->target != GL_TEXTURE_CUBE_MAP) + return TexSubImage3D(ctx, tex->target, level, xoffset, yoffset, zoffset, width, height, depth, format, type, pixels); + // A cube map's faces are its layers. + if (zoffset < 0 || depth < 0 || zoffset + depth > 6) return ctx.setError(GL_INVALID_VALUE); + const size_t stride = imageStride(ctx, format, type, width, height); + for (GLsizei i = 0; i < depth; ++i) + TexSubImage2D(ctx, GL_TEXTURE_CUBE_MAP_POSITIVE_X + static_cast(zoffset + i), level, xoffset, yoffset, width, + height, format, type, static_cast(pixels) + i * stride); +} + +void CompressedTextureSubImage1D(Context& ctx, GLuint texture, GLint level, GLint xoffset, GLsizei width, GLenum format, + GLsizei imageSize, const void* data) { + RefPtr tex = textureFor(ctx, "glCompressedTextureSubImage1D", texture, {GL_TEXTURE_1D}); + if (!tex) return; + TextureScope scope(ctx, tex); + CompressedTexSubImage1D(ctx, tex->target, level, xoffset, width, format, imageSize, data); +} + +void CompressedTextureSubImage2D(Context& ctx, GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLsizei width, + GLsizei height, GLenum format, GLsizei imageSize, const void* data) { + RefPtr tex = + textureFor(ctx, "glCompressedTextureSubImage2D", texture, {GL_TEXTURE_2D, GL_TEXTURE_1D_ARRAY, GL_TEXTURE_RECTANGLE}); + if (!tex) return; + TextureScope scope(ctx, tex); + CompressedTexSubImage2D(ctx, tex->target, level, xoffset, yoffset, width, height, format, imageSize, data); +} + +void CompressedTextureSubImage3D(Context& ctx, GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, + GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const void* data) { + RefPtr tex = textureFor(ctx, "glCompressedTextureSubImage3D", texture, + {GL_TEXTURE_3D, GL_TEXTURE_2D_ARRAY, GL_TEXTURE_CUBE_MAP_ARRAY, GL_TEXTURE_CUBE_MAP}); + if (!tex) return; + TextureScope scope(ctx, tex); + if (tex->target != GL_TEXTURE_CUBE_MAP) + return CompressedTexSubImage3D(ctx, tex->target, level, xoffset, yoffset, zoffset, width, height, depth, format, + imageSize, data); + if (zoffset < 0 || depth <= 0 || zoffset + depth > 6 || imageSize % depth) return ctx.setError(GL_INVALID_VALUE); + const GLsizei faceSize = imageSize / depth; + for (GLsizei i = 0; i < depth; ++i) + CompressedTexSubImage2D(ctx, GL_TEXTURE_CUBE_MAP_POSITIVE_X + static_cast(zoffset + i), level, xoffset, yoffset, + width, height, format, faceSize, static_cast(data) + i * faceSize); +} + +void CopyTextureSubImage1D(Context& ctx, GLuint texture, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width) { + RefPtr tex = textureFor(ctx, "glCopyTextureSubImage1D", texture, {GL_TEXTURE_1D}); + if (!tex) return; + TextureScope scope(ctx, tex); + CopyTexSubImage1D(ctx, tex->target, level, xoffset, x, y, width); +} + +void CopyTextureSubImage2D(Context& ctx, GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, + GLsizei width, GLsizei height) { + RefPtr tex = + textureFor(ctx, "glCopyTextureSubImage2D", texture, {GL_TEXTURE_2D, GL_TEXTURE_1D_ARRAY, GL_TEXTURE_RECTANGLE}); + if (!tex) return; + TextureScope scope(ctx, tex); + CopyTexSubImage2D(ctx, tex->target, level, xoffset, yoffset, x, y, width, height); +} + +void CopyTextureSubImage3D(Context& ctx, GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, + GLint y, GLsizei width, GLsizei height) { + RefPtr tex = textureFor(ctx, "glCopyTextureSubImage3D", texture, + {GL_TEXTURE_3D, GL_TEXTURE_2D_ARRAY, GL_TEXTURE_CUBE_MAP_ARRAY, GL_TEXTURE_CUBE_MAP}); + if (!tex) return; + TextureScope scope(ctx, tex); + if (tex->target != GL_TEXTURE_CUBE_MAP) + return CopyTexSubImage3D(ctx, tex->target, level, xoffset, yoffset, zoffset, x, y, width, height); + if (zoffset < 0 || zoffset > 5) return ctx.setError(GL_INVALID_VALUE); + CopyTexSubImage2D(ctx, GL_TEXTURE_CUBE_MAP_POSITIVE_X + static_cast(zoffset), level, xoffset, yoffset, x, y, width, + height); +} + +#define WMGL_NAMED_TEXTURE(command, name) \ + RefPtr tex = textureFor(ctx, command, name, {}); \ + if (!tex) return; \ + TextureScope scope(ctx, tex) + +namespace { + +// Texture parameters: none on buffer textures; no sampler state on +// multisample textures (INVALID_OPERATION, GL 4.5 section 8.10). +bool parameterTarget(Context& ctx, const Texture& tex, GLenum pname) { + if (tex.target == GL_TEXTURE_BUFFER) return ctx.setError(GL_INVALID_OPERATION), false; + const bool multisample = tex.target == GL_TEXTURE_2D_MULTISAMPLE || tex.target == GL_TEXTURE_2D_MULTISAMPLE_ARRAY; + switch (pname) { + case GL_TEXTURE_MIN_FILTER: case GL_TEXTURE_MAG_FILTER: case GL_TEXTURE_WRAP_S: case GL_TEXTURE_WRAP_T: + case GL_TEXTURE_WRAP_R: case GL_TEXTURE_MIN_LOD: case GL_TEXTURE_MAX_LOD: case GL_TEXTURE_LOD_BIAS: + case GL_TEXTURE_COMPARE_MODE: case GL_TEXTURE_COMPARE_FUNC: case GL_TEXTURE_BORDER_COLOR: case GL_TEXTURE_MAX_ANISOTROPY: + if (multisample) return ctx.setError(GL_INVALID_OPERATION), false; + break; + default: break; + } + return true; +} + +bool getParameterTarget(Context& ctx, const Texture& tex) { + if (tex.target == GL_TEXTURE_BUFFER) return ctx.setError(GL_INVALID_OPERATION), false; + return true; +} + +} // namespace + +void TextureParameterf(Context& ctx, GLuint texture, GLenum pname, GLfloat param) { + WMGL_NAMED_TEXTURE("glTextureParameterf", texture); + if (!parameterTarget(ctx, *tex, pname)) return; + TexParameterf(ctx, tex->target, pname, param); +} + +void TextureParameterfv(Context& ctx, GLuint texture, GLenum pname, const GLfloat* param) { + WMGL_NAMED_TEXTURE("glTextureParameterfv", texture); + if (!parameterTarget(ctx, *tex, pname)) return; + TexParameterfv(ctx, tex->target, pname, param); +} + +void TextureParameteri(Context& ctx, GLuint texture, GLenum pname, GLint param) { + WMGL_NAMED_TEXTURE("glTextureParameteri", texture); + if (!parameterTarget(ctx, *tex, pname)) return; + TexParameteri(ctx, tex->target, pname, param); +} + +void TextureParameteriv(Context& ctx, GLuint texture, GLenum pname, const GLint* param) { + WMGL_NAMED_TEXTURE("glTextureParameteriv", texture); + if (!parameterTarget(ctx, *tex, pname)) return; + TexParameteriv(ctx, tex->target, pname, param); +} + +void TextureParameterIiv(Context& ctx, GLuint texture, GLenum pname, const GLint* params) { + WMGL_NAMED_TEXTURE("glTextureParameterIiv", texture); + if (!parameterTarget(ctx, *tex, pname)) return; + TexParameterIiv(ctx, tex->target, pname, params); +} + +void TextureParameterIuiv(Context& ctx, GLuint texture, GLenum pname, const GLuint* params) { + WMGL_NAMED_TEXTURE("glTextureParameterIuiv", texture); + if (!parameterTarget(ctx, *tex, pname)) return; + TexParameterIuiv(ctx, tex->target, pname, params); +} + +void GetTextureParameterfv(Context& ctx, GLuint texture, GLenum pname, GLfloat* params) { + WMGL_NAMED_TEXTURE("glGetTextureParameterfv", texture); + if (!getParameterTarget(ctx, *tex)) return; + GetTexParameterfv(ctx, tex->target, pname, params); +} + +void GetTextureParameteriv(Context& ctx, GLuint texture, GLenum pname, GLint* params) { + WMGL_NAMED_TEXTURE("glGetTextureParameteriv", texture); + if (!getParameterTarget(ctx, *tex)) return; + GetTexParameteriv(ctx, tex->target, pname, params); +} + +void GetTextureParameterIiv(Context& ctx, GLuint texture, GLenum pname, GLint* params) { + WMGL_NAMED_TEXTURE("glGetTextureParameterIiv", texture); + if (!getParameterTarget(ctx, *tex)) return; + GetTexParameterIiv(ctx, tex->target, pname, params); +} + +void GetTextureParameterIuiv(Context& ctx, GLuint texture, GLenum pname, GLuint* params) { + WMGL_NAMED_TEXTURE("glGetTextureParameterIuiv", texture); + if (!getParameterTarget(ctx, *tex)) return; + GetTexParameterIuiv(ctx, tex->target, pname, params); +} + +void GetTextureLevelParameterfv(Context& ctx, GLuint texture, GLint level, GLenum pname, GLfloat* params) { + WMGL_NAMED_TEXTURE("glGetTextureLevelParameterfv", texture); + // A cube map answers for its faces (all have the same shape). + GetTexLevelParameterfv(ctx, tex->target == GL_TEXTURE_CUBE_MAP ? GL_TEXTURE_CUBE_MAP_POSITIVE_X : tex->target, level, pname, + params); +} + +void GetTextureLevelParameteriv(Context& ctx, GLuint texture, GLint level, GLenum pname, GLint* params) { + WMGL_NAMED_TEXTURE("glGetTextureLevelParameteriv", texture); + GetTexLevelParameteriv(ctx, tex->target == GL_TEXTURE_CUBE_MAP ? GL_TEXTURE_CUBE_MAP_POSITIVE_X : tex->target, level, pname, + params); +} + +void GenerateTextureMipmap(Context& ctx, GLuint texture) { + WMGL_NAMED_TEXTURE("glGenerateTextureMipmap", texture); + GenerateMipmap(ctx, tex->target); +} + +void TextureBuffer(Context& ctx, GLuint texture, GLenum internalformat, GLuint buffer) { + RefPtr tex = textureFor(ctx, "glTextureBuffer", texture, {GL_TEXTURE_BUFFER}); + if (!tex) return; + TextureScope scope(ctx, tex); + TexBuffer(ctx, GL_TEXTURE_BUFFER, internalformat, buffer); +} + +void TextureBufferRange(Context& ctx, GLuint texture, GLenum internalformat, GLuint buffer, GLintptr offset, GLsizeiptr size) { + RefPtr tex = textureFor(ctx, "glTextureBufferRange", texture, {GL_TEXTURE_BUFFER}); + if (!tex) return; + TextureScope scope(ctx, tex); + TexBufferRange(ctx, GL_TEXTURE_BUFFER, internalformat, buffer, offset, size); +} + +void GetTextureImage(Context& ctx, GLuint texture, GLint level, GLenum format, GLenum type, GLsizei bufSize, void* pixels) { + WMGL_NAMED_TEXTURE("glGetTextureImage", texture); + if (tex->target == GL_TEXTURE_BUFFER || tex->target == GL_TEXTURE_2D_MULTISAMPLE || + tex->target == GL_TEXTURE_2D_MULTISAMPLE_ARRAY) + return ctx.setError(GL_INVALID_OPERATION); + if (tex->target != GL_TEXTURE_CUBE_MAP) return GetnTexImage(ctx, tex->target, level, format, type, bufSize, pixels); + // Every face must have the level, alike (cube complete at that level). + if (level < 0 || level >= Texture::kMaxLevels) return ctx.setError(GL_INVALID_VALUE); + for (int f = 1; f < 6; ++f) { + const TextureImage& a = tex->image(0, level), &b = tex->image(f, level); + if (!a.defined() || !b.defined() || a.width != b.width || a.height != b.height || a.internalFormat != b.internalFormat) + return ctx.setError(GL_INVALID_OPERATION); + } + // A cube map: its six faces, one image after another. + const TextureImage& image = tex->image(0, level >= 0 && level < Texture::kMaxLevels ? level : 0); + PixelStoreParams ps = pixelStoreParams(ctx.state.pack, true); + const size_t face = transferLayout(format, type, image.width, image.height, 2, ps).imageStride; + if (face && static_cast(bufSize) < 6 * face) return ctx.setError(GL_INVALID_OPERATION); + for (int f = 0; f < 6; ++f) + GetnTexImage(ctx, GL_TEXTURE_CUBE_MAP_POSITIVE_X + static_cast(f), level, format, type, + static_cast(face), static_cast(pixels) + f * face); +} + +void GetCompressedTextureImage(Context& ctx, GLuint texture, GLint level, GLsizei bufSize, void* pixels) { + WMGL_NAMED_TEXTURE("glGetCompressedTextureImage", texture); + if (tex->target != GL_TEXTURE_CUBE_MAP) return GetnCompressedTexImage(ctx, tex->target, level, bufSize, pixels); + GLint size = 0; + GetTexLevelParameteriv(ctx, GL_TEXTURE_CUBE_MAP_POSITIVE_X, level, GL_TEXTURE_COMPRESSED_IMAGE_SIZE, &size); + if (static_cast(bufSize) < 6 * static_cast(size)) return ctx.setError(GL_INVALID_OPERATION); + for (int f = 0; f < 6; ++f) + GetnCompressedTexImage(ctx, GL_TEXTURE_CUBE_MAP_POSITIVE_X + static_cast(f), level, size, + static_cast(pixels) + f * size); +} + +#undef WMGL_NAMED_TEXTURE + +// ------------------------------------------------------------ framebuffers and renderbuffers + +namespace { + +// framebuffer 0 names the default framebuffer where the command allows it. +bool namedFramebuffer(Context& ctx, const char* command, GLuint name, bool allowDefault, RefPtr& out) { + if (!dsa(ctx, command)) return false; + if (!name) { + if (!allowDefault) { + ctx.setError(GL_INVALID_OPERATION); + return false; + } + out = nullptr; + return true; + } + out = ctx.framebuffers.getRef(name); + if (!out) ctx.setError(GL_INVALID_OPERATION); + return out != nullptr; +} + +} // namespace + +#define WMGL_DRAW_FRAMEBUFFER(command, name, allowDefault) \ + RefPtr fb_; \ + if (!namedFramebuffer(ctx, command, name, allowDefault, fb_)) return; \ + BindingSwap> drawSwap_(ctx.state.drawFramebuffer, fb_) + +namespace { + +// COLOR_ATTACHMENTm with m at or past MAX_COLOR_ATTACHMENTS is INVALID_OPERATION (GL 4.5 section 9.2). +bool colorAttachmentInRange(Context& ctx, GLenum attachment) { + if (attachment >= GL_COLOR_ATTACHMENT0 + limits::kMaxDrawBuffers && attachment <= GL_COLOR_ATTACHMENT0 + 31) + return ctx.setError(GL_INVALID_OPERATION), false; + return true; +} + +} // namespace + +void NamedFramebufferRenderbuffer(Context& ctx, GLuint framebuffer, GLenum attachment, GLenum renderbuffertarget, + GLuint renderbuffer) { + WMGL_DRAW_FRAMEBUFFER("glNamedFramebufferRenderbuffer", framebuffer, false); + if (!colorAttachmentInRange(ctx, attachment)) return; + FramebufferRenderbuffer(ctx, GL_DRAW_FRAMEBUFFER, attachment, renderbuffertarget, renderbuffer); +} + +void NamedFramebufferTexture(Context& ctx, GLuint framebuffer, GLenum attachment, GLuint texture, GLint level) { + WMGL_DRAW_FRAMEBUFFER("glNamedFramebufferTexture", framebuffer, false); + FramebufferTexture(ctx, GL_DRAW_FRAMEBUFFER, attachment, texture, level); +} + +void NamedFramebufferTextureLayer(Context& ctx, GLuint framebuffer, GLenum attachment, GLuint texture, GLint level, GLint layer) { + WMGL_DRAW_FRAMEBUFFER("glNamedFramebufferTextureLayer", framebuffer, false); + FramebufferTextureLayer(ctx, GL_DRAW_FRAMEBUFFER, attachment, texture, level, layer); +} + +namespace { + +// A draw buffer token of GL 4.5 tables 17.5 and 17.6. +bool drawBufferToken(GLenum buf) { + switch (buf) { + case GL_NONE: case GL_FRONT: case GL_BACK: case GL_LEFT: case GL_RIGHT: case GL_FRONT_AND_BACK: case GL_FRONT_LEFT: + case GL_FRONT_RIGHT: case GL_BACK_LEFT: case GL_BACK_RIGHT: + return true; + default: return buf >= GL_COLOR_ATTACHMENT0 && buf <= GL_COLOR_ATTACHMENT0 + 31; + } +} + +} // namespace + +void NamedFramebufferDrawBuffer(Context& ctx, GLuint framebuffer, GLenum buf) { + WMGL_DRAW_FRAMEBUFFER("glNamedFramebufferDrawBuffer", framebuffer, true); + if (!drawBufferToken(buf)) return ctx.setError(GL_INVALID_ENUM); + DrawBuffer(ctx, buf); +} + +void NamedFramebufferDrawBuffers(Context& ctx, GLuint framebuffer, GLsizei n, const GLenum* bufs) { + WMGL_DRAW_FRAMEBUFFER("glNamedFramebufferDrawBuffers", framebuffer, true); + if (n < 0 || n > limits::kMaxDrawBuffers) return ctx.setError(GL_INVALID_VALUE); + // FRONT, LEFT, RIGHT and FRONT_AND_BACK name several buffers, which DrawBuffers rejects. + for (GLsizei i = 0; bufs && i < n; ++i) + if (!drawBufferToken(bufs[i]) || bufs[i] == GL_FRONT || bufs[i] == GL_LEFT || bufs[i] == GL_RIGHT || + bufs[i] == GL_FRONT_AND_BACK) + return ctx.setError(GL_INVALID_ENUM); + DrawBuffers(ctx, n, bufs); +} + +void NamedFramebufferReadBuffer(Context& ctx, GLuint framebuffer, GLenum src) { + RefPtr fb; + if (!namedFramebuffer(ctx, "glNamedFramebufferReadBuffer", framebuffer, true, fb)) return; + // A framebuffer object reads only its colour attachments (GL 4.5 section 18.2.1). + if (fb && src != GL_NONE && (!drawBufferToken(src) || src < GL_COLOR_ATTACHMENT0 || + src >= GL_COLOR_ATTACHMENT0 + limits::kMaxDrawBuffers)) + return ctx.setError(drawBufferToken(src) ? GL_INVALID_OPERATION : GL_INVALID_ENUM); + BindingSwap> readSwap(ctx.state.readFramebuffer, fb); + ReadBuffer(ctx, src); +} + +void NamedFramebufferParameteri(Context& ctx, GLuint framebuffer, GLenum pname, GLint param) { + WMGL_DRAW_FRAMEBUFFER("glNamedFramebufferParameteri", framebuffer, false); + FramebufferParameteri(ctx, GL_DRAW_FRAMEBUFFER, pname, param); +} + +void GetNamedFramebufferParameteriv(Context& ctx, GLuint framebuffer, GLenum pname, GLint* param) { + WMGL_DRAW_FRAMEBUFFER("glGetNamedFramebufferParameteriv", framebuffer, true); + GetFramebufferParameteriv(ctx, GL_DRAW_FRAMEBUFFER, pname, param); +} + +void GetNamedFramebufferAttachmentParameteriv(Context& ctx, GLuint framebuffer, GLenum attachment, GLenum pname, + GLint* params) { + WMGL_DRAW_FRAMEBUFFER("glGetNamedFramebufferAttachmentParameteriv", framebuffer, true); + if (framebuffer && !colorAttachmentInRange(ctx, attachment)) return; + GetFramebufferAttachmentParameteriv(ctx, GL_DRAW_FRAMEBUFFER, attachment, pname, params); +} + +void InvalidateNamedFramebufferData(Context& ctx, GLuint framebuffer, GLsizei numAttachments, const GLenum* attachments) { + WMGL_DRAW_FRAMEBUFFER("glInvalidateNamedFramebufferData", framebuffer, true); + InvalidateFramebuffer(ctx, GL_DRAW_FRAMEBUFFER, numAttachments, attachments); +} + +void InvalidateNamedFramebufferSubData(Context& ctx, GLuint framebuffer, GLsizei numAttachments, const GLenum* attachments, + GLint x, GLint y, GLsizei width, GLsizei height) { + WMGL_DRAW_FRAMEBUFFER("glInvalidateNamedFramebufferSubData", framebuffer, true); + InvalidateSubFramebuffer(ctx, GL_DRAW_FRAMEBUFFER, numAttachments, attachments, x, y, width, height); +} + +void ClearNamedFramebufferiv(Context& ctx, GLuint framebuffer, GLenum buffer, GLint drawbuffer, const GLint* value) { + WMGL_DRAW_FRAMEBUFFER("glClearNamedFramebufferiv", framebuffer, true); + ClearBufferiv(ctx, buffer, drawbuffer, value); +} + +void ClearNamedFramebufferuiv(Context& ctx, GLuint framebuffer, GLenum buffer, GLint drawbuffer, const GLuint* value) { + WMGL_DRAW_FRAMEBUFFER("glClearNamedFramebufferuiv", framebuffer, true); + ClearBufferuiv(ctx, buffer, drawbuffer, value); +} + +void ClearNamedFramebufferfv(Context& ctx, GLuint framebuffer, GLenum buffer, GLint drawbuffer, const GLfloat* value) { + WMGL_DRAW_FRAMEBUFFER("glClearNamedFramebufferfv", framebuffer, true); + ClearBufferfv(ctx, buffer, drawbuffer, value); +} + +void ClearNamedFramebufferfi(Context& ctx, GLuint framebuffer, GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil) { + WMGL_DRAW_FRAMEBUFFER("glClearNamedFramebufferfi", framebuffer, true); + ClearBufferfi(ctx, buffer, drawbuffer, depth, stencil); +} + +#undef WMGL_DRAW_FRAMEBUFFER + +void BlitNamedFramebuffer(Context& ctx, GLuint readFramebuffer, GLuint drawFramebuffer, GLint srcX0, GLint srcY0, GLint srcX1, + GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter) { + RefPtr read, draw; + if (!namedFramebuffer(ctx, "glBlitNamedFramebuffer", readFramebuffer, true, read) || + !namedFramebuffer(ctx, "glBlitNamedFramebuffer", drawFramebuffer, true, draw)) + return; + BindingSwap> r(ctx.state.readFramebuffer, read), d(ctx.state.drawFramebuffer, draw); + BlitFramebuffer(ctx, srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter); +} + +GLenum CheckNamedFramebufferStatus(Context& ctx, GLuint framebuffer, GLenum target) { + if (target != GL_FRAMEBUFFER && target != GL_DRAW_FRAMEBUFFER && target != GL_READ_FRAMEBUFFER) { + if (!dsa(ctx, "glCheckNamedFramebufferStatus")) return 0; + ctx.setError(GL_INVALID_ENUM); + return 0; + } + RefPtr fb; + if (!namedFramebuffer(ctx, "glCheckNamedFramebufferStatus", framebuffer, true, fb)) return 0; + BindingSwap> r(ctx.state.readFramebuffer, fb), d(ctx.state.drawFramebuffer, fb); + return CheckFramebufferStatus(ctx, target); +} + +void NamedRenderbufferStorage(Context& ctx, GLuint renderbuffer, GLenum internalformat, GLsizei width, GLsizei height) { + if (!dsa(ctx, "glNamedRenderbufferStorage")) return; + RefPtr rb = namedRenderbuffer(ctx, renderbuffer); + if (!rb) return ctx.setError(GL_INVALID_OPERATION); + BindingSwap> bound(ctx.state.renderbuffer, rb); + RenderbufferStorage(ctx, GL_RENDERBUFFER, internalformat, width, height); +} + +void NamedRenderbufferStorageMultisample(Context& ctx, GLuint renderbuffer, GLsizei samples, GLenum internalformat, + GLsizei width, GLsizei height) { + if (!dsa(ctx, "glNamedRenderbufferStorageMultisample")) return; + RefPtr rb = namedRenderbuffer(ctx, renderbuffer); + if (!rb) return ctx.setError(GL_INVALID_OPERATION); + // More samples than the format allows is INVALID_OPERATION (GL 4.5 section 9.2.4). + GLint maxSamples = 0, maxIntegerSamples = 0; + GetIntegerv(ctx, GL_MAX_SAMPLES, &maxSamples); + GetIntegerv(ctx, GL_MAX_INTEGER_SAMPLES, &maxIntegerSamples); + const FormatInfo* info = formatInfo(internalformat); + if (samples > maxSamples || (info && info->isInteger() && samples > maxIntegerSamples)) + return ctx.setError(GL_INVALID_OPERATION); + BindingSwap> bound(ctx.state.renderbuffer, rb); + RenderbufferStorageMultisample(ctx, GL_RENDERBUFFER, samples, internalformat, width, height); +} + +void GetNamedRenderbufferParameteriv(Context& ctx, GLuint renderbuffer, GLenum pname, GLint* params) { + if (!dsa(ctx, "glGetNamedRenderbufferParameteriv")) return; + RefPtr rb = namedRenderbuffer(ctx, renderbuffer); + if (!rb) return ctx.setError(GL_INVALID_OPERATION); + BindingSwap> bound(ctx.state.renderbuffer, rb); + GetRenderbufferParameteriv(ctx, GL_RENDERBUFFER, pname, params); +} + +// ------------------------------------------------------------ vertex arrays + +#define WMGL_NAMED_VAO(command, name) \ + if (!dsa(ctx, command)) return; \ + RefPtr vao_ = (name) ? ctx.vertexArrays.getRef(name) : nullptr; \ + if (!vao_) return ctx.setError(GL_INVALID_OPERATION); \ + BindingSwap> vaoSwap_(ctx.state.vertexArray, vao_); \ + ctx.markDirty(kDirtyVertexInput) + +void VertexArrayElementBuffer(Context& ctx, GLuint vaobj, GLuint buffer) { + WMGL_NAMED_VAO("glVertexArrayElementBuffer", vaobj); + if (buffer && !namedBuffer(ctx, buffer)) return ctx.setError(GL_INVALID_OPERATION); + BindingSwap> keep(ctx.state.arrayBuffer, ctx.state.arrayBuffer); // only the element binding changes + BindBuffer(ctx, GL_ELEMENT_ARRAY_BUFFER, buffer); +} + +void VertexArrayVertexBuffer(Context& ctx, GLuint vaobj, GLuint bindingindex, GLuint buffer, GLintptr offset, GLsizei stride) { + WMGL_NAMED_VAO("glVertexArrayVertexBuffer", vaobj); + if (buffer && !namedBuffer(ctx, buffer)) return ctx.setError(GL_INVALID_OPERATION); + BindVertexBuffer(ctx, bindingindex, buffer, offset, stride); +} + +void VertexArrayVertexBuffers(Context& ctx, GLuint vaobj, GLuint first, GLsizei count, const GLuint* buffers, + const GLintptr* offsets, const GLsizei* strides) { + WMGL_NAMED_VAO("glVertexArrayVertexBuffers", vaobj); + BindVertexBuffers(ctx, first, count, buffers, offsets, strides); +} + +void VertexArrayAttribBinding(Context& ctx, GLuint vaobj, GLuint attribindex, GLuint bindingindex) { + WMGL_NAMED_VAO("glVertexArrayAttribBinding", vaobj); + VertexAttribBinding(ctx, attribindex, bindingindex); +} + +void VertexArrayAttribFormat(Context& ctx, GLuint vaobj, GLuint attribindex, GLint size, GLenum type, GLboolean normalized, + GLuint relativeoffset) { + WMGL_NAMED_VAO("glVertexArrayAttribFormat", vaobj); + VertexAttribFormat(ctx, attribindex, size, type, normalized, relativeoffset); +} + +void VertexArrayAttribIFormat(Context& ctx, GLuint vaobj, GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset) { + WMGL_NAMED_VAO("glVertexArrayAttribIFormat", vaobj); + VertexAttribIFormat(ctx, attribindex, size, type, relativeoffset); +} + +void VertexArrayAttribLFormat(Context& ctx, GLuint vaobj, GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset) { + WMGL_NAMED_VAO("glVertexArrayAttribLFormat", vaobj); + VertexAttribLFormat(ctx, attribindex, size, type, relativeoffset); +} + +void VertexArrayBindingDivisor(Context& ctx, GLuint vaobj, GLuint bindingindex, GLuint divisor) { + WMGL_NAMED_VAO("glVertexArrayBindingDivisor", vaobj); + VertexBindingDivisor(ctx, bindingindex, divisor); +} + +void EnableVertexArrayAttrib(Context& ctx, GLuint vaobj, GLuint index) { + WMGL_NAMED_VAO("glEnableVertexArrayAttrib", vaobj); + EnableVertexAttribArray(ctx, index); +} + +void DisableVertexArrayAttrib(Context& ctx, GLuint vaobj, GLuint index) { + WMGL_NAMED_VAO("glDisableVertexArrayAttrib", vaobj); + DisableVertexAttribArray(ctx, index); +} + +void GetVertexArrayiv(Context& ctx, GLuint vaobj, GLenum pname, GLint* param) { + WMGL_NAMED_VAO("glGetVertexArrayiv", vaobj); + if (pname != GL_ELEMENT_ARRAY_BUFFER_BINDING) return ctx.setError(GL_INVALID_ENUM); + *param = vao_->elementBuffer ? static_cast(vao_->elementBuffer->name) : 0; +} + +void GetVertexArrayIndexediv(Context& ctx, GLuint vaobj, GLuint index, GLenum pname, GLint* param) { + WMGL_NAMED_VAO("glGetVertexArrayIndexediv", vaobj); + switch (pname) { + case GL_VERTEX_ATTRIB_ARRAY_ENABLED: case GL_VERTEX_ATTRIB_ARRAY_SIZE: case GL_VERTEX_ATTRIB_ARRAY_STRIDE: + case GL_VERTEX_ATTRIB_ARRAY_TYPE: case GL_VERTEX_ATTRIB_ARRAY_NORMALIZED: case GL_VERTEX_ATTRIB_ARRAY_INTEGER: + case GL_VERTEX_ATTRIB_ARRAY_LONG: case GL_VERTEX_ATTRIB_ARRAY_DIVISOR: case GL_VERTEX_ATTRIB_RELATIVE_OFFSET: + GetVertexAttribiv(ctx, index, pname, param); + return; + default: return ctx.setError(GL_INVALID_ENUM); + } +} + +void GetVertexArrayIndexed64iv(Context& ctx, GLuint vaobj, GLuint index, GLenum pname, GLint64* param) { + WMGL_NAMED_VAO("glGetVertexArrayIndexed64iv", vaobj); + if (pname != GL_VERTEX_BINDING_OFFSET) return ctx.setError(GL_INVALID_ENUM); + if (index >= static_cast(VertexArray::kMaxBindings)) return ctx.setError(GL_INVALID_VALUE); + *param = static_cast(vao_->bindings[index].offset); +} + +#undef WMGL_NAMED_VAO + +// ------------------------------------------------------------ transform feedback and queries + +namespace { + +void transformFeedbackBuffer(Context& ctx, const char* command, GLuint xfb, GLuint index, GLuint buffer, GLintptr offset, + GLsizeiptr size, bool range) { + if (!dsa(ctx, command)) return; + RefPtr object = xfb ? ctx.transformFeedbacks.getRef(xfb) : ctx.defaultTransformFeedback; + if (!object) return ctx.setError(GL_INVALID_OPERATION); + if (buffer && !namedBuffer(ctx, buffer)) return ctx.setError(GL_INVALID_OPERATION); + BindingSwap> bound(ctx.state.transformFeedback, object); + BindingSwap> generic(ctx.state.transformFeedbackBuffer, ctx.state.transformFeedbackBuffer); + if (range) BindBufferRange(ctx, GL_TRANSFORM_FEEDBACK_BUFFER, index, buffer, offset, size); + else BindBufferBase(ctx, GL_TRANSFORM_FEEDBACK_BUFFER, index, buffer); +} + +template +void queryBufferObject(Context& ctx, const char* command, GLuint id, GLuint buffer, GLenum pname, GLintptr offset, + void (*get)(Context&, GLuint, GLenum, T*)) { + if (!dsa(ctx, command)) return; + RefPtr object = namedBuffer(ctx, buffer); + if (!object) return ctx.setError(GL_INVALID_OPERATION); + if (offset < 0) return ctx.setError(GL_INVALID_VALUE); + BindingSwap> bound(ctx.state.queryBuffer, object); + get(ctx, id, pname, reinterpret_cast(offset)); +} + +} // namespace + +void TransformFeedbackBufferBase(Context& ctx, GLuint xfb, GLuint index, GLuint buffer) { + transformFeedbackBuffer(ctx, "glTransformFeedbackBufferBase", xfb, index, buffer, 0, 0, false); +} + +void TransformFeedbackBufferRange(Context& ctx, GLuint xfb, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size) { + transformFeedbackBuffer(ctx, "glTransformFeedbackBufferRange", xfb, index, buffer, offset, size, true); +} + +void GetQueryBufferObjectiv(Context& ctx, GLuint id, GLuint buffer, GLenum pname, GLintptr offset) { + queryBufferObject(ctx, "glGetQueryBufferObjectiv", id, buffer, pname, offset, &GetQueryObjectiv); +} + +void GetQueryBufferObjectuiv(Context& ctx, GLuint id, GLuint buffer, GLenum pname, GLintptr offset) { + queryBufferObject(ctx, "glGetQueryBufferObjectuiv", id, buffer, pname, offset, &GetQueryObjectuiv); +} + +void GetQueryBufferObjecti64v(Context& ctx, GLuint id, GLuint buffer, GLenum pname, GLintptr offset) { + queryBufferObject(ctx, "glGetQueryBufferObjecti64v", id, buffer, pname, offset, &GetQueryObjecti64v); +} + +void GetQueryBufferObjectui64v(Context& ctx, GLuint id, GLuint buffer, GLenum pname, GLintptr offset) { + queryBufferObject(ctx, "glGetQueryBufferObjectui64v", id, buffer, pname, offset, &GetQueryObjectui64v); +} + +// glMemoryBarrierByRegion (GL 4.5, GL_ARB_ES3_1_compatibility): the +// framebuffer-local subset of glMemoryBarrier, which orders at least as much. +void MemoryBarrierByRegion(Context& ctx, GLbitfield barriers) { + if (ctx.version() < 45 && !ctx.hasExtension("GL_ARB_ES3_1_compatibility")) return ctx.unimplemented("glMemoryBarrierByRegion"); + constexpr GLbitfield kRegionBits = GL_ATOMIC_COUNTER_BARRIER_BIT | GL_FRAMEBUFFER_BARRIER_BIT | + GL_SHADER_IMAGE_ACCESS_BARRIER_BIT | GL_SHADER_STORAGE_BARRIER_BIT | + GL_TEXTURE_FETCH_BARRIER_BIT | GL_UNIFORM_BARRIER_BIT; + if (barriers != GL_ALL_BARRIER_BITS && (barriers & ~kRegionBits)) return ctx.setError(GL_INVALID_VALUE); + MemoryBarrier(ctx, barriers); +} + +} // namespace wmgl::api diff --git a/src/core/api/framebuffer.mm b/src/core/api/framebuffer.mm index 5d84785f..964c5981 100644 --- a/src/core/api/framebuffer.mm +++ b/src/core/api/framebuffer.mm @@ -175,6 +175,9 @@ void attachTexture(Context& ctx, GLenum target, GLenum attachment, GLenum textar t != GL_TEXTURE_CUBE_MAP_ARRAY) return ctx.setError(GL_INVALID_OPERATION); if (layer < 0) return ctx.setError(GL_INVALID_VALUE); + // A layer past the target's limit (MAX_3D_TEXTURE_SIZE or MAX_ARRAY_TEXTURE_LAYERS). + if (layer >= (t == GL_TEXTURE_3D ? limits::kMax3DTextureSize : limits::kMaxArrayLayers)) + return ctx.setError(GL_INVALID_VALUE); } a.type = GL_TEXTURE; a.texture = tex; @@ -370,6 +373,26 @@ void GetFramebufferParameteriv(Context& ctx, GLenum target, GLenum pname, GLint* RefPtr* binding = framebufferBinding(ctx, target); if (!binding) return ctx.setError(GL_INVALID_ENUM); Framebuffer* fb = binding->get(); + // GL 4.5: the framebuffer-dependent state of glGetIntegerv, for the + // framebuffer bound to `target` (the default framebuffer included). + if (ctx.version() >= 45 && (pname == GL_DOUBLEBUFFER || pname == GL_STEREO || pname == GL_SAMPLES || + pname == GL_SAMPLE_BUFFERS || pname == GL_IMPLEMENTATION_COLOR_READ_FORMAT || + pname == GL_IMPLEMENTATION_COLOR_READ_TYPE)) { + if (!params) return; + if (pname == GL_STEREO) return void(*params = GL_FALSE); + if (pname == GL_DOUBLEBUFFER) { + Surface* surface = ctx.drawSurface(); + *params = !fb && surface && surface->pixelFormat().double_buffer ? GL_TRUE : GL_FALSE; + return; + } + RefPtr current = *binding; + RefPtr draw = ctx.state.drawFramebuffer, read = ctx.state.readFramebuffer; + ctx.state.drawFramebuffer = ctx.state.readFramebuffer = current; + GetIntegerv(ctx, pname, params); + ctx.state.drawFramebuffer = draw; + ctx.state.readFramebuffer = read; + return; + } if (!fb) return ctx.setError(GL_INVALID_OPERATION); if (!params) return; switch (pname) { @@ -393,7 +416,9 @@ void GetFramebufferAttachmentParameteriv(Context& ctx, GLenum target, GLenum att const bool color = attachment == GL_FRONT_LEFT || attachment == GL_BACK_LEFT || attachment == GL_FRONT || attachment == GL_BACK || attachment == GL_LEFT; const bool depth = attachment == GL_DEPTH, stencil = attachment == GL_STENCIL; - if (!color && !depth && !stencil) return ctx.setError(GL_INVALID_ENUM); + // The right buffers exist only in stereo formats, which WineMetalGL has none of. + const bool right = attachment == GL_FRONT_RIGHT || attachment == GL_BACK_RIGHT || attachment == GL_RIGHT; + if (!color && !depth && !stencil && !right) return ctx.setError(GL_INVALID_ENUM); const bool present = surface && (color || (depth && surface->hasDepth()) || (stencil && surface->hasStencil())); switch (pname) { case GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE: *params = present ? GL_FRAMEBUFFER_DEFAULT : GL_NONE; return; diff --git a/src/core/api/get.mm b/src/core/api/get.mm index a62130dc..09c97dfe 100644 --- a/src/core/api/get.mm +++ b/src/core/api/get.mm @@ -11,6 +11,7 @@ #include "core/limits.h" #include "core/metal/device.h" #include "core/program.h" +#include "core/shader/spirv_input.h" #include "gl_api.gen.h" namespace wmgl::api { @@ -139,7 +140,7 @@ bool getState(Context& ctx, GLenum pname, Value& out) { case GL_NUM_SHADING_LANGUAGE_VERSIONS: // The glGetStringi(GL_SHADING_LANGUAGE_VERSION) list (api/state.mm). if (ctx.version() < 43) return false; - out = ints({ctx.version() - 43 + 12}); + out = ints({ctx.version() - 43 + 12 + (ctx.version() >= 45 ? 1 : 0)}); return true; case GL_RESET_NOTIFICATION_STRATEGY: out = ints({ctx.config().resetNotification}); return true; case 0x90F3: // GL_CONTEXT_ROBUST_ACCESS (GL_KHR_robustness) @@ -202,6 +203,18 @@ bool getState(Context& ctx, GLenum pname, Value& out) { if (ctx.version() < 43 && !ctx.hasExtension("GL_ARB_explicit_uniform_location")) return false; out = ints({kMaxUniformLocations}); return true; + case GL_QUERY_BUFFER_BINDING: // GL 4.4, GL_ARB_query_buffer_object + if (ctx.version() < 44) return false; + out = ints({nameOf(s.queryBuffer)}); + return true; + case GL_MAX_SHADER_COMPILER_THREADS_ARB: + if (!ctx.parallelShaderCompile()) return false; + out = ints({static_cast(s.maxShaderCompilerThreads)}); + return true; + case GL_PARAMETER_BUFFER_BINDING: // GL 4.6, GL_ARB_indirect_parameters + if (!ctx.indirectParameters()) return false; + out = ints({nameOf(s.parameterBuffer)}); + return true; // GL 4.3 shader storage blocks (limits.h): vertex, fragment and compute stages. case GL_SHADER_STORAGE_BUFFER_BINDING: case GL_MAX_SHADER_STORAGE_BUFFER_BINDINGS: case GL_MAX_SHADER_STORAGE_BLOCK_SIZE: case GL_SHADER_STORAGE_BUFFER_OFFSET_ALIGNMENT: case GL_MAX_VERTEX_SHADER_STORAGE_BLOCKS: @@ -237,12 +250,19 @@ bool getState(Context& ctx, GLenum pname, Value& out) { case GL_MAX_VARYING_VECTORS: out = ints({16}); return true; // GL_ARB_ES2_compatibility. case GL_SHADER_COMPILER: out = bools({true}); return true; - case GL_NUM_SHADER_BINARY_FORMATS: out = ints({0}); return true; + // GL 4.6 / GL_ARB_gl_spirv: SPIR-V is the one shader binary format. + case GL_NUM_SHADER_BINARY_FORMATS: out = ints({ctx.glSpirv() ? 1 : 0}); return true; + case GL_NUM_SPIR_V_EXTENSIONS: + if (!ctx.glSpirv()) return false; + out = ints({static_cast(shader::spirvExtensions().size())}); + return true; case GL_NUM_PROGRAM_BINARY_FORMATS: case GL_PROGRAM_BINARY_FORMATS: if (ctx.version() < 41 && !ctx.hasExtension("GL_ARB_get_program_binary")) return false; out = pname == GL_NUM_PROGRAM_BINARY_FORMATS ? ints({1}) : ints({kProgramBinaryFormat}); return true; - case GL_SHADER_BINARY_FORMATS: out = ints({}); return true; + case GL_SHADER_BINARY_FORMATS: + out = ctx.glSpirv() ? ints({GL_SHADER_BINARY_FORMAT_SPIR_V}) : ints({}); + return true; case GL_IMPLEMENTATION_COLOR_READ_FORMAT: case GL_IMPLEMENTATION_COLOR_READ_TYPE: { GLenum format = GL_RGBA, type = GL_UNSIGNED_BYTE; @@ -254,17 +274,17 @@ bool getState(Context& ctx, GLenum pname, Value& out) { out = ints({pname == GL_IMPLEMENTATION_COLOR_READ_FORMAT ? format : type}); return true; } - case GL_MAX_VERTEX_OUTPUT_COMPONENTS: out = ints({64}); return true; - case GL_MAX_GEOMETRY_INPUT_COMPONENTS: out = ints({64}); return true; - case GL_MAX_GEOMETRY_OUTPUT_COMPONENTS: out = ints({128}); return true; - case GL_MAX_FRAGMENT_INPUT_COMPONENTS: out = ints({128}); return true; + case GL_MAX_VERTEX_OUTPUT_COMPONENTS: out = ints({kMaxVertexOutputComponents}); return true; + case GL_MAX_GEOMETRY_INPUT_COMPONENTS: out = ints({kMaxGeometryInputComponents}); return true; + case GL_MAX_GEOMETRY_OUTPUT_COMPONENTS: out = ints({kMaxInterfaceComponents}); return true; + case GL_MAX_FRAGMENT_INPUT_COMPONENTS: out = ints({kMaxInterfaceComponents}); return true; case GL_MAX_GEOMETRY_OUTPUT_VERTICES: out = ints({256}); return true; // Primitive restart is treated as disabled for GL_PATCHES (GL 4.3 section 10.3.5). case GL_PRIMITIVE_RESTART_FOR_PATCHES_SUPPORTED: if (!ctx.tessellationShaders()) return false; out = bools({false}); return true; - // GL 4.2 atomic counters (limits.h); vertex and tessellation stages have none. + // GL 4.2 atomic counters (limits.h); tessellation stages have none. case GL_ATOMIC_COUNTER_BUFFER_BINDING: case GL_MAX_ATOMIC_COUNTER_BUFFER_BINDINGS: case GL_MAX_ATOMIC_COUNTER_BUFFER_SIZE: case GL_MAX_VERTEX_ATOMIC_COUNTER_BUFFERS: case GL_MAX_TESS_CONTROL_ATOMIC_COUNTER_BUFFERS: case GL_MAX_TESS_EVALUATION_ATOMIC_COUNTER_BUFFERS: case GL_MAX_GEOMETRY_ATOMIC_COUNTER_BUFFERS: @@ -342,7 +362,7 @@ bool getState(Context& ctx, GLenum pname, Value& out) { case GL_MAX_COMBINED_TESS_CONTROL_UNIFORM_COMPONENTS: case GL_MAX_COMBINED_TESS_EVALUATION_UNIFORM_COMPONENTS: out = ints({kMaxDefaultUniformComponents + static_cast(kMaxUniformBlocksPerStage) * kMaxUniformBlockSize / 4}); break; - default: out = ints({128}); break; // input and output components + default: out = ints({kMaxInterfaceComponents}); break; // input and output components } return true; case GL_MAX_GEOMETRY_TOTAL_OUTPUT_COMPONENTS: out = ints({1024}); return true; @@ -381,6 +401,14 @@ bool getState(Context& ctx, GLenum pname, Value& out) { case GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS: out = ints({4}); return true; case GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_COMPONENTS: out = ints({4}); return true; case GL_MAX_CLIP_DISTANCES: out = ints({8}); return true; + case GL_CONTEXT_RELEASE_BEHAVIOR: // GL 4.5, GL_KHR_context_flush_control: releasing a context flushes it + if (ctx.version() < 45 && !ctx.hasExtension("GL_KHR_context_flush_control")) return false; + out = ints({GL_CONTEXT_RELEASE_BEHAVIOR_FLUSH}); + return true; + case GL_MAX_CULL_DISTANCES: case GL_MAX_COMBINED_CLIP_AND_CULL_DISTANCES: // GL 4.5, GL_ARB_cull_distance + if (ctx.version() < 45 && !ctx.hasExtension("GL_ARB_cull_distance")) return false; + out = ints({8}); + return true; case GL_MAX_SERVER_WAIT_TIMEOUT: out = int64v(INT64_MAX); return true; // GL 4.3 / GL_ARB_ES3_compatibility, which the KHR-GL32 API coverage test queries in // every core context. Element arrays take full 32-bit indices. @@ -644,6 +672,34 @@ bool getIndexed(Context& ctx, GLenum pname, GLuint index, Value& out, bool& badI const ContextState& s = ctx.state; badIndex = false; switch (pname) { + // The texture and sampler bindings of a unit (GL 4.4, GL_ARB_multi_bind). + case GL_TEXTURE_BINDING_1D: case GL_TEXTURE_BINDING_2D: case GL_TEXTURE_BINDING_3D: case GL_TEXTURE_BINDING_1D_ARRAY: + case GL_TEXTURE_BINDING_2D_ARRAY: case GL_TEXTURE_BINDING_RECTANGLE: case GL_TEXTURE_BINDING_CUBE_MAP: + case GL_TEXTURE_BINDING_BUFFER: case GL_TEXTURE_BINDING_CUBE_MAP_ARRAY: case GL_TEXTURE_BINDING_2D_MULTISAMPLE: + case GL_TEXTURE_BINDING_2D_MULTISAMPLE_ARRAY: case GL_SAMPLER_BINDING: { + if (ctx.version() < 44 && !ctx.hasExtension("GL_ARB_multi_bind")) return false; + if (index >= static_cast(limits::kMaxCombinedTextureUnits)) { badIndex = true; return true; } + const TextureUnit& unit = s.textureUnits[index]; + if (pname == GL_SAMPLER_BINDING) { + out = ints({unit.sampler ? static_cast(unit.sampler->name) : 0}); + return true; + } + static const std::pair kTargets[] = { + {GL_TEXTURE_BINDING_1D, GL_TEXTURE_1D}, {GL_TEXTURE_BINDING_2D, GL_TEXTURE_2D}, + {GL_TEXTURE_BINDING_3D, GL_TEXTURE_3D}, {GL_TEXTURE_BINDING_1D_ARRAY, GL_TEXTURE_1D_ARRAY}, + {GL_TEXTURE_BINDING_2D_ARRAY, GL_TEXTURE_2D_ARRAY}, {GL_TEXTURE_BINDING_RECTANGLE, GL_TEXTURE_RECTANGLE}, + {GL_TEXTURE_BINDING_CUBE_MAP, GL_TEXTURE_CUBE_MAP}, {GL_TEXTURE_BINDING_BUFFER, GL_TEXTURE_BUFFER}, + {GL_TEXTURE_BINDING_CUBE_MAP_ARRAY, GL_TEXTURE_CUBE_MAP_ARRAY}, + {GL_TEXTURE_BINDING_2D_MULTISAMPLE, GL_TEXTURE_2D_MULTISAMPLE}, + {GL_TEXTURE_BINDING_2D_MULTISAMPLE_ARRAY, GL_TEXTURE_2D_MULTISAMPLE_ARRAY}}; + for (const auto& [binding, target] : kTargets) { + if (binding != pname) continue; + const int t = textureTargetIndex(target); + const Texture* tex = t >= 0 ? unit.bound[static_cast(t)].get() : nullptr; + out = ints({tex ? static_cast(tex->name) : 0}); + } + return true; + } case GL_MAX_COMPUTE_WORK_GROUP_COUNT: case GL_MAX_COMPUTE_WORK_GROUP_SIZE: if (!ctx.computeShaders()) return false; if (index >= 3) { badIndex = true; return true; } diff --git a/src/core/api/image.mm b/src/core/api/image.mm index 195c959d..285be546 100644 --- a/src/core/api/image.mm +++ b/src/core/api/image.mm @@ -54,4 +54,47 @@ void BindImageTexture(Context& ctx, GLuint unit, GLuint texture, GLint level, GL ctx.markDirty(kDirtyTextures); } +// glBindImageTextures (GL 4.4, GL_ARB_multi_bind): a range of image units, +// each bound to level 0 of a texture, layered, read-write, in the format of +// that level. Zero (or a null array) unbinds a unit. +void BindImageTextures(Context& ctx, GLuint first, GLsizei count, const GLuint* textures) { + if (ctx.version() < 44 && !ctx.hasExtension("GL_ARB_multi_bind")) return ctx.unimplemented("glBindImageTextures"); + if (!ctx.imageLoadStore()) return ctx.unimplemented("glBindImageTextures"); + if (count < 0) return ctx.setError(GL_INVALID_VALUE); + if (static_cast(first) + static_cast(count) > static_cast(ContextState::kMaxImageUnits)) + return ctx.setError(GL_INVALID_OPERATION); + for (GLsizei i = 0; i < count; ++i) { + ImageUnit& u = ctx.state.imageUnits[first + static_cast(i)]; + const GLuint name = textures ? textures[i] : 0; + if (!name) { + u = ImageUnit(); + continue; + } + RefPtr tex; + { + ShareLock lock(ctx); + tex = ctx.share().textures.getRef(name); + } + // Level 0 must exist (a buffer texture: its buffer) in an image format. + GLenum format = 0; + if (tex && tex->target == GL_TEXTURE_BUFFER) format = tex->buffer ? tex->bufferFormat : 0; + else if (tex && tex->image(0, 0).defined()) format = tex->image(0, 0).internalFormat; + if (!format || !isImageFormat(format)) { + ctx.setError(GL_INVALID_OPERATION); + continue; + } + u.texture = tex; + u.level = 0; + u.layered = true; + u.layer = 0; + u.access = GL_READ_WRITE; + u.format = format; + if (!tex->shaderWrite) { + tex->shaderWrite = true; + tex->storageDirty = true; + } + } + ctx.markDirty(kDirtyTextures); +} + } // namespace wmgl::api diff --git a/src/core/api/program.mm b/src/core/api/program.mm index d37502d4..41540e16 100644 --- a/src/core/api/program.mm +++ b/src/core/api/program.mm @@ -3,6 +3,8 @@ #include #include +#include +#include #include #include #include @@ -10,6 +12,7 @@ #include "core/context.h" #include "core/limits.h" #include "core/program.h" +#include "core/shader/spirv_input.h" #include "core/stats.h" #include "core/util/log.h" #include "gl_api.gen.h" @@ -322,11 +325,16 @@ void ShaderSource(Context& ctx, GLuint shader, GLsizei count, const GLchar* cons else source.append(strings[i]); } s->source = std::move(source); + // A source replaces a SPIR-V module (SPIR_V_BINARY becomes FALSE). + s->spirv = s->specialized = false; + s->spirvWords.clear(); + s->spirvGlsl.clear(); } void CompileShader(Context& ctx, GLuint shader) { Shader* s = shaderOrError(ctx, shader); if (!s) return; + if (s->spirv) return ctx.setError(GL_INVALID_OPERATION); // SPIR-V is specialized instead shader::CompileResult result = shader::compile(s->stage, s->source); s->compiled = true; s->compileStatus = result.ok; @@ -356,8 +364,50 @@ void GetShaderiv(Context& ctx, GLuint shader, GLenum pname, GLint* params) { case GL_COMPILE_STATUS: *params = s->compileStatus; return; case GL_INFO_LOG_LENGTH: *params = s->infoLog.empty() ? 0 : static_cast(s->infoLog.size() + 1); return; case GL_SHADER_SOURCE_LENGTH: *params = s->source.empty() ? 0 : static_cast(s->source.size() + 1); return; - default: ctx.setError(GL_INVALID_ENUM); + case GL_SPIR_V_BINARY: + if (!ctx.glSpirv()) break; + *params = s->spirv; + return; + case GL_COMPLETION_STATUS_ARB: // compiles finish before glCompileShader returns + if (!ctx.parallelShaderCompile()) break; + *params = GL_TRUE; + return; + default: break; + } + ctx.setError(GL_INVALID_ENUM); +} + +// GL 4.6 / GL_ARB_gl_spirv: the shader's module for its stage at +// `pEntryPoint`, with specialization constant pConstantIndex[i] set to +// pConstantValue[i], becomes its compiled form (GLSL, shader/spirv_input.h). +void SpecializeShader(Context& ctx, GLuint shader, const GLchar* pEntryPoint, GLuint numSpecializationConstants, + const GLuint* pConstantIndex, const GLuint* pConstantValue) { + if (!ctx.glSpirv()) return ctx.unimplemented("glSpecializeShader"); + Shader* s = shaderOrError(ctx, shader); + if (!s) return; + if (!s->spirv || s->specialized) return ctx.setError(GL_INVALID_OPERATION); + std::vector> constants; + for (GLuint i = 0; i < numSpecializationConstants && pConstantIndex && pConstantValue; ++i) + constants.emplace_back(pConstantIndex[i], pConstantValue[i]); + const shader::SpirvSpecialization result = + shader::specializeSpirv(s->spirvWords, s->stage, pEntryPoint ? pEntryPoint : "", constants); + if (result.status == shader::SpirvSpecialization::kBadEntryPoint || result.status == shader::SpirvSpecialization::kBadConstant) + return ctx.setError(GL_INVALID_VALUE); + s->specialized = s->compiled = true; + s->spirvNamed = result.named; + if (result.status != shader::SpirvSpecialization::kOk) { + s->compileStatus = false; + s->infoLog = result.log; + } else { + const shader::CompileResult compiled = shader::compile(s->stage, result.glsl); + s->compileStatus = compiled.ok; + s->infoLog = compiled.log; + s->spirvGlsl = result.glsl; } + if (!s->compileStatus) WMGL_INFO("SPIR-V shader %u failed to specialize:\n%s", shader, s->infoLog.c_str()); + // WINEMETALGL_DUMP_SHADERS=1 prints the GLSL a module became. + if (const char* v = std::getenv("WINEMETALGL_DUMP_SHADERS"); v && v[0] == '1') + std::fprintf(stderr, "winemetalgl: SPIR-V shader %u as GLSL:\n%s\n", shader, s->spirvGlsl.c_str()); } void GetShaderInfoLog(Context& ctx, GLuint shader, GLsizei bufSize, GLsizei* length, GLchar* infoLog) { @@ -374,11 +424,37 @@ void GetShaderSource(Context& ctx, GLuint shader, GLsizei bufSize, GLsizei* leng void ReleaseShaderCompiler(Context& ctx) {} +void MaxShaderCompilerThreadsKHR(Context& ctx, GLuint count) { + if (!ctx.parallelShaderCompile()) return ctx.unimplemented("glMaxShaderCompilerThreadsKHR"); + ctx.state.maxShaderCompilerThreads = count; +} + +// The one shader binary format is SPIR-V (GL 4.6 / GL_ARB_gl_spirv): the +// module goes to every listed shader, to be specialized for its stage. void ShaderBinary(Context& ctx, GLsizei count, const GLuint* shaders, GLenum binaryFormat, const void* binary, GLsizei length) { if (count < 0 || length < 0) return ctx.setError(GL_INVALID_VALUE); - // NUM_SHADER_BINARY_FORMATS is 0: no binary format is accepted. - ctx.setError(GL_INVALID_ENUM); + if (!ctx.glSpirv() || binaryFormat != GL_SHADER_BINARY_FORMAT_SPIR_V) return ctx.setError(GL_INVALID_ENUM); + std::vector targets; + for (GLsizei i = 0; i < count && shaders; ++i) { + Shader* s = shaderOrError(ctx, shaders[i]); + if (!s) return; + // At most one shader of each stage. + for (const Shader* other : targets) + if (other->type == s->type) return ctx.setError(GL_INVALID_OPERATION); + targets.push_back(s); + } + if (length % 4 || (length && !binary)) return ctx.setError(GL_INVALID_VALUE); + std::vector words(static_cast(length) / 4); + if (length) std::memcpy(words.data(), binary, static_cast(length)); + if (!shader::spirvHeaderValid(words)) return ctx.setError(GL_INVALID_VALUE); + for (Shader* s : targets) { + s->spirv = true; + s->specialized = s->compiled = s->compileStatus = false; + s->spirvWords = words; + s->spirvGlsl.clear(); + s->infoLog.clear(); + } } void GetShaderPrecisionFormat(Context& ctx, GLenum shadertype, GLenum precisiontype, GLint* range, GLint* precision) { @@ -540,10 +616,27 @@ void LinkProgram(Context& ctx, GLuint program) { bool ok = true; for (const RefPtr& s : p->attached) { if (!s->compileStatus) { - log += "error: attached shader " + std::to_string(s->name) + " is not compiled\n"; + log += "error: attached shader " + std::to_string(s->name) + (s->spirv ? " is not specialized\n" : " is not compiled\n"); ok = false; } - input.shaders.push_back({s->stage, s->source}); + // SPIR-V shaders link as the GLSL they were specialized into, and + // only with SPIR-V shaders. + if (s->spirv != p->attached.front()->spirv) { + log += "error: SPIR-V and GLSL shaders cannot be linked together\n"; + ok = false; + } + input.spirv |= s->spirv; + input.spirvUnnamed |= s->spirv && !s->spirvNamed; + std::string source = s->spirv ? s->spirvGlsl : s->source; + if (s->spirv && s->stage != shader::Stage::Compute) { + // Inputs match the outputs of the closest earlier stage. + int previous = -1; + for (const RefPtr& other : p->attached) + if (other->stage < s->stage && other->stage != shader::Stage::Compute) + previous = std::max(previous, static_cast(other->stage)); + if (previous >= 0) source = shader::spirvLinkInputs(source, static_cast(previous)); + } + input.shaders.push_back({s->stage, std::move(source)}); } input.attribBindings = p->attribBindings; input.fragDataBindings = p->fragDataBindings; @@ -662,6 +755,8 @@ void putString(std::string& out, const std::string& s) { for (const std::string& v : in.transformFeedbackVaryings) putString(out, v); put32(out, in.transformFeedbackMode); put32(out, in.separable ? 1 : 0); + // Since 3.0.0: SPIR-V programs (absent from older binaries). + put32(out, (in.spirv ? 1u : 0u) | (in.spirvUnnamed ? 2u : 0u)); return out; } @@ -717,6 +812,11 @@ bool deserialize(const void* data, size_t size, shader::LinkInput& in) { for (uint32_t i = 0; r.ok && i < varyings; ++i) in.transformFeedbackVaryings.push_back(r.str()); in.transformFeedbackMode = r.u32(); in.separable = r.u32() != 0; + if (r.ok && r.left >= 4) { + const uint32_t spirv = r.u32(); + in.spirv = spirv & 1u; + in.spirvUnnamed = spirv & 2u; + } return r.ok && r.left == 0; } @@ -782,15 +882,19 @@ void GetProgramiv(Context& ctx, GLuint program, GLenum pname, GLint* params) { Program* p = programOrError(ctx, program); if (!p || !params) return; const shader::LinkResult* link = p->linked && p->linkStatus ? &p->linked->link : nullptr; - auto maxLength = [](const auto& list, size_t count = SIZE_MAX) { + auto maxLength = [&](const auto& list, size_t count = SIZE_MAX) { GLint n = 0; for (size_t i = 0; i < std::min(count, list.size()); ++i) - n = std::max(n, static_cast(list[i].name.size() + 1)); + n = std::max(n, static_cast(shader::spirvDisplayName(*link, list[i].name).size() + 1)); return n; }; switch (pname) { case GL_DELETE_STATUS: *params = p->deletePending; return; case GL_LINK_STATUS: *params = p->linkStatus; return; + case GL_COMPLETION_STATUS_ARB: // links finish before glLinkProgram returns + if (!ctx.parallelShaderCompile()) break; + *params = GL_TRUE; + return; case GL_VALIDATE_STATUS: *params = p->validateStatus; return; case GL_INFO_LOG_LENGTH: *params = p->infoLog.empty() ? 0 : static_cast(p->infoLog.size() + 1); return; case GL_ATTACHED_SHADERS: *params = static_cast(p->attached.size()); return; @@ -889,7 +993,7 @@ void GetActiveAttrib(Context& ctx, GLuint program, GLuint index, GLsizei bufSize if (!p->linked || !p->linkStatus || index >= p->linked->link.attribs.size()) return ctx.setError(GL_INVALID_VALUE); const shader::AttribInfo& a = p->linked->link.attribs[index]; // Lowered legacy attributes report their built-in name (wmgl_Vertex -> gl_Vertex). - copyString(a.conventional ? "gl_" + a.name.substr(5) : a.name, bufSize, length, name); + copyString(shader::spirvDisplayName(p->linked->link, a.conventional ? "gl_" + a.name.substr(5) : a.name), bufSize, length, name); if (size) *size = a.size; if (type) *type = a.type; } @@ -923,7 +1027,7 @@ void GetActiveUniform(Context& ctx, GLuint program, GLuint index, GLsizei bufSiz if (!p) return; if (!p->linked || !p->linkStatus || index >= p->linked->link.visibleUniformCount()) return ctx.setError(GL_INVALID_VALUE); const shader::UniformInfo& u = p->linked->link.uniforms[index]; - copyString(u.name, bufSize, length, name); + copyString(shader::spirvDisplayName(p->linked->link, u.name), bufSize, length, name); if (size) *size = u.size; if (type) *type = u.type; } @@ -945,7 +1049,7 @@ void GetActiveUniformsiv(Context& ctx, GLuint program, GLsizei count, const GLui switch (pname) { case GL_UNIFORM_TYPE: params[i] = static_cast(u.type); break; case GL_UNIFORM_SIZE: params[i] = u.size; break; - case GL_UNIFORM_NAME_LENGTH: params[i] = static_cast(u.name.size() + 1); break; + case GL_UNIFORM_NAME_LENGTH: params[i] = static_cast(shader::spirvDisplayName(p->linked->link, u.name).size() + 1); break; case GL_UNIFORM_BLOCK_INDEX: params[i] = u.blockIndex; break; // Block members and atomic counters (GL 4.2) have offsets and strides. case GL_UNIFORM_OFFSET: params[i] = u.blockIndex >= 0 || u.atomicBufferIndex >= 0 ? u.offset : -1; break; @@ -1033,7 +1137,7 @@ void GetActiveUniformBlockiv(Context& ctx, GLuint program, GLuint blockIndex, GL switch (pname) { case GL_UNIFORM_BLOCK_BINDING: *params = static_cast(p->uniformBlockBindings[blockIndex]); return; case GL_UNIFORM_BLOCK_DATA_SIZE: *params = b.dataSize; return; - case GL_UNIFORM_BLOCK_NAME_LENGTH: *params = static_cast(b.name.size() + 1); return; + case GL_UNIFORM_BLOCK_NAME_LENGTH: *params = static_cast(shader::spirvDisplayName(p->linked->link, b.name).size() + 1); return; case GL_UNIFORM_BLOCK_ACTIVE_UNIFORMS: *params = static_cast(b.activeUniforms.size()); return; case GL_UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES: for (size_t i = 0; i < b.activeUniforms.size(); ++i) params[i] = b.activeUniforms[i]; @@ -1061,7 +1165,7 @@ void GetActiveUniformBlockName(Context& ctx, GLuint program, GLuint blockIndex, Program* p = programOrError(ctx, program); if (!p) return; if (!p->linked || !p->linkStatus || blockIndex >= p->linked->link.visibleBlockCount()) return ctx.setError(GL_INVALID_VALUE); - copyString(p->linked->link.blocks[blockIndex].name, bufSize, length, name); + copyString(shader::spirvDisplayName(p->linked->link, p->linked->link.blocks[blockIndex].name), bufSize, length, name); } void UniformBlockBinding(Context& ctx, GLuint program, GLuint blockIndex, GLuint binding) { @@ -1277,7 +1381,7 @@ void GetTransformFeedbackVarying(Context& ctx, GLuint program, GLuint index, GLs if (!p) return; if (!p->linked || !p->linkStatus || index >= p->linked->link.transformFeedback.size()) return ctx.setError(GL_INVALID_VALUE); const auto& v = p->linked->link.transformFeedback[index]; - copyString(v.name, bufSize, length, name); + copyString(shader::spirvDisplayName(p->linked->link, v.name), bufSize, length, name); if (size) *size = v.size; if (type) *type = v.type; } diff --git a/src/core/api/program_interface.mm b/src/core/api/program_interface.mm index c4bb3df9..3b55d88f 100644 --- a/src/core/api/program_interface.mm +++ b/src/core/api/program_interface.mm @@ -6,6 +6,7 @@ #include #include "core/context.h" +#include "core/shader/spirv_input.h" #include "core/program.h" #include "gl_api.gen.h" @@ -18,7 +19,7 @@ enum class Kind { Uniform, UniformBlock, AtomicCounterBuffer, Input, Output, Subroutine, SubroutineUniform, TransformFeedbackVarying, - BufferVariable, StorageBlock, + BufferVariable, StorageBlock, TransformFeedbackBuffer, }; bool programInterfaceQuery(Context& ctx, const char* command) { @@ -39,6 +40,10 @@ bool interfaceKind(Context& ctx, GLenum iface, Kind& kind, Stage& stage) { case GL_TRANSFORM_FEEDBACK_VARYING: kind = Kind::TransformFeedbackVarying; return true; case GL_BUFFER_VARIABLE: kind = Kind::BufferVariable; return true; case GL_SHADER_STORAGE_BLOCK: kind = Kind::StorageBlock; return true; + case GL_TRANSFORM_FEEDBACK_BUFFER: // GL 4.4, GL_ARB_enhanced_layouts + if (ctx.version() < 44 && !ctx.hasExtension("GL_ARB_enhanced_layouts")) return ctx.setError(GL_INVALID_ENUM), false; + kind = Kind::TransformFeedbackBuffer; + return true; case GL_VERTEX_SUBROUTINE: case GL_VERTEX_SUBROUTINE_UNIFORM: stage = Stage::Vertex; break; case GL_TESS_CONTROL_SUBROUTINE: case GL_TESS_CONTROL_SUBROUTINE_UNIFORM: stage = Stage::TessControl; break; case GL_TESS_EVALUATION_SUBROUTINE: case GL_TESS_EVALUATION_SUBROUTINE_UNIFORM: stage = Stage::TessEval; break; @@ -63,6 +68,18 @@ bool interfaceKind(Context& ctx, GLenum iface, Kind& kind, Stage& stage) { const LinkResult* linkOf(const Program& p) { return p.linked && p.linkStatus ? &p.linked->link : nullptr; } +// GL_TRANSFORM_FEEDBACK_BUFFER resources: the buffer bindings that captured +// varyings use, in binding order. +std::vector xfbBuffers(const LinkResult& link) { + std::vector buffers; + for (const shader::TransformFeedbackVarying& v : link.transformFeedback) + if (v.name.rfind("gl_SkipComponents", 0) != 0 && v.name != "gl_NextBuffer" && + std::find(buffers.begin(), buffers.end(), v.buffer) == buffers.end()) + buffers.push_back(v.buffer); + std::sort(buffers.begin(), buffers.end()); + return buffers; +} + size_t resourceCount(const LinkResult* link, Kind kind, Stage stage) { if (!link) return 0; switch (kind) { @@ -76,6 +93,7 @@ size_t resourceCount(const LinkResult* link, Kind kind, Stage stage) { case Kind::TransformFeedbackVarying: return link->transformFeedback.size(); case Kind::BufferVariable: return link->bufferVariables.size(); case Kind::StorageBlock: return link->storageBlocks.size(); + case Kind::TransformFeedbackBuffer: return xfbBuffers(*link).size(); } return 0; } @@ -98,6 +116,7 @@ size_t resourceCount(const LinkResult* link, Kind kind, Stage stage) { case Kind::TransformFeedbackVarying: return link.transformFeedback[index].name; case Kind::BufferVariable: return link.bufferVariables[index].name; case Kind::StorageBlock: return link.storageBlocks[index].name; + case Kind::TransformFeedbackBuffer: return {}; } return {}; } @@ -155,8 +174,8 @@ bool resourceProperty(const Program& p, const LinkResult& link, Kind kind, Stage Stage referenced; const bool referencedProp = referencedProperty(prop, referenced); if (prop == GL_NAME_LENGTH) { - if (kind == Kind::AtomicCounterBuffer) return false; - out.push_back(static_cast(resourceName(link, kind, stage, index).size() + 1)); + if (kind == Kind::AtomicCounterBuffer || kind == Kind::TransformFeedbackBuffer) return false; + out.push_back(static_cast(shader::spirvDisplayName(link, resourceName(link, kind, stage, index)).size() + 1)); return true; } switch (kind) { @@ -217,6 +236,7 @@ bool resourceProperty(const Program& p, const LinkResult& link, Kind kind, Stage case GL_TYPE: out.push_back(static_cast(v.type)); return true; case GL_ARRAY_SIZE: out.push_back(v.size); return true; case GL_LOCATION: out.push_back(v.location); return true; + case GL_LOCATION_COMPONENT: out.push_back(v.location < 0 ? 0 : v.component); return true; case GL_IS_PER_PATCH: out.push_back(v.perPatch ? 1 : 0); return true; case GL_LOCATION_INDEX: if (kind != Kind::Output) return false; @@ -242,6 +262,31 @@ bool resourceProperty(const Program& p, const LinkResult& link, Kind kind, Stage switch (prop) { case GL_TYPE: out.push_back(static_cast(v.type)); return true; case GL_ARRAY_SIZE: out.push_back(v.size); return true; + case GL_OFFSET: out.push_back(static_cast(v.offset * 4)); return true; + case GL_TRANSFORM_FEEDBACK_BUFFER_INDEX: { + const std::vector buffers = xfbBuffers(link); + const auto it = std::find(buffers.begin(), buffers.end(), v.buffer); + out.push_back(it == buffers.end() ? -1 : static_cast(it - buffers.begin())); + return true; + } + default: return false; + } + } + case Kind::TransformFeedbackBuffer: { + const int buffer = xfbBuffers(link)[index]; + std::vector active; + for (size_t i = 0; i < link.transformFeedback.size(); ++i) { + const shader::TransformFeedbackVarying& v = link.transformFeedback[i]; + if (v.buffer == buffer && v.name.rfind("gl_SkipComponents", 0) != 0 && v.name != "gl_NextBuffer") + active.push_back(static_cast(i)); + } + switch (prop) { + case GL_BUFFER_BINDING: out.push_back(buffer); return true; + case GL_NUM_ACTIVE_VARIABLES: out.push_back(static_cast(active.size())); return true; + case GL_ACTIVE_VARIABLES: out.assign(active.begin(), active.end()); return true; + case GL_TRANSFORM_FEEDBACK_BUFFER_STRIDE: + out.push_back(static_cast(buffer >= 0 && buffer < 4 ? link.xfbStride[buffer] * 4 : 0)); + return true; default: return false; } } @@ -277,6 +322,7 @@ bool knownProperty(GLenum prop) { case GL_REFERENCED_BY_GEOMETRY_SHADER: case GL_REFERENCED_BY_FRAGMENT_SHADER: case GL_REFERENCED_BY_COMPUTE_SHADER: case GL_TOP_LEVEL_ARRAY_SIZE: case GL_TOP_LEVEL_ARRAY_STRIDE: case GL_LOCATION: case GL_LOCATION_INDEX: case GL_IS_PER_PATCH: case GL_NUM_COMPATIBLE_SUBROUTINES: case GL_COMPATIBLE_SUBROUTINES: + case GL_LOCATION_COMPONENT: case GL_TRANSFORM_FEEDBACK_BUFFER_INDEX: case GL_TRANSFORM_FEEDBACK_BUFFER_STRIDE: return true; default: return false; } @@ -296,17 +342,19 @@ void GetProgramInterfaceiv(Context& ctx, GLuint program, GLenum programInterface switch (pname) { case GL_ACTIVE_RESOURCES: *params = static_cast(count); return; case GL_MAX_NAME_LENGTH: { - if (kind == Kind::AtomicCounterBuffer) return ctx.setError(GL_INVALID_OPERATION); + if (kind == Kind::AtomicCounterBuffer || kind == Kind::TransformFeedbackBuffer) return ctx.setError(GL_INVALID_OPERATION); GLint n = 0; if (kind == Kind::Subroutine && link) for (const auto& f : link->subroutines[static_cast(stage)].functions) n = std::max(n, static_cast(f.name.size() + 1)); else if (kind != Kind::Subroutine) - for (size_t i = 0; i < count; ++i) n = std::max(n, static_cast(resourceName(*link, kind, stage, i).size() + 1)); + for (size_t i = 0; i < count; ++i) + n = std::max(n, static_cast(shader::spirvDisplayName(*link, resourceName(*link, kind, stage, i)).size() + 1)); *params = n; return; } case GL_MAX_NUM_ACTIVE_VARIABLES: { - if (kind != Kind::UniformBlock && kind != Kind::StorageBlock && kind != Kind::AtomicCounterBuffer) + if (kind != Kind::UniformBlock && kind != Kind::StorageBlock && kind != Kind::AtomicCounterBuffer && + kind != Kind::TransformFeedbackBuffer) return ctx.setError(GL_INVALID_OPERATION); GLint n = 0; std::vector values; @@ -334,7 +382,7 @@ GLuint GetProgramResourceIndex(Context& ctx, GLuint program, GLenum programInter Kind kind; Stage stage; if (!interfaceKind(ctx, programInterface, kind, stage)) return GL_INVALID_INDEX; - if (kind == Kind::AtomicCounterBuffer) { + if (kind == Kind::AtomicCounterBuffer || kind == Kind::TransformFeedbackBuffer) { ctx.setError(GL_INVALID_ENUM); return GL_INVALID_INDEX; } @@ -366,13 +414,13 @@ void GetProgramResourceName(Context& ctx, GLuint program, GLenum programInterfac Kind kind; Stage stage; if (!interfaceKind(ctx, programInterface, kind, stage)) return; - if (kind == Kind::AtomicCounterBuffer) return ctx.setError(GL_INVALID_ENUM); + if (kind == Kind::AtomicCounterBuffer || kind == Kind::TransformFeedbackBuffer) return ctx.setError(GL_INVALID_ENUM); Program* p = programOrError(ctx, program); if (!p) return; if (bufSize < 0) return ctx.setError(GL_INVALID_VALUE); const LinkResult* link = linkOf(*p); if (!validIndex(link, kind, stage, index)) return ctx.setError(GL_INVALID_VALUE); - const std::string s = resourceName(*link, kind, stage, index); + const std::string s = shader::spirvDisplayName(*link, resourceName(*link, kind, stage, index)); if (length) *length = 0; if (bufSize <= 0 || !name) return; const GLsizei n = std::min(bufSize - 1, static_cast(s.size())); diff --git a/src/core/api/query.mm b/src/core/api/query.mm index a1a9df88..0399418e 100644 --- a/src/core/api/query.mm +++ b/src/core/api/query.mm @@ -10,15 +10,23 @@ #include #include #include +#include #include "core/context.h" +#include "core/draw.h" +#include "core/metal/device.h" #include "core/query.h" #include "core/util/log.h" #include "gl_api.gen.h" namespace wmgl { +static_assert(std::tuple_size::value == kQuerySlotCount); + int queryTargetIndex(Context& ctx, GLenum target) { + const bool overflow = ctx.version() >= 46 || ctx.hasExtension("GL_ARB_transform_feedback_overflow_query"); + const int statistics = ctx.version() >= 46 || ctx.hasExtension("GL_ARB_pipeline_statistics_query") ? kQueryStatisticsFirst : -1; + auto statistic = [&](PipelineStatistic s) { return statistics < 0 ? -1 : statistics + s; }; switch (target) { case GL_SAMPLES_PASSED: return kQuerySamplesPassed; case GL_ANY_SAMPLES_PASSED: return kQueryAnySamplesPassed; @@ -26,6 +34,19 @@ int queryTargetIndex(Context& ctx, GLenum target) { case GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN: return kQueryXfbPrimitivesWritten; case GL_TIME_ELAPSED: return kQueryTimeElapsed; case GL_ANY_SAMPLES_PASSED_CONSERVATIVE: return ctx.version() >= 43 ? kQueryAnySamplesPassedConservative : -1; + case GL_TRANSFORM_FEEDBACK_OVERFLOW: return overflow ? kQueryXfbOverflow : -1; + case GL_TRANSFORM_FEEDBACK_STREAM_OVERFLOW: return overflow ? kQueryXfbStreamOverflow : -1; + case GL_VERTICES_SUBMITTED: return statistic(kStatVerticesSubmitted); + case GL_PRIMITIVES_SUBMITTED: return statistic(kStatPrimitivesSubmitted); + case GL_VERTEX_SHADER_INVOCATIONS: return statistic(kStatVertexShaderInvocations); + case GL_TESS_CONTROL_SHADER_PATCHES: return statistic(kStatTessControlShaderPatches); + case GL_TESS_EVALUATION_SHADER_INVOCATIONS: return statistic(kStatTessEvaluationShaderInvocations); + case GL_GEOMETRY_SHADER_INVOCATIONS: return statistic(kStatGeometryShaderInvocations); + case GL_GEOMETRY_SHADER_PRIMITIVES_EMITTED: return statistic(kStatGeometryShaderPrimitivesEmitted); + case GL_FRAGMENT_SHADER_INVOCATIONS: return statistic(kStatFragmentShaderInvocations); + case GL_COMPUTE_SHADER_INVOCATIONS: return statistic(kStatComputeShaderInvocations); + case GL_CLIPPING_INPUT_PRIMITIVES: return statistic(kStatClippingInputPrimitives); + case GL_CLIPPING_OUTPUT_PRIMITIVES: return statistic(kStatClippingOutputPrimitives); default: return -1; } } @@ -36,6 +57,7 @@ int queryTargetIndex(Context& ctx, GLenum target, GLuint stream) { if (stream >= static_cast(kMaxVertexStreams)) return -2; if (index == kQueryPrimitivesGenerated) return kQueryStreamFirst + static_cast(stream) - 1; if (index == kQueryXfbPrimitivesWritten) return kQueryStreamFirst + 3 + static_cast(stream) - 1; + if (index == kQueryXfbStreamOverflow) return kQueryStreamFirst + 6 + static_cast(stream) - 1; return -2; } @@ -64,6 +86,17 @@ void finalize(Query& q) { q.visibilitySlots.clear(); break; } + case GL_FRAGMENT_SHADER_INVOCATIONS: { + uint64_t invocations = 0; + for (const auto& [buffer, offset] : q.visibilitySlots) { + uint32_t value; + std::memcpy(&value, static_cast(buffer.contents) + offset, sizeof(value)); + invocations += value; + } + q.result = invocations; + q.visibilitySlots.clear(); + break; + } case GL_TIME_ELAPSED: if (q.timer && q.timer->startSet && q.timer->endSet) q.result = static_cast(std::max(0.0, (q.timer->end - q.timer->start) * 1e9)); @@ -127,6 +160,62 @@ void queriesCountPrimitives(Context& ctx, GLenum mode, GLsizei count, GLsizei in if (q) q->result += primitiveCount(mode, count) * static_cast(std::max(0, instances)); } +void queriesNoteXfbOverflow(Context& ctx, GLuint stream) { + for (int index : {static_cast(kQueryXfbOverflow), queryTargetIndex(ctx, GL_TRANSFORM_FEEDBACK_STREAM_OVERFLOW, stream)}) + if (Query* q = index >= 0 ? ctx.state.activeQueries[index].get() : nullptr) q->result = 1; +} + +bool queriesCountingStatistics(Context& ctx) { + for (int s = 0; s < kStatCount; ++s) + if (ctx.state.activeQueries[kQueryStatisticsFirst + s]) return true; + return false; +} + +bool queriesCounting(Context& ctx, PipelineStatistic statistic) { + return ctx.state.activeQueries[kQueryStatisticsFirst + statistic] != nullptr; +} + +void queriesCountStatistic(Context& ctx, PipelineStatistic statistic, uint64_t n) { + if (Query* q = ctx.state.activeQueries[kQueryStatisticsFirst + statistic].get()) q->result += n; +} + +void queriesCountSubmitted(Context& ctx, GLenum mode, GLsizei vertices, GLsizei instances, bool clipped) { + if (!queriesCountingStatistics(ctx)) return; + const uint64_t n = static_cast(std::max(0, vertices)), copies = static_cast(std::max(0, instances)); + // A line loop of two vertices is one primitive here, as the CTS expects. + const uint64_t primitives = mode == GL_LINE_LOOP ? (n > 2 ? n : n == 2 ? 1 : 0) + : mode == GL_PATCHES ? n / static_cast(std::max(1, ctx.state.patchVertices)) + : primitiveCount(mode, vertices); + queriesCountStatistic(ctx, kStatVerticesSubmitted, n * copies); + queriesCountStatistic(ctx, kStatPrimitivesSubmitted, primitives * copies); + queriesCountStatistic(ctx, kStatVertexShaderInvocations, n * copies); + if (!clipped) return; + queriesCountStatistic(ctx, kStatClippingInputPrimitives, primitives * copies); + queriesCountStatistic(ctx, kStatClippingOutputPrimitives, primitives * copies); +} + +bool queriesFragmentCounter(Context& ctx, __strong id& buffer, NSUInteger& offset) { + Query* q = ctx.state.activeQueries[kQueryStatisticsFirst + kStatFragmentShaderInvocations].get(); + if (!q) return false; + const uint64_t serial = ctx.stream().serial(); + if (q->visibilitySlots.empty() || q->counterSerial != serial) { + // A new zeroed counter per command buffer, from pages of 1024. + constexpr uint32_t kPage = 4096; + id page = q->visibilitySlots.empty() ? nil : q->visibilitySlots.back().first; + uint32_t next = q->visibilitySlots.empty() ? kPage : q->visibilitySlots.back().second + 4; + if (next >= kPage) { + page = [Device::get().mtl() newBufferWithLength:kPage options:MTLResourceStorageModeShared]; + next = 0; + } + q->visibilitySlots.emplace_back(page, next); + q->counterSerial = serial; + q->pendingSerial = std::max(q->pendingSerial, serial); + } + buffer = q->visibilitySlots.back().first; + offset = q->visibilitySlots.back().second; + return true; +} + void queriesPrepareVisibility(Context& ctx) { if (anyOcclusionActive(ctx) && !ctx.stream().visibilitySlotAvailable()) ctx.stream().flush(); } @@ -165,9 +254,12 @@ void queriesSuspendVisibility(Context& ctx, id encoder) bool conditionalRenderAllows(Context& ctx) { auto& cr = ctx.state.conditionalRender; if (!cr.active || !cr.query) return true; - const bool wait = cr.mode == GL_QUERY_WAIT || cr.mode == GL_QUERY_BY_REGION_WAIT; + // GL 4.5 / ARB_conditional_render_inverted: the *_INVERTED modes render when no samples passed. + const bool inverted = cr.mode >= GL_QUERY_WAIT_INVERTED && cr.mode <= GL_QUERY_BY_REGION_NO_WAIT_INVERTED; + const bool wait = cr.mode == GL_QUERY_WAIT || cr.mode == GL_QUERY_BY_REGION_WAIT || cr.mode == GL_QUERY_WAIT_INVERTED || + cr.mode == GL_QUERY_BY_REGION_WAIT_INVERTED; if (!queryResultReady(ctx, *cr.query, wait)) return true; // NO_WAIT: render while unknown - return cr.query->result != 0; + return (cr.query->result != 0) != inverted; } } // namespace wmgl @@ -277,7 +369,7 @@ void beginQuery(Context& ctx, GLenum target, int index, GLuint name) { if (!q) { q = makeRef(name); ctx.queries.set(name, q); - } else if (q->everActive && q->target != target) { + } else if ((q->everActive || q->target) && q->target != target) { // a created query has its target (GL 4.5) return ctx.setError(GL_INVALID_OPERATION); } for (const RefPtr& active : ctx.state.activeQueries) @@ -290,6 +382,7 @@ void beginQuery(Context& ctx, GLenum target, int index, GLuint name) { q->resultFinal = false; q->pendingSerial = 0; q->visibilitySlots.clear(); + q->counterSerial = 0; q->timer.reset(); ctx.state.activeQueries[index] = q; if (isOcclusion(index)) { @@ -317,6 +410,8 @@ void endQuery(Context& ctx, int index) { if (q->visibilitySlots.empty()) q->resultFinal = true; } else if (index == kQueryTimeElapsed) { markTimerEnd(ctx, *q); + } else if (index == kQueryStatisticsFirst + kStatFragmentShaderInvocations) { + if (q->visibilitySlots.empty()) q->resultFinal = true; // otherwise summed once its command buffers complete } else { q->resultFinal = true; // counted on the CPU } @@ -347,7 +442,13 @@ void QueryCounter(Context& ctx, GLuint name, GLenum target) { void getQuery(Context& ctx, int index, GLenum pname, GLint* params) { switch (pname) { case GL_CURRENT_QUERY: *params = ctx.state.activeQueries[index] ? static_cast(ctx.state.activeQueries[index]->name) : 0; return; - case GL_QUERY_COUNTER_BITS: *params = index == kQueryAnySamplesPassed || index == kQueryAnySamplesPassedConservative ? 1 : 64; return; + case GL_QUERY_COUNTER_BITS: { + const bool boolean = index == kQueryAnySamplesPassed || index == kQueryAnySamplesPassedConservative || + index == kQueryXfbOverflow || index == kQueryXfbStreamOverflow || + index >= kQueryStreamFirst + 6; + *params = boolean ? 1 : 64; + return; + } default: return ctx.setError(GL_INVALID_ENUM); } } @@ -378,7 +479,19 @@ void GetQueryIndexediv(Context& ctx, GLenum target, GLuint stream, GLenum pname, template void getQueryObject(Context& ctx, GLuint name, GLenum pname, T* params) { Query* q = ctx.queries.get(name); - if (!q || !q->everActive || q->active) return ctx.setError(GL_INVALID_OPERATION); + // A query created by glCreateQueries (GL 4.5) has its target before it is first used. + if (!q || (!q->everActive && !q->target) || q->active) return ctx.setError(GL_INVALID_OPERATION); + if (pname != GL_QUERY_RESULT && pname != GL_QUERY_RESULT_AVAILABLE && pname != GL_QUERY_RESULT_NO_WAIT && + !(pname == GL_QUERY_TARGET && ctx.version() >= 45)) + return ctx.setError(GL_INVALID_ENUM); + // GL 4.4 (GL_ARB_query_buffer_object): with a buffer bound to + // GL_QUERY_BUFFER, `params` is an offset into it. + Buffer* queryBuffer = ctx.version() >= 44 ? ctx.state.queryBuffer.get() : nullptr; + if (queryBuffer) { + const intptr_t offset = reinterpret_cast(params); + if (offset < 0 || offset % static_cast(sizeof(T)) || offset + static_cast(sizeof(T)) > queryBuffer->size) + return ctx.setError(GL_INVALID_OPERATION); + } uint64_t value = 0; switch (pname) { case GL_QUERY_RESULT: @@ -398,11 +511,25 @@ void getQueryObject(Context& ctx, GLuint name, GLenum pname, T* params) { if (!queryResultReady(ctx, *q, false)) return; value = q->result; break; + case GL_QUERY_TARGET: value = q->target; break; default: return ctx.setError(GL_INVALID_ENUM); } // Results that do not fit the requested type saturate. if (value > static_cast(std::numeric_limits::max())) value = static_cast(std::numeric_limits::max()); - *params = static_cast(value); + const T result = static_cast(value); + if (!queryBuffer) { + *params = result; + return; + } + // The result goes into the query buffer through an ordered GPU copy, so + // draws before and after see the buffer in command order. + if (!queryBuffer->mtl) return; + TransientAllocation staging = ctx.draw().transient().allocate(sizeof(T), ctx.stream().serial(), 16); + std::memcpy(staging.data, &result, sizeof(T)); + [ctx.stream().blit() copyFromBuffer:staging.buffer sourceOffset:staging.offset toBuffer:queryBuffer->mtl + destinationOffset:static_cast(reinterpret_cast(params)) size:sizeof(T)]; + queryBuffer->lastUseSerial = queryBuffer->lastWriteSerial = ctx.stream().serial(); + ++queryBuffer->version; } } // namespace @@ -415,15 +542,19 @@ void getQueryObject(Context& ctx, GLuint name, GLenum pname, T* params) { // ------------------------------------------------------------ conditional rendering void BeginConditionalRender(Context& ctx, GLuint name, GLenum mode) { + const bool inverted = (ctx.version() >= 45 || ctx.hasExtension("GL_ARB_conditional_render_inverted")) && + mode >= GL_QUERY_WAIT_INVERTED && mode <= GL_QUERY_BY_REGION_NO_WAIT_INVERTED; if (mode != GL_QUERY_WAIT && mode != GL_QUERY_NO_WAIT && mode != GL_QUERY_BY_REGION_WAIT && - mode != GL_QUERY_BY_REGION_NO_WAIT) + mode != GL_QUERY_BY_REGION_NO_WAIT && !inverted) return ctx.setError(GL_INVALID_ENUM); auto& cr = ctx.state.conditionalRender; if (cr.active) return ctx.setError(GL_INVALID_OPERATION); Query* q = ctx.queries.get(name); if (!q || !q->everActive || q->active) return ctx.setError(GL_INVALID_OPERATION); + // GL 4.6 / ARB_transform_feedback_overflow_query: overflow queries render when they overflowed. if (q->target != GL_SAMPLES_PASSED && q->target != GL_ANY_SAMPLES_PASSED && - q->target != GL_ANY_SAMPLES_PASSED_CONSERVATIVE) + q->target != GL_ANY_SAMPLES_PASSED_CONSERVATIVE && q->target != GL_TRANSFORM_FEEDBACK_OVERFLOW && + q->target != GL_TRANSFORM_FEEDBACK_STREAM_OVERFLOW) return ctx.setError(GL_INVALID_OPERATION); cr.query = q; cr.mode = mode; diff --git a/src/core/api/state.mm b/src/core/api/state.mm index d3bcf391..2e2e24d7 100644 --- a/src/core/api/state.mm +++ b/src/core/api/state.mm @@ -9,6 +9,7 @@ #include "core/context.h" #include "core/draw.h" #include "core/metal/device.h" +#include "core/shader/spirv_input.h" #include "core/util/version.h" #include "gl_api.gen.h" @@ -715,15 +716,26 @@ void MinSampleShading(Context& ctx, GLfloat value) { } return reinterpret_cast(ctx.extensions()[index].c_str()); } + if (name == GL_SPIR_V_EXTENSIONS && ctx.glSpirv()) { + const auto& extensions = shader::spirvExtensions(); + if (index >= extensions.size()) { + ctx.setError(GL_INVALID_VALUE); + return nullptr; + } + return reinterpret_cast(extensions[index]); + } if (name == GL_SHADING_LANGUAGE_VERSION && ctx.version() >= 43) { // "4N0 core" for every 4.N up to the context's, then the 3.x and 1.x // versions; "" is GLSL 1.10 without a #version directive; "300 es" - // is GLSL ES 3.00 (GL_ARB_ES3_compatibility). + // is GLSL ES 3.00 (GL_ARB_ES3_compatibility) and "310 es" GLSL ES + // 3.10 (GL 4.5, GL_ARB_ES3_1_compatibility). static const char* const versions[] = {"460 core", "450 core", "440 core", "430 core", "420 core", "410 core", "400 core", "330 core", "150 core", "140", - "130", "120", "110", "", "300 es"}; + "130", "120", "110", "", "300 es", + "310 es"}; const GLuint skip = static_cast(46 - ctx.version()); - if (index >= std::size(versions) - skip) { + const GLuint count = static_cast(std::size(versions)) - skip - (ctx.version() >= 45 ? 0 : 1); + if (index >= count) { ctx.setError(GL_INVALID_VALUE); return nullptr; } diff --git a/src/core/api/texture.mm b/src/core/api/texture.mm index ca96b34a..52bdbf4f 100644 --- a/src/core/api/texture.mm +++ b/src/core/api/texture.mm @@ -5,7 +5,10 @@ #include #include #include +#include +#include #include +#include #include #include "core/context.h" @@ -140,6 +143,9 @@ int maxLevelFor(GLenum target) { // ------------------------------------------------------------ transfers +// STENCIL_INDEX textures (GL 4.4, GL_ARB_texture_stencil8). +bool stencilTextures(Context& ctx) { return ctx.version() >= 44 || ctx.hasExtension("GL_ARB_texture_stencil8"); } + bool isTypeEnum(GLenum type) { switch (type) { case GL_UNSIGNED_BYTE: case GL_BYTE: case GL_UNSIGNED_SHORT: case GL_SHORT: case GL_UNSIGNED_INT: case GL_INT: @@ -404,8 +410,9 @@ bool sizeAllowed(Context& ctx, const ImageTarget& t, GLint level, GLsizei w, GLs // TexImage/TexSubImage source formats for depth images: DEPTH_COMPONENT or // DEPTH_STENCIL data (the depth part is used for depth-only images); -// STENCIL_INDEX uploads need stencil texturing (GL 4.4). +// stencil-only images (GL 4.4) take STENCIL_INDEX data. bool textureTransferCompatible(GLenum format, GLenum type, const FormatInfo& info) { + if (info.stencilBits && !info.depthBits) return format == GL_STENCIL_INDEX; // GL 4.4 stencil textures if (info.isDepthOrStencil()) return format == GL_DEPTH_COMPONENT || format == GL_DEPTH_STENCIL; return transferCompatible(format, type, info); } @@ -427,7 +434,7 @@ void texImage(Context& ctx, int dims, GLenum target, GLint level, GLint internal if (!imageTarget(ctx, target, dims, true, t)) return; if (!checkFormatType(ctx, format, type)) return; const GLenum requested = static_cast(internalformat); - if (!isValidTextureInternalFormat(requested, ctx.isCompat())) return ctx.setError(GL_INVALID_VALUE); + if (!isValidTextureInternalFormat(requested, ctx.isCompat(), stencilTextures(ctx))) return ctx.setError(GL_INVALID_VALUE); bool tooLarge; if (!sizeAllowed(ctx, t, level, width, height, depth, border, tooLarge)) return; const FormatInfo* info = formatInfo(resolveInternalFormat(requested, format, type)); @@ -730,7 +737,7 @@ void copyTexImage(Context& ctx, int dims, GLenum target, GLint level, GLenum int GLsizei width, GLsizei height, GLint border) { ImageTarget t; if (!imageTarget(ctx, target, dims, false, t)) return; - if (!isValidTextureInternalFormat(internalformat, ctx.isCompat())) return ctx.setError(GL_INVALID_VALUE); + if (!isValidTextureInternalFormat(internalformat, ctx.isCompat(), stencilTextures(ctx))) return ctx.setError(GL_INVALID_VALUE); bool tooLarge; if (!sizeAllowed(ctx, t, level, width, dims == 1 ? 1 : height, 1, border, tooLarge)) return; const FormatInfo* probe = formatInfo(resolveInternalFormat(internalformat, 0, 0)); @@ -816,10 +823,12 @@ ParamValues paramFloats(const GLfloat* v, int count) { bool isVectorParam(GLenum pname) { return pname == GL_TEXTURE_BORDER_COLOR || pname == GL_TEXTURE_SWIZZLE_RGBA; } -// GL_CLAMP is compatibility-profile only. -bool validWrap(GLenum v, bool compat) { +// GL_CLAMP is compatibility-profile only; GL_MIRROR_CLAMP_TO_EDGE is GL 4.4 +// (GL_ARB_texture_mirror_clamp_to_edge). +bool validWrap(GLenum v, Context& ctx) { return v == GL_CLAMP_TO_EDGE || v == GL_REPEAT || v == GL_MIRRORED_REPEAT || v == GL_CLAMP_TO_BORDER || - (compat && v == GL_CLAMP); + (ctx.isCompat() && v == GL_CLAMP) || + (v == GL_MIRROR_CLAMP_TO_EDGE && (ctx.version() >= 44 || ctx.hasExtension("GL_ARB_texture_mirror_clamp_to_edge"))); } bool validCompareFunc(GLenum v) { return v >= GL_NEVER && v <= GL_ALWAYS; } @@ -833,7 +842,7 @@ bool enumValue(const ParamValues& v, GLenum& out) { // Applies a sampler-state parameter. Returns GL_NO_ERROR, an error, or // GL_INVALID_ENUM for names that are not sampler state. -GLenum setSamplerParam(SamplerParams& p, GLenum pname, const ParamValues& v, GLenum target, bool compat) { +GLenum setSamplerParam(SamplerParams& p, GLenum pname, const ParamValues& v, GLenum target, Context& ctx) { const bool rect = target == GL_TEXTURE_RECTANGLE; GLenum e = 0; switch (pname) { @@ -850,7 +859,7 @@ GLenum setSamplerParam(SamplerParams& p, GLenum pname, const ParamValues& v, GLe p.magFilter = e; return GL_NO_ERROR; case GL_TEXTURE_WRAP_S: case GL_TEXTURE_WRAP_T: case GL_TEXTURE_WRAP_R: - if (!enumValue(v, e) || !validWrap(e, compat)) return GL_INVALID_ENUM; + if (!enumValue(v, e) || !validWrap(e, ctx)) return GL_INVALID_ENUM; if (rect && e != GL_CLAMP_TO_EDGE && e != GL_CLAMP_TO_BORDER && e != GL_CLAMP) return GL_INVALID_ENUM; (pname == GL_TEXTURE_WRAP_S ? p.wrapS : pname == GL_TEXTURE_WRAP_T ? p.wrapT : p.wrapR) = e; return GL_NO_ERROR; @@ -911,7 +920,7 @@ void texParameter(Context& ctx, GLenum target, GLenum pname, const ParamValues& if (pname == GL_TEXTURE_MAX_ANISOTROPY && !ctx.hasExtension("GL_EXT_texture_filter_anisotropic") && ctx.version() < 46) return ctx.setError(GL_INVALID_ENUM); - const GLenum error = setSamplerParam(tex->sampler, pname, v, target, ctx.isCompat()); + const GLenum error = setSamplerParam(tex->sampler, pname, v, target, ctx); if (error) return ctx.setError(error); ctx.markDirty(kDirtyTextures); return; @@ -1630,6 +1639,196 @@ void TexBufferRange(Context& ctx, GLenum target, GLenum internalformat, GLuint b texBuffer(ctx, target, internalformat, buffer, offset, size, true); } +// ------------------------------------------------------------ texture clears (GL 4.4) + +namespace { + +// glClearTexImage and glClearTexSubImage (GL 4.4 section 8.21): one texel of +// `data` (zeros when it is null), converted as by TexSubImage without the +// pixel store state, repeated over a region of one level. Cube maps clear +// their faces as layers. +// glClearTex(Sub)Image of a multisample image, which Metal cannot upload +// to: a render pass per layer sets every sample of the region (a load +// action for whole layers). Colour texels are written bit for bit through +// an unsigned integer view of the storage. +void clearMultisample(Context& ctx, Texture& tex, const FormatInfo& info, const uint8_t* texel, int x, int y, int z, int w, + int h, int d) { + id mtl = tex.mtl; + const TextureImage& image = tex.image(0, 0); + const bool color = !info.isDepthOrStencil(); + const size_t bytes = storageBytes(info.storage); + uint32_t words[4] = {}; + float depth = 0.0f; + uint32_t stencil = 0; + id view = mtl; + if (color) { + const MTLPixelFormat uintFormat = bytes == 1 ? MTLPixelFormatR8Uint : bytes == 2 ? MTLPixelFormatR16Uint + : bytes == 4 ? MTLPixelFormatR32Uint : bytes == 8 ? MTLPixelFormatRG32Uint + : MTLPixelFormatRGBA32Uint; + if (bytes == 1) words[0] = texel[0]; + else if (bytes == 2) words[0] = static_cast(texel[0]) | static_cast(texel[1]) << 8; + else std::memcpy(words, texel, std::min(bytes, sizeof(words))); + view = [mtl newTextureViewWithPixelFormat:uintFormat textureType:mtl.textureType levels:NSMakeRange(0, 1) + slices:NSMakeRange(0, mtl.arrayLength)]; + if (!view) return; + } else { + if (info.depthBits) std::memcpy(&depth, texel, 4); + if (info.stencilBits) stencil = texel[info.depthBits ? 4 : 0]; + } + static std::mutex mutex; + static std::unordered_map> pipelines; + static id depthStencilStates[4]; + const bool whole = x == 0 && y == 0 && w == image.width && h == image.height; + for (int layer = z; layer < z + d; ++layer) { + RenderTargetDesc target; + target.width = static_cast(image.width); + target.height = static_cast(image.height); + target.samples = static_cast(mtl.sampleCount); + const RenderTargetDesc::Target t{view, 0, static_cast(layer)}; + if (color) target.color[0] = t; + if (info.depthBits) target.depth = t; + if (info.stencilBits) target.stencil = t; + if (whole) { + PendingClear clear; + clear.colorMask = color ? 1 : 0; + clear.color[0] = MTLClearColorMake(words[0], words[1], words[2], words[3]); + clear.depth = info.depthBits != 0; + clear.depthValue = depth; + clear.stencil = info.stencilBits != 0; + clear.stencilValue = stencil; + if (ctx.stream().clearWithLoadAction(target, clear)) continue; + } + id pipeline; + id depthStencil; + { + std::lock_guard lock(mutex); + const MTLPixelFormat depthFormat = info.depthBits ? mtl.pixelFormat : MTLPixelFormatInvalid; + const MTLPixelFormat stencilFormat = info.stencilBits ? mtl.pixelFormat : MTLPixelFormatInvalid; + const std::string key = std::to_string(color ? view.pixelFormat : 0) + "/" + std::to_string(depthFormat) + "/" + + std::to_string(stencilFormat) + "/" + std::to_string(target.samples); + pipeline = pipelines[key]; + if (!pipeline) { + id library = Device::get().utilityLibrary(); + MTLRenderPipelineDescriptor* desc = [MTLRenderPipelineDescriptor new]; + desc.vertexFunction = [library newFunctionWithName:@"wmgl_clear_vs"]; + desc.fragmentFunction = [library newFunctionWithName:color ? @"wmgl_clear_uint_fs" : @"wmgl_clear_depth_only_fs"]; + if (color) desc.colorAttachments[0].pixelFormat = view.pixelFormat; + desc.depthAttachmentPixelFormat = depthFormat; + desc.stencilAttachmentPixelFormat = stencilFormat; + desc.rasterSampleCount = target.samples; + NSError* error = nil; + pipeline = [Device::get().mtl() newRenderPipelineStateWithDescriptor:desc error:&error]; + if (!pipeline) WMGL_ERR("multisample clear pipeline: %s", error.localizedDescription.UTF8String); + pipelines[key] = pipeline; + } + const int which = (info.depthBits ? 1 : 0) | (info.stencilBits ? 2 : 0); + if (!depthStencilStates[which]) { + MTLDepthStencilDescriptor* desc = [MTLDepthStencilDescriptor new]; + desc.depthCompareFunction = MTLCompareFunctionAlways; + desc.depthWriteEnabled = info.depthBits != 0; + if (info.stencilBits) { + MTLStencilDescriptor* face = [MTLStencilDescriptor new]; + face.stencilCompareFunction = MTLCompareFunctionAlways; + face.depthStencilPassOperation = MTLStencilOperationReplace; + face.writeMask = 0xff; + desc.frontFaceStencil = desc.backFaceStencil = face; + } + depthStencilStates[which] = [Device::get().mtl() newDepthStencilStateWithDescriptor:desc]; + } + depthStencil = depthStencilStates[which]; + } + if (!pipeline) return; + id encoder = ctx.stream().render(target); + [encoder setRenderPipelineState:pipeline]; + [encoder setDepthStencilState:depthStencil]; + [encoder setStencilReferenceValue:stencil]; + [encoder setViewport:(MTLViewport){0, 0, static_cast(image.width), static_cast(image.height), 0, 1}]; + [encoder setScissorRect:(MTLScissorRect){static_cast(x), static_cast(y), static_cast(w), + static_cast(h)}]; + const float position[4] = {depth, 0, 0, 0}; + [encoder setVertexBytes:position length:sizeof(position) atIndex:0]; + uint32_t colors[8][4] = {}; + std::memcpy(colors[0], words, sizeof(words)); + [encoder setFragmentBytes:colors length:sizeof(colors) atIndex:0]; + [encoder drawPrimitives:MTLPrimitiveTypeTriangle vertexStart:0 vertexCount:3]; + ctx.stream().endEncoder(); // the pass's state is not the GL draws' + } +} + +void clearTexture(Context& ctx, const char* command, GLuint texture, GLint level, GLint x, GLint y, GLint z, GLsizei w, + GLsizei h, GLsizei d, bool whole, GLenum format, GLenum type, const void* data) { + if (ctx.version() < 44 && !ctx.hasExtension("GL_ARB_clear_texture")) return ctx.unimplemented(command); + Texture* tex = texture ? ctx.texture(texture) : nullptr; + if (!tex || tex->target == GL_TEXTURE_BUFFER) return ctx.setError(GL_INVALID_OPERATION); + if (!checkFormatType(ctx, format, type)) return; + if (level < 0 || level >= Texture::kMaxLevels) return ctx.setError(GL_INVALID_VALUE); + const TextureImage& image = tex->image(0, level); + if (!image.defined()) return ctx.setError(GL_INVALID_OPERATION); + const FormatInfo& info = *image.format; + if (info.compressed) return ctx.setError(GL_INVALID_OPERATION); + // The format names the image's kind: depth, depth/stencil, stencil or colour + // (and integer colour only with integer formats). + const bool depthOnly = info.depthBits && !info.stencilBits, stencilOnly = info.stencilBits && !info.depthBits; + const bool depthStencil = info.depthBits && info.stencilBits; + if (depthOnly != (format == GL_DEPTH_COMPONENT) || depthStencil != (format == GL_DEPTH_STENCIL) || + stencilOnly != (format == GL_STENCIL_INDEX)) + return ctx.setError(GL_INVALID_OPERATION); + if (!info.isDepthOrStencil() && !transferCompatible(format, type, info)) return ctx.setError(GL_INVALID_OPERATION); + const bool cube = tex->target == GL_TEXTURE_CUBE_MAP; + const int layers = cube ? 6 : image.depth; + if (whole) { + x = y = z = 0; + w = image.width; + h = image.height; + d = layers; + } else { + if (w < 0 || h < 0 || d < 0) return ctx.setError(GL_INVALID_VALUE); + if (x < 0 || y < 0 || z < 0 || x + w > image.width || y + h > image.height || z + d > layers) + return ctx.setError(GL_INVALID_OPERATION); + } + if (!w || !h || !d) return; + if (!ensureTextureStorage(ctx, *tex) || !imageHasStorage(ctx, *tex, 0, level)) return; + const size_t bytes = storageBytes(info.storage); + std::vector texel(std::max(bytes, 8), 0); + if (data) { + PixelStoreParams tight; + tight.alignment = 1; + unpackPixels(data, format, type, 1, 1, 1, tight, info, texel.data()); + } + if (image.samples > 1) { + clearMultisample(ctx, *tex, info, texel.data(), x, y, z, w, h, d); + imageChanged(ctx, *tex); + return; + } + // One layer (or 3D slice, or cube face) at a time bounds the staging size. + std::vector fill(static_cast(w) * h * bytes); + for (size_t i = 0; i < fill.size(); i += bytes) std::memcpy(&fill[i], texel.data(), bytes); + if (tex->target == GL_TEXTURE_1D_ARRAY) { + // A 1D array's layers are its rows (y and height). + int rx = x, ry = y, rz = 0, rw = w, rh = h, rd = 1; + metalRegion(*tex, rx, ry, rz, rw, rh, rd); + uploadTexels(ctx, *tex, 0, level, rx, ry, rz, rw, rh, rd, fill.data(), static_cast(rw) * bytes, + static_cast(rw) * rh * bytes); + } else { + for (int layer = z; layer < z + d; ++layer) + uploadTexels(ctx, *tex, cube ? layer : 0, level, x, y, cube ? 0 : layer, w, h, 1, fill.data(), + static_cast(w) * bytes, static_cast(w) * h * bytes); + } + imageChanged(ctx, *tex); +} + +} // namespace + +void ClearTexImage(Context& ctx, GLuint texture, GLint level, GLenum format, GLenum type, const void* data) { + clearTexture(ctx, "glClearTexImage", texture, level, 0, 0, 0, 0, 0, 0, true, format, type, data); +} + +void ClearTexSubImage(Context& ctx, GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, + GLsizei height, GLsizei depth, GLenum format, GLenum type, const void* data) { + clearTexture(ctx, "glClearTexSubImage", texture, level, xoffset, yoffset, zoffset, width, height, depth, false, format, + type, data); +} + void GenerateMipmap(Context& ctx, GLenum target) { if (target != GL_TEXTURE_1D && target != GL_TEXTURE_2D && target != GL_TEXTURE_3D && target != GL_TEXTURE_1D_ARRAY && target != GL_TEXTURE_2D_ARRAY && target != GL_TEXTURE_CUBE_MAP && @@ -1892,6 +2091,184 @@ void GetCompressedTexImage(Context& ctx, GLenum target, GLint level, void* img) std::memcpy(dst + layout.start + z * layout.imageStride + y * layout.rowStride, from, layout.blocksX * layout.blockBytes); } +namespace { + +// glGetTextureSubImage and glGetCompressedTextureSubImage (GL 4.5 section +// 8.11.4): the texture and region checks they share. A cube map's faces are +// its z (a cube map array's layer-faces are already). +struct SubImage { + RefPtr tex; + const TextureImage* image = nullptr; + int depth = 1; // images in z +}; + +bool subImageRegion(Context& ctx, const char* command, GLuint texture, GLint level, GLint x, GLint y, GLint z, GLsizei w, + GLsizei h, GLsizei d, SubImage& out) { + if (ctx.version() < 45 && !ctx.hasExtension("GL_ARB_get_texture_sub_image")) { + ctx.unimplemented(command); + return false; + } + { + ShareLock lock(ctx); + out.tex = texture ? ctx.share().textures.getRef(texture) : nullptr; + } + if (!out.tex || !out.tex->target) return ctx.setError(GL_INVALID_OPERATION), false; + const GLenum target = out.tex->target; + if (target == GL_TEXTURE_BUFFER || target == GL_TEXTURE_2D_MULTISAMPLE || target == GL_TEXTURE_2D_MULTISAMPLE_ARRAY) + return ctx.setError(GL_INVALID_OPERATION), false; + if (level < 0 || level > maxLevelFor(target)) return ctx.setError(GL_INVALID_VALUE), false; + if (x < 0 || y < 0 || z < 0 || w < 0 || h < 0 || d < 0) return ctx.setError(GL_INVALID_VALUE), false; + if (target == GL_TEXTURE_1D && (y != 0 || h != 1)) return ctx.setError(GL_INVALID_VALUE), false; + if ((target == GL_TEXTURE_1D || target == GL_TEXTURE_1D_ARRAY || target == GL_TEXTURE_2D || target == GL_TEXTURE_RECTANGLE) && + (z != 0 || d != 1)) + return ctx.setError(GL_INVALID_VALUE), false; + out.image = &out.tex->image(0, level); + out.depth = target == GL_TEXTURE_CUBE_MAP ? 6 : out.image->depth; + if (target == GL_TEXTURE_CUBE_MAP) + for (int f = 1; f < 6; ++f) { + const TextureImage& face = out.tex->image(f, level); + if (!face.defined() || face.width != out.image->width || face.height != out.image->height || + face.internalFormat != out.image->internalFormat) + out.depth = 0; // not cube complete at this level + } + if (static_cast(x) + w > out.image->width || static_cast(y) + h > out.image->height || + static_cast(z) + d > out.depth) + return ctx.setError(GL_INVALID_VALUE), false; + return true; +} + +// The Metal slice of GL image z (a 1D array's layer is its y). +uint32_t subImageSlice(const Texture& tex, int z) { + return tex.target == GL_TEXTURE_3D ? 0u : static_cast(z); +} + +// One slice of a compressed image decoded to float RGBA. +std::vector decodedSlice(Context& ctx, Texture& tex, const FormatInfo& info, int level, uint32_t slice, int width, + int height) { + std::vector rgba(static_cast(width) * height * 4); + if (!isRgtcStorage(info.storage)) { + decodeCompressedSlice(ctx, tex, info.storage, static_cast(level), slice, width, height, rgba.data()); + return rgba; + } + const size_t rowBytes = ((static_cast(width) + 3) / 4) * storageBytes(info.storage); + const size_t imageBytes = rowBytes * ((static_cast(height) + 3) / 4); + id staging = [Device::get().mtl() newBufferWithLength:std::max(1, imageBytes) + options:MTLResourceStorageModeShared]; + [ctx.stream().blit() copyFromTexture:tex.mtl sourceSlice:slice sourceLevel:static_cast(level) + sourceOrigin:MTLOriginMake(0, 0, 0) sourceSize:MTLSizeMake(width, height, 1) toBuffer:staging + destinationOffset:0 destinationBytesPerRow:rowBytes destinationBytesPerImage:imageBytes]; + ctx.stream().finish(); + decodeRgtc(info.storage, static_cast(staging.contents), width, height, 1, rgba.data()); + return rgba; +} + +} // namespace + +void GetTextureSubImage(Context& ctx, GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, + GLsizei height, GLsizei depth, GLenum format, GLenum type, GLsizei bufSize, void* pixels) { + SubImage sub; + if (!subImageRegion(ctx, "glGetTextureSubImage", texture, level, xoffset, yoffset, zoffset, width, height, depth, sub)) + return; + if (!checkFormatType(ctx, format, type)) return; + Texture& tex = *sub.tex; + const TextureImage& image = *sub.image; + if (!width || !height || !depth || !image.defined()) return; + const FormatInfo& info = *image.format; + const bool depthRequest = format == GL_DEPTH_COMPONENT, stencilRequest = format == GL_STENCIL_INDEX, + depthStencilRequest = format == GL_DEPTH_STENCIL; + if ((depthRequest && !info.depthBits) || (depthStencilRequest && !(info.depthBits && info.stencilBits)) || + (stencilRequest && !info.stencilBits) || (!depthRequest && !stencilRequest && !depthStencilRequest && info.isDepthOrStencil())) + return ctx.setError(GL_INVALID_OPERATION); + if (!info.isDepthOrStencil() && !transferCompatible(format, type, info)) return ctx.setError(GL_INVALID_OPERATION); + // Cube maps and arrays pack as three-dimensional images. + const bool threeD = tex.target == GL_TEXTURE_3D || tex.target == GL_TEXTURE_2D_ARRAY || tex.target == GL_TEXTURE_CUBE_MAP || + tex.target == GL_TEXTURE_CUBE_MAP_ARRAY; + const PixelStoreParams ps = pixelStoreParams(ctx.state.pack, threeD); + const TransferLayout layout = transferLayout(format, type, width, height, depth, ps); + if (bufSize < 0) return ctx.setError(GL_INVALID_VALUE); + if (!ctx.state.pixelPackBuffer && layout.bytesNeeded > static_cast(bufSize)) return ctx.setError(GL_INVALID_OPERATION); + uint8_t* dst = nullptr; + if (!packDestination(ctx, pixels, layout.bytesNeeded, typeSize(type), dst)) return; + for (int f = 0; f < (tex.target == GL_TEXTURE_CUBE_MAP ? 6 : 1); ++f) + if (!imageHasStorage(ctx, tex, f, level)) return; + if (!dst) return; + if (info.compressed || isRgtcStorage(info.storage)) { + // Decode whole slices, keep the region. + std::vector rgba(static_cast(width) * height * depth * 4); + for (int k = 0; k < depth; ++k) { + const std::vector slice = + decodedSlice(ctx, tex, info, level, subImageSlice(tex, zoffset + k), image.width, image.height); + for (int row = 0; row < height; ++row) + std::memcpy(&rgba[((static_cast(k) * height + row) * width) * 4], + &slice[((static_cast(yoffset) + row) * image.width + xoffset) * 4], + static_cast(width) * 16); + } + packPixels(rgba.data(), *formatInfo(GL_RGBA32F), width, height, depth, format, type, ps, false, dst); + return; + } + std::vector texels; + if (tex.target == GL_TEXTURE_3D) { + texels = readImageTexels(ctx, tex.mtl, info.storage, static_cast(level), 0, xoffset, yoffset, zoffset, width, + height, depth); + } else { + int x = xoffset, y = yoffset, z = zoffset, w = width, h = height, d = depth; + metalRegion(tex, x, y, z, w, h, d); + texels = readImageTexels(ctx, tex.mtl, info.storage, static_cast(level), static_cast(z), x, y, 0, w, + h, d); + } + packPixels(texels.data(), info, width, height, depth, format, type, ps, false, dst); +} + +void GetCompressedTextureSubImage(Context& ctx, GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, + GLsizei width, GLsizei height, GLsizei depth, GLsizei bufSize, void* pixels) { + SubImage sub; + if (!subImageRegion(ctx, "glGetCompressedTextureSubImage", texture, level, xoffset, yoffset, zoffset, width, height, depth, + sub)) + return; + Texture& tex = *sub.tex; + const TextureImage& image = *sub.image; + if (!image.defined() || !image.format->compressed) return ctx.setError(GL_INVALID_OPERATION); + // Whole 4x4 blocks, except where the region reaches the image's edge. + if (xoffset % 4 || yoffset % 4 || (width % 4 && xoffset + width != image.width) || + (height % 4 && yoffset + height != image.height)) + return ctx.setError(GL_INVALID_OPERATION); + const bool threeD = tex.target == GL_TEXTURE_3D || tex.target == GL_TEXTURE_2D_ARRAY || tex.target == GL_TEXTURE_CUBE_MAP || + tex.target == GL_TEXTURE_CUBE_MAP_ARRAY; + const size_t size = compressedSize(*image.format, width, height, depth); + const CompressedLayout layout = compressedLayout(ctx.state.pack, *image.format, threeD ? 3 : 2, width, height, depth); + const size_t needed = layout.active ? layout.bytesNeeded() : size; + if (bufSize < 0) return ctx.setError(GL_INVALID_VALUE); + if (!ctx.state.pixelPackBuffer && needed > static_cast(bufSize)) return ctx.setError(GL_INVALID_OPERATION); + uint8_t* dst = nullptr; + if (!packDestination(ctx, pixels, needed, 1, dst)) return; + if (!width || !height || !depth) return; + for (int f = 0; f < (tex.target == GL_TEXTURE_CUBE_MAP ? 6 : 1); ++f) + if (!imageHasStorage(ctx, tex, f, level)) return; + if (!dst) return; + const size_t rowBytes = ((static_cast(width) + 3) / 4) * storageBytes(image.format->storage); + const size_t imageBytes = rowBytes * ((static_cast(height) + 3) / 4); + id staging = [Device::get().mtl() newBufferWithLength:std::max(1, size) + options:MTLResourceStorageModeShared]; + id blit = ctx.stream().blit(); + for (int k = 0; k < depth; ++k) { + const bool volume = tex.target == GL_TEXTURE_3D; + [blit copyFromTexture:tex.mtl sourceSlice:volume ? 0 : subImageSlice(tex, zoffset + k) + sourceLevel:static_cast(level) + sourceOrigin:MTLOriginMake(xoffset, yoffset, volume ? zoffset + k : 0) + sourceSize:MTLSizeMake(width, height, 1) toBuffer:staging + destinationOffset:imageBytes * static_cast(k) destinationBytesPerRow:rowBytes destinationBytesPerImage:imageBytes]; + } + ctx.stream().finish(); + if (!layout.active) { + std::memcpy(dst, staging.contents, size); + return; + } + const uint8_t* from = static_cast(staging.contents); + for (size_t z = 0; z < layout.depth; ++z) + for (size_t y = 0; y < layout.blocksY; ++y, from += layout.blocksX * layout.blockBytes) + std::memcpy(dst + layout.start + z * layout.imageStride + y * layout.rowStride, from, layout.blocksX * layout.blockBytes); +} + void GetTexLevelParameteriv(Context& ctx, GLenum target, GLint level, GLenum pname, GLint* params) { getTexLevelParameter(ctx, target, level, pname, params); } @@ -1983,6 +2360,58 @@ void BindSampler(Context& ctx, GLuint unit, GLuint sampler) { ctx.markDirty(kDirtyTextures); } +// glBindTextures and glBindSamplers (GL 4.4, GL_ARB_multi_bind): a range of +// units at once. Zero (or a null array) resets every target of a unit to its +// default texture, or removes its sampler. An error in one unit leaves it +// alone; a range past the last unit changes nothing. +void BindTextures(Context& ctx, GLuint first, GLsizei count, const GLuint* textures) { + if (ctx.version() < 44 && !ctx.hasExtension("GL_ARB_multi_bind")) return ctx.unimplemented("glBindTextures"); + if (count < 0) return ctx.setError(GL_INVALID_VALUE); + if (static_cast(first) + static_cast(count) > static_cast(kMaxCombinedTextureUnits)) + return ctx.setError(GL_INVALID_OPERATION); + for (GLsizei i = 0; i < count; ++i) { + TextureUnit& unit = ctx.state.textureUnits[first + static_cast(i)]; + const GLuint name = textures ? textures[i] : 0; + if (!name) { + for (size_t t = 0; t < unit.bound.size(); ++t) unit.bound[t] = ctx.defaultTextures[t]; + continue; + } + RefPtr tex; + { + ShareLock lock(ctx); + tex = ctx.share().textures.getRef(name); + } + const int index = tex ? textureTargetIndex(tex->target) : -1; + if (index < 0) { + ctx.setError(GL_INVALID_OPERATION); + continue; + } + unit.bound[static_cast(index)] = tex; + } + ctx.markDirty(kDirtyTextures); +} + +void BindSamplers(Context& ctx, GLuint first, GLsizei count, const GLuint* samplers) { + if (ctx.version() < 44 && !ctx.hasExtension("GL_ARB_multi_bind")) return ctx.unimplemented("glBindSamplers"); + if (count < 0) return ctx.setError(GL_INVALID_VALUE); + if (static_cast(first) + static_cast(count) > static_cast(kMaxCombinedTextureUnits)) + return ctx.setError(GL_INVALID_OPERATION); + for (GLsizei i = 0; i < count; ++i) { + const GLuint name = samplers ? samplers[i] : 0; + RefPtr object; + if (name) { + ShareLock lock(ctx); + object = ctx.share().samplers.getRef(name); + } + if (name && !object) { + ctx.setError(GL_INVALID_OPERATION); + continue; + } + ctx.state.textureUnits[first + static_cast(i)].sampler = object; + } + ctx.markDirty(kDirtyTextures); +} + namespace { void samplerParameter(Context& ctx, GLuint sampler, GLenum pname, const ParamValues& v, bool vectorCall) { @@ -1991,7 +2420,7 @@ void samplerParameter(Context& ctx, GLuint sampler, GLenum pname, const ParamVal if (!isSamplerParam(pname) || (!vectorCall && isVectorParam(pname))) return ctx.setError(GL_INVALID_ENUM); if (pname == GL_TEXTURE_MAX_ANISOTROPY && !ctx.hasExtension("GL_EXT_texture_filter_anisotropic") && ctx.version() < 46) return ctx.setError(GL_INVALID_ENUM); - const GLenum error = setSamplerParam(object->params, pname, v, 0, ctx.isCompat()); + const GLenum error = setSamplerParam(object->params, pname, v, 0, ctx); if (error) return ctx.setError(error); ctx.markDirty(kDirtyTextures); } diff --git a/src/core/api/transform_feedback.mm b/src/core/api/transform_feedback.mm index 3db627d5..16098202 100644 --- a/src/core/api/transform_feedback.mm +++ b/src/core/api/transform_feedback.mm @@ -21,7 +21,7 @@ void BeginTransformFeedback(Context& ctx, GLenum primitiveMode) { Program* p = lp->pipeline ? lp->stageOwner[static_cast(shader::Stage::Vertex)] : ctx.state.program.get(); // Every buffer the program captures into must be bound. for (int b = 0; b < lp->link.xfbBufferCount; ++b) - if (!xfb.buffers[b].buffer) return ctx.setError(GL_INVALID_OPERATION); + if (lp->link.xfbBufferUsed(b) && !xfb.buffers[b].buffer) return ctx.setError(GL_INVALID_OPERATION); xfb.active = true; xfb.paused = false; xfb.primitiveMode = primitiveMode; diff --git a/src/core/api/vertex.mm b/src/core/api/vertex.mm index 6255f542..d26e4cd0 100644 --- a/src/core/api/vertex.mm +++ b/src/core/api/vertex.mm @@ -85,6 +85,8 @@ bool validFormat(Context& ctx, GLint size, GLenum type, bool normalized, bool in default: return fail(GL_INVALID_ENUM); } if (packed && size != 4 && size != GL_BGRA && type != GL_UNSIGNED_INT_10F_11F_11F_REV) return fail(GL_INVALID_OPERATION); + // GL_UNSIGNED_INT_10F_11F_11F_REV holds exactly three components (GL 4.4). + if (type == GL_UNSIGNED_INT_10F_11F_11F_REV && size != 3) return fail(GL_INVALID_OPERATION); } return true; } @@ -170,14 +172,15 @@ void VertexAttribLPointer(Context& ctx, GLuint index, GLint size, GLenum type, G } // GL 3.3 core (appendix E) only makes VertexAttribPointer an error without a -// bound VAO; GL 4.5 extends that to every command that changes VAO state. +// bound VAO; GL 4.5 extends that to the commands that change VAO state. +// Enabling an array is not checked: the CTS (get_uniform) enables one in a +// core context without a VAO and expects no error, as drivers behave. static bool vaoStateWritable(Context& ctx) { return !(ctx.isCore() && ctx.version() >= 45 && currentVao(ctx) == ctx.defaultVertexArray.get()); } void EnableVertexAttribArray(Context& ctx, GLuint index) { if (!checkIndex(ctx, index)) return; - if (!vaoStateWritable(ctx)) return ctx.setError(GL_INVALID_OPERATION); currentVao(ctx)->attribs[index].enabled = true; ++currentVao(ctx)->version; ctx.markDirty(kDirtyVertexInput); @@ -185,7 +188,6 @@ void EnableVertexAttribArray(Context& ctx, GLuint index) { void DisableVertexAttribArray(Context& ctx, GLuint index) { if (!checkIndex(ctx, index)) return; - if (!vaoStateWritable(ctx)) return ctx.setError(GL_INVALID_OPERATION); currentVao(ctx)->attribs[index].enabled = false; ++currentVao(ctx)->version; ctx.markDirty(kDirtyVertexInput); @@ -268,6 +270,54 @@ void BindVertexBuffer(Context& ctx, GLuint bindingindex, GLuint buffer, GLintptr ctx.markDirty(kDirtyVertexInput); } +// glBindVertexBuffers (GL 4.4, GL_ARB_multi_bind): a range of the current +// vertex array's buffer bindings. A null array unbinds them (offset 0, +// stride 16); an error in one binding leaves it alone. +void BindVertexBuffers(Context& ctx, GLuint first, GLsizei count, const GLuint* buffers, const GLintptr* offsets, + const GLsizei* strides) { + if (ctx.version() < 44 && !ctx.hasExtension("GL_ARB_multi_bind")) return ctx.unimplemented("glBindVertexBuffers"); + if (!attribBindings(ctx, "glBindVertexBuffers") || !bindingStateWritable(ctx)) return; + if (count < 0) return ctx.setError(GL_INVALID_VALUE); + if (static_cast(first) + static_cast(count) > static_cast(VertexArray::kMaxBindings)) + return ctx.setError(GL_INVALID_OPERATION); + VertexArray& vao = *currentVao(ctx); + for (GLsizei i = 0; i < count; ++i) { + const GLuint index = first + static_cast(i); + VertexBinding& b = vao.bindings[index]; + if (!buffers) { + b.buffer = nullptr; + b.offset = 0; + b.stride = 16; + vao.resolveBinding(index); + continue; + } + const GLintptr offset = offsets[i]; + const GLsizei stride = strides[i]; + if (offset < 0 || stride < 0 || stride > limits::kMaxVertexAttribStride) { + ctx.setError(GL_INVALID_VALUE); + continue; + } + RefPtr object; + if (buffers[i]) { + ShareLock lock(ctx); + object = ctx.share().buffers.getRef(buffers[i]); + if (!object && ctx.share().buffers.isGenerated(buffers[i])) { + object = makeRef(buffers[i]); + ctx.share().buffers.set(buffers[i], object); + } + } + if (buffers[i] && !object) { + ctx.setError(GL_INVALID_OPERATION); + continue; + } + b.buffer = object; + b.offset = offset; + b.stride = stride; + vao.resolveBinding(index); + } + ctx.markDirty(kDirtyVertexInput); +} + void VertexAttribFormat(Context& ctx, GLuint attribindex, GLint size, GLenum type, GLboolean normalized, GLuint relativeoffset) { setFormat(ctx, attribindex, size, type, normalized != GL_FALSE, false, false, relativeoffset, "glVertexAttribFormat"); } diff --git a/src/core/context.h b/src/core/context.h index 4fbbad50..f1ee540c 100644 --- a/src/core/context.h +++ b/src/core/context.h @@ -105,6 +105,14 @@ class Context { bool atomicCounters() const { return version() >= 42 || hasExtension("GL_ARB_shader_atomic_counters"); } // Sample shading: GL 4.0 or GL_ARB_sample_shading. bool sampleShading() const { return version() >= 40 || hasExtension("GL_ARB_sample_shading"); } + // GL_ARB_parallel_shader_compile / GL_KHR_parallel_shader_compile. + bool parallelShaderCompile() const { + return hasExtension("GL_ARB_parallel_shader_compile") || hasExtension("GL_KHR_parallel_shader_compile"); + } + // SPIR-V shaders: GL 4.6 or GL_ARB_gl_spirv. + bool glSpirv() const { return version() >= 46 || hasExtension("GL_ARB_gl_spirv"); } + // Draw counts from PARAMETER_BUFFER: GL 4.6 or GL_ARB_indirect_parameters. + bool indirectParameters() const { return version() >= 46 || hasExtension("GL_ARB_indirect_parameters"); } // glUniformSubroutinesuiv selections per stage (api/subroutine.mm), for // the executable they were made with; reset by UseProgram and pipeline diff --git a/src/core/context.mm b/src/core/context.mm index fa201ed0..786c345f 100644 --- a/src/core/context.mm +++ b/src/core/context.mm @@ -147,13 +147,16 @@ void noCurrentContext(const char* command) { extensions_.push_back("GL_ARB_clip_control"); extensions_.push_back("GL_ARB_get_program_binary"); extensions_.push_back("GL_ARB_buffer_storage"); + // Client memory as buffer storage: Wine maps 32-bit (WoW64) guests' + // buffers through it, and limits them to GL 4.3 without it. + extensions_.push_back("GL_AMD_pinned_memory"); extensions_.push_back("GL_KHR_debug"); extensions_.push_back("GL_ARB_debug_output"); extensions_.push_back("GL_ARB_separate_shader_objects"); for (const char* name : {"GL_KHR_robustness", "GL_ARB_robustness", "GL_ARB_internalformat_query", "GL_ARB_invalidate_subdata", "GL_ARB_copy_image", "GL_NVX_gpu_memory_info", "GL_ATI_meminfo"}) extensions_.push_back(name); - // The extensions GL 4.0 to 4.3 made core, in contexts of the + // The extensions GL 4.0 to 4.6 made core, in contexts of the // version that includes them (their features are gated on the version). const int version = config_.major * 10 + config_.minor; if (version >= 40) @@ -183,11 +186,34 @@ void noCurrentContext(const char* command) { // GL 4.5, implemented: the Khronos CTS calls glTextureBarrier in GL 4.3 view tests "GL_ARB_texture_barrier"}) extensions_.push_back(name); + if (version >= 44) + for (const char* name : + {"GL_ARB_clear_texture", "GL_ARB_enhanced_layouts", "GL_ARB_multi_bind", "GL_ARB_query_buffer_object", + "GL_ARB_texture_mirror_clamp_to_edge", "GL_ARB_texture_stencil8", "GL_ARB_vertex_type_10f_11f_11f_rev"}) + extensions_.push_back(name); + if (version >= 45) + for (const char* name : + {"GL_ARB_conditional_render_inverted", "GL_ARB_cull_distance", "GL_ARB_derivative_control", + "GL_ARB_direct_state_access", "GL_ARB_ES3_1_compatibility", "GL_ARB_get_texture_sub_image", + "GL_ARB_shader_texture_image_samples", "GL_KHR_context_flush_control"}) + extensions_.push_back(name); + if (version >= 46) + for (const char* name : {"GL_ARB_gl_spirv", "GL_ARB_spirv_extensions", "GL_ARB_indirect_parameters", + "GL_ARB_pipeline_statistics_query", "GL_ARB_shader_atomic_counter_ops", + "GL_ARB_shader_draw_parameters", "GL_ARB_shader_group_vote", + "GL_ARB_transform_feedback_overflow_query", "GL_ARB_parallel_shader_compile", + "GL_KHR_parallel_shader_compile", "GL_KHR_no_error"}) + extensions_.push_back(name); // Later extensions the core implements, in 4.x contexts: wined3d reads // EXT_shader_integer_mix and polygon_offset_clamp for its feature level. if (version >= 40) for (const char* name : {"GL_EXT_shader_integer_mix", "GL_ARB_polygon_offset_clamp", "GL_EXT_polygon_offset_clamp"}) extensions_.push_back(name); + // gl_Layer and gl_ViewportIndex from vertex and tessellation evaluation + // shaders, which Metal vertex functions write natively (the geometry + // emulation's pass-through vertex function already does). + if (version >= 41) extensions_.push_back("GL_ARB_shader_viewport_layer_array"); + if (version >= 40) extensions_.push_back("GL_EXT_texture_shadow_lod"); extensionString_.clear(); for (const std::string& ext : extensions_) { if (!extensionString_.empty()) extensionString_ += ' '; @@ -301,6 +327,8 @@ void noCurrentContext(const char* command) { if (storageBuffers() && target == GL_SHADER_STORAGE_BUFFER) return &state.shaderStorageBuffer; if (atomicCounters() && target == GL_ATOMIC_COUNTER_BUFFER) return &state.atomicCounterBuffer; if (version() >= 44 && target == GL_QUERY_BUFFER) return &state.queryBuffer; + if (indirectParameters() && target == GL_PARAMETER_BUFFER) return &state.parameterBuffer; + if (target == GL_EXTERNAL_VIRTUAL_MEMORY_BUFFER_AMD) return &state.externalVirtualMemoryBuffer; return nullptr; } diff --git a/src/core/draw.h b/src/core/draw.h index 4cd785b5..4043c27b 100644 --- a/src/core/draw.h +++ b/src/core/draw.h @@ -81,6 +81,12 @@ class DrawState { ~BaseInstanceScope() { draw.baseInstance_ = 0; } DrawState& draw; }; + // gl_DrawID of the draws in scope: their index in a multi-draw command. + struct DrawIdScope { + DrawIdScope(DrawState& d, GLsizei id) : draw(d) { draw.drawId_ = id; } + ~DrawIdScope() { draw.drawId_ = 0; } + DrawState& draw; + }; private: bool validateDraw(GLenum mode); @@ -98,14 +104,23 @@ class DrawState { GLint firstVertex, GLsizei vertexCount, GLsizei instances, const std::vector* gather = nullptr); // Writes the transform feedback output of a draw: `vertices` are the - // vertex numbers (gl_VertexID) in draw order. - void captureTransformFeedback(GLenum mode, const std::vector& vertices, GLsizei instances); + // vertex numbers (gl_VertexID) in draw order. Returns kNotCaptured (the + // draw still has to run its vertex stage), kCaptured (a capture pass ran + // it for every vertex) or kCapturedAndDrawn (the draw is done: a vertex + // stage that writes memory captures while rasterizing, so it runs once). + enum Capture { kNotCaptured, kCaptured, kCapturedAndDrawn }; + Capture captureTransformFeedback(GLenum mode, const std::vector& vertices, GLsizei instances); // Runs the program's capture vertex function (rasterization off) over // `list`, writing vertex k of instance i to capture slot // k + i * list.size() (below `limit`) of each buffer at its base word. + // With `drawMode` (GL_POINTS, GL_LINES or GL_TRIANGLES) the list is drawn + // as those primitives with the current state instead, rasterized, and + // *rasterized says whether it was (not when the state draws nothing). bool runVertexCapture(LinkedProgram& program, const std::vector& list, GLsizei instances, uint32_t limit, int bufferCount, const __strong id* buffers, const uint32_t* baseWords, - const uint32_t* strides); + const uint32_t* strides, GLenum drawMode = GL_NONE, bool* rasterized = nullptr); + // The vertex stage writes memory (storage blocks, atomic counters or images). + static bool vertexWritesMemory(LinkedProgram& program); // Draws `vertices` (vertex numbers in draw order, one restart-free // segment) through the geometry shader emulation (shader/geometry.h). // primitiveBase is the gl_PrimitiveIDIn of the first primitive. @@ -154,6 +169,9 @@ class DrawState { Program* stageProgram(LinkedProgram& program, int stage); std::pair blockSource(LinkedProgram& program, size_t block); std::pair samplerSource(LinkedProgram& program, size_t index); + // The clamp-to-border emulation variant the textures and samplers bound + // for stage `stage`'s samplers need (shader::borderVariantId; 0: none). + uint16_t borderVariant(LinkedProgram& program, int stage); std::pair imageSource(LinkedProgram& program, size_t index); // The view image slot `index` reads (GL 4.2), or a dummy image. id imageBinding(LinkedProgram& program, size_t index); @@ -204,6 +222,8 @@ class DrawState { TransientAllocator transient_; uint64_t lastPassGeneration_ = 0; GLuint baseInstance_ = 0; + GLint glBaseVertex_ = 0; // the command's base vertex (gl_BaseVertex), 0 for array draws + GLsizei drawId_ = 0; // First vertex of the draw being bound when issue() renumbers vertices from it. GLint vertexRebase_ = 0; // GL sample mask the current draw's fragment function writes (~0: none; prepare()). diff --git a/src/core/draw.mm b/src/core/draw.mm index 7f562dee..0ca2de92 100644 --- a/src/core/draw.mm +++ b/src/core/draw.mm @@ -424,9 +424,10 @@ uint32_t attachmentLayers(const Attachment& a) { VertexFetch vertexFetch(const shader::StageOutput::VertexInput& input, const VertexAttrib& a) { if (input.kind == 'd') { - const uint32_t element = a.isDouble ? a.elementBytes() : 0, start = 16u * static_cast(input.part); + const uint32_t element = a.isDouble ? a.elementBytes() : 0, + start = 16u * static_cast(input.part) + static_cast(input.byteOffset); if (element <= start) return {MTLVertexFormatInvalid, static_cast(a.offset), 0}; - const uint32_t bytes = std::min(16u, element - start); + const uint32_t bytes = std::min({16u, element - start, static_cast(input.components) * 4u}); return {bytes == 8 ? MTLVertexFormatUInt2 : MTLVertexFormatUInt4, static_cast(a.offset) + start, bytes}; } return {a.isDouble ? MTLVertexFormatInvalid : mtlVertexFormat(a.type, a.size, a.normalized, a.integer), @@ -436,13 +437,11 @@ VertexFetch vertexFetch(const shader::StageOutput::VertexInput& input, const Ver } // namespace size_t DrawState::vertexBufferSlots(LinkedProgram& program) { - // A vertex function's atomic counter buffer takes the last vertex buffer slot. + // A vertex function's storage block argument buffer (storage blocks and + // atomic counter buffers) takes slot 28. const size_t vertex = static_cast(Stage::Vertex); - // Its storage block argument buffer takes slot 28 (and leaves 29 for a counter buffer). if (!program.link.stages[vertex].storageArgs.empty()) return shader::kSlotStorageBlocks - shader::kSlotFirstVertexBuffer; - for (const shader::AtomicCounterBufferInfo& b : program.link.atomicBuffers) - if (b.stageSlot[vertex] >= 0) return shader::kMaxVertexBufferSlots - shader::kMaxVertexAtomicCounterBuffers; return shader::kMaxVertexBufferSlots; } @@ -588,6 +587,7 @@ VertexFetch vertexFetch(const shader::StageOutput::VertexInput& input, const Ver const std::vector* order; // gathered: the vertex fetched for each draw vertex char kind; int part; // 'd': 16-byte part of the element + int byteOffset = 0; // 'd': where in the part (layout(component) packing) }; std::vector converted; // Current values, one constant element each, packed into one buffer: @@ -595,6 +595,7 @@ VertexFetch vertexFetch(const shader::StageOutput::VertexInput& input, const Ver struct ConstantInput { int location, attrib, part; char kind; + int byteOffset = 0; }; std::vector constants; for (const shader::StageOutput::VertexInput& input : program.stage(Stage::Vertex).vertexInputs) { @@ -625,7 +626,7 @@ VertexFetch vertexFetch(const shader::StageOutput::VertexInput& input, const Ver : input.kind == 'u' || input.kind == 'd' ? MTLVertexFormatUInt4 : MTLVertexFormatFloat4); out.offset = static_cast(constants.size() * 16); - constants.push_back({location, input.source(), input.part, input.kind}); + constants.push_back({location, input.source(), input.part, input.kind, input.byteOffset}); break; case Plan::Direct: { const size_t g = static_cast(plan.group[static_cast(location)]); @@ -640,7 +641,7 @@ VertexFetch vertexFetch(const shader::StageOutput::VertexInput& input, const Ver ctx_.setError(GL_INVALID_OPERATION); return false; } - converted.push_back({location, &a, partner ? linePartners_ : gather, input.kind, input.part}); + converted.push_back({location, &a, partner ? linePartners_ : gather, input.kind, input.part, input.byteOffset}); break; case Plan::None: break; } @@ -709,7 +710,7 @@ VertexFetch vertexFetch(const shader::StageOutput::VertexInput& input, const Ver if (group[m]->kind == 'd') { // Raw 64-bit components (shader/fp64.h); anything but an L array reads zeros. std::memset(to, 0, 16); - const uint32_t start = 16u * static_cast(group[m]->part); + const uint32_t start = 16u * static_cast(group[m]->part) + static_cast(group[m]->byteOffset); if (a.isDouble && elementBytes > start) std::memcpy(to, element + start, std::min(16u, elementBytes - start)); } else if (a.integer) { int32_t values[4]; @@ -741,7 +742,11 @@ VertexFetch vertexFetch(const shader::StageOutput::VertexInput& input, const Ver for (size_t c = 0; c < constants.size(); ++c) { const ContextState::GenericAttrib& g = ctx_.state.genericAttribs[static_cast(constants[c].attrib)]; uint8_t* dst = static_cast(alloc.data) + c * 16; - if (constants[c].kind == 'd') std::memcpy(dst, reinterpret_cast(g.d) + 16 * constants[c].part, 16); + if (constants[c].kind == 'd') { + const size_t start = 16 * static_cast(constants[c].part) + static_cast(constants[c].byteOffset); + std::memset(dst, 0, 16); + if (start < sizeof(g.d)) std::memcpy(dst, reinterpret_cast(g.d) + start, std::min(16, sizeof(g.d) - start)); + } else std::memcpy(dst, &g.value, 16); } const int layout = addLayout(alloc.buffer, alloc.offset, static_cast(constants.size() * 16), 0, true); @@ -785,7 +790,7 @@ VertexFetch vertexFetch(const shader::StageOutput::VertexInput& input, const Ver } void DrawState::applyBuiltinUniforms(Program& program) { - // gl_DepthRange (viewport 0's) and gl_NumSamples (glsl_upgrade.h). + // gl_DepthRange (viewport 0's), gl_NumSamples and the draw parameters (glsl_upgrade.h). if (!program.linked) return; const shader::LinkResult::BuiltinUniforms& b = program.linked->link.builtinUniforms; auto store = [&](int offset, const void* value) { @@ -804,6 +809,11 @@ VertexFetch vertexFetch(const shader::StageOutput::VertexInput& input, const Ver const int32_t samples = std::max(1, lastKey_.sampleCount); store(b.numSamples, &samples); } + // Draw parameters: the command's base vertex (0 for array draws), base instance and draw index. + const int32_t parameters[3] = {glBaseVertex_, static_cast(baseInstance_), drawId_}; + const int offsets[3] = {b.baseVertex, b.baseInstance, b.drawId}; + for (int i = 0; i < 3; ++i) + if (offsets[i] >= 0) store(offsets[i], ¶meters[i]); } GLint DrawState::imageUnit(LinkedProgram& program, size_t index) { @@ -856,6 +866,7 @@ VertexFetch vertexFetch(const shader::StageOutput::VertexInput& input, const Ver void DrawState::beginDraw(GLenum mode) { drawMode_ = mode; + glBaseVertex_ = 0; ffChosen_ = false; ffProgram_ = nullptr; ffPointSize_ = false; @@ -998,11 +1009,15 @@ VertexFetch vertexFetch(const shader::StageOutput::VertexInput& input, const Ver id buffer = scratch; NSUInteger offset = 0; uint32_t size = 0; - const GLuint binding = owner && arg.block >= 0 && static_cast(arg.block) < owner->storageBlockBindings.size() + const bool counter = arg.counterBinding >= 0; + const GLuint binding = counter ? static_cast(arg.counterBinding) + : owner && arg.block >= 0 && static_cast(arg.block) < owner->storageBlockBindings.size() ? owner->storageBlockBindings[static_cast(arg.block)] : 0; - if (binding < static_cast(ContextState::kMaxShaderStorageBufferBindings)) { - const IndexedBufferBinding& ib = ctx_.state.shaderStorageBufferBindings[binding]; + if (binding < static_cast(counter ? ContextState::kMaxAtomicCounterBufferBindings + : ContextState::kMaxShaderStorageBufferBindings)) { + const IndexedBufferBinding& ib = counter ? ctx_.state.atomicCounterBufferBindings[binding] + : ctx_.state.shaderStorageBufferBindings[binding]; if (ib.buffer && ib.buffer->mtl && ib.offset >= 0 && ib.offset < ib.buffer->size) { buffer = ib.buffer->mtl; offset = static_cast(ib.offset); @@ -1067,8 +1082,39 @@ VertexFetch vertexFetch(const shader::StageOutput::VertexInput& input, const Ver if (texture->target == GL_TEXTURE_BUFFER && texture->buffer) texture->buffer->lastUseSerial = texture->lastUseSerial; } else { view = dummyTexture(slot.type); - sampler = samplerState(ctx_, SamplerParams(), slot.shadow); + // Shadow lookups of an incomplete texture return 0 (the CTS's + // incomplete_texture_access): a comparison that never passes. + SamplerParams params; + if (slot.shadow) { + params.compareMode = GL_COMPARE_REF_TO_TEXTURE; + params.compareFunc = GL_NEVER; + } + sampler = samplerState(ctx_, params, slot.shadow); + } +} + +uint16_t DrawState::borderVariant(LinkedProgram& program, int stage) { + std::vector slots; + for (size_t i = 0; i < program.link.samplers.size(); ++i) { + const shader::SamplerSlotInfo& slot = program.link.samplers[i]; + const int index = slot.stageTexture[stage]; + if (index < 0 || slot.textureTarget < 0) continue; + const auto [owner, ownIndex] = samplerSource(program, i); + const GLint unit = owner && ownIndex < owner->samplerUnits.size() ? owner->samplerUnits[ownIndex] : 0; + if (unit < 0 || unit >= ContextState::kMaxTextureUnits) continue; + Texture* texture = ctx_.state.textureUnits[unit].bound[slot.textureTarget].get(); + if (!texture) continue; + const Sampler* samplerObject = ctx_.state.textureUnits[unit].sampler.get(); + const SamplerParams& params = samplerObject ? samplerObject->params : texture->sampler; + if (params.wrapS != GL_CLAMP_TO_BORDER && params.wrapT != GL_CLAMP_TO_BORDER && params.wrapR != GL_CLAMP_TO_BORDER) + continue; + shader::BorderSlot border; + if (!textureComplete(ctx_, *texture, ¶ms) || !borderEmulation(*texture, params, slot.shadow, border)) continue; + border.texture = static_cast(index); + slots.push_back(border); } + return slots.empty() ? 0 + : shader::borderVariantId(shader::borderDefinitions(slots, stage == static_cast(Stage::Fragment))); } void DrawState::prepareSamplerStorage(LinkedProgram& program) { @@ -1191,6 +1237,9 @@ VertexFetch vertexFetch(const shader::StageOutput::VertexInput& input, const Ver return nil; LinkedProgram* program = executable(); if (!program) return nil; + // Before the render encoder: completeness checks may build storage. + const uint16_t borders[2] = {borderVariant(*program, static_cast(Stage::Vertex)), + borderVariant(*program, static_cast(Stage::Fragment))}; // Scissor rectangles of every viewport index (an empty one clips // everything). Without gl_ViewportIndex only index 0 is used, and an // empty rectangle there draws nothing. @@ -1247,6 +1296,13 @@ VertexFetch vertexFetch(const shader::StageOutput::VertexInput& input, const Ver // Metal requires the input topology class when the vertex function // writes the layer or viewport index; otherwise it stays unspecified. key.topologyClass = program->writesLayerOrViewport() ? static_cast(topologyClass(mode)) : 0; + // Cull distances: the fragment function drops primitives with a distance + // negative at every vertex. + key.cull = static_cast(program->cullDistances()); + key.borders[0] = borders[0]; + key.borders[1] = key.rasterization ? borders[1] : 0; + // FRAGMENT_SHADER_INVOCATIONS: a fragment function variant counts them. + key.countFragments = key.rasterization && queriesCounting(ctx_, kStatFragmentShaderInvocations); for (int i = 0; i < 8; ++i) key.colorFormats[i] = static_cast(formats.color[i]); // GL_COLOR_LOGIC_OP replaces blending on unsigned normalized and integer // attachments; the fragment function applies the operation (framebuffer @@ -1584,6 +1640,10 @@ void appendMetalList(GLenum mode, const std::vector& v, bool rotate, s [encoder setScissorRects:lastScissors_ count:ContextState::kMaxViewports]; bindUniforms(encoder, program); bindTextures(encoder, program); + id counter = nil; + NSUInteger counterOffset = 0; + if (lastKey_.countFragments && queriesFragmentCounter(ctx_, counter, counterOffset)) + [encoder setFragmentBuffer:counter offset:counterOffset atIndex:shader::kSlotFragmentInvocations]; } // ------------------------------------------------------ transform feedback @@ -1638,19 +1698,30 @@ void appendMetalList(GLenum mode, const std::vector& v, bool rotate, s [encoder setVertexBuffer:zero offset:0 atIndex:shader::kSlotFirstXfbBuffer + static_cast(b)]; } -void DrawState::captureTransformFeedback(GLenum mode, const std::vector& vertices, GLsizei instances) { +bool DrawState::vertexWritesMemory(LinkedProgram& program) { + const int vertex = static_cast(Stage::Vertex); + for (const shader::StageOutput::StorageArg& arg : program.link.stages[vertex].storageArgs) + if (arg.writes) return true; + for (const shader::ImageSlotInfo& image : program.link.images) + if (image.stageTexture[vertex] >= 0) return true; + return false; +} + +DrawState::Capture DrawState::captureTransformFeedback(GLenum mode, const std::vector& vertices, GLsizei instances) { TransformFeedback& xfb = *ctx_.state.transformFeedback; LinkedProgram* lp = executable(); - if (!xfb.active || xfb.paused || !lp || !lp->link.xfbVertexCapture) return; + if (!xfb.active || xfb.paused || !lp || !lp->link.xfbVertexCapture) return kNotCaptured; const shader::LinkResult& link = lp->link; const uint32_t perPrimitive = xfb.primitiveMode == GL_POINTS ? 1 : xfb.primitiveMode == GL_LINES ? 2 : 3; const std::vector list = assembleForCapture(mode, vertices); - if (list.empty()) return; + if (list.empty()) return kNotCaptured; // Whole primitives that fit in every bound buffer. - uint64_t primitives = (list.size() / perPrimitive) * static_cast(std::max(1, instances)); + const uint64_t requested = (list.size() / perPrimitive) * static_cast(std::max(1, instances)); + uint64_t primitives = requested; for (int b = 0; b < link.xfbBufferCount; ++b) { + if (!link.xfbBufferUsed(b)) continue; const TransformFeedback::Binding& binding = xfb.buffers[b]; - if (!binding.buffer || !binding.buffer->mtl) return; + if (!binding.buffer || !binding.buffer->mtl) return kNotCaptured; // The bound range, clamped to the buffer's current size (it may have shrunk since binding). const int64_t available = std::max(0, binding.buffer->size - binding.offset); const int64_t size = binding.size ? std::min(binding.size, available) : available; @@ -1659,51 +1730,81 @@ void appendMetalList(GLenum mode, const std::vector& v, bool rotate, s if (vertexBytes) primitives = std::min(primitives, static_cast(remaining) / vertexBytes / perPrimitive); } if (Query* q = ctx_.state.activeQueries[kQueryXfbPrimitivesWritten].get()) q->result += primitives; + if (primitives < requested) queriesNoteXfbOverflow(ctx_, 0); xfb.primitivesWritten[0] += primitives; - if (!primitives) return; + if (!primitives) return kNotCaptured; id buffers[4] = {}; uint32_t baseWords[4] = {}; for (int b = 0; b < link.xfbBufferCount; ++b) { + if (!link.xfbBufferUsed(b)) continue; buffers[b] = xfb.buffers[b].buffer->mtl; baseWords[b] = static_cast((xfb.buffers[b].offset + xfb.written[b]) / 4); } + // A vertex stage that writes memory must run once per vertex: the draw + // rasterizes the captured list (as independent primitives) itself, when + // nothing else rewrites it (provoking-vertex rotation, polygon points, + // wide lines, tessellation). + const GLenum drawn = listMode(mode); + const bool drawHere = !ctx_.state.rasterizerDiscard && vertexWritesMemory(*lp) && !tessInputs_.active && !ffWideLines_ && + !pointPolygons(drawn) && !rotateProvoking(drawn, *lp); + bool rasterized = false; if (!runVertexCapture(*lp, list, instances, static_cast(primitives * perPrimitive), link.xfbBufferCount, buffers, - baseWords, link.xfbStride)) - return; + baseWords, link.xfbStride, drawHere ? drawn : GL_NONE, &rasterized)) + return kNotCaptured; const uint64_t serial = ctx_.stream().serial(); for (int b = 0; b < link.xfbBufferCount; ++b) { + if (!link.xfbBufferUsed(b)) continue; Buffer& buffer = *xfb.buffers[b].buffer; xfb.written[b] += static_cast(primitives * perPrimitive * link.xfbStride[b] * 4); buffer.lastUseSerial = buffer.lastWriteSerial = serial; ++buffer.version; } + return rasterized ? kCapturedAndDrawn : kCaptured; } bool DrawState::runVertexCapture(LinkedProgram& lp, const std::vector& list, GLsizei instances, uint32_t limit, int bufferCount, const __strong id* buffers, const uint32_t* baseWords, - const uint32_t* strides) { + const uint32_t* strides, GLenum drawMode, bool* rasterized) { waitForCpuVertexReads(lp, true); prepareSamplerStorage(lp); - RenderTargetDesc target; - TargetFormats formats; - if (!drawTarget(target, formats, false)) return false; - id encoder = ctx_.stream().render(target); + id encoder = nil; PipelineKey key; - std::memset(&key, 0, sizeof(key)); - key.programId = lp.uid; - key.sampleCount = static_cast(target.samples); - key.rasterization = 0; - key.topologyClass = static_cast(MTLPrimitiveTopologyClassPoint); - for (int i = 0; i < 8; ++i) key.colorFormats[i] = static_cast(formats.color[i]); - key.depthFormat = static_cast(formats.depth); - key.stencilFormat = static_cast(formats.stencil); + if (drawMode != GL_NONE) { + // Rasterized with the current state (when it draws anything). + MTLPrimitiveType primitive = MTLPrimitiveTypeTriangle; + encoder = prepare(drawMode, primitive); + if (encoder) queriesBeforeDraw(ctx_, encoder); + else drawMode = GL_NONE; + key = lastKey_; + } + if (rasterized) *rasterized = drawMode != GL_NONE; + if (drawMode == GL_NONE) { + const uint16_t borders = borderVariant(lp, static_cast(Stage::Vertex)); + RenderTargetDesc target; + TargetFormats formats; + if (!drawTarget(target, formats, false)) return false; + encoder = ctx_.stream().render(target); + std::memset(&key, 0, sizeof(key)); + key.programId = lp.uid; + key.borders[0] = borders; + key.sampleCount = static_cast(target.samples); + key.rasterization = 0; + key.topologyClass = static_cast(MTLPrimitiveTopologyClassPoint); + for (int i = 0; i < 8; ++i) key.colorFormats[i] = static_cast(formats.color[i]); + key.depthFormat = static_cast(formats.depth); + key.stencilFormat = static_cast(formats.stencil); + } if (!bindVertexInputs(encoder, key, lp, 0, static_cast(list.size()), instances, &list)) return false; id pipeline = ctx_.share().pipelines().render(key, lp); if (!pipeline) return false; [encoder setRenderPipelineState:pipeline]; - bindUniforms(encoder, lp); - bindTextures(encoder, lp); + if (drawMode != GL_NONE) { + bindDrawState(encoder, lp); + } else { + bindUniforms(encoder, lp); + bindTextures(encoder, lp); + } const uint64_t serial = ctx_.stream().serial(); // Control block: active, vertex count, slot limit, pad, base[4], stride[4], vertex ids. const size_t controlBytes = 12 * 4 + list.size() * 4; @@ -1716,11 +1817,15 @@ void appendMetalList(GLenum mode, const std::vector& v, bool rotate, s for (int b = 0; b < bufferCount; ++b) { words[4 + b] = baseWords[b]; words[8 + b] = strides[b]; - [encoder setVertexBuffer:buffers[b] offset:0 atIndex:shader::kSlotFirstXfbBuffer + static_cast(b)]; + static id unused = [Device::get().mtl() newBufferWithLength:16 options:MTLResourceStorageModePrivate]; + [encoder setVertexBuffer:buffers[b] ?: unused offset:0 atIndex:shader::kSlotFirstXfbBuffer + static_cast(b)]; } std::memcpy(words + 12, list.data(), list.size() * 4); [encoder setVertexBuffer:control.buffer offset:control.offset atIndex:shader::kSlotXfbControl]; - [encoder drawPrimitives:MTLPrimitiveTypePoint vertexStart:0 vertexCount:list.size() + const MTLPrimitiveType primitive = drawMode == GL_LINES ? MTLPrimitiveTypeLine + : drawMode == GL_TRIANGLES ? MTLPrimitiveTypeTriangle + : MTLPrimitiveTypePoint; + [encoder drawPrimitives:primitive vertexStart:0 vertexCount:list.size() instanceCount:static_cast(std::max(1, instances)) baseInstance:baseInstance_]; // Later draws in this pass may read the captured data (as vertex, // uniform or texture buffers): Metal does not order work inside one @@ -1847,16 +1952,24 @@ GLenum geometryInputFor(GLenum mode) { LinkedProgram* current = executable(); if (!current) return; LinkedProgram& lp = *current; - id pipeline = lp.computePipeline(); - if (!pipeline) return; if (indirect && indirect->lastWriteSerial) ctx_.stream().waitFor(indirect->lastWriteSerial); prepareSamplerStorage(lp); + const int cs = static_cast(Stage::Compute); + const uint16_t borders = borderVariant(lp, cs); + id pipeline = borders ? lp.kernelWithBorders(lp.stage(Stage::Compute), cs, borders) : lp.computePipeline(); + if (!pipeline) return; id encoder = ctx_.stream().compute(); [encoder setComputePipelineState:pipeline]; bindKernelResources(encoder, lp, lp.stage(Stage::Compute).usesDefaultUniforms, static_cast(Stage::Compute), &lp.stage(Stage::Compute)); const uint32_t* local = lp.link.computeLocalSize; const MTLSize threads = MTLSizeMake(local[0], local[1], local[2]); + if (queriesCounting(ctx_, kStatComputeShaderInvocations)) { + uint32_t groups[3] = {x, y, z}; + if (indirect) std::memcpy(groups, indirect->contents() + offset, sizeof(groups)); // written by the GPU before the wait above + queriesCountStatistic(ctx_, kStatComputeShaderInvocations, + uint64_t{groups[0]} * groups[1] * groups[2] * local[0] * local[1] * local[2]); + } if (indirect) { indirect->lastUseSerial = ctx_.stream().serial(); [encoder dispatchThreadgroupsWithIndirectBuffer:indirect->mtl indirectBufferOffset:static_cast(offset) @@ -1937,8 +2050,11 @@ GLenum geometryInputFor(GLenum mode) { const uint64_t total = static_cast(primitives) * copies; // Kernel threads: one per input primitive and invocation (GS instancing). const uint64_t threads = total * static_cast(std::max(1, g.invocations)); + queriesCountStatistic(ctx_, kStatGeometryShaderInvocations, threads); const uint32_t maxV = static_cast(g.maxVertices); - id kernel = lp.geometryKernel(); + const uint16_t borders = borderVariant(lp, static_cast(Stage::Compute)); // the kernel's samplers + id kernel = + borders ? lp.kernelWithBorders(g.kernel, static_cast(Stage::Geometry), borders) : lp.geometryKernel(); if (!kernel || !lp.geometryPassthrough()) return; // Anything that flushes must happen before the transient allocations below. waitForCpuVertexReads(lp, true); @@ -2037,6 +2153,10 @@ GLenum geometryInputFor(GLenum mode) { writtenQuery[s] = ctx_.state.activeQueries[queryTargetIndex(ctx_, GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN, s)].get(); anyQuery |= generated[s] || writtenQuery[s]; } + const bool statistics = queriesCounting(ctx_, kStatGeometryShaderPrimitivesEmitted) || + queriesCounting(ctx_, kStatClippingInputPrimitives) || + queriesCounting(ctx_, kStatClippingOutputPrimitives); + anyQuery |= statistics; if (!capture && !anyQuery) return; ctx_.stream().waitFor(serial); const auto* slots = static_cast(indices.data); @@ -2065,8 +2185,13 @@ GLenum geometryInputFor(GLenum mode) { } } const uint32_t perPrimitive = g.outputType == GL_POINTS ? 1 : g.outputType == GL_LINE_STRIP ? 2 : 3; - for (int s = 0; s < kMaxVertexStreams; ++s) + for (int s = 0; s < kMaxVertexStreams; ++s) { if (generated[s]) generated[s]->result += emitted[s].size() / perPrimitive; + queriesCountStatistic(ctx_, kStatGeometryShaderPrimitivesEmitted, emitted[s].size() / perPrimitive); + } + // Stream 0 is the one rasterized. + queriesCountStatistic(ctx_, kStatClippingInputPrimitives, emitted[0].size() / perPrimitive); + queriesCountStatistic(ctx_, kStatClippingOutputPrimitives, emitted[0].size() / perPrimitive); if (!capture) return; const shader::LinkResult& link = lp.link; // Primitives each stream writes: what fits in every buffer it feeds; @@ -2075,6 +2200,7 @@ GLenum geometryInputFor(GLenum mode) { bool captured[kMaxVertexStreams] = {}; for (int s = 0; s < kMaxVertexStreams; ++s) written[s] = emitted[s].size() / perPrimitive; for (int b = 0; b < link.xfbBufferCount; ++b) { + if (!link.xfbBufferUsed(b)) continue; const TransformFeedback::Binding& binding = xfb.buffers[b]; if (!binding.buffer || !binding.buffer->mtl) return; const int s = link.xfbStream[b]; @@ -2088,6 +2214,7 @@ GLenum geometryInputFor(GLenum mode) { } for (int s = 0; s < kMaxVertexStreams; ++s) { if (!captured[s]) written[s] = 0; + else if (written[s] < emitted[s].size() / perPrimitive) queriesNoteXfbOverflow(ctx_, static_cast(s)); if (writtenQuery[s]) writtenQuery[s]->result += written[s]; xfb.primitivesWritten[static_cast(s)] += written[s]; } @@ -2107,6 +2234,7 @@ GLenum geometryInputFor(GLenum mode) { } } for (int b = 0; b < link.xfbBufferCount; ++b) { + if (!link.xfbBufferUsed(b)) continue; xfb.written[b] += static_cast(written[link.xfbStream[b]] * perPrimitive * link.xfbStride[b] * 4); ++xfb.buffers[b].buffer->version; } @@ -2145,9 +2273,11 @@ GLenum geometryInputFor(GLenum mode) { std::vector records; // control: the patch records, copied out of the transient memory const uint32_t recordWords = t.recordWords(); if (t.hasControl) { + const uint16_t borders = borderVariant(lp, static_cast(Stage::TessControl)); TransientAllocation output = transient_.allocate(total * recordWords * 4, serial, 16); id compute = ctx_.stream().compute(); - [compute setComputePipelineState:lp.tessControlKernel()]; + [compute setComputePipelineState:borders ? lp.kernelWithBorders(t.control, static_cast(Stage::TessControl), borders) + : lp.tessControlKernel()]; bindKernelResources(compute, lp, t.control.usesDefaultUniforms, static_cast(Stage::TessControl), &t.control); const uint32_t control[4] = {patchVertices, static_cast(total), static_cast(patches), 0}; [compute setBuffer:captured.buffer offset:captured.offset atIndex:shader::kSlotFirstXfbBuffer]; @@ -2199,6 +2329,13 @@ GLenum geometryInputFor(GLenum mode) { for (uint32_t index : patch.indices) indices.push_back(base + index); } const GLenum listMode = t.pointMode ? GL_POINTS : t.mode == GL_ISOLINES ? GL_LINES : GL_TRIANGLES; + if (t.hasControl) queriesCountStatistic(ctx_, kStatTessControlShaderPatches, total); + queriesCountStatistic(ctx_, kStatTessEvaluationShaderInvocations, points.size() / 4); + if (!evaluation->link.geometry.active) { + const uint64_t primitives = indices.size() / (listMode == GL_POINTS ? 1 : listMode == GL_LINES ? 2 : 3); + queriesCountStatistic(ctx_, kStatClippingInputPrimitives, primitives); + queriesCountStatistic(ctx_, kStatClippingOutputPrimitives, primitives); + } if (indices.empty()) return; // 4. The evaluation stage over the domain points, drawn (or captured, or @@ -2232,9 +2369,12 @@ GLenum geometryInputFor(GLenum mode) { uint32_t primitiveBase = 0; drawGeometry(listMode, indices, 1, primitiveBase); } else { - if (ctx_.state.transformFeedback->active) captureTransformFeedback(listMode, indices, 1); - issue(listMode, 0, static_cast(indices.size()), 1, indices.data(), nil, 0, MTLIndexTypeUInt32, 0, 0, - static_cast(points.size() / 4)); + const Capture captured = ctx_.state.transformFeedback->active ? captureTransformFeedback(listMode, indices, 1) : kNotCaptured; + if (captured == kCaptured && ctx_.state.rasterizerDiscard) + queriesCountPrimitives(ctx_, listMode, static_cast(indices.size()), 1); + else + issue(listMode, 0, static_cast(indices.size()), 1, indices.data(), nil, 0, MTLIndexTypeUInt32, 0, 0, + static_cast(points.size() / 4)); } override_ = nullptr; tessInputs_ = {}; @@ -2249,6 +2389,8 @@ GLenum geometryInputFor(GLenum mode) { } if (!validateDraw(mode)) return; if (count == 0 || instances == 0 || !conditionalRenderAllows(ctx_)) return; + // Pipeline statistics of the command; tessellation and geometry shaders count their own clipping. + queriesCountSubmitted(ctx_, mode, count, instances, mode != GL_PATCHES && executable() && !executable()->link.geometry.active); if (executable() && ffWideLines_) { std::vector vertices(static_cast(count)); for (GLsizei i = 0; i < count; ++i) vertices[static_cast(i)] = static_cast(first + i); @@ -2271,7 +2413,12 @@ GLenum geometryInputFor(GLenum mode) { if (ctx_.state.transformFeedback->active) { std::vector vertices(static_cast(count)); for (GLsizei i = 0; i < count; ++i) vertices[static_cast(i)] = static_cast(first + i); - captureTransformFeedback(mode, vertices, instances); + // Once the capture ran the vertex stage, a discarding draw has nothing left to do. + const Capture captured = captureTransformFeedback(mode, vertices, instances); + if (captured == kCapturedAndDrawn || (captured == kCaptured && ctx_.state.rasterizerDiscard)) { + queriesCountPrimitives(ctx_, mode, count, instances); + return; + } } issue(mode, first, count, instances, nullptr, nil, 0, MTLIndexTypeUInt16, 0, first, count); } @@ -2280,6 +2427,7 @@ GLenum geometryInputFor(GLenum mode) { GLint baseVertex) { ImageShadowScope shadows(*this); beginDraw(mode); + glBaseVertex_ = baseVertex; if (count < 0 || instances < 0) { ctx_.setError(GL_INVALID_VALUE); return; @@ -2393,6 +2541,16 @@ GLenum geometryInputFor(GLenum mode) { } }; const uint32_t bias = static_cast(baseVertex); + if (queriesCountingStatistics(ctx_)) { + // Restart indices are not vertices; each run between them counts its primitives. + const bool clipped = mode != GL_PATCHES && !executable()->link.geometry.active; + if (hasRestart && mode != GL_PATCHES) + forEachSegment(0, [&](const std::vector& segment) { + queriesCountSubmitted(ctx_, mode, static_cast(segment.size()), instances, clipped); + }); + else + queriesCountSubmitted(ctx_, mode, count, instances, clipped); + } if (ffWideLines_) { std::vector lines; forEachSegment(bias, [&](const std::vector& segment) { @@ -2428,7 +2586,11 @@ GLenum geometryInputFor(GLenum mode) { const std::vector part = assembleForCapture(mode, segment); captured.insert(captured.end(), part.begin(), part.end()); }); - if (!captured.empty()) captureTransformFeedback(listMode(mode), captured, instances); + const Capture done = captured.empty() ? kNotCaptured : captureTransformFeedback(listMode(mode), captured, instances); + if (done == kCapturedAndDrawn || (done == kCaptured && ctx_.state.rasterizerDiscard)) { + queriesCountPrimitives(ctx_, listMode(mode), static_cast(captured.size()), instances); + return; + } } const GLint spanFirst = static_cast(minIndex) + baseVertex; const GLsizei span = static_cast(std::min(uint64_t{maxIndex} - minIndex + 1, INT32_MAX)); diff --git a/src/core/driver.mm b/src/core/driver.mm index ca828080..7f9370e9 100644 --- a/src/core/driver.mm +++ b/src/core/driver.mm @@ -1,4 +1,4 @@ -// winemac.drv-facing ABI (include/winemetalgl/driver.h). +// The driver ABI the Wine module uses (include/winemetalgl/driver.h). #include "winemetalgl/driver.h" @@ -28,7 +28,7 @@ constexpr int kLoseContextOnReset = 0x8252; // The highest version this core implements completely. -constexpr int kHonestMajor = 4, kHonestMinor = 3; +constexpr int kHonestMajor = 4, kHonestMinor = 6; void honestMaxVersion(int& major, int& minor) { major = kHonestMajor; @@ -70,7 +70,7 @@ bool validVersion(int major, int minor) { WMGL_EXPORT int wmgl_initialize(unsigned int abi_version) { if (abi_version != WMGL_DRIVER_ABI_VERSION) { - WMGL_ERR("driver ABI mismatch: winemac %u, core %u", abi_version, WMGL_DRIVER_ABI_VERSION); + WMGL_ERR("driver ABI mismatch: caller %u, core %u", abi_version, WMGL_DRIVER_ABI_VERSION); return -1; } if (!Device::get().mtl()) return -1; @@ -218,6 +218,16 @@ WMGL_EXPORT int wmgl_make_current(wmgl_context* handle, wmgl_surface* draw, wmgl return rendererString().c_str(); } +namespace wmgl::api { +extern void* (*g_bufferAllocate)(size_t); +extern void (*g_bufferRelease)(void*, size_t); +} + +WMGL_EXPORT void wmgl_set_buffer_memory(void* (*allocate)(size_t), void (*release)(void*, size_t)) { + wmgl::api::g_bufferAllocate = allocate; + wmgl::api::g_bufferRelease = release; +} + WMGL_EXPORT void wmgl_version(int* major, int* minor, int* patch) { int v[3] = {0, 0, 0}; std::sscanf(WMGL_VERSION, "%d.%d.%d", &v[0], &v[1], &v[2]); diff --git a/src/core/format/formats.cpp b/src/core/format/formats.cpp index 6e70038e..5790360b 100644 --- a/src/core/format/formats.cpp +++ b/src/core/format/formats.cpp @@ -264,7 +264,7 @@ GLenum resolveInternalFormat(GLenum internalFormat, GLenum format, GLenum type) } } -bool isValidTextureInternalFormat(GLenum internalFormat, bool compatibility) { +bool isValidTextureInternalFormat(GLenum internalFormat, bool compatibility, bool stencilTextures) { switch (internalFormat) { case GL_RED: case GL_RG: case GL_RGB: case GL_RGBA: case GL_DEPTH_COMPONENT: case GL_DEPTH_STENCIL: case GL_COMPRESSED_RED: case GL_COMPRESSED_RG: case GL_COMPRESSED_RGB: case GL_COMPRESSED_RGBA: @@ -272,9 +272,10 @@ bool isValidTextureInternalFormat(GLenum internalFormat, bool compatibility) { return true; case 1: case 2: case 3: case 4: case GL_ALPHA: case GL_LUMINANCE: case GL_LUMINANCE_ALPHA: case GL_INTENSITY: return compatibility; - case GL_STENCIL_INDEX: - case GL_STENCIL_INDEX1: case GL_STENCIL_INDEX4: case GL_STENCIL_INDEX8: case GL_STENCIL_INDEX16: - return false; // stencil texturing is GL 4.4 + case GL_STENCIL_INDEX: case GL_STENCIL_INDEX8: + return stencilTextures; // GL 4.4, GL_ARB_texture_stencil8 + case GL_STENCIL_INDEX1: case GL_STENCIL_INDEX4: case GL_STENCIL_INDEX16: + return false; default: break; } const FormatInfo* info = formatInfo(internalFormat); diff --git a/src/core/format/formats.h b/src/core/format/formats.h index 351a1f0c..f8ac94a5 100644 --- a/src/core/format/formats.h +++ b/src/core/format/formats.h @@ -74,7 +74,8 @@ const FormatInfo* formatInfo(GLenum internalFormat); GLenum resolveInternalFormat(GLenum internalFormat, GLenum format, GLenum type); // True when the internal format is accepted by glTexImage* in this profile. -bool isValidTextureInternalFormat(GLenum internalFormat, bool compatibility); +// `stencilTextures`: STENCIL_INDEX formats are texture formats (GL 4.4, GL_ARB_texture_stencil8). +bool isValidTextureInternalFormat(GLenum internalFormat, bool compatibility, bool stencilTextures = false); // Bytes per texel for uncompressed storage, or per 4x4 block for BC formats. uint32_t storageBytes(Storage storage); diff --git a/src/core/limits.h b/src/core/limits.h index 1c9b6745..f0ab3f73 100644 --- a/src/core/limits.h +++ b/src/core/limits.h @@ -8,7 +8,11 @@ namespace wmgl::limits { constexpr GLint kMaxTextureSize = 16384, kMax3DTextureSize = 2048, kMaxArrayLayers = 2048; // Combined limits cover the five graphics stages of GL 4.0 (5 x per stage). constexpr GLint kMaxDrawBuffers = 8, kMaxVertexAttribs = 16, kMaxTextureUnitsPerStage = 16, kMaxCombinedTextureUnits = 80; -constexpr GLint kMaxUniformBlocksPerStage = 14, kMaxCombinedUniformBlocks = 70, kMaxUniformBufferBindings = 72; +// Varying components per stage interface (GL_MAX_*_INPUT/OUTPUT_COMPONENTS; +// four per location): the vertex outputs and geometry inputs at GL's +// minimum, the rest at 128. +constexpr GLint kMaxVertexOutputComponents = 64, kMaxGeometryInputComponents = 64, kMaxInterfaceComponents = 128; +constexpr GLint kMaxUniformBlocksPerStage = 14, kMaxCombinedUniformBlocks = 70, kMaxUniformBufferBindings = 84; constexpr GLint kMaxUniformBlockSize = 65536, kMaxDefaultUniformComponents = 4096; constexpr GLint kMaxTextureBufferSize = 1 << 27; // GL 4.3 glTexBufferRange: the largest alignment GL allows, which every Metal @@ -24,17 +28,20 @@ constexpr GLint kMaxComputeSharedMemorySize = 32768; constexpr GLint kMaxPatchVertices = 32, kMaxTessGenLevel = 64; // GL 4.2 image load/store and atomic counters. Counters live in fragment, // geometry (a compute kernel) and compute functions, whose Metal buffer -// slots have room for 8 counter buffers; vertex functions have none left -// (shader/compiler.h). -constexpr GLint kMaxImageUnits = 8, kMaxImageUniformsPerStage = 8, kMaxCombinedImageUniforms = 48; +// slots have room for 8 counter buffers, and in vertex functions, which keep +// theirs in the storage block argument buffer (shader/compiler.h). Image +// units are binding points only; a stage takes 8 images that it reads and +// writes (Metal's read_write textures), and more that it only reads or only +// writes (shader/compiler.h), so the per-stage limits stay at 8. +constexpr GLint kMaxImageUnits = 16, kMaxImageUniformsPerStage = 8, kMaxCombinedImageUniforms = 48; constexpr GLint kMaxAtomicCounterBufferBindings = 8, kMaxStageAtomicCounterBuffers = 8, kMaxStageAtomicCounters = 8; -constexpr GLint kMaxVertexAtomicCounterBuffers = 1; +constexpr GLint kMaxVertexAtomicCounterBuffers = 8; constexpr GLint kMaxAtomicCounterBufferSize = 16384; // GL 4.3 shader storage blocks: an argument buffer per stage function, // emulated geometry and tessellation stages included (shader/compiler.h). constexpr GLint kMaxShaderStorageBufferBindings = 16, kMaxStageStorageBlocks = 16, kMaxCombinedStorageBlocks = 48; constexpr GLint kMaxShaderStorageBlockSize = 1 << 27, kShaderStorageBufferOffsetAlignment = 16; -constexpr GLint kMaxCombinedShaderOutputResources = kMaxCombinedStorageBlocks + kMaxImageUnits + kMaxDrawBuffers; +constexpr GLint kMaxCombinedShaderOutputResources = kMaxCombinedStorageBlocks + kMaxImageUniformsPerStage + kMaxDrawBuffers; constexpr GLint kMaxUniformLocations = 1024; // GL 4.3 explicit uniform locations // GL 4.3 vertex attribute bindings (GL_MAX_VERTEX_ATTRIB_RELATIVE_OFFSET), GL 4.4 stride limit. constexpr GLint kMaxVertexAttribRelativeOffset = 2047, kMaxVertexAttribStride = 2048; diff --git a/src/core/metal/pipeline_cache.h b/src/core/metal/pipeline_cache.h index 13fa854d..e78d4e38 100644 --- a/src/core/metal/pipeline_cache.h +++ b/src/core/metal/pipeline_cache.h @@ -43,6 +43,9 @@ struct PipelineKey { uint8_t rasterization; uint8_t topologyClass; // MTLPrimitiveTopologyClass uint8_t geometryPass; // vertex function: the geometry emulation's pass-through + uint8_t cull; // cull distances the vertex function writes (LinkedProgram::fragmentVariant) + uint8_t countFragments; // fragment function counts its invocations (FRAGMENT_SHADER_INVOCATIONS) + uint16_t borders[2]; // clamp-to-border emulation variants: vertex, fragment (shader::borderVariantId) uint16_t colorFormats[8]; // MTLPixelFormat uint16_t depthFormat, stencilFormat; struct Blend { diff --git a/src/core/metal/pipeline_cache.mm b/src/core/metal/pipeline_cache.mm index ccaf1924..a9429b6d 100644 --- a/src/core/metal/pipeline_cache.mm +++ b/src/core/metal/pipeline_cache.mm @@ -32,14 +32,18 @@ stats::Timer timer(&stats::Frame::pipelineNanos); const bool pointSizeAllowed = key.topologyClass == MTLPrimitiveTopologyClassUnspecified || key.topologyClass == MTLPrimitiveTopologyClassPoint; - id vertex = !key.rasterization ? program.vertexNoRaster() - : !pointSizeAllowed ? program.vertexWithoutPointSize(key.geometryPass) - : key.geometryPass ? program.geometryPassthrough() - : program.function(shader::Stage::Vertex); - id fragment = key.sampleMask || key.logicOp || key.sampleShading || key.ignoreSampleMask - ? program.fragmentVariant(key.sampleMask, key.logicOp, key.colorFormats, key.sampleShading, - key.ignoreSampleMask) - : program.function(shader::Stage::Fragment); + id vertex = key.borders[0] && !key.geometryPass + ? program.vertexWithBorders(key.rasterization, pointSizeAllowed, key.borders[0]) + : !key.rasterization ? program.vertexNoRaster() + : !pointSizeAllowed ? program.vertexWithoutPointSize(key.geometryPass) + : key.geometryPass ? program.geometryPassthrough() + : program.function(shader::Stage::Vertex); + id fragment = + key.sampleMask || key.logicOp || key.sampleShading || key.ignoreSampleMask || key.cull || key.borders[1] || + key.countFragments + ? program.fragmentVariant(key.sampleMask, key.logicOp, key.colorFormats, key.sampleShading, key.ignoreSampleMask, + key.cull, key.borders[1], key.countFragments) + : program.function(shader::Stage::Fragment); if (!vertex) return nil; MTLRenderPipelineDescriptor* desc = [MTLRenderPipelineDescriptor new]; desc.vertexFunction = vertex; diff --git a/src/core/objects.h b/src/core/objects.h index 59c94268..110eccde 100644 --- a/src/core/objects.h +++ b/src/core/objects.h @@ -37,6 +37,9 @@ class Buffer : public RefCounted, public Labeled { GLsizeiptr size = 0; GLenum usage = GL_STATIC_DRAW; bool immutable = false; + // GL_AMD_pinned_memory: the storage is the application's memory (never + // replaced behind its back). + bool pinned = false; GLbitfield storageFlags = 0; // Mapping state (glMapBuffer*). bool mapped = false; @@ -68,6 +71,11 @@ class Buffer : public RefCounted, public Labeled { uint8_t* contents() const { return mtl ? static_cast(mtl.contents) : nullptr; } // Mapped in a way that forbids GPU use (persistent mappings allow it). bool mappedNonPersistent() const { return mapped && !(mapAccess & GL_MAP_PERSISTENT_BIT); } + // Whether [offset, offset + size) overlaps a non-persistent mapping: the + // range commands' INVALID_OPERATION (GL 4.5 sections 6.2.2, 6.3, 6.6). + bool rangeMapped(GLintptr offset, GLsizeiptr size) const { + return mappedNonPersistent() && offset < mapOffset + mapLength && mapOffset < offset + size; + } }; // --------------------------------------------------------------- samplers @@ -290,6 +298,11 @@ class Shader : public RefCounted, public Labeled { bool compiled = false; // glCompileShader has been called since the last source change bool compileStatus = false; std::string infoLog; + // GL_ARB_gl_spirv: the module glShaderBinary gave (SPIR_V_BINARY) and the + // GLSL glSpecializeShader made of it (shader/spirv_input.h). + bool spirv = false, specialized = false, spirvNamed = false; + std::vector spirvWords; + std::string spirvGlsl; bool deletePending = false; int attachCount = 0; }; @@ -360,8 +373,11 @@ class Query : public RefCounted, public Labeled { uint64_t result = 0; uint64_t pendingSerial = 0; // result is final once this serial completes bool resultFinal = true; // result holds the final value - // Visibility-result slots used by occlusion queries (summed on completion). + // Visibility-result slots used by occlusion queries, or the 32-bit + // counters of FRAGMENT_SHADER_INVOCATIONS (one per command buffer); summed + // on completion. std::vector, uint32_t>> visibilitySlots; + uint64_t counterSerial = 0; // the command buffer of the last counter slot // GPU times (seconds, host time base) for TIME_ELAPSED and TIMESTAMP, // written by command-buffer completion handlers. struct Timer { diff --git a/src/core/program.h b/src/core/program.h index 50d5c3d4..54764a7e 100644 --- a/src/core/program.h +++ b/src/core/program.h @@ -41,8 +41,19 @@ struct LinkedProgram { // mask (shader::fragmentWithSampleMask), and applying logical operation // `logicOp` - 1 (0: none; shader::fragmentWithLogicOp) to attachments of // `colorFormats` (MTLPixelFormat). Nil if the stage cannot have it. + // `cull`: the drawing vertex function's cull distance count, 0 for none + // (shader::fragmentWithCullDistances). + // `borders`: the clamp-to-border emulation variant (shader::borderVariantId; 0: none). + // `countInvocations`: FRAGMENT_SHADER_INVOCATIONS (shader::fragmentCountingInvocations). id fragmentVariant(bool sampleMask, unsigned logicOp, const uint16_t colorFormats[8], - bool sampleRate = false, bool ignoreSampleMask = false); + bool sampleRate = false, bool ignoreSampleMask = false, unsigned cull = 0, + uint16_t borders = 0, bool countInvocations = false); + // The vertex function (with rasterization, with or without [[point_size]], + // or without rasterization) with clamp-to-border emulation variant `borders`. + id vertexWithBorders(bool rasterization, bool pointSize, uint16_t borders); + // A kernel (the compute stage, or the geometry or tessellation control + // emulation's: `output`) with clamp-to-border emulation variant `borders`. + id kernelWithBorders(const shader::StageOutput& output, int stage, uint16_t borders); // The fragment stage writes gl_SampleMask. bool writesSampleMask(); id fragmentSampleMask() { return fragmentVariant(true, 0, nullptr); } @@ -87,6 +98,8 @@ struct LinkedProgram { bool writesViewportIndex() const { return (outputFlags() & kWritesViewport) != 0; } // ... or gl_Layer: Metal then needs the pipeline's input topology class. bool writesLayerOrViewport() const { return (outputFlags() & (kWritesViewport | kWritesLayer)) != 0; } + // The gl_CullDistance elements it writes (shader::addCullDistances). + int cullDistances() const { return (outputFlags() >> kCullShift) & 15; } const std::string& drawingVertexMsl() const { return link.geometry.active ? link.geometry.passthrough.msl : stage(shader::Stage::Vertex).msl; } @@ -99,7 +112,7 @@ struct LinkedProgram { std::atomic readsVertexId_{-1}; // What the drawing vertex and fragment functions write, found once in // their MSL (draws ask on every call): kWrites* bits. - enum : int { kWritesViewport = 1, kWritesLayer = 2, kWritesSampleMask = 4 }; + enum : int { kWritesViewport = 1, kWritesLayer = 2, kWritesSampleMask = 4, kCullShift = 4 }; mutable std::atomic outputFlags_{-1}; int outputFlags() const; id functions_[shader::kStageCount] = {}; @@ -107,6 +120,8 @@ struct LinkedProgram { id vertexNoRaster_ = nil; bool vertexNoRasterFailed_ = false; std::unordered_map> fragmentVariants_; // nil entries: failed + std::unordered_map> borderVertex_; + std::unordered_map> borderKernels_; id passthrough_ = nil; id noPointSize_[2] = {}; bool noPointSizeFailed_[2] = {}; diff --git a/src/core/program.mm b/src/core/program.mm index 6a5c8573..66fd5cb3 100644 --- a/src/core/program.mm +++ b/src/core/program.mm @@ -5,6 +5,7 @@ #include "core/metal/device.h" #include "core/objects.h" +#include "core/shader/border.h" #include "core/util/log.h" namespace wmgl { @@ -33,6 +34,14 @@ uint64_t nextLinkedProgramId() { return function; } +// A vertex function without the [[point_size]] output. The member stays +// (the code writing it is unchanged) as an unread varying. +std::string withoutPointSize(std::string msl) { + const std::string from = "[[point_size]]", to = "[[user(wmgl_point_size)]]"; + for (size_t at = msl.find(from); at != std::string::npos; at = msl.find(from, at + to.size())) msl.replace(at, from.size(), to); + return msl; +} + } // namespace id LinkedProgram::function(shader::Stage stage) { @@ -106,6 +115,9 @@ uint64_t nextLinkedProgramId() { const std::string& vertex = drawingVertexMsl(); flags = (vertex.find("[[viewport_array_index]]") != std::string::npos ? kWritesViewport : 0) | (vertex.find("[[render_target_array_index]]") != std::string::npos ? kWritesLayer : 0); + int culls = 0; + while (culls < 8 && vertex.find("[[user(cull" + std::to_string(culls) + ")]]") != std::string::npos) ++culls; + flags |= culls << kCullShift; if (pipeline) { const auto& fs = stageExecutable[static_cast(shader::Stage::Fragment)]; if (fs && fs->writesSampleMask()) flags |= kWritesSampleMask; @@ -120,19 +132,27 @@ uint64_t nextLinkedProgramId() { bool LinkedProgram::writesSampleMask() { return (outputFlags() & kWritesSampleMask) != 0; } id LinkedProgram::fragmentVariant(bool sampleMask, unsigned logicOp, const uint16_t colorFormats[8], - bool sampleRate, bool ignoreSampleMask) { + bool sampleRate, bool ignoreSampleMask, unsigned cull, uint16_t borders, + bool countInvocations) { if (pipeline) { const auto& fs = stageExecutable[static_cast(shader::Stage::Fragment)]; - return fs ? fs->fragmentVariant(sampleMask, logicOp, colorFormats, sampleRate, ignoreSampleMask) : nil; + return fs ? fs->fragmentVariant(sampleMask, logicOp, colorFormats, sampleRate, ignoreSampleMask, cull, borders, + countInvocations) + : nil; } std::string key = std::string(sampleMask ? "m" : "-") + (sampleRate ? "s" : "-") + (ignoreSampleMask ? "i" : "-") + - std::to_string(logicOp); + (countInvocations ? "n" : "-") + std::to_string(logicOp) + "c" + std::to_string(cull) + "b" + + std::to_string(borders); if (logicOp) for (int i = 0; i < 8; ++i) key += "/" + std::to_string(colorFormats[i]); std::lock_guard lock(mutex_); if (auto it = fragmentVariants_.find(key); it != fragmentVariants_.end()) return it->second; const shader::StageOutput& output = link.stages[static_cast(shader::Stage::Fragment)]; std::string msl = output.present ? output.msl : std::string(); + if (!msl.empty() && borders) msl = shader::mslWithBorders(msl, shader::borderVariantDefinitions(borders)); + // Counted before the cull test is added, which then runs first: culled primitives have no fragments. + if (!msl.empty() && countInvocations) msl = shader::fragmentCountingInvocations(msl); + if (!msl.empty() && cull) msl = shader::fragmentWithCullDistances(msl, static_cast(cull)); if (!msl.empty() && logicOp) { shader::LogicOpTarget targets[8]; for (int i = 0; i < 8; ++i) targets[i] = logicOpTarget(static_cast(colorFormats[i])); @@ -171,22 +191,36 @@ uint64_t nextLinkedProgramId() { if (noPointSize_[v] || noPointSizeFailed_[v]) return noPointSize_[v]; const shader::StageOutput& output = geometryPass ? link.geometry.passthrough : link.stages[static_cast(shader::Stage::Vertex)]; if (output.msl.empty()) return nil; - std::string msl = output.msl; - // The member stays (the code writing it is unchanged) as an unread varying. - const std::string from = "[[point_size]]", to = "[[user(wmgl_point_size)]]"; - for (size_t at = msl.find(from); at != std::string::npos; at = msl.find(from, at + to.size())) msl.replace(at, from.size(), to); - noPointSize_[v] = compileFunction(msl, output.entryPoint, 0); + noPointSize_[v] = compileFunction(withoutPointSize(output.msl), output.entryPoint, 0); noPointSizeFailed_[v] = noPointSize_[v] == nil; return noPointSize_[v]; } +id LinkedProgram::vertexWithBorders(bool rasterization, bool pointSize, uint16_t borders) { + const int vs = static_cast(shader::Stage::Vertex); + if (pipeline) return stageExecutable[vs] ? stageExecutable[vs]->vertexWithBorders(rasterization, pointSize, borders) : nil; + const std::string key = std::string(rasterization ? "r" : "-") + (pointSize ? "p" : "-") + std::to_string(borders); + std::lock_guard lock(mutex_); + if (auto it = borderVertex_.find(key); it != borderVertex_.end()) return it->second; + const shader::StageOutput& output = link.stages[vs]; + std::string msl = rasterization ? output.msl : output.mslNoRaster; + if (!msl.empty() && rasterization && !pointSize) msl = withoutPointSize(msl); + if (!msl.empty()) msl = shader::mslWithBorders(msl, shader::borderVariantDefinitions(borders)); + id function = msl.empty() ? nil : compileFunction(msl, output.entryPoint, vs); + borderVertex_[key] = function; + return function; +} + namespace { // `threads`: the work group size a compute program dispatches (0: any); the // compiler then fits the kernel to it (GL guarantees 1024 invocations). -id computePipelineFor(const shader::StageOutput& output, int stage, NSUInteger threads = 0) { +id computePipelineFor(const shader::StageOutput& output, int stage, NSUInteger threads = 0, + uint16_t borders = 0) { if (output.msl.empty()) return nil; - id function = compileFunction(output.msl, output.entryPoint, stage); + id function = + compileFunction(borders ? shader::mslWithBorders(output.msl, shader::borderVariantDefinitions(borders)) : output.msl, + output.entryPoint, stage); if (!function) return nil; NSError* error = nil; MTLComputePipelineDescriptor* desc = [[MTLComputePipelineDescriptor alloc] init]; @@ -231,6 +265,22 @@ uint64_t nextLinkedProgramId() { return compute_; } +id LinkedProgram::kernelWithBorders(const shader::StageOutput& output, int stage, uint16_t borders) { + if (pipeline && stage == static_cast(shader::Stage::Compute)) { + const auto& cs = stageExecutable[stage]; + return cs ? cs->kernelWithBorders(cs->link.stages[stage], stage, borders) : nil; + } + const std::string key = std::to_string(reinterpret_cast(&output)) + "/" + std::to_string(borders); + std::lock_guard lock(mutex_); + if (auto it = borderKernels_.find(key); it != borderKernels_.end()) return it->second; + const NSUInteger threads = stage == static_cast(shader::Stage::Compute) && &output == &link.stages[stage] + ? NSUInteger(link.computeLocalSize[0]) * link.computeLocalSize[1] * link.computeLocalSize[2] + : 0; + id state = computePipelineFor(output, stage, threads, borders); + borderKernels_[key] = state; + return state; +} + id LinkedProgram::tessControlKernel() { std::lock_guard lock(mutex_); if (tessKernel_ || tessKernelFailed_) return tessKernel_; diff --git a/src/core/query.h b/src/core/query.h index 878e00c6..a320f6a6 100644 --- a/src/core/query.h +++ b/src/core/query.h @@ -19,13 +19,35 @@ enum QueryIndex { kQueryXfbPrimitivesWritten, kQueryTimeElapsed, kQueryAnySamplesPassedConservative, - kQueryIndexCount, - // GL 4.0 indexed queries: PRIMITIVES_GENERATED and - // TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN of vertex streams 1-3. + // GL 4.6 / ARB_transform_feedback_overflow_query: TRANSFORM_FEEDBACK_OVERFLOW, + // and TRANSFORM_FEEDBACK_STREAM_OVERFLOW of vertex stream 0. + kQueryXfbOverflow, + kQueryXfbStreamOverflow, + // GL 4.6 / ARB_pipeline_statistics_query: kQueryStatisticsFirst + PipelineStatistic. + kQueryStatisticsFirst, + kQueryIndexCount = kQueryStatisticsFirst + 11, + // GL 4.0 indexed queries: PRIMITIVES_GENERATED, + // TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN and (GL 4.6) + // TRANSFORM_FEEDBACK_STREAM_OVERFLOW of vertex streams 1-3. kQueryStreamFirst = kQueryIndexCount, - kQuerySlotCount = kQueryStreamFirst + 6 + kQuerySlotCount = kQueryStreamFirst + 9 }; constexpr int kMaxVertexStreams = 4; +enum PipelineStatistic { + kStatVerticesSubmitted, + kStatPrimitivesSubmitted, + kStatVertexShaderInvocations, + kStatTessControlShaderPatches, + kStatTessEvaluationShaderInvocations, + kStatGeometryShaderInvocations, + kStatGeometryShaderPrimitivesEmitted, + kStatFragmentShaderInvocations, + kStatComputeShaderInvocations, + kStatClippingInputPrimitives, + kStatClippingOutputPrimitives, + kStatCount +}; +static_assert(kQueryStatisticsFirst + kStatCount == kQueryIndexCount); int queryTargetIndex(Context& ctx, GLenum target); // -1 if not a query target // The activeQueries slot of `target` for vertex stream `stream` (-1: invalid). int queryTargetIndex(Context& ctx, GLenum target, GLuint stream); @@ -39,6 +61,24 @@ void queriesPrepareVisibility(Context& ctx); void queriesBeforeDraw(Context& ctx, id encoder); void queriesSuspendVisibility(Context& ctx, id encoder); void queriesCountPrimitives(Context& ctx, GLenum mode, GLsizei count, GLsizei instances); +// Transform feedback of vertex stream `stream` dropped primitives for lack of +// buffer space: sets the active overflow queries. +void queriesNoteXfbOverflow(Context& ctx, GLuint stream); + +// Pipeline statistics (GL 4.6) counted on the CPU from what the draw paths +// do; FRAGMENT_SHADER_INVOCATIONS is counted by the fragment functions. +bool queriesCountingStatistics(Context& ctx); // any statistics query is active +bool queriesCounting(Context& ctx, PipelineStatistic statistic); +void queriesCountStatistic(Context& ctx, PipelineStatistic statistic, uint64_t n); +// One restart-delimited run of a draw command's vertices: +// VERTICES_SUBMITTED, PRIMITIVES_SUBMITTED, VERTEX_SHADER_INVOCATIONS and, +// with `clipped` (its primitives reach the clipper unchanged: no tessellation +// or geometry shader), CLIPPING_INPUT/OUTPUT_PRIMITIVES. Metal does not +// report what clipping does, so clipping output counts its input. +void queriesCountSubmitted(Context& ctx, GLenum mode, GLsizei vertices, GLsizei instances, bool clipped); +// FRAGMENT_SHADER_INVOCATIONS: the counter the current command buffer's +// draws increment, or false with no query active. +bool queriesFragmentCounter(Context& ctx, __strong id& buffer, NSUInteger& offset); uint64_t primitiveCount(GLenum mode, GLsizei count); // False when conditional rendering discards the current command. diff --git a/src/core/shader/border.cpp b/src/core/shader/border.cpp new file mode 100644 index 00000000..4d123214 --- /dev/null +++ b/src/core/shader/border.cpp @@ -0,0 +1,420 @@ +#include "core/shader/border.h" + +#include +#include +#include +#include +#include + +namespace wmgl::shader { +namespace { + +// The emulation's MSL helpers (one copy per variant function). +// +// A macro's arguments: r, the sampled or gathered value; t, s, the texture +// and sampler; c, the coordinates as GL gives them (an array's layer last); +// o, the texel offset (0: none); for samples lk (0: implicit level of +// detail, 1: explicit la, 2: implicit plus bias la, 3: gradients gx, gy) +// and d, the depth reference; for gathers k, the component. +// +// The value stays the hardware's while the filter footprint is inside the +// image along every CLAMP_TO_BORDER axis. Otherwise nearest filtering gives +// the border colour, and linear filtering is done here from texel reads, +// with the border colour for texels outside the image (and the texture's +// other wrap modes); a gather takes the border colour for its texels outside +// the image. +constexpr const char* kLibrary = R"MSL( +// ---- WineMetalGL: clamp-to-border emulation (core/shader/border.h) +struct wmgl_bp { + uint axes; // bits 0-2: S, T, R wrap CLAMP_TO_BORDER + uint filter; // 1: magnification linear, 2: minification linear, 4: mipmaps nearest, 8: linear, 16: rectangle + uint wrap; // 4 bits per axis: 0 border, 1 repeat, 2 mirrored repeat, 3 clamp to edge, 4 mirror clamp to edge + uint cmp; // compare function (GL_NEVER + n), 8: none + float minLod, maxLod, lodBias; +}; +inline int3 wmgl_off(int o) { return int3(o, 0, 0); } +inline int3 wmgl_off(int2 o) { return int3(o, 0); } +inline int3 wmgl_off(int3 o) { return o; } +inline float3 wmgl_f3(int) { return float3(0.0); } +inline float3 wmgl_f3(float v) { return float3(v, 0.0, 0.0); } +inline float3 wmgl_f3(float2 v) { return float3(v, 0.0); } +inline float3 wmgl_f3(float3 v) { return v; } +// Spatial coordinates, their count (w) and the layer. +template inline float4 wmgl_uvw(texture2d, float c) { return float4(c, 0.0, 0.0, 1.0); } +template inline float4 wmgl_uvw(texture2d, float2 c) { return float4(c, 0.0, 2.0); } +template inline float4 wmgl_uvw(texture2d_array, float2 c) { return float4(c.x, 0.0, 0.0, 1.0); } +template inline float4 wmgl_uvw(texture2d_array, float3 c) { return float4(c.xy, 0.0, 2.0); } +template inline float4 wmgl_uvw(texture3d, float3 c) { return float4(c, 3.0); } +inline float4 wmgl_uvw(depth2d, float c) { return float4(c, 0.0, 0.0, 1.0); } +inline float4 wmgl_uvw(depth2d, float2 c) { return float4(c, 0.0, 2.0); } +inline float4 wmgl_uvw(depth2d_array, float2 c) { return float4(c.x, 0.0, 0.0, 1.0); } +inline float4 wmgl_uvw(depth2d_array, float3 c) { return float4(c.xy, 0.0, 2.0); } +inline float wmgl_layer(float) { return 0.0; } +inline float wmgl_layer(float2 c) { return c.y; } +inline float wmgl_layer(float3 c) { return c.z; } +template inline uint wmgl_layers(texture2d) { return 1u; } +template inline uint wmgl_layers(texture3d) { return 1u; } +inline uint wmgl_layers(depth2d) { return 1u; } +template inline uint wmgl_layers(texture2d_array t) { return t.get_array_size(); } +inline uint wmgl_layers(depth2d_array t) { return t.get_array_size(); } +template inline uint wmgl_layer_of(T t, C c) { + return wmgl_layers(t) > 1u ? uint(clamp(floor(wmgl_layer(c) + 0.5), 0.0, float(wmgl_layers(t) - 1u))) : 0u; +} +template inline float3 wmgl_size(texture2d t, uint l) { return float3(t.get_width(l), t.get_height(l), 1.0); } +template inline float3 wmgl_size(texture2d_array t, uint l) { return float3(t.get_width(l), t.get_height(l), 1.0); } +template inline float3 wmgl_size(texture3d t, uint l) { + return float3(t.get_width(l), t.get_height(l), t.get_depth(l)); +} +inline float3 wmgl_size(depth2d t, uint l) { return float3(t.get_width(l), t.get_height(l), 1.0); } +inline float3 wmgl_size(depth2d_array t, uint l) { return float3(t.get_width(l), t.get_height(l), 1.0); } +template inline vec wmgl_read(texture2d t, int3 i, uint, uint l) { return t.read(uint2(i.xy), l); } +template inline vec wmgl_read(texture2d_array t, int3 i, uint a, uint l) { return t.read(uint2(i.xy), a, l); } +template inline vec wmgl_read(texture3d t, int3 i, uint, uint l) { return t.read(uint3(i), l); } +inline float wmgl_read(depth2d t, int3 i, uint, uint l) { return t.read(uint2(i.xy), l); } +inline float wmgl_read(depth2d_array t, int3 i, uint a, uint l) { return t.read(uint2(i.xy), a, l); } +#if WMGL_BORDER_FRAGMENT +template inline float wmgl_auto_lod(texture2d t, sampler s, float4 c) { return t.calculate_unclamped_lod(s, c.xy); } +template inline float wmgl_auto_lod(texture2d_array t, sampler s, float4 c) { return t.calculate_unclamped_lod(s, c.xy); } +template inline float wmgl_auto_lod(texture3d t, sampler s, float4 c) { return t.calculate_unclamped_lod(s, c.xyz); } +inline float wmgl_auto_lod(depth2d t, sampler s, float4 c) { return t.calculate_unclamped_lod(s, c.xy); } +inline float wmgl_auto_lod(depth2d_array t, sampler s, float4 c) { return t.calculate_unclamped_lod(s, c.xy); } +#else +template inline float wmgl_auto_lod(T, sampler, float4) { return 0.0; } +#endif +inline bool wmgl_integer(int) { return true; } +inline bool wmgl_integer(uint) { return true; } +inline bool wmgl_integer(float) { return false; } +inline float wmgl_compare(uint f, float ref, float texel) { + switch (f) { + case 0u: return 0.0; + case 1u: return ref < texel ? 1.0 : 0.0; + case 2u: return ref == texel ? 1.0 : 0.0; + case 3u: return ref <= texel ? 1.0 : 0.0; + case 4u: return ref > texel ? 1.0 : 0.0; + case 5u: return ref != texel ? 1.0 : 0.0; + case 6u: return ref >= texel ? 1.0 : 0.0; + default: return 1.0; + } +} +// A texel index after the axis' wrap mode; -1 outside a border axis. +inline int wmgl_wrap(int i, int size, uint mode) { + switch (mode) { + case 1u: return ((i % size) + size) % size; + case 2u: { const int m = ((i % (2 * size)) + 2 * size) % (2 * size); return m < size ? m : 2 * size - 1 - m; } + case 3u: return clamp(i, 0, size - 1); + case 4u: return clamp(i < 0 ? -1 - i : i, 0, size - 1); + default: return i >= 0 && i < size ? i : -1; + } +} +// The fraction of the filter footprint inside the image along the border axes. +inline float wmgl_inside(float4 c, int3 o, float3 size, uint axes, bool linear, bool rect) { + float w = 1.0; + for (uint a = 0u; a < uint(c.w); ++a) { + if (!(axes & (1u << a))) continue; + float x = (rect ? c[a] : c[a] * size[a]) + float(o[a]); + if (linear) { + x -= 0.5; + const float i0 = floor(x), f = x - i0; + w *= (i0 >= 0.0 && i0 < size[a] ? 1.0 - f : 0.0) + (i0 + 1.0 >= 0.0 && i0 + 1.0 < size[a] ? f : 0.0); + } else { + const float i = floor(x); + w *= i >= 0.0 && i < size[a] ? 1.0 : 0.0; + } + } + return w; +} +// Linear filtering at one level from texel reads (taps outside a border axis +// take `border`). Compare: each texel compared with ref first. +template inline V wmgl_linear(T t, float4 c, int3 o, uint layer, uint l, constant wmgl_bp& p, V border, + float ref) { + const bool rect = (p.filter & 16u) != 0u; + const float3 size = wmgl_size(t, l); + const uint dims = uint(c.w); + float3 x = (rect ? c.xyz : c.xyz * size) + float3(o) - 0.5; + const float3 base = floor(x), a = x - base; + V sum = V(0); + for (uint corner = 0u; corner < (1u << dims); ++corner) { + int3 i = int3(base); + float weight = 1.0; + bool inside = true; + for (uint axis = 0u; axis < dims; ++axis) { + const bool high = (corner >> axis) & 1u; + i[axis] += high ? 1 : 0; + weight *= high ? a[axis] : 1.0 - a[axis]; + const int wrapped = wmgl_wrap(i[axis], int(size[axis]), (p.wrap >> (4u * axis)) & 15u); + inside = inside && wrapped >= 0; + i[axis] = max(wrapped, 0); + } + if (dims < 2u) i.y = 0; + if (dims < 3u) i.z = 0; + V texel = border; + if (inside) texel = V(wmgl_read(t, i, layer, l)); + sum += weight * texel; + } + return sum; +} +template inline float wmgl_linear_compare(T t, float4 c, int3 o, uint layer, uint l, constant wmgl_bp& p, + float border, float ref) { + const bool rect = (p.filter & 16u) != 0u; + const float3 size = wmgl_size(t, l); + const uint dims = uint(c.w); + float3 x = (rect ? c.xyz : c.xyz * size) + float3(o) - 0.5; + const float3 base = floor(x), a = x - base; + float sum = 0.0; + for (uint corner = 0u; corner < (1u << dims); ++corner) { + int3 i = int3(base); + float weight = 1.0; + bool inside = true; + for (uint axis = 0u; axis < dims; ++axis) { + const bool high = (corner >> axis) & 1u; + i[axis] += high ? 1 : 0; + weight *= high ? a[axis] : 1.0 - a[axis]; + const int wrapped = wmgl_wrap(i[axis], int(size[axis]), (p.wrap >> (4u * axis)) & 15u); + inside = inside && wrapped >= 0; + i[axis] = max(wrapped, 0); + } + if (dims < 2u) i.y = 0; + sum += weight * wmgl_compare(p.cmp, ref, inside ? float(wmgl_read(t, i, layer, l)) : border); + } + return sum; +} +// Levels and filter (GL 4.6 section 8.14). +struct wmgl_lod_choice { uint l0, l1; float f; bool linear; }; +template inline wmgl_lod_choice wmgl_choose(T t, sampler s, float4 c, int lk, float la, float3 gx, float3 gy, + constant wmgl_bp& p) { + const bool rect = (p.filter & 16u) != 0u; + float lod = la; + if (lk == 3) { + const float3 size = rect ? float3(1.0) : wmgl_size(t, 0u); + lod = log2(max(length(gx * size), length(gy * size))); + } else if (lk != 1) { + lod = wmgl_auto_lod(t, s, c) + (lk == 2 ? la : 0.0); + } + lod = clamp(lod + p.lodBias, p.minLod, p.maxLod); + const uint top = rect ? 0u : t.get_num_mip_levels() - 1u; + wmgl_lod_choice r = {0u, 0u, 0.0, false}; + if (lod <= 0.0) { + r.linear = (p.filter & 1u) != 0u; + } else { + r.linear = (p.filter & 2u) != 0u; + if (p.filter & 4u) { + r.l0 = r.l1 = lod <= 0.5 ? 0u : min(uint(ceil(lod + 0.5)) - 1u, top); + } else if (p.filter & 8u) { + r.l0 = min(uint(floor(lod)), top); + r.l1 = min(r.l0 + 1u, top); + r.f = r.l0 == r.l1 ? 0.0 : fract(lod); + } + } + return r; +} +template +inline vec wmgl_border_sample(vec r, T t, sampler s, C cc, int3 o, int lk, float la, float3 gx, float3 gy, float, + constant wmgl_bp& p, vec border) { + const float4 c = wmgl_uvw(t, cc); + const bool rect = (p.filter & 16u) != 0u; + const wmgl_lod_choice q = wmgl_choose(t, s, c, lk, la, gx, gy, p); + float w = wmgl_inside(c, o, wmgl_size(t, q.l0), p.axes, q.linear, rect); + if (q.f > 0.0) w = mix(w, wmgl_inside(c, o, wmgl_size(t, q.l1), p.axes, q.linear, rect), q.f); + if (w >= 1.0) return r; + if (!q.linear && q.f == 0.0) return border; + if (wmgl_integer(B(0))) return w > 0.5 ? r : border; // integer textures are never filtered + const uint layer = wmgl_layer_of(t, cc); + float4 v = float4(wmgl_linear(t, c, o, layer, q.l0, p, float4(border), 0.0)); + if (q.f > 0.0) v = mix(v, float4(wmgl_linear(t, c, o, layer, q.l1, p, float4(border), 0.0)), q.f); + return vec(v); +} +// Shadow lookups: the comparison of the border's depth. +template +inline float wmgl_border_sample(float r, T t, sampler s, C cc, int3 o, int lk, float la, float3 gx, float3 gy, float ref, + constant wmgl_bp& p, float4 border) { + const float4 c = wmgl_uvw(t, cc); + const bool rect = (p.filter & 16u) != 0u; + const wmgl_lod_choice q = wmgl_choose(t, s, c, lk, la, gx, gy, p); + float w = wmgl_inside(c, o, wmgl_size(t, q.l0), p.axes, q.linear, rect); + if (q.f > 0.0) w = mix(w, wmgl_inside(c, o, wmgl_size(t, q.l1), p.axes, q.linear, rect), q.f); + if (w >= 1.0) return r; + if (!q.linear && q.f == 0.0) return wmgl_compare(p.cmp, ref, border.x); + const uint layer = wmgl_layer_of(t, cc); + float v = wmgl_linear_compare(t, c, o, layer, q.l0, p, border.x, ref); + if (q.f > 0.0) v = mix(v, wmgl_linear_compare(t, c, o, layer, q.l1, p, border.x, ref), q.f); + return v; +} +// Gathers (level 0): the four texels i0j1, i1j1, i1j0, i0j0. +inline bool4 wmgl_gather_outside(float4 c, int3 o, float3 size, uint axes, bool rect) { + const float2 x = (rect ? c.xy : c.xy * size.xy) + float2(o.xy) - 0.5; + const float2 i0 = floor(x), i1 = i0 + 1.0; + const bool ox0 = (axes & 1u) && (i0.x < 0.0 || i0.x >= size.x), ox1 = (axes & 1u) && (i1.x < 0.0 || i1.x >= size.x); + const bool oy0 = (axes & 2u) && (i0.y < 0.0 || i0.y >= size.y), oy1 = (axes & 2u) && (i1.y < 0.0 || i1.y >= size.y); + return bool4(ox0 || oy1, ox1 || oy1, ox1 || oy0, ox0 || oy0); +} +template +inline vec wmgl_border_gather(vec r, T t, C cc, int3 o, int k, float, constant wmgl_bp& p, vec border) { + const bool4 out = wmgl_gather_outside(wmgl_uvw(t, cc), o, wmgl_size(t, 0u), p.axes, (p.filter & 16u) != 0u); + return select(r, vec(border[clamp(k, 0, 3)]), out); +} +template +inline float4 wmgl_border_gather(float4 r, T t, C cc, int3 o, int, float ref, constant wmgl_bp& p, float4 border, bool) { + const bool4 out = wmgl_gather_outside(wmgl_uvw(t, cc), o, wmgl_size(t, 0u), p.axes, (p.filter & 16u) != 0u); + return select(r, float4(wmgl_compare(p.cmp, ref, border.x)), out); +} +// ---- end of clamp-to-border emulation +)MSL"; + +const char* kSampleArgs = "(r, t, s, c, o, lk, la, gx, gy, d)"; + +std::string hexBits(uint32_t bits) { + char text[16]; + std::snprintf(text, sizeof(text), "0x%08xu", bits); + return text; +} + +} // namespace + +std::string BorderMsl::to_texture_op(const spirv_cross::Instruction& i, bool sparse, bool* forward, + spirv_cross::SmallVector& inherited_expressions) { + std::string expr = CompilerMSL::to_texture_op(i, sparse, forward, inherited_expressions); + if (sparse) return expr; + const uint32_t* ops = stream(i); + bool gather = false, dref = false, proj = false; + switch (static_cast(i.op)) { + case spv::OpImageSampleImplicitLod: case spv::OpImageSampleExplicitLod: break; + case spv::OpImageSampleDrefImplicitLod: case spv::OpImageSampleDrefExplicitLod: dref = true; break; + case spv::OpImageSampleProjImplicitLod: case spv::OpImageSampleProjExplicitLod: proj = true; break; + case spv::OpImageSampleProjDrefImplicitLod: case spv::OpImageSampleProjDrefExplicitLod: proj = dref = true; break; + case spv::OpImageGather: gather = true; break; + case spv::OpImageDrefGather: gather = dref = true; break; + default: return expr; + } + const uint32_t img = ops[2], coord = ops[3]; + const spirv_cross::SPIRVariable* var = maybe_get_backing_variable(img); + if (!var) return expr; + auto slot = textureSlots.find(var->self); + if (slot == textureSlots.end() && var->basevariable) slot = textureSlots.find(var->basevariable); + if (slot == textureSlots.end()) return expr; + const spirv_cross::SPIRType& type = expression_type(img); + const spv::Dim dim = type.image.dim; + if (type.image.ms || (dim != spv::Dim1D && dim != spv::Dim2D && dim != spv::Dim3D && dim != spv::DimRect)) return expr; + uint32_t at = 4, drefId = 0, componentId = 0; + if (dref) drefId = ops[at++]; + else if (gather) componentId = ops[at++]; + const uint32_t mask = at < i.length ? ops[at++] : 0; + uint32_t bias = 0, lod = 0, gradX = 0, gradY = 0, offset = 0; + if (mask & spv::ImageOperandsBiasMask) bias = ops[at++]; + if (mask & spv::ImageOperandsLodMask) lod = ops[at++]; + if (mask & spv::ImageOperandsGradMask) { + gradX = ops[at++]; + gradY = ops[at++]; + } + if (mask & spv::ImageOperandsConstOffsetMask) offset = ops[at++]; + if (mask & spv::ImageOperandsOffsetMask) offset = ops[at++]; + if (mask & spv::ImageOperandsConstOffsetsMask) return expr; // textureGatherOffsets: not emulated + // The coordinates the image takes (an array's layer last): a depth + // comparison's coordinates may carry the reference too. + static const char* const kComponents[] = {"x", "y", "z", "w"}; + static const char* const kPrefix[] = {"", ".x", ".xy", ".xyz", ".xyzw"}; + const uint32_t size = expression_type(coord).vecsize; + const uint32_t n = (dim == spv::Dim1D ? 1 : dim == spv::Dim3D ? 3 : 2) + (type.image.arrayed ? 1 : 0); + std::string c = to_enclosed_expression(coord); + if (proj) { + if (size <= n || type.image.arrayed) return expr; + c = "(" + c + kPrefix[n] + " / " + c + "." + kComponents[size - 1] + ")"; + } else if (size > n) { + c += kPrefix[n]; + } + const std::string slotText = std::to_string(slot->second); + const std::string tex = to_expression(img), sampler = to_sampler_expression(img); + const std::string off = offset ? to_expression(offset) : "0"; + const std::string ref = drefId ? to_expression(drefId) : "0.0"; + if (gather) + return "wmgl_bcg_" + slotText + "(" + expr + ", " + tex + ", " + sampler + ", " + c + ", " + off + ", " + + (componentId ? to_expression(componentId) : "0") + ", " + ref + ")"; + const char* kind = lod ? "1" : bias ? "2" : gradX ? "3" : "0"; + const std::string la = lod ? to_expression(lod) : bias ? to_expression(bias) : "0.0"; + const std::string gx = gradX ? to_expression(gradX) : "0", gy = gradY ? to_expression(gradY) : "0"; + return "wmgl_bcs_" + slotText + "(" + expr + ", " + tex + ", " + sampler + ", " + c + ", " + off + ", " + kind + ", " + la + + ", " + gx + ", " + gy + ", " + ref + ")"; +} + +void addDefaultBorderMacros(std::string& msl) { + std::set names; + for (const char* prefix : {"wmgl_bcs_", "wmgl_bcg_"}) + for (size_t at = msl.find(prefix); at != std::string::npos; at = msl.find(prefix, at + 1)) { + size_t end = at + std::strlen(prefix); + while (end < msl.size() && msl[end] >= '0' && msl[end] <= '9') ++end; + if (end < msl.size() && msl[end] == '(') names.insert(msl.substr(at, end - at)); + } + if (names.empty()) return; + std::string defines = "\n// Clamp-to-border emulation off (core/shader/border.h).\n"; + for (const std::string& name : names) defines += "#define " + name + "(r, ...) (r)\n"; + const std::string anchor = "using namespace metal;\n"; + const size_t at = msl.find(anchor); + if (at == std::string::npos) msl.insert(0, defines); + else msl.insert(at + anchor.size(), defines); +} + +std::string borderDefinitions(const std::vector& slots, bool fragment) { + std::string out = fragment ? "#define WMGL_BORDER_FRAGMENT 1\n" : "#define WMGL_BORDER_FRAGMENT 0\n"; + for (const BorderSlot& s : slots) { + const std::string n = std::to_string(s.texture); + const uint32_t filter = (s.magLinear ? 1u : 0u) | (s.minLinear ? 2u : 0u) | (s.mip == 1 ? 4u : s.mip == 2 ? 8u : 0u) | + (s.rect ? 16u : 0u); + char params[192]; + std::snprintf(params, sizeof(params), "{%uu, %uu, %uu, %uu, %.9g, %.9g, %.9g}", s.axes, filter, s.wrap, + s.compare ? s.compare - 0x0200u : 8u, s.minLod, s.maxLod, s.lodBias); + const std::string p(params); + const std::string type = s.kind == 'i' ? "int4" : s.kind == 'u' ? "uint4" : "float4"; + const std::string element = s.kind == 'i' ? "int" : s.kind == 'u' ? "uint" : "float"; + std::string border = type + "("; + for (int k = 0; k < 4; ++k) + border += (k ? ", " : "") + std::string("as_type<") + element + ">(" + hexBits(s.border[k]) + ")"; + border += ")"; + out += "constant wmgl_bp wmgl_bp_" + n + " = " + p + ";\n"; + out += "#define wmgl_bcs_" + n + kSampleArgs + " wmgl_border_sample(r, t, s, c, wmgl_off(o), lk, la, wmgl_f3(gx), " + "wmgl_f3(gy), d, wmgl_bp_" + n + ", " + border + ")\n"; + out += "#define wmgl_bcg_" + n + "(r, t, s, c, o, k, d) wmgl_border_gather(r, t, c, wmgl_off(o), k, d, wmgl_bp_" + n + + ", " + border + (s.compare ? ", true" : "") + ")\n"; + } + return out; +} + +std::string mslWithBorders(const std::string& msl, const std::string& definitions) { + std::string out = msl; + // Drop the default definitions of the macros the variant defines. + for (size_t at = definitions.find("#define wmgl_bc"); at != std::string::npos; + at = definitions.find("#define wmgl_bc", at + 1)) { + const size_t open = definitions.find('(', at); + const std::string name = definitions.substr(at + 8, open - at - 8); + const std::string line = "#define " + name + "(r, ...) (r)\n"; + if (const size_t found = out.find(line); found != std::string::npos) out.erase(found, line.size()); + } + const std::string anchor = "using namespace metal;\n"; + const size_t at = out.find(anchor); + const std::string text = std::string(kLibrary).insert(0, definitions.substr(0, definitions.find('\n') + 1)) + + definitions.substr(definitions.find('\n') + 1); + if (at == std::string::npos) out.insert(0, text); + else out.insert(at + anchor.size(), text); + return out; +} + +namespace { +std::mutex variantsMutex; +std::map variantIds; +std::vector variants; +} // namespace + +uint16_t borderVariantId(const std::string& definitions) { + std::lock_guard lock(variantsMutex); + if (auto it = variantIds.find(definitions); it != variantIds.end()) return it->second; + if (variants.size() >= 0xfffe) return 0; // past 65534 distinct borders: emulation off + variants.push_back(definitions); + const uint16_t id = static_cast(variants.size()); + variantIds.emplace(definitions, id); + return id; +} + +std::string borderVariantDefinitions(uint16_t id) { + std::lock_guard lock(variantsMutex); + return id && id <= variants.size() ? variants[id - 1] : std::string(); +} + +} // namespace wmgl::shader diff --git a/src/core/shader/border.h b/src/core/shader/border.h new file mode 100644 index 00000000..0dea6c06 --- /dev/null +++ b/src/core/shader/border.h @@ -0,0 +1,65 @@ +// Sampler border colours Metal cannot give (it has three: transparent black, +// opaque black and opaque white), and integer textures' border colours: +// clamp-to-border emulated in the shader. +// +// Every stage is translated with BorderMsl, which wraps each sample and +// gather of a non-arrayed 1D, 2D, 3D or rectangle texture in a macro named +// after the texture's Metal index (wmgl_bcs_N for samples, wmgl_bcg_N for +// gathers). The default definitions (addDefaultBorderMacros) keep only the +// sampled value, so the common path costs nothing. A draw that binds such a +// border uses a variant of the function (mslWithBorders) whose macros for +// those textures substitute the border colour where the filter footprint +// leaves the image along a CLAMP_TO_BORDER axis. +#pragma once + +#include + +#include +#include +#include +#include + +namespace wmgl::shader { + +class BorderMsl : public spirv_cross::CompilerMSL { +public: + using CompilerMSL::CompilerMSL; + // Metal texture index of each non-array sampled image variable. + std::unordered_map textureSlots; + +protected: + std::string to_texture_op(const spirv_cross::Instruction& i, bool sparse, bool* forward, + spirv_cross::SmallVector& inherited_expressions) override; +}; + +// Defines the wrapping macros the MSL uses (after "using namespace metal;"). +void addDefaultBorderMacros(std::string& msl); + +// One texture a draw samples with an emulated border. +struct BorderSlot { + uint32_t texture = 0; // Metal texture index in the stage + char kind = 'f'; // texel type: 'f' (float, depth), 'i', 'u' + uint32_t axes = 0; // bits 0-2: S, T, R wrap CLAMP_TO_BORDER + uint32_t wrap = 0; // 4 bits per axis: 0 border, 1 repeat, 2 mirrored repeat, 3 clamp to edge, 4 mirror clamp to edge + bool magLinear = false, minLinear = false; + int mip = 0; // 0: no mipmaps, 1: nearest, 2: linear + bool rect = false; // unnormalized coordinates + float minLod = -1000.0f, maxLod = 1000.0f, lodBias = 0.0f; + uint32_t compare = 0; // GL compare function of a shadow sampler, 0: none + uint32_t border[4] = {}; // the GL border colour after the texture's swizzle (bits) +}; + +// The macro definitions a draw's border textures need in one stage +// (deterministic text: also the variant's cache key). +std::string borderDefinitions(const std::vector& slots, bool fragment); + +// `msl` with the definitions of `definitions` in place of the defaults, and +// the emulation's helper functions. +std::string mslWithBorders(const std::string& msl, const std::string& definitions); + +// Definitions are numbered from 1 as they first appear (pipeline keys hold +// the number); 0 is none. +uint16_t borderVariantId(const std::string& definitions); +std::string borderVariantDefinitions(uint16_t id); + +} // namespace wmgl::shader diff --git a/src/core/shader/compiler.cpp b/src/core/shader/compiler.cpp index 0d557c71..88794e69 100644 --- a/src/core/shader/compiler.cpp +++ b/src/core/shader/compiler.cpp @@ -11,11 +11,13 @@ #include #include #include +#include #include #include #include #include +#include "core/shader/border.h" #include "core/shader/fp64.h" #include "core/shader/glsl_upgrade.h" #include "core/limits.h" @@ -70,6 +72,9 @@ const TBuiltInResource* resources() { x.maxVertexUniformComponents = x.maxFragmentUniformComponents = x.maxGeometryUniformComponents = x.maxTessControlUniformComponents = x.maxTessEvaluationUniformComponents = x.maxComputeUniformComponents = L::kMaxDefaultUniformComponents; + // gl_MaxVertexUniformVectors and gl_MaxFragmentUniformVectors match the + // API's GL_MAX_*_UNIFORM_VECTORS (KHR-GL46.limits). + x.maxVertexUniformVectors = x.maxFragmentUniformVectors = L::kMaxDefaultUniformComponents / 4; x.maxComputeTextureImageUnits = L::kMaxTextureUnitsPerStage; x.maxComputeWorkGroupCountX = x.maxComputeWorkGroupCountY = x.maxComputeWorkGroupCountZ = L::kMaxComputeWorkGroupCount; x.maxComputeWorkGroupSizeX = L::kMaxComputeWorkGroupSize[0]; @@ -109,8 +114,13 @@ spv::ExecutionModel executionModel(Stage stage) { const EShMessages kMessages = static_cast(EShMsgSpvRules | EShMsgVulkanRules); +// Macros of extensions glslang implements without knowing their names +// (raiseComputeVersion), predefined like its own. +constexpr const char* kPreamble = "#define GL_ARB_cull_distance 1\n"; + // Configures a shader for codegen: relaxed Vulkan rules, one default block. void configure(glslang::TShader& shader, EShLanguage lang) { + shader.setPreamble(kPreamble); shader.setEnvInput(glslang::EShSourceGlsl, lang, glslang::EShClientVulkan, 100); shader.setEnvClient(glslang::EShClientVulkan, glslang::EShTargetVulkan_1_1); shader.setEnvTarget(glslang::EShTargetSpv, glslang::EShTargetSpv_1_3); @@ -154,6 +164,46 @@ void ensureVertexOutput(std::string& msl) { msl = head + main; } +// Cull distances (the gl_CullDistance output is masked, so SPIRV-Cross +// declares it as a local array): each element becomes a [[user(cullN)]] +// varying, the name SPIRV-Cross gives a fragment stage's gl_CullDistance +// inputs, and the fragment stage culls (fragmentWithCullDistances). +void addCullDistances(std::string& msl) { + const size_t fn = msl.find("vertex main0_out main0("); + const size_t structAt = msl.find("struct main0_out\n{\n"); + if (fn == std::string::npos || structAt == std::string::npos || structAt > fn) return; + // "spvUnsafeArray gl_CullDistance" or "float gl_CullDistance[N]". + int count = 0; + if (const size_t at = msl.find("> gl_CullDistance", fn); at != std::string::npos) { + const size_t comma = msl.rfind(", ", at); + if (comma != std::string::npos) count = std::atoi(msl.c_str() + comma + 2); + } else if (const size_t native = msl.find("float gl_CullDistance[", fn); native != std::string::npos) { + count = std::atoi(msl.c_str() + native + 22); + } + // gl_PerVertex declares the array whether or not the shader writes it. + size_t uses = 0; + for (size_t at = msl.find("gl_CullDistance", fn); at != std::string::npos; at = msl.find("gl_CullDistance", at + 1)) ++uses; + if (count <= 0 || uses < 2) return; + // A second varying per distance is 0 at vertices where it is negative and + // 1e30 elsewhere. Interpolated, it is exactly 0 only where every vertex + // of the primitive has it negative (or at a fragment on an edge or + // vertex of such vertices). + std::string members, assign; + for (int i = 0; i < count; ++i) { + const std::string k = std::to_string(i); + members += " float wmgl_cull" + k + " [[user(cull" + k + ")]];\n"; + members += " float wmgl_cull_keep" + k + " [[user(wmgl_cull" + k + ")]];\n"; + assign += "out.wmgl_cull" + k + " = gl_CullDistance[" + k + "]; "; + assign += "out.wmgl_cull_keep" + k + " = gl_CullDistance[" + k + "] < 0.0 ? 0.0 : 1e30; "; + } + std::string main = msl.substr(fn); + for (size_t at = main.find("return out;"); at != std::string::npos; at = main.find("return out;", at + assign.size() + 11)) + main.insert(at, assign); + std::string head = msl.substr(0, fn); + head.insert(structAt + std::string("struct main0_out\n{\n").size(), members); + msl = head + main; +} + // Every vertex function writes [[point_size]] (Metal leaves it undefined // otherwise). GL 3.3: the size is the shader's gl_PointSize when // PROGRAM_POINT_SIZE is enabled, else glPointSize; both come from the driver @@ -385,6 +435,49 @@ std::string fragmentIgnoringSampleMask(const std::string& msl) { return out; } +std::string fragmentWithCullDistances(const std::string& msl, int count) { + if (count <= 0 || count > 8) return msl; + std::string out = msl; + size_t fn = out.find("fragment main0_out main0("); + if (fn == std::string::npos) fn = out.find("fragment void main0("); + if (fn == std::string::npos) return std::string(); + const std::string inStruct = "struct main0_in\n{\n"; + size_t structAt = out.find(inStruct); + if (structAt == std::string::npos || structAt > fn) { + const size_t open = out.find('(', fn); + const bool noParameters = out[open + 1] == ')'; + out.insert(open + 1, std::string("main0_in in [[stage_in]]") + (noParameters ? "" : ", ")); + // Before the function's line, which may start with [[early_fragment_tests]]. + const size_t line = out.rfind('\n', fn) + 1; + out.insert(line, inStruct + "};\n\n"); + structAt = line; + } + std::string members, test = "\n if ("; + for (int k = 0; k < count; ++k) { + const std::string n = std::to_string(k); + members += " float wmgl_cull_keep" + n + " [[user(wmgl_cull" + n + "), center_no_perspective]];\n"; + test += std::string(k ? " || " : "") + "in.wmgl_cull_keep" + n + " == 0.0"; + } + out.insert(out.find("};", structAt), members); + fn = out.find("fragment ", structAt); + out.insert(out.find('{', out.find(')', fn)) + 1, test + ")\n discard_fragment();"); + return out; +} + +std::string fragmentCountingInvocations(const std::string& msl) { + std::string out = msl; + size_t fn = out.find("fragment main0_out main0("); + if (fn == std::string::npos) fn = out.find("fragment void main0("); + if (fn == std::string::npos) return std::string(); + const size_t open = out.find('(', fn); + const bool noParameters = out[open + 1] == ')'; + out.insert(open + 1, "device atomic_uint* wmgl_fs_invocations [[buffer(" + std::to_string(kSlotFragmentInvocations) + ")]]" + + (noParameters ? "" : ", ")); + out.insert(out.find('{', out.find(')', fn)) + 1, + "\n atomic_fetch_add_explicit(wmgl_fs_invocations, 1u, memory_order_relaxed);"); + return out; +} + std::string fragmentWithSampleMask(const std::string& msl) { const std::string signature = "fragment main0_out main0(", member = "struct main0_out\n{\n"; const size_t structAt = msl.find(member); @@ -1183,19 +1276,106 @@ const char* glTypeName(GLenum type) { namespace { -// GL_ARB_compute_shader makes compute shaders available to any GLSL version; -// glslang only accepts them from 4.20, so such sources are compiled as 4.30. +// Extensions glslang takes only in a later GLSL version. GL_ARB_compute_shader +// makes compute shaders available to any GLSL version; glslang only accepts +// them from 4.20, so such sources are compiled as 4.30. std::string raiseComputeVersion(Stage stage, const std::string& source) { - if (stage != Stage::Compute || parseGlslVersion(source).number >= 420) return source; - if (source.find("GL_ARB_compute_shader") == std::string::npos) return source; - const size_t at = source.find("#version"); - if (at == std::string::npos) return "#version 430\n" + source; - const size_t end = source.find('\n', at); std::string raised = source; + // GL_ARB_cull_distance (GLSL 4.50 core): glslang has gl_CullDistance in + // GLSL 4.50 only and no such extension, so the #extension line goes + // (kPreamble defines the macro) and the shader becomes GLSL 4.50. + const GlslVersion version = parseGlslVersion(source); + bool cull = false; + for (size_t at = raised.find("#extension"); at != std::string::npos; at = raised.find("#extension", at + 1)) { + const size_t end = raised.find('\n', at); + const std::string line = raised.substr(at, end == std::string::npos ? std::string::npos : end - at); + std::istringstream words(line.substr(10)); + std::string name; + words >> name; + if (name != "GL_ARB_cull_distance") continue; + raised.replace(at, line.size(), ""); + cull = true; + } + if (cull && version.number < 450 && !version.es) { + const size_t at = raised.find("#version"); + if (at != std::string::npos) { + const size_t end = raised.find('\n', at); + raised.replace(at, end == std::string::npos ? std::string::npos : end - at, + version.compatibility ? "#version 450 compatibility" : "#version 450"); + } + } + if (stage != Stage::Compute || version.number >= 420 || cull) return raised; + if (raised.find("GL_ARB_compute_shader") == std::string::npos) return raised; + const size_t at = raised.find("#version"); + if (at == std::string::npos) return "#version 430\n" + raised; + const size_t end = raised.find('\n', at); raised.replace(at, end == std::string::npos ? std::string::npos : end - at, "#version 430"); return raised; } +// Explicit locations of a stage's inputs and outputs must fit the stage's +// interface (GL 4.6 section 11.1.2.1: a compile-time or link-time error): +// GL_MAX_VERTEX_ATTRIBS for vertex inputs, GL_MAX_DRAW_BUFFERS for fragment +// outputs, the GL_MAX_*_COMPONENTS limits / 4 between stages. +std::string checkInterfaceLocations(glslang::TShader& shader, Stage stage) { + const glslang::TIntermediate* intermediate = shader.getIntermediate(); + glslang::TIntermAggregate* root = + intermediate && intermediate->getTreeRoot() ? intermediate->getTreeRoot()->getAsAggregate() : nullptr; + if (!root) return {}; + namespace L = wmgl::limits; + const EShLanguage lang = language(stage); + auto limit = [&](bool input) -> int { + switch (stage) { + case Stage::Vertex: return input ? L::kMaxVertexAttribs : L::kMaxVertexOutputComponents / 4; + case Stage::Geometry: return (input ? L::kMaxGeometryInputComponents : L::kMaxInterfaceComponents) / 4; + case Stage::Fragment: return input ? L::kMaxInterfaceComponents / 4 : L::kMaxDrawBuffers; + case Stage::Compute: return 0; + default: return L::kMaxInterfaceComponents / 4; + } + }; + auto size = [&](const glslang::TType& type) { + if (type.isArray() && type.getQualifier().isArrayedIo(lang)) { + const glslang::TType element(type, 0); + return glslang::TIntermediate::computeTypeLocationSize(element, lang); + } + return glslang::TIntermediate::computeTypeLocationSize(type, lang); + }; + for (TIntermNode* node : root->getSequence()) { + glslang::TIntermAggregate* objects = node->getAsAggregate(); + if (!objects || objects->getOp() != glslang::EOpLinkerObjects) continue; + for (TIntermNode* object : objects->getSequence()) { + glslang::TIntermSymbol* symbol = object->getAsSymbolNode(); + if (!symbol) continue; + const glslang::TType& type = symbol->getType(); + const glslang::TQualifier& q = type.getQualifier(); + const bool input = q.storage == glslang::EvqVaryingIn, output = q.storage == glslang::EvqVaryingOut; + if ((!input && !output) || q.builtIn != glslang::EbvNone || q.patch) continue; + const int max = limit(input); + if (type.getBasicType() == glslang::EbtBlock) { + // Members take the block's location onwards, or their own. + int next = q.hasLocation() ? static_cast(q.layoutLocation) : -1; + for (const glslang::TTypeLoc& member : *type.getStruct()) { + const glslang::TQualifier& mq = member.type->getQualifier(); + if (mq.builtIn != glslang::EbvNone) continue; + if (mq.hasLocation()) next = static_cast(mq.layoutLocation); + if (next < 0) continue; + next += glslang::TIntermediate::computeTypeLocationSize(*member.type, lang); + if (next > max) + return "ERROR: block '" + unescapeName(type.getTypeName().c_str()) + + "': a member's location is outside the stage's interface\n"; + } + continue; + } + if (!q.hasLocation()) continue; + if (static_cast(q.layoutLocation) + size(type) > max) + return "ERROR: '" + unescapeName(symbol->getName().c_str()) + "': location " + + std::to_string(q.layoutLocation) + " is outside the stage's interface (" + std::to_string(max) + + " locations)\n"; + } + } + return {}; +} + // layout(binding) on a uniform block must leave every element of the block // within GL_MAX_UNIFORM_BUFFER_BINDINGS (a compile-time error in GLSL). // glslang checks opaque bindings itself but has no limit for blocks. @@ -1244,6 +1424,7 @@ CompileResult compile(Stage stage, const std::string& original) { glslang::TShader shader(lang); const char* text = source.c_str(); shader.setStrings(&text, 1); + shader.setPreamble(kPreamble); shader.setEnvInput(glslang::EShSourceGlsl, lang, glslang::EShClientNone, 0); shader.setEnvClient(glslang::EShClientNone, glslang::EShTargetClientVersion(0)); shader.setEnvTarget(glslang::EShTargetNone, glslang::EShTargetLanguageVersion(0)); @@ -1254,6 +1435,7 @@ CompileResult compile(Stage stage, const std::string& original) { // desktop drivers evaluate it. Accept the shader if that is the only problem. glslang::TShader relaxed(lang); relaxed.setStrings(&text, 1); + relaxed.setPreamble(kPreamble); relaxed.setEnvInput(glslang::EShSourceGlsl, lang, glslang::EShClientNone, 0); relaxed.setEnvClient(glslang::EShClientNone, glslang::EShTargetClientVersion(0)); relaxed.setEnvTarget(glslang::EShTargetNone, glslang::EShTargetLanguageVersion(0)); @@ -1266,10 +1448,11 @@ CompileResult compile(Stage stage, const std::string& original) { } } if (result.ok) { - const std::string bindings = checkBlockBindings(shader); - if (!bindings.empty()) { + std::string errors = checkBlockBindings(shader); + if (errors.empty()) errors = checkInterfaceLocations(shader, stage); + if (!errors.empty()) { result.ok = false; - result.log += bindings; + result.log += errors; } } if (result.ok) { @@ -1319,7 +1502,7 @@ GLenum glTypeOfAst(const glslang::TType& t) { } // The outputs a stage can capture: its user outputs plus the built-in -// gl_Position, gl_PointSize and gl_ClipDistance. +// gl_Position, gl_PointSize, gl_ClipDistance and gl_CullDistance. std::vector captureableOutputs(glslang::TIntermediate* intermediate) { std::vector outputs; bool clipDistance = false; @@ -1342,7 +1525,20 @@ std::vector captureableOutputs(glslang::TIntermediate* intermedi outputs.push_back({"gl_ClipDistance", GL_FLOAT, m.getOuterArraySize()}); clipDistance = true; } - if (blockName == "gl_PerVertex" || field.rfind("gl_", 0) == 0 || type.isArray()) continue; + if (field == "gl_CullDistance" && m.isSizedArray()) + outputs.push_back({"gl_CullDistance", GL_FLOAT, m.getOuterArraySize()}); + if (blockName == "gl_PerVertex" || field.rfind("gl_", 0) == 0) continue; + if (type.isArray()) { + // An array of blocks: "Block[k].member" (GL 4.6 section 7.3.1.1). + if (anonymous || !type.isSizedArray()) continue; + for (int k = 0; k < type.getOuterArraySize(); ++k) { + const std::string element = "[" + std::to_string(k) + "]."; + outputs.push_back({blockName + element + field, glTypeOfAst(m), + m.isSizedArray() ? m.getOuterArraySize() : 0, + std::string(symbol->getName().c_str()) + element + m.getFieldName().c_str()}); + } + continue; + } // User blocks: members are named "Block.member" (GL 3.3 section 2.15); // members of a block without an instance name also by their own name. const std::string expression = anonymous ? m.getFieldName().c_str() @@ -1375,6 +1571,122 @@ std::vector captureableOutputs(glslang::TIntermediate* intermedi return outputs; } +// Transform feedback declared in the shader (GL 4.4, enhanced layouts: +// xfb_buffer, xfb_offset, xfb_stride on the last vertex processing stage's +// outputs). It replaces glTransformFeedbackVaryings. Returns false when the +// stage declares none. +bool layoutShaderTransformFeedback(glslang::TIntermediate* last, const std::vector& outputs, LinkResult& result) { + if (!last || !last->getXfbMode()) return false; + glslang::TIntermAggregate* root = last->getTreeRoot() ? last->getTreeRoot()->getAsAggregate() : nullptr; + if (!root) return false; + std::vector varyings; + // A struct is captured member by member from its offset, each member + // aligned to its own size class (4 bytes, 8 for doubles). + std::function addStruct = + [&](const std::string& name, const glslang::TType& type, int buffer, uint32_t& offset) { + const int elements = type.isSizedArray() ? type.getOuterArraySize() : 1; + for (int k = 0; k < elements; ++k) { + const std::string element = type.isSizedArray() ? name + "[" + std::to_string(k) + "]" : name; + for (const glslang::TTypeLoc& member : *type.getStruct()) { + const glslang::TType& m = *member.type; + const std::string path = element + "." + unescapeName(m.getFieldName().c_str()); + if (m.getBasicType() == glslang::EbtStruct) { + addStruct(path, m, buffer, offset); + continue; + } + TransformFeedbackVarying v; + v.name = path; + v.type = glTypeOfAst(m); + v.size = m.isSizedArray() ? m.getOuterArraySize() : 1; + v.array = m.isSizedArray(); + v.components = glTypeWords(v.type) * v.size; + const uint32_t align = m.getBasicType() == glslang::EbtDouble ? 8 : 4; + offset = (offset + align - 1) / align * align; + v.buffer = buffer; + v.offset = offset / 4; + v.expression = path; + v.source = path; + offset += static_cast(v.components) * 4; + varyings.push_back(v); + } + } + }; + auto add = [&](const std::string& name, const glslang::TType& type, const glslang::TQualifier& q, uint32_t extraBytes = 0) { + if (!q.hasXfbOffset()) return; + if (type.getBasicType() == glslang::EbtStruct) { + uint32_t offset = q.layoutXfbOffset + extraBytes; + addStruct(name, type, q.hasXfbBuffer() ? static_cast(q.layoutXfbBuffer) : 0, offset); + return; + } + TransformFeedbackVarying v; + v.name = name; + v.type = glTypeOfAst(type); + v.size = type.isSizedArray() ? type.getOuterArraySize() : 1; + v.array = type.isSizedArray(); + v.components = glTypeWords(v.type) * v.size; + v.buffer = q.hasXfbBuffer() ? static_cast(q.layoutXfbBuffer) : 0; + v.offset = (q.layoutXfbOffset + extraBytes) / 4; + v.expression = name; + v.source = name; + for (const StageOutputVar& o : outputs) + if (o.name == name) { + if (!o.expression.empty()) v.expression = o.expression; + if (!o.qualified.empty()) v.source = o.qualified; + } + varyings.push_back(v); + }; + for (TIntermNode* node : root->getSequence()) { + glslang::TIntermAggregate* objects = node->getAsAggregate(); + if (!objects || objects->getOp() != glslang::EOpLinkerObjects) continue; + for (TIntermNode* object : objects->getSequence()) { + glslang::TIntermSymbol* symbol = object->getAsSymbolNode(); + if (!symbol || symbol->getQualifier().storage != glslang::EvqVaryingOut) continue; + const glslang::TType& type = symbol->getType(); + if (type.getBasicType() == glslang::EbtBlock) { + const std::string blockName = unescapeName(type.getTypeName().c_str()); + const bool anonymous = glslang::IsAnonymous(symbol->getName()); + // An array of blocks is captured block after block: element + // k's members at their offsets plus k times the block's span. + const int elements = type.isSizedArray() && !anonymous ? type.getOuterArraySize() : 0; + uint32_t first = ~0u, end = 0; + for (const glslang::TTypeLoc& member : *type.getStruct()) { + const glslang::TQualifier& q = member.type->getQualifier(); + if (!q.hasXfbOffset()) continue; + const uint32_t words = static_cast(glTypeWords(glTypeOfAst(*member.type))) * + (member.type->isSizedArray() ? member.type->getOuterArraySize() : 1); + first = std::min(first, q.layoutXfbOffset); + end = std::max(end, q.layoutXfbOffset + words * 4); + } + for (int k = 0; k < std::max(1, elements); ++k) + for (const glslang::TTypeLoc& member : *type.getStruct()) { + const std::string field = unescapeName(member.type->getFieldName().c_str()); + const std::string element = elements ? "[" + std::to_string(k) + "]" : ""; + const std::string name = field.rfind("gl_", 0) == 0 || anonymous ? field : blockName + element + "." + field; + add(name, *member.type, member.type->getQualifier(), first == ~0u ? 0 : static_cast(k) * (end - first)); + } + continue; + } + add(unescapeName(symbol->getName().c_str()), type, symbol->getQualifier()); + } + } + if (varyings.empty()) return false; + std::sort(varyings.begin(), varyings.end(), [](const TransformFeedbackVarying& a, const TransformFeedbackVarying& b) { + return a.buffer != b.buffer ? a.buffer < b.buffer : a.offset < b.offset; + }); + int buffers = 0; + for (const TransformFeedbackVarying& v : varyings) buffers = std::max(buffers, v.buffer + 1); + for (int b = 0; b < buffers; ++b) { + const unsigned stride = last->getXfbStride(b); + uint32_t words = stride != glslang::TQualifier::layoutXfbStrideEnd ? stride / 4 : 0; + for (const TransformFeedbackVarying& v : varyings) + if (v.buffer == b) words = std::max(words, v.offset + static_cast(v.components)); + result.xfbStride[b] = words; + } + result.transformFeedback = varyings; + result.xfbBufferCount = buffers; + return true; +} + } // namespace namespace { @@ -1396,6 +1708,7 @@ std::map uniformInitializers(const std::vector< glslang::TShader parsed(lang); const char* text = source.c_str(); parsed.setStrings(&text, 1); + parsed.setPreamble(kPreamble); parsed.setEnvInput(glslang::EShSourceGlsl, lang, glslang::EShClientNone, 0); parsed.setEnvClient(glslang::EShClientNone, glslang::EShTargetClientVersion(0)); parsed.setEnvTarget(glslang::EShTargetNone, glslang::EShTargetLanguageVersion(0)); @@ -1528,8 +1841,26 @@ struct TessellationLink { std::string geometryGlsl; }; +static LinkResult linkImplCore(const LinkInput& input, const CaptureCode* capture, GeometryShaderInfo* geometry, + LinkResult* geometryCapture, TessellationLink* tessellation); + +// Internal programs (emulation kernels and stages) that fail to link are +// WineMetalGL bugs: WINEMETALGL_DUMP_SHADERS=1 prints their sources. static LinkResult linkImpl(const LinkInput& input, const CaptureCode* capture, GeometryShaderInfo* geometry = nullptr, LinkResult* geometryCapture = nullptr, TessellationLink* tessellation = nullptr) { + LinkResult result = linkImplCore(input, capture, geometry, geometryCapture, tessellation); + result.spirv = input.spirv; + result.spirvUnnamed = input.spirvUnnamed; + if (!result.ok && input.internal) + if (const char* v = std::getenv("WINEMETALGL_DUMP_SHADERS"); v && v[0] == '1') + for (const ShaderSource& shader : input.shaders) + std::fprintf(stderr, "winemetalgl: internal stage %d failed to link:\n%s\n", static_cast(shader.stage), + shader.source.c_str()); + return result; +} + +static LinkResult linkImplCore(const LinkInput& input, const CaptureCode* capture, GeometryShaderInfo* geometry, + LinkResult* geometryCapture, TessellationLink* tessellation) { initGlslang(); LinkResult result; std::vector parsed(input.shaders.size()); @@ -1626,7 +1957,16 @@ static LinkResult linkImpl(const LinkInput& input, const CaptureCode* capture, G // GL accepts them without checking (unlike OpenGL ES). const bool controlOnly = input.separable && stagePresent[static_cast(Stage::TessControl)] && !tessellated && !stagePresent[static_cast(Stage::Geometry)] && !stagePresent[static_cast(Stage::Vertex)]; - if (!capture && !input.transformFeedbackVaryings.empty() && !controlOnly) { + // Transform feedback declared in the shader (GL 4.4) replaces the API's varyings. + bool shaderCapture = false; + if (!capture && !controlOnly) { + const bool geometry = stagePresent[static_cast(Stage::Geometry)]; + glslang::TIntermediate* last = program.getIntermediate(geometry ? EShLangGeometry + : tessellated ? EShLangTessEvaluation + : EShLangVertex); + shaderCapture = last && layoutShaderTransformFeedback(last, captureableOutputs(last), result); + } + if (!capture && !shaderCapture && !input.transformFeedbackVaryings.empty() && !controlOnly) { const bool geometry = stagePresent[static_cast(Stage::Geometry)]; if (!geometry && !tessellated && !stagePresent[static_cast(Stage::Vertex)]) { result.log += "error: transform feedback varyings need a vertex or geometry shader\n"; @@ -1758,20 +2098,25 @@ static LinkResult linkImpl(const LinkInput& input, const CaptureCode* capture, G struct PipeVar { std::string name; GLenum type; int size; unsigned location; uint32_t stages; int index; bool array = false; bool patch = false; + int component = 0; }; std::vector pipeInputs, pipeOutputs; for (int i = 0; i < program.getNumPipeInputs(); ++i) { const auto& v = program.getPipeInput(i); + const glslang::TType* type = v.getType(); pipeInputs.push_back({unescapeName(v.name), static_cast(v.glDefineType), v.size, v.layoutLocation(), - static_cast(v.stages), 0, v.getType() && v.getType()->isArray(), - v.getType() && v.getType()->getQualifier().patch}); + static_cast(v.stages), 0, type && type->isArray(), type && type->getQualifier().patch, + type && type->getQualifier().hasComponent() ? static_cast(type->getQualifier().layoutComponent) + : 0}); } for (int i = 0; i < program.getNumPipeOutputs(); ++i) { const auto& v = program.getPipeOutput(i); const glslang::TType* type = v.getType(); const int index = type && type->getQualifier().hasIndex() ? static_cast(type->getQualifier().layoutIndex) : 0; pipeOutputs.push_back({unescapeName(v.name), static_cast(v.glDefineType), v.size, v.layoutLocation(), - static_cast(v.stages), index, type && type->isArray(), type && type->getQualifier().patch}); + static_cast(v.stages), index, type && type->isArray(), type && type->getQualifier().patch, + type && type->getQualifier().hasComponent() ? static_cast(type->getQualifier().layoutComponent) + : 0}); } for (const PipeVar& v : pipeInputs) if (v.location != kNoLocation && !(v.stages & EShLangVertexMask)) explicitVaryings.insert(v.name); @@ -1843,9 +2188,17 @@ static LinkResult linkImpl(const LinkInput& input, const CaptureCode* capture, G result.geometryOutputType = info.outputType; result.geometryVerticesOut = info.maxVertices; } - if (!layoutTransformFeedback(tessellationCaptureVaryings(t.hasControl ? t.control : t.evaluation), GL_INTERLEAVED_ATTRIBS, - captureableOutputs(program.getIntermediate(EShLangVertex)), t.capture, result.log, - UINT32_MAX)) + // Inputs the vertex stage does not write (allowed when the control + // or evaluation stage never reads them) are not captured. + const std::vector vertexOutputs = captureableOutputs(program.getIntermediate(EShLangVertex)); + std::vector varyings; + for (const std::string& v : tessellationCaptureVaryings(t.hasControl ? t.control : t.evaluation)) + for (const StageOutputVar& o : vertexOutputs) + if (o.name == v) { + varyings.push_back(v); + break; + } + if (!layoutTransformFeedback(varyings, GL_INTERLEAVED_ATTRIBS, vertexOutputs, t.capture, result.log, UINT32_MAX)) return result; } else if (geometry && result.stages[static_cast(Stage::Geometry)].present) { std::string glsl; @@ -1897,6 +2250,7 @@ static LinkResult linkImpl(const LinkInput& input, const CaptureCode* capture, G // Default uniform block members. std::map blockIndexByName; std::map samplerUniformByName, imageUniformByName; + std::map samplerUniformCount; // uniforms per sampler variable (arrays of arrays: several) for (int s = 0; s < kStageCount; ++s) { if (!result.stages[s].present) continue; spirv_cross::Compiler compiler(result.stages[s].spirv); @@ -1979,27 +2333,45 @@ static LinkResult linkImpl(const LinkInput& input, const CaptureCode* capture, G if (!liveSamplers.count(topLevelName(name))) continue; const spirv_cross::SPIRType& sampled = compiler.get_type(base.image.type); const GLenum glType = samplerTypeWithComponent(glTypeOf(base), sampled.basetype); - const uint32_t count = type.array.empty() ? 1 : std::max(1u, type.array.back()); + // An array of arrays is a uniform per innermost array ("s[i][0]", + // GL 4.6 section 7.3.1.1), its elements in consecutive slots. + std::vector dims(type.array.begin(), type.array.end()); + std::reverse(dims.begin(), dims.end()); // outermost first + for (uint32_t& d : dims) d = std::max(1u, d); + const uint32_t inner = dims.empty() ? 1 : dims.back(); + uint32_t outer = 1; + for (size_t d = 0; d + 1 < dims.size(); ++d) outer *= dims[d]; auto it = samplerUniformByName.find(name); if (it == samplerUniformByName.end()) { - UniformInfo u; - u.name = type.array.empty() ? name : name + "[0]"; - u.type = glType; - u.size = static_cast(count); - u.samplerSlot = static_cast(result.samplers.size()); - u.stageMask = 1u << s; - for (uint32_t e = 0; e < count; ++e) { - SamplerSlotInfo slot; - slot.type = glType; - slot.textureTarget = textureTargetForSampler(glType); - slot.shadow = isShadowSampler(glType); - result.samplers.push_back(slot); - } - if (auto b = explicitBindings.find(name); b != explicitBindings.end()) u.explicitBinding = b->second; samplerUniformByName[name] = static_cast(result.uniforms.size()); - result.uniforms.push_back(u); + samplerUniformCount[name] = static_cast(outer); + for (uint32_t k = 0; k < outer; ++k) { + UniformInfo u; + u.name = name; + for (size_t d = 0, rest = k; d + 1 < dims.size(); ++d) { + uint32_t span = 1; + for (size_t e = d + 1; e + 1 < dims.size(); ++e) span *= dims[e]; + u.name += "[" + std::to_string(rest / span) + "]"; + rest %= span; + } + if (!type.array.empty()) u.name += "[0]"; + u.type = glType; + u.size = static_cast(inner); + u.samplerSlot = static_cast(result.samplers.size()); + u.stageMask = 1u << s; + for (uint32_t e = 0; e < inner; ++e) { + SamplerSlotInfo slot; + slot.type = glType; + slot.textureTarget = textureTargetForSampler(glType); + slot.shadow = isShadowSampler(glType); + result.samplers.push_back(slot); + } + if (auto b = explicitBindings.find(name); b != explicitBindings.end()) + u.explicitBinding = b->second + static_cast(k * inner); + result.uniforms.push_back(u); + } } else { - result.uniforms[it->second].stageMask |= 1u << s; + for (int k = 0; k < samplerUniformCount[name]; ++k) result.uniforms[it->second + k].stageMask |= 1u << s; } } // Images (GL 4.2 image load/store): uniforms whose values are image units. @@ -2036,6 +2408,17 @@ static LinkResult linkImpl(const LinkInput& input, const CaptureCode* capture, G for (const auto& ssbo : resources.storage_buffers) { const int binding = atomicCounterBlockBinding(compiler.get_name(ssbo.base_type_id)); if (binding < 0) continue; + // Only the counters the stage's code uses are active in it. + bool all = false; + const std::set accessed = accessedMembers(compiler, ssbo.id, all); + const spirv_cross::SPIRType& blockType = compiler.get_type(ssbo.base_type_id); + for (uint32_t m = 0; m < blockType.member_types.size(); ++m) { + const spirv_cross::SPIRType& memberType = compiler.get_type(blockType.member_types[m]); + uint32_t elements = 1; + for (uint32_t d : memberType.array) elements *= std::max(1u, d); + stageCounters += elements; + } + if (accessed.empty() && !all) continue; auto buffer = std::find_if(result.atomicBuffers.begin(), result.atomicBuffers.end(), [&](const AtomicCounterBufferInfo& b) { return b.binding == static_cast(binding); }); if (buffer == result.atomicBuffers.end()) { @@ -2052,8 +2435,11 @@ static LinkResult linkImpl(const LinkInput& input, const CaptureCode* capture, G const std::string name = unescapeName(compiler.get_member_name(type.self, m)); const uint32_t offset = compiler.type_struct_member_offset(type, m); const uint32_t count = memberType.array.empty() ? 1 : std::max(1u, memberType.array.back()); - stageCounters += count; - buffer->dataSize = std::max(buffer->dataSize, static_cast(offset + 4 * count)); + // Every element of an array of arrays is a counter. + uint32_t elements = 1; + for (uint32_t d : memberType.array) elements *= std::max(1u, d); + buffer->dataSize = std::max(buffer->dataSize, static_cast(offset + 4 * elements)); + if (!all && !accessed.count(m)) continue; const std::string glName = memberType.array.empty() ? name : name + "[0]"; auto it = std::find_if(result.uniforms.begin(), result.uniforms.end(), [&](const UniformInfo& u) { return u.name == glName && u.atomicBufferIndex >= 0; }); @@ -2159,6 +2545,9 @@ static LinkResult linkImpl(const LinkInput& input, const CaptureCode* capture, G if (name == "DepthRange.far") result.builtinUniforms.depthRange[1] = static_cast(u.offset); if (name == "DepthRange.diff") result.builtinUniforms.depthRange[2] = static_cast(u.offset); if (name == "NumSamples") result.builtinUniforms.numSamples = static_cast(u.offset); + if (name == "BaseVertex") result.builtinUniforms.baseVertex = static_cast(u.offset); + if (name == "BaseInstance") result.builtinUniforms.baseInstance = static_cast(u.offset); + if (name == "DrawID") result.builtinUniforms.drawId = static_cast(u.offset); continue; } if (u.blockIndex < 0 && isSubroutineSelectionUniform(u.name)) { @@ -2186,6 +2575,16 @@ static LinkResult linkImpl(const LinkInput& input, const CaptureCode* capture, G result.uniforms = std::move(kept); } } + // GL_MAX_COMBINED_IMAGE_UNIFORMS: an image uniform counts once per stage that uses it. + { + uint64_t combinedImages = 0; + for (const UniformInfo& u : result.uniforms) + if (u.imageSlot >= 0) combinedImages += static_cast(u.size) * static_cast(__builtin_popcount(u.stageMask)); + if (combinedImages > static_cast(wmgl::limits::kMaxCombinedImageUniforms)) { + result.log += "error: too many image uniforms in the program (GL_MAX_COMBINED_IMAGE_UNIFORMS)\n"; + return result; + } + } for (SubroutineStageInfo& info : result.subroutines) { // Only uniforms the stage uses are active; their locations: explicit // ones first, then the lowest free ones. @@ -2294,6 +2693,7 @@ static LinkResult linkImpl(const LinkInput& input, const CaptureCode* capture, G a.array = in.array; a.locationCount = glTypeLocations(a.type) * a.size; a.location = in.location != kNoLocation ? static_cast(in.location) : -1; + a.component = in.component; // Lowered legacy attributes (gl_Vertex, ...) read the conventional arrays. const int conventional = conventionalAttribLocation(a.name); if (conventional >= 0) { @@ -2331,14 +2731,22 @@ static LinkResult linkImpl(const LinkInput& input, const CaptureCode* capture, G a.location = location; for (int l = 0; l < a.locationCount && location + l < 64; ++l) usedLocations[location + l] = true; } - int attribSlots = 0; + // Attributes packed by component or aliased share their locations; a + // location holding a dvec3 or dvec4 column counts twice. + int slotWeight[64] = {}; for (const AttribInfo& a : result.attribs) { if (!a.conventional && a.location + a.locationCount > 16) { result.log += "error: vertex attribute '" + a.name + "' uses a location beyond GL_MAX_VERTEX_ATTRIBS\n"; return result; } - if (!a.conventional) attribSlots += glTypeAttribSlots(a.type) * a.size; + if (a.conventional || a.location < 0) continue; + const int perElement = std::max(1, a.locationCount / std::max(1, a.size)); + const int weight = std::max(1, glTypeAttribSlots(a.type) / perElement); + for (int l = 0; l < a.locationCount && a.location + l < 64; ++l) + slotWeight[a.location + l] = std::max(slotWeight[a.location + l], weight); } + int attribSlots = 0; + for (int w : slotWeight) attribSlots += w; // dvec3 and dvec4 columns count twice (GL_ARB_vertex_attrib_64bit). if (attribSlots > 16) { result.log += "error: vertex attributes exceed GL_MAX_VERTEX_ATTRIBS\n"; @@ -2393,6 +2801,7 @@ static LinkResult linkImpl(const LinkInput& input, const CaptureCode* capture, G info.size = std::max(1, v.size); info.location = v.location != kNoLocation && v.name.rfind("gl_", 0) != 0 ? static_cast(v.location) : -1; info.perPatch = v.patch; + info.component = v.component; info.stageMask = v.stages; return info; }; @@ -2442,9 +2851,11 @@ static LinkResult linkImpl(const LinkInput& input, const CaptureCode* capture, G const bool noRaster = variant == 1; try { // Doubles become soft-float code (fp64.h); no-op without them. - std::vector spirv = cubeStorageImagesAs2DArrays(lowerGatherOffsets(rectImagesAs2D(stage.spirv))); + std::vector spirv = + inferStorageImageAccess( + flattenArraysOfArrays(cubeStorageImagesAs2DArrays(lowerGatherOffsets(rectImagesAs2D(stage.spirv))))); if (!lowerFp64(spirv, result.log)) return result; - spirv_cross::CompilerMSL msl(std::move(spirv)); + BorderMsl msl(std::move(spirv)); spirv_cross::CompilerMSL::Options mslOptions; mslOptions.disable_rasterization = noRaster; mslOptions.platform = spirv_cross::CompilerMSL::Options::macOS; @@ -2461,6 +2872,9 @@ static LinkResult linkImpl(const LinkInput& input, const CaptureCode* capture, G common.vertex.flip_vert_y = true; // GL row 0 = Metal row 0 (see docs/design/core.md) common.vertex.fixup_clipspace = true; // z: [-w, w] -> [0, w] msl.set_common_options(common); + // Metal has no cull distance output: the shader writes a local + // gl_CullDistance array, passed on as varyings (addCullDistances). + if (s == static_cast(Stage::Vertex)) msl.mask_stage_output_by_builtin(spv::BuiltInCullDistance); const spv::ExecutionModel model = executionModel(static_cast(s)); const spirv_cross::ShaderResources resources = msl.get_shader_resources(); @@ -2509,7 +2923,7 @@ static LinkResult linkImpl(const LinkInput& input, const CaptureCode* capture, G {"wmgl_TcsControl", kSlotFirstXfbBuffer + 2}, {"wmgl_TesVertices", kSlotTessVertices}, {"wmgl_TesPatches", kSlotTessPatches}, {"wmgl_TesControl", kSlotTessControl}, }; - uint32_t nextAtomicSlot = kSlotFirstAtomicCounterBuffer; + uint32_t nextAtomicSlot = kSlotFirstAtomicCounterBuffer, vertexCounterBuffers = 0; stage.storageArgs.clear(); for (const auto& ssbo : resources.storage_buffers) { const std::string blockName = msl.get_name(ssbo.base_type_id); @@ -2527,7 +2941,9 @@ static LinkResult linkImpl(const LinkInput& input, const CaptureCode* capture, G stage.storageArgs.push_back( {it == result.storageBlocks.end() ? -1 : static_cast(it - result.storageBlocks.begin()), writes}); } - if (stage.storageArgs.size() > kMaxStageStorageBlocks) { + if (std::count_if(stage.storageArgs.begin(), stage.storageArgs.end(), + [](const StageOutput::StorageArg& a) { return a.counterBinding < 0; }) > + static_cast(kMaxStageStorageBlocks)) { result.log += "error: too many shader storage blocks in one stage\n"; return result; } @@ -2551,15 +2967,25 @@ static LinkResult linkImpl(const LinkInput& input, const CaptureCode* capture, G const int counterBinding = atomicCounterBlockBinding(blockName); if (auto internal = kInternalBuffers.find(blockName); internal != kInternalBuffers.end()) b.msl_buffer = internal->second; else if (blockName.rfind("wmgl_XfbData", 0) == 0) b.msl_buffer = kSlotFirstXfbBuffer + std::stoul(blockName.substr(12)); - else if (counterBinding >= 0) { - const bool vertexStage = s == static_cast(Stage::Vertex); - if (!vertexStage && s != static_cast(Stage::Fragment) && s != static_cast(Stage::Compute)) { + else if (counterBinding >= 0 && s == static_cast(Stage::Vertex)) { + // A vertex function's counter buffers: argument buffer entries (it has no free slots). + const uint32_t id = static_cast(stage.storageArgs.size()); + if (++vertexCounterBuffers > kMaxVertexAtomicCounterBuffers) { + result.log += "error: too many atomic counter buffers in one stage\n"; + return result; + } + stage.storageArgs.push_back({-1, true, counterBinding}); + msl.set_decoration(ssbo.id, spv::DecorationDescriptorSet, 1); + b.desc_set = 1; + b.count = 1; + b.msl_buffer = id; + b.msl_texture = id; + } else if (counterBinding >= 0) { + if (s != static_cast(Stage::Fragment) && s != static_cast(Stage::Compute)) { result.log += "error: atomic counters are not supported in this shader stage\n"; return result; } - const uint32_t slotEnd = vertexStage ? kSlotVertexAtomicCounterBuffer + kMaxVertexAtomicCounterBuffers - : kSlotFirstAtomicCounterBuffer + kMaxStageAtomicCounterBuffers; - if (vertexStage && nextAtomicSlot < kSlotVertexAtomicCounterBuffer) nextAtomicSlot = kSlotVertexAtomicCounterBuffer; + const uint32_t slotEnd = kSlotFirstAtomicCounterBuffer + kMaxStageAtomicCounterBuffers; if (nextAtomicSlot >= slotEnd) { result.log += "error: too many atomic counter buffers in one stage\n"; return result; @@ -2604,6 +3030,7 @@ static LinkResult linkImpl(const LinkInput& input, const CaptureCode* capture, G b.msl_texture = nextTexture; b.msl_sampler = nextTexture; msl.add_msl_resource_binding(b); + if (type.array.empty()) msl.textureSlots[image.id] = nextTexture; auto it = samplerUniformByName.find(name); if (it != samplerUniformByName.end()) { const int first = result.uniforms[it->second].samplerSlot; @@ -2615,8 +3042,9 @@ static LinkResult linkImpl(const LinkInput& input, const CaptureCode* capture, G return result; } } - // Images: textures kSlotFirstImage.. (read_write textures). - uint32_t nextImage = kSlotFirstImage; + // Images: textures kSlotFirstImage..; at most 8 of them read_write + // (inferStorageImageAccess). + uint32_t nextImage = kSlotFirstImage, readWriteImages = 0; for (const auto& image : resources.storage_images) { const std::string name = unescapeName(msl.get_name(image.id)); const spirv_cross::SPIRType& type = msl.get_type(image.type_id); @@ -2634,7 +3062,10 @@ static LinkResult linkImpl(const LinkInput& input, const CaptureCode* capture, G for (uint32_t e = 0; e < count; ++e) result.images[first + e].stageTexture[s] = static_cast(nextImage + e); } nextImage += count; - if (nextImage > kSlotFirstImage + kMaxStageImages) { + if (!msl.has_decoration(image.id, spv::DecorationNonWritable) && + !msl.has_decoration(image.id, spv::DecorationNonReadable)) + readWriteImages += count; + if (nextImage > kSlotFirstImage + kMaxStageImages || readWriteImages > kMaxStageReadWriteImages) { result.log += "error: too many images in one stage\n"; return result; } @@ -2662,7 +3093,11 @@ static LinkResult linkImpl(const LinkInput& input, const CaptureCode* capture, G const spirv_cross::SPIRType& type = msl.get_type(in.type_id); const GLint attrib = it->location + column; GLint metal = attrib; - if (part == 1) { + // Part 1, and a double packed by layout(component) into a location + // another 64-bit input already reads, take a free Metal index. + const bool shared = part == 0 && std::any_of(stage.vertexInputs.begin(), stage.vertexInputs.end(), + [&](const StageOutput::VertexInput& v) { return v.location == attrib; }); + if (part == 1 || shared) { while (nextFree >= 0 && usedAttributes.count(nextFree)) --nextFree; if (nextFree < 0) { result.log += "error: too many 64-bit vertex attribute components\n"; @@ -2671,7 +3106,9 @@ static LinkResult linkImpl(const LinkInput& input, const CaptureCode* capture, G metal = nextFree--; } msl.set_decoration(in.id, spv::DecorationLocation, static_cast(metal)); - stage.vertexInputs.push_back({metal, static_cast(type.vecsize), 'd', attrib, part}); + StageOutput::VertexInput input{metal, static_cast(type.vecsize), 'd', attrib, part}; + input.byteOffset = part == 0 ? it->component * 4 : 0; + stage.vertexInputs.push_back(input); } for (const auto& in : resources.stage_inputs) { const std::string name = unescapeName(msl.get_name(in.id)); @@ -2704,14 +3141,17 @@ static LinkResult linkImpl(const LinkInput& input, const CaptureCode* capture, G } if (noRaster) { stage.mslNoRaster = finishFp64Msl(msl.compile()); + addDefaultBorderMacros(stage.mslNoRaster); if (const char* v = std::getenv("WINEMETALGL_DUMP_SHADERS"); v && v[0] == '1') std::fprintf(stderr, "winemetalgl: MSL vertex stage without rasterization:\n%s\n", stage.mslNoRaster.c_str()); instanceIdFromZero(stage.mslNoRaster); continue; } stage.msl = finishFp64Msl(msl.compile()); + addDefaultBorderMacros(stage.msl); if (s == static_cast(Stage::Vertex)) { ensureVertexOutput(stage.msl); + addCullDistances(stage.msl); addPointSize(stage.msl); instanceIdFromZero(stage.msl); } @@ -2785,6 +3225,9 @@ bool useVertexCapture(const LinkInput& input, const LinkResult& layout, LinkResu LinkResult captured = linkImpl(again, &code); if (!captured.ok) { result.log += "error: internal error building vertex output capture:\n" + captured.log; + if (const char* v = std::getenv("WINEMETALGL_DUMP_SHADERS"); v && v[0] == '1') + std::fprintf(stderr, "winemetalgl: capture declarations:\n%s\ncapture epilogue:\n%s\n", code.declarations.c_str(), + code.epilogue.c_str()); return false; } const int vs = static_cast(Stage::Vertex); @@ -2900,8 +3343,13 @@ bool buildTessellationEmulation(const LinkInput& input, const TessellationLink& } if (dump) std::fprintf(stderr, "winemetalgl: tessellation evaluation vertex GLSL:\n%s\n", vertex.c_str()); LinkInput evaluationInput; - evaluationInput.shaders = {{Stage::Vertex, vertex}}; - if (!t.geometryGlsl.empty()) evaluationInput.shaders.push_back({Stage::Geometry, t.geometryGlsl}); + // A separable program's outputs have locations hashed from their names: + // the evaluation stage declares them without and is hashed the same way + // (as the geometry pass-through). + evaluationInput.separable = input.separable; + evaluationInput.shaders = {{Stage::Vertex, input.separable ? stripHashedLocations(vertex) : vertex}}; + if (!t.geometryGlsl.empty()) + evaluationInput.shaders.push_back({Stage::Geometry, input.separable ? stripHashedLocations(t.geometryGlsl) : t.geometryGlsl}); // Varyings by their qualified names: the geometry stage here is GLSL // translated by SPIRV-Cross, which names anonymous blocks. for (const TransformFeedbackVarying& v : result.transformFeedback) { @@ -2911,7 +3359,7 @@ bool buildTessellationEmulation(const LinkInput& input, const TessellationLink& : v.source + (open == std::string::npos ? "" : v.name.substr(open))); } evaluationInput.transformFeedbackMode = input.transformFeedbackMode; - evaluationInput.internal = true; + evaluationInput.internal = !input.separable; auto evaluation = std::make_shared(link(evaluationInput)); if (!evaluation->ok) { result.log += "error: internal error building the tessellation evaluation stage:\n" + evaluation->log; diff --git a/src/core/shader/compiler.h b/src/core/shader/compiler.h index 4c4d5def..9c005a7c 100644 --- a/src/core/shader/compiler.h +++ b/src/core/shader/compiler.h @@ -33,6 +33,9 @@ constexpr uint32_t kSlotFirstXfbBuffer = 16; // buffers 16..19: transform feed constexpr uint32_t kSlotFirstVertexBuffer = 20; // buffers 20..29: vertex buffers constexpr uint32_t kMaxVertexBufferSlots = 10; constexpr uint32_t kSlotXfbControl = 30; // buffer 30: transform feedback capture control +// Fragment functions bind no transform feedback buffers: buffer 16 of a +// fragment function counting its invocations (fragmentCountingInvocations). +constexpr uint32_t kSlotFragmentInvocations = kSlotFirstXfbBuffer; constexpr uint32_t kMaxStageSamplers = 16; // textures/samplers 0..15 // Tessellation emulation (tessellation.h): the control kernel reads the // captured vertices and writes patch records through buffers 16..18; the @@ -41,14 +44,16 @@ constexpr uint32_t kMaxStageSamplers = 16; // textures/samplers 0..15 constexpr uint32_t kSlotTessVertices = 20, kSlotTessPatches = 21, kSlotTessControl = 22; // Atomic counter buffers (GL 4.2): buffers 20..27 of fragment and compute // functions (the geometry kernel included), which bind no vertex buffers. -// A vertex function's one counter buffer takes the last vertex buffer slot -// (29), leaving its program 9 vertex buffers (DrawState::vertexBufferSlots). +// A vertex function's counter buffers are entries of its storage block +// argument buffer (kSlotStorageBlocks), after or between its storage blocks. constexpr uint32_t kSlotFirstAtomicCounterBuffer = 20; -constexpr uint32_t kSlotVertexAtomicCounterBuffer = kSlotFirstVertexBuffer + kMaxVertexBufferSlots - 1; -constexpr uint32_t kMaxVertexAtomicCounterBuffers = 1; -// Image uniforms (GL 4.2 image load/store): textures 16..23 of every stage. +constexpr uint32_t kMaxVertexAtomicCounterBuffers = 8; +// Image uniforms (GL 4.2 image load/store): textures 16..31 of every stage, +// at most 8 of them read_write (Metal's limit per function); images a stage +// only reads or only writes are access::read or access::write textures. constexpr uint32_t kSlotFirstImage = 16; -constexpr uint32_t kMaxStageImages = 8; +constexpr uint32_t kMaxStageImages = 16; +constexpr uint32_t kMaxStageReadWriteImages = 8; constexpr uint32_t kMaxStageAtomicCounterBuffers = 8; // Shader storage blocks (GL 4.3): one argument buffer per stage function at // buffer 28 (geometry and tessellation emulation kernels and the evaluation @@ -82,6 +87,9 @@ struct LinkInput { // transformFeedbackVaryings list the outputs an emulation captures: no // MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS limit. bool captureLayout = false; + // GL_ARB_gl_spirv: the shaders are SPIR-V modules translated to GLSL + // (spirv_input.h); `spirvUnnamed`: one of them has no debug names. + bool spirv = false, spirvUnnamed = false; }; struct UniformInfo { @@ -150,6 +158,7 @@ struct AttribInfo { GLint size = 1; GLint location = -1; int locationCount = 1; // matrices use one location per column + int component = 0; // layout(component) (GL 4.4): its first component in the location bool array = false; // declared as an array ("name[i]" names its elements) // A lowered legacy attribute (wmgl_Vertex for gl_Vertex, ...) at its // conventional-array location; the API reports it as gl_* at location -1. @@ -180,6 +189,7 @@ struct InterfaceVariableInfo { GLint size = 1; GLint location = -1; // -1: a built-in GLint locationIndex = -1; // fragment outputs: layout(index) + GLint component = 0; // layout(component) (GL 4.4) bool perPatch = false; uint32_t stageMask = 0; }; @@ -221,6 +231,7 @@ struct StageOutput { char kind; // 'f', 'i', 'u', 'd' GLint attrib = -1; // GL attribute supplying the data (-1: `location`) int part = 0; + int byteOffset = 0; // 'd': where the data starts in its part (layout(component) packing) GLint source() const { return attrib >= 0 ? attrib : location; } }; std::vector vertexInputs; @@ -228,8 +239,9 @@ struct StageOutput { struct FragOutput { GLint location; GLint index; char kind; }; std::vector fragOutputs; // Storage blocks in the kSlotStorageBlocks argument buffer: entry i (id i) - // is element `block` of the LinkResult that compiled this stage. - struct StorageArg { int block; bool writes; }; + // is element `block` of the LinkResult that compiled this stage, or, when + // counterBinding >= 0, the atomic counter buffer bound there (vertex stage). + struct StorageArg { int block; bool writes; int counterBinding = -1; }; std::vector storageArgs; }; @@ -355,12 +367,21 @@ struct LinkResult { struct BuiltinUniforms { int depthRange[3] = {-1, -1, -1}; int numSamples = -1; + int baseVertex = -1, baseInstance = -1, drawId = -1; } builtinUniforms; std::vector transformFeedback; uint32_t xfbStride[4] = {}; // 32-bit words per captured vertex, per buffer int xfbStream[4] = {}; // the vertex stream each buffer captures (geometry streams) int xfbBufferCount = 0; + // Whether a varying is captured into buffer `b` (enhanced layouts may + // leave buffers below xfbBufferCount unused; they need no binding). + bool xfbBufferUsed(int b) const { + for (const TransformFeedbackVarying& v : transformFeedback) + if (v.buffer == b && v.type != GL_NONE) return true; + return false; + } bool xfbVertexCapture = false; // the vertex stage MSL writes captured vertices + bool spirv = false, spirvUnnamed = false; // LinkInput's: names as spirvDisplayName reports them uint32_t defaultBlockSize = 0; // The default block's initial contents: values of uniform initializers // ("uniform vec4 v = vec4(1.0);"), zero elsewhere. @@ -411,6 +432,17 @@ std::string fragmentAtSampleRate(const std::string& msl); // too, so this variant writes all ones (empty if the stage writes no mask). std::string fragmentIgnoringSampleMask(const std::string& msl); +// GL 4.5 cull distances (Metal has none): the fragment function discards +// the fragments of primitives with one of the vertex stage's `count` +// distances negative at every vertex (the [[user(wmgl_cullN)]] varyings, +// addCullDistances). Empty if the stage has no entry point. +std::string fragmentWithCullDistances(const std::string& msl, int count); + +// GL 4.6 FRAGMENT_SHADER_INVOCATIONS: the fragment function counts its +// invocations into the uint at buffer kSlotFragmentInvocations. Empty if the +// stage has no entry point. +std::string fragmentCountingInvocations(const std::string& msl); + LinkResult link(const LinkInput& input); // Copies stage `from`'s uniform block, atomic counter buffer, image and diff --git a/src/core/shader/fp64.cpp b/src/core/shader/fp64.cpp index 9cb3272e..a17aa504 100644 --- a/src/core/shader/fp64.cpp +++ b/src/core/shader/fp64.cpp @@ -1067,7 +1067,17 @@ bool Lowering::lowerFunctions() { const uint32_t toType = itf.input ? itf.members[k] : itf.mappedMembers[k]; code.push_back({Op::OpAccessChain, {pointerTo(itf.input ? storage : spv::StorageClassPrivate, fromType), from, itf.input ? itf.vars[0] : itf.privateVar, index}}); - code.push_back({Op::OpLoad, {fromType, loaded, from}}); + valueTypes_[from] = pointerTo(itf.input ? storage : spv::StorageClassPrivate, fromType); + // A block member that is an array (a mapped double) is a native + // array in MSL: it moves element by element (SPIRV-Cross assigns + // whole arrays, which MSL rejects for native arrays). + if (itf.input) { + const uint32_t elements = loadElements(code, from, fromType); + code.push_back({Op::OpCopyObject, {fromType, loaded, elements}}); + } else { + code.push_back({Op::OpLoad, {fromType, loaded, from}}); + } + valueTypes_[loaded] = fromType; uint32_t value = loaded; if (fromType != toType) { std::vector words; @@ -1082,7 +1092,9 @@ bool Lowering::lowerFunctions() { } code.push_back({Op::OpAccessChain, {pointerTo(itf.input ? spv::StorageClassPrivate : storage, toType), to, itf.input ? itf.privateVar : itf.vars[0], index}}); - code.push_back({Op::OpStore, {to, value}}); + valueTypes_[to] = pointerTo(itf.input ? spv::StorageClassPrivate : storage, toType); + if (itf.input) code.push_back({Op::OpStore, {to, value}}); + else storeElements(code, to, toType, value); } }; auto convertInputs = [&](std::vector& code) { @@ -1245,8 +1257,18 @@ bool Lowering::lowerFunctions() { if (in.op == Op::OpLoad) { const uint32_t storage = storageOf(valueType(in.w[2])); if (storage == spv::StorageClassUniform || storage == spv::StorageClassStorageBuffer || - storage == spv::StorageClassPushConstant) + storage == spv::StorageClassPushConstant) { + // So do arrays of doubles loaded from buffers: SPIRV-Cross + // copies a block's native arrays into spvUnsafeArray values by + // plain assignment, which MSL rejects. + const uint32_t type = pointee(valueType(in.w[2])); + const Inst* d = typeDef(type); + if (d && d->op == Op::OpTypeArray && containsDouble(type)) { + out.push_back({Op::OpCopyObject, {type, in.w[1], loadElements(out, in.w[2], type)}}); + continue; + } bufferLoads_.insert(in.w[1]); + } } if (!lowerInstruction(in, out)) return false; } diff --git a/src/core/shader/geometry.cpp b/src/core/shader/geometry.cpp index 6b7a4364..4b9f5fef 100644 --- a/src/core/shader/geometry.cpp +++ b/src/core/shader/geometry.cpp @@ -57,7 +57,7 @@ GLenum glslTypeEnum(const std::string& t) { } // "flat out vec4 name[3]" -> qualifiers stripped; returns direction ("in"/"out", or ""). -std::string parseVariable(const std::string& decl, std::string& type, std::string& name, int& arraySize) { +std::string parseVariable(const std::string& decl, std::string& type, std::string& name, int& arraySize, int* innerSize = nullptr) { std::string d = decl; if (d.compare(0, 7, "layout(") == 0) d = trim(d.substr(d.find(')') + 1)); std::istringstream words(d); @@ -81,7 +81,12 @@ std::string parseVariable(const std::string& decl, std::string& type, std::strin const size_t open = name.find('['); if (open != std::string::npos) { arraySize = std::atoi(name.c_str() + open + 1); + // A second dimension (a geometry input's array per vertex). + const size_t second = name.find('[', open + 1); + if (innerSize) *innerSize = second != std::string::npos ? std::atoi(name.c_str() + second + 1) : 0; name = name.substr(0, open); + } else if (innerSize) { + *innerSize = 0; } return direction; } @@ -101,7 +106,17 @@ int layoutStream(const std::string& decl) { // The declaration without its stream qualifier (vertex shaders have none). std::string withoutStream(const std::string& decl) { std::string out = decl; - for (size_t at = out.find("stream"); at != std::string::npos; at = out.find("stream", at)) { + // The word "stream" inside a layout qualifier, not in a name such as "out_stream0". + auto qualifier = [&](size_t from) { + for (size_t at = out.find("stream", from); at != std::string::npos; at = out.find("stream", at + 1)) { + const bool word = (at == 0 || !(std::isalnum(static_cast(out[at - 1])) || out[at - 1] == '_')) && + (at + 6 >= out.size() || !(std::isalnum(static_cast(out[at + 6])) || out[at + 6] == '_')); + const size_t layout = out.rfind("layout(", at); + if (word && layout != std::string::npos && out.find(')', layout) > at) return at; + } + return std::string::npos; + }; + for (size_t at = qualifier(0); at != std::string::npos; at = qualifier(at)) { size_t end = at + 6; while (end < out.size() && (out[end] == ' ' || out[end] == '=' || std::isdigit(static_cast(out[end])))) ++end; size_t start = at; @@ -196,6 +211,9 @@ std::string renameGlIn(const std::string& text, GeometryShaderInfo& info) { } else if (member == "gl_ClipDistance") { info.readsClipDistance = true; out += "wmgl_in_clipdistance[" + index + "]"; + } else if (member == "gl_CullDistance") { + info.readsCullDistance = true; + out += "wmgl_in_culldistance[" + index + "]"; } else { out += "wmgl_in_position[" + index + "]"; } @@ -283,8 +301,8 @@ bool parseGeometryGlsl(const std::string& glsl, GeometryShaderInfo& info, std::s continue; } std::string type, name; - int arraySize = 0; - const std::string direction = parseVariable(flat, type, name, arraySize); + int arraySize = 0, innerSize = 0; + const std::string direction = parseVariable(flat, type, name, arraySize, &innerSize); if (direction.empty()) { info.globals += flat + "\n"; continue; @@ -294,8 +312,14 @@ bool parseGeometryGlsl(const std::string& glsl, GeometryShaderInfo& info, std::s log += "error: geometry shader " + direction + "put '" + name + "' has an unsupported type\n"; return false; } - if (name.rfind("gl_", 0) == 0) continue; - GeometryVar v{type, glType, name, "", "", direction == "out" ? arraySize : 0, flat}; + if (name.rfind("gl_", 0) == 0) { + // Redeclared built-in arrays: "out float gl_ClipDistance[N];". + if (direction == "out" && name == "gl_ClipDistance") info.outputClipDistances = arraySize; + if (direction == "out" && name == "gl_CullDistance") info.outputCullDistances = arraySize; + continue; + } + // Inputs are arrays of vertices: their own array size is the second dimension. + GeometryVar v{type, glType, name, "", "", direction == "out" ? arraySize : innerSize, flat}; if (direction == "out") v.stream = layoutStream(flat); (direction == "in" ? info.inputs : info.outputs).push_back(v); continue; @@ -317,6 +341,8 @@ bool parseGeometryGlsl(const std::string& glsl, GeometryShaderInfo& info, std::s } std::string instance = tail; const size_t bracket = instance.find('['); + // Input blocks are arrays of vertices; an output block may be an array of blocks. + const int instanceSize = bracket != std::string::npos && direction == "out" ? std::atoi(instance.c_str() + bracket + 1) : 0; if (bracket != std::string::npos) instance = instance.substr(0, bracket); std::istringstream members(body); std::string member; @@ -326,6 +352,7 @@ bool parseGeometryGlsl(const std::string& glsl, GeometryShaderInfo& info, std::s int arraySize = 0; parseVariable(trim(member), type, name, arraySize); if (name == "gl_ClipDistance") (direction == "in" ? info.inputClipDistances : info.outputClipDistances) = arraySize; + if (name == "gl_CullDistance") (direction == "in" ? info.inputCullDistances : info.outputCullDistances) = arraySize; } continue; } @@ -345,6 +372,7 @@ bool parseGeometryGlsl(const std::string& glsl, GeometryShaderInfo& info, std::s if (direction == "out") { v.declaration = flat; // whole block, emitted once by the pass-through v.stream = layoutStream(member) ? layoutStream(member) : layoutStream(header); + v.instanceSize = instance.empty() ? 0 : instanceSize; } (direction == "in" ? info.inputs : info.outputs).push_back(v); } @@ -365,10 +393,15 @@ bool parseGeometryGlsl(const std::string& glsl, GeometryShaderInfo& info, std::s replaceWord(functions, "gl_PrimitiveIDIn", "").find("gl_PrimitiveID") != std::string::npos; if (functions.find("gl_ClipDistance") != std::string::npos && !info.outputClipDistances) info.outputClipDistances = 8; if (info.readsClipDistance && !info.inputClipDistances) info.inputClipDistances = 8; + // gl_PerVertex declares gl_CullDistance whether or not the shader writes + // it: unwritten distances must not reach the cull test. + if (functions.find("gl_CullDistance") == std::string::npos) info.outputCullDistances = 0; + else if (!info.outputCullDistances) info.outputCullDistances = std::max(1, 8 - info.outputClipDistances); + if (info.readsCullDistance && !info.inputCullDistances) info.inputCullDistances = 8; const std::pair renames[] = { {"gl_PrimitiveIDIn", "wmgl_primitive_id_in"}, {"gl_PrimitiveID", "wmgl_out_primitive_id"}, {"gl_Position", "wmgl_out_position"}, {"gl_PointSize", "wmgl_out_pointsize"}, - {"gl_ClipDistance", "wmgl_out_clipdistance"}, {"gl_Layer", "wmgl_out_layer"}, + {"gl_ClipDistance", "wmgl_out_clipdistance"}, {"gl_CullDistance", "wmgl_out_culldistance"}, {"gl_Layer", "wmgl_out_layer"}, {"gl_ViewportIndex", "wmgl_out_viewport"}, {"gl_InvocationID", "wmgl_invocation_id"}, {"EmitVertex", "wmgl_EmitVertex"}, {"EndPrimitive", "wmgl_EndPrimitive"}, {"EmitStreamVertex", "wmgl_EmitStreamVertex"}, {"EndStreamPrimitive", "wmgl_EndStreamPrimitive"}, @@ -391,6 +424,7 @@ std::vector geometryCaptureVaryings(const GeometryShaderInfo& info) std::vector varyings = {"gl_Position"}; if (info.readsPointSize) varyings.push_back("gl_PointSize"); if (info.readsClipDistance) varyings.push_back("gl_ClipDistance"); + if (info.readsCullDistance) varyings.push_back("gl_CullDistance"); for (const GeometryVar& v : info.inputs) varyings.push_back(captureName(v)); return varyings; } @@ -425,11 +459,22 @@ GeometrySources geometrySources(const GeometryShaderInfo& info, const LinkResult addVar("gl_Position", "wmgl_out_position", GL_FLOAT_VEC4, "vec4", 0); if (info.writesPointSize) addVar("gl_PointSize", "wmgl_out_pointsize", GL_FLOAT, "float", 0); if (info.outputClipDistances) addVar("gl_ClipDistance", "wmgl_out_clipdistance", GL_FLOAT, "float", info.outputClipDistances); + if (info.outputCullDistances) addVar("gl_CullDistance", "wmgl_out_culldistance", GL_FLOAT, "float", info.outputCullDistances); if (info.writesLayer) addVar("gl_Layer", "wmgl_out_layer", GL_INT, "int", 0); if (info.writesViewportIndex) addVar("gl_ViewportIndex", "wmgl_out_viewport", GL_INT, "int", 0); - for (const GeometryVar& v : info.outputs) - addVar(v.member.empty() ? v.name : v.blockName + "." + v.member, v.member.empty() ? v.name : v.name + "." + v.member, - v.type, v.glslType, v.arraySize); + for (const GeometryVar& v : info.outputs) { + if (!v.instanceSize) { + addVar(v.member.empty() ? v.name : v.blockName + "." + v.member, v.member.empty() ? v.name : v.name + "." + v.member, + v.type, v.glslType, v.arraySize); + continue; + } + // An array of blocks: each element's member ("Block[k].member"). + for (int k = 0; k < v.instanceSize; ++k) { + const std::string element = "[" + std::to_string(k) + "]."; + addVar(v.blockName + element + v.member, v.name + element + v.member, v.type, v.glslType, v.arraySize); + out.fields.back().stream = v.stream; + } + } // Vertex streams: the record ends with the stream it was emitted to. if (info.streams) out.streamOffset = words++; out.outputStride = words; @@ -445,7 +490,8 @@ GeometrySources geometrySources(const GeometryShaderInfo& info, const LinkResult std::map inputBlocks; // instance -> struct name for (const GeometryVar& v : info.inputs) { if (v.member.empty()) { - c += v.glslType + " " + v.name + "[" + std::to_string(n) + "];\n"; + c += v.glslType + " " + v.name + "[" + std::to_string(n) + "]" + (v.arraySize ? "[" + std::to_string(v.arraySize) + "]" : "") + + ";\n"; } else if (!inputBlocks.count(v.name)) { std::string structName = "wmgl_in_" + v.blockName; c += "struct " + structName + " {"; @@ -466,7 +512,8 @@ GeometrySources geometrySources(const GeometryShaderInfo& info, const LinkResult for (const GeometryVar& m : info.outputs) if (m.name == v.name) c += " " + m.glslType + " " + m.member + (m.arraySize ? "[" + std::to_string(m.arraySize) + "]" : "") + ";"; - c += " };\nwmgl_out_" + v.blockName + " " + v.name + ";\n"; + c += " };\nwmgl_out_" + v.blockName + " " + v.name + (v.instanceSize ? "[" + std::to_string(v.instanceSize) + "]" : "") + + ";\n"; outputBlocks[v.name] = true; } } @@ -474,9 +521,12 @@ GeometrySources geometrySources(const GeometryShaderInfo& info, const LinkResult if (info.readsPointSize) c += "float wmgl_in_pointsize[" + std::to_string(n) + "];\n"; if (info.readsClipDistance) c += "float wmgl_in_clipdistance[" + std::to_string(n) + "][" + std::to_string(info.inputClipDistances) + "];\n"; + if (info.readsCullDistance) + c += "float wmgl_in_culldistance[" + std::to_string(n) + "][" + std::to_string(info.inputCullDistances) + "];\n"; c += "vec4 wmgl_out_position;\nfloat wmgl_out_pointsize;\nint wmgl_out_layer;\nint wmgl_out_viewport;\nint wmgl_out_primitive_id;\n" "int wmgl_primitive_id_in;\nint wmgl_invocation_id;\n"; if (info.outputClipDistances) c += "float wmgl_out_clipdistance[" + std::to_string(info.outputClipDistances) + "];\n"; + if (info.outputCullDistances) c += "float wmgl_out_culldistance[" + std::to_string(info.outputCullDistances) + "];\n"; c += "buffer wmgl_GsInput { uint wmgl_gs_in[]; };\n" "buffer wmgl_GsOutput { uint wmgl_gs_out[]; };\n" "buffer wmgl_GsIndices { uint wmgl_gs_indices[]; };\n" @@ -524,6 +574,10 @@ GeometrySources geometrySources(const GeometryShaderInfo& info, const LinkResult for (int k = 0; k < std::min(info.inputClipDistances, p->size); ++k) c += " wmgl_in_clipdistance[wmgl_j][" + std::to_string(k) + "] = " + loadValue("float", GL_FLOAT, "wmgl_gs_in", "wmgl_b + " + std::to_string(p->offset + k) + "u") + ";\n"; + if (const TransformFeedbackVarying* p = info.readsCullDistance ? captured("gl_CullDistance") : nullptr) + for (int k = 0; k < std::min(info.inputCullDistances, p->size); ++k) + c += " wmgl_in_culldistance[wmgl_j][" + std::to_string(k) + "] = " + + loadValue("float", GL_FLOAT, "wmgl_gs_in", "wmgl_b + " + std::to_string(p->offset + k) + "u") + ";\n"; for (const GeometryVar& v : info.inputs) { const TransformFeedbackVarying* p = captured(captureName(v)); if (!p) continue; @@ -567,6 +621,7 @@ GeometrySources geometrySources(const GeometryShaderInfo& info, const LinkResult p += withoutStream(v.declaration) + "\n"; } if (info.outputClipDistances) p += "out float gl_ClipDistance[" + std::to_string(info.outputClipDistances) + "];\n"; + if (info.outputCullDistances) p += "out float gl_CullDistance[" + std::to_string(info.outputCullDistances) + "];\n"; p += "void main() {\n uint wmgl_b = uint(gl_VertexID) * " + stride + "u;\n"; for (const Field& f : fields) { std::string target = f.expr; @@ -575,6 +630,7 @@ GeometrySources geometrySources(const GeometryShaderInfo& info, const LinkResult else if (target == "wmgl_out_layer") target = "gl_Layer"; else if (target == "wmgl_out_viewport") target = "gl_ViewportIndex"; else if (target.rfind("wmgl_out_clipdistance", 0) == 0) target = "gl_ClipDistance" + target.substr(21); + else if (target.rfind("wmgl_out_culldistance", 0) == 0) target = "gl_CullDistance" + target.substr(21); p += " " + target + " = " + loadValue(f.glslType, f.type, "wmgl_gs_v", "wmgl_b + " + std::to_string(f.offset) + "u") + ";\n"; } // Only stream 0 is rasterized: other streams' vertices land outside the clip volume. diff --git a/src/core/shader/geometry.h b/src/core/shader/geometry.h index 47702bbf..62469210 100644 --- a/src/core/shader/geometry.h +++ b/src/core/shader/geometry.h @@ -43,6 +43,7 @@ struct GeometryVar { int arraySize = 0; // array size (0 = not an array); for inputs, of a block member (not the vertex index) std::string declaration; // outputs: the original declaration line(s) int stream = 0; // outputs: layout(stream = N) (GL 4.0 vertex streams) + int instanceSize = 0; // outputs: elements of an array of blocks (0 = not an array) }; struct GeometryShaderInfo { @@ -53,10 +54,10 @@ struct GeometryShaderInfo { int invocations = 1; // GL 4.0 instancing: layout(invocations = N) std::vector inputs; // user inputs (each an array of verticesIn elements) std::vector outputs; // user outputs - bool readsPointSize = false, readsClipDistance = false; - int inputClipDistances = 0; + bool readsPointSize = false, readsClipDistance = false, readsCullDistance = false; + int inputClipDistances = 0, inputCullDistances = 0; bool writesPointSize = false, writesLayer = false, writesPrimitiveId = false, writesViewportIndex = false; - int outputClipDistances = 0; + int outputClipDistances = 0, outputCullDistances = 0; // GL 4.0 vertex streams: outputs of streams other than 0, or // EmitStreamVertex (only with points output). Emitted vertices then carry // their stream; only stream 0 is rasterized. diff --git a/src/core/shader/glsl_upgrade.cpp b/src/core/shader/glsl_upgrade.cpp index ce75a57a..22498670 100644 --- a/src/core/shader/glsl_upgrade.cpp +++ b/src/core/shader/glsl_upgrade.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -629,13 +630,24 @@ std::string checkReservedWords(const std::string& source) { std::string lowerBuiltinUniforms(Stage stage, const std::string& source) { const bool fragment = stage == Stage::Fragment; + // Vertex shaders' draw parameters (GL 4.6 names, or the ARB ones with the extension). + bool drawParameters = false, drawParametersArb = false; + if (stage == Stage::Vertex && (source.find("gl_BaseVertex") != std::string::npos || + source.find("gl_BaseInstance") != std::string::npos || + source.find("gl_DrawID") != std::string::npos)) { + static const std::regex kVersion(R"(#[ \t]*version[ \t]+([0-9]+))"); + std::smatch m; + drawParameters = std::regex_search(source, m, kVersion) && std::stoi(m[1]) >= 460; + drawParametersArb = source.find("GL_ARB_shader_draw_parameters") != std::string::npos; + } if (source.find("gl_DepthRange") == std::string::npos && - (!fragment || source.find("gl_NumSamples") == std::string::npos)) + (!fragment || source.find("gl_NumSamples") == std::string::npos) && !drawParameters && !drawParametersArb) return source; const std::string prefix = kBuiltinUniformPrefix; std::string out; out.reserve(source.size() + 256); bool depthRange = false, numSamples = false, lineStart = true; + std::set parameters; // draw parameters used: "BaseVertex", "BaseInstance", "DrawID" size_t insertAt = std::string::npos; // before the first token, outside any #if block int conditionalDepth = 0; size_t conditionalStart = 0; @@ -706,6 +718,12 @@ std::string lowerBuiltinUniforms(Stage stage, const std::string& source) { } else if (fragment && word == "gl_NumSamples") { numSamples = true; out += prefix + word.substr(3); + } else if ((drawParameters && (word == "gl_BaseVertex" || word == "gl_BaseInstance" || word == "gl_DrawID")) || + (drawParametersArb && + (word == "gl_BaseVertexARB" || word == "gl_BaseInstanceARB" || word == "gl_DrawIDARB"))) { + const std::string name = word.substr(3, word.size() - 3 - (word.back() == 'B' ? 3 : 0)); + parameters.insert(name); + out += prefix + name; } else { out += word; } @@ -719,6 +737,7 @@ std::string lowerBuiltinUniforms(Stage stage, const std::string& source) { decls += "struct " + prefix + "DepthRangeParameters { float near; float far; float diff; };\n" "uniform " + prefix + "DepthRangeParameters " + prefix + "DepthRange;\n"; if (numSamples) decls += "uniform int " + prefix + "NumSamples;\n"; + for (const std::string& name : parameters) decls += "uniform int " + prefix + name + ";\n"; if (decls.empty()) return source; if (insertAt == std::string::npos) insertAt = out.size(); const long line = 1 + std::count(out.begin(), out.begin() + static_cast(insertAt), '\n'); diff --git a/src/core/shader/glsl_upgrade.h b/src/core/shader/glsl_upgrade.h index f0512eca..555e50dc 100644 --- a/src/core/shader/glsl_upgrade.h +++ b/src/core/shader/glsl_upgrade.h @@ -68,8 +68,12 @@ std::string checkReservedWords(const std::string& source); // Built-in uniforms (gl_DepthRange, and gl_NumSamples in fragment shaders) // exist only in GL-rules parses; the code generator's SPIR-V rules lack -// them. They become hidden default-block uniforms, kBuiltinUniformPrefix + -// the name without "gl_", that the draw path fills (applyBuiltinUniforms). +// them. Vertex shaders' draw parameters (gl_BaseVertex, gl_BaseInstance, +// gl_DrawID, or with the ARB suffix) have no MSL equivalent: Metal's base +// vertex is not GL's after WineMetalGL's index rewrites, and draws have no +// index. All become hidden default-block uniforms, kBuiltinUniformPrefix + +// the name without "gl_" (and "ARB"), that the draw path fills +// (applyBuiltinUniforms). inline constexpr const char* kBuiltinUniformPrefix = "wmgl_bi_"; std::string lowerBuiltinUniforms(Stage stage, const std::string& source); diff --git a/src/core/shader/spirv_input.cpp b/src/core/shader/spirv_input.cpp new file mode 100644 index 00000000..d561277e --- /dev/null +++ b/src/core/shader/spirv_input.cpp @@ -0,0 +1,280 @@ +#include "core/shader/spirv_input.h" + +#include + +#include +#include +#include +#include + +namespace wmgl::shader { +namespace { + +// The tag of a stage in the names of its outputs. +const char* stageTag(Stage stage) { + switch (stage) { + case Stage::Vertex: return "v"; + case Stage::TessControl: return "tc"; + case Stage::TessEval: return "te"; + case Stage::Geometry: return "g"; + case Stage::Fragment: return "f"; + default: return "c"; + } +} + +spv::ExecutionModel executionModel(Stage stage) { + switch (stage) { + case Stage::Vertex: return spv::ExecutionModelVertex; + case Stage::TessControl: return spv::ExecutionModelTessellationControl; + case Stage::TessEval: return spv::ExecutionModelTessellationEvaluation; + case Stage::Geometry: return spv::ExecutionModelGeometry; + case Stage::Fragment: return spv::ExecutionModelFragment; + default: return spv::ExecutionModelGLCompute; + } +} + +// Walks the module's instructions: fn(opcode, operands, count). +void forEachInstruction(const std::vector& words, + const std::function& fn) { + for (size_t at = 5; at < words.size();) { + const uint32_t count = words[at] >> 16, opcode = words[at] & 0xffffu; + if (count == 0 || at + count > words.size()) break; + fn(opcode, &words[at + 1], count - 1); + at += count; + } +} + +std::string literalString(const uint32_t* operands, uint32_t count) { + std::string out; + for (uint32_t i = 0; i < count; ++i) + for (int b = 0; b < 4; ++b) { + const char c = static_cast((operands[i] >> (8 * b)) & 0xffu); + if (!c) return out; + out += c; + } + return out; +} + +// Names the module's unnamed resources and every inter-stage variable +// (see spirv_input.h). Needs the module's internals, hence the subclass. +class SpirvToGlsl : public spirv_cross::CompilerGLSL { +public: + using CompilerGLSL::CompilerGLSL; + +protected: + // GL atomic counters (AtomicCounter storage), which SPIRV-Cross's GLSL + // declares and reads as plain uints: "uniform atomic_uint" and the + // atomicCounter* functions. + std::string type_to_glsl(const spirv_cross::SPIRType& type, uint32_t id) override { + const auto* var = id ? maybe_get(id) : nullptr; + if (type.basetype == spirv_cross::SPIRType::UInt && + (type.storage == spv::StorageClassAtomicCounter || (var && var->storage == spv::StorageClassAtomicCounter))) + return "atomic_uint"; + return CompilerGLSL::type_to_glsl(type, id); + } + // Built-ins SPIRV-Cross's GLSL lacks. + std::string builtin_to_glsl(spv::BuiltIn builtin, spv::StorageClass storage) override { + if (builtin == spv::BuiltInPatchVertices) return "gl_PatchVerticesIn"; + return CompilerGLSL::builtin_to_glsl(builtin, storage); + } + const char* to_storage_qualifiers_glsl(const spirv_cross::SPIRVariable& var) override { + if (var.storage == spv::StorageClassAtomicCounter) return "uniform "; + return CompilerGLSL::to_storage_qualifiers_glsl(var); + } + void emit_instruction(const spirv_cross::Instruction& instruction) override { + const uint32_t* ops = stream(instruction); + const auto op = static_cast(instruction.op); + const char* function = nullptr; + switch (op) { + case spv::OpAtomicLoad: function = "atomicCounter"; break; + case spv::OpAtomicIAdd: function = "atomicCounterAdd"; break; + case spv::OpAtomicISub: function = "atomicCounterSubtract"; break; + case spv::OpAtomicUMin: case spv::OpAtomicSMin: function = "atomicCounterMin"; break; + case spv::OpAtomicUMax: case spv::OpAtomicSMax: function = "atomicCounterMax"; break; + case spv::OpAtomicAnd: function = "atomicCounterAnd"; break; + case spv::OpAtomicOr: function = "atomicCounterOr"; break; + case spv::OpAtomicXor: function = "atomicCounterXor"; break; + case spv::OpAtomicExchange: function = "atomicCounterExchange"; break; + case spv::OpAtomicCompareExchange: function = "atomicCounterCompSwap"; break; + default: break; + } + if (!function || instruction.length < 3 || expression_type(ops[2]).storage != spv::StorageClassAtomicCounter) + return CompilerGLSL::emit_instruction(instruction); + forced_temporaries.insert(ops[1]); + std::string call = std::string(function) + "(" + to_non_uniform_aware_expression(ops[2]); + if (op == spv::OpAtomicCompareExchange) call += ", " + to_expression(ops[7]) + ", " + to_expression(ops[6]); + else if (op != spv::OpAtomicLoad) call += ", " + to_expression(ops[5]); + emit_op(ops[0], ops[1], call + ")", false); + flush_all_atomic_capable_variables(); + } + +public: + + void nameResources(Stage stage) { + using namespace spirv_cross; + const std::string p = kSpirvNamePrefix; + std::set structNames; + ir.for_each_typed_id([&](uint32_t id, SPIRVariable& var) { + if (var.storage == spv::StorageClassFunction || var.storage == spv::StorageClassPrivate || + var.storage == spv::StorageClassWorkgroup) + return; + const SPIRType& type = get(var.basetype); + const SPIRType& base = get(type.self); + const bool block = has_decoration(base.self, spv::DecorationBlock) || has_decoration(base.self, spv::DecorationBufferBlock); + const bool builtin = has_decoration(id, spv::DecorationBuiltIn) || + (block && !base.member_types.empty() && has_member_decoration(base.self, 0, spv::DecorationBuiltIn)); + if (builtin) return; + auto location = [&]() -> std::string { + uint32_t l = has_decoration(id, spv::DecorationLocation) ? get_decoration(id, spv::DecorationLocation) + : block && has_member_decoration(base.self, 0, spv::DecorationLocation) + ? get_member_decoration(base.self, 0, spv::DecorationLocation) + : 0xffffffffu; + if (l == 0xffffffffu) return "v" + std::to_string(id); + std::string out = std::to_string(l); + if (has_decoration(id, spv::DecorationComponent)) + out += "c" + std::to_string(get_decoration(id, spv::DecorationComponent)); + if (has_decoration(id, spv::DecorationIndex)) out += "i" + std::to_string(get_decoration(id, spv::DecorationIndex)); + return out; + }; + auto binding = [&]() { + return has_decoration(id, spv::DecorationBinding) ? std::to_string(get_decoration(id, spv::DecorationBinding)) + : "v" + std::to_string(id); + }; + auto nameMembers = [&](const SPIRType& t) { + for (uint32_t m = 0; m < t.member_types.size(); ++m) + if (get_member_name(t.self, m).empty()) set_member_name(t.self, m, p + "m" + std::to_string(m)); + }; + const bool input = var.storage == spv::StorageClassInput, output = var.storage == spv::StorageClassOutput; + if (input || output) { + // The application-facing ends keep their names; the rest match by location. + const bool end = (input && stage == Stage::Vertex) || (output && stage == Stage::Fragment); + const std::string kind = has_decoration(id, spv::DecorationPatch) ? "p" : "l"; + if (end) { + if (get_name(id).empty()) set_name(id, p + (input ? "a" : "o") + location()); + return; + } + // Outputs carry their stage's tag; inputs get the previous + // stage's at link time (spirvLinkInputs), as a stage's input + // and output at one location need different names. + const std::string side = input ? "in_" : std::string(stageTag(stage)) + "_"; + set_name(id, p + side + kind + location()); + if (block) { + set_name(base.self, p + side + "L" + kind + location()); + for (uint32_t m = 0; m < base.member_types.size(); ++m) set_member_name(base.self, m, p + "m" + std::to_string(m)); + } + return; + } + if (var.storage == spv::StorageClassUniform || var.storage == spv::StorageClassStorageBuffer) { + const bool storage = var.storage == spv::StorageClassStorageBuffer || has_decoration(base.self, spv::DecorationBufferBlock); + if (get_name(base.self).empty()) set_name(base.self, p + (storage ? "S" : "B") + binding()); + if (get_name(id).empty()) set_name(id, p + (storage ? "s" : "b") + binding()); + nameMembers(base); + return; + } + if (var.storage == spv::StorageClassAtomicCounter) { + if (get_name(id).empty()) + set_name(id, p + "c" + binding() + "o" + + std::to_string(has_decoration(id, spv::DecorationOffset) ? get_decoration(id, spv::DecorationOffset) : 0)); + return; + } + if (var.storage == spv::StorageClassUniformConstant && get_name(id).empty()) + set_name(id, p + "u" + (has_decoration(id, spv::DecorationLocation) ? location() : "b" + binding())); + }); + // Unnamed structures: one name per layout, so stages declaring the same + // structure agree (GLSL matches uniform structures by name). + ir.for_each_typed_id([&](uint32_t id, SPIRType& type) { + if (type.basetype != SPIRType::Struct || type.self != id || !get_name(id).empty()) return; + if (has_decoration(id, spv::DecorationBlock) || has_decoration(id, spv::DecorationBufferBlock)) return; + std::string layout; + for (uint32_t member : type.member_types) layout += type_to_glsl(get(member), 0) + ";"; + std::string name = p + "T" + std::to_string(std::hash()(layout) % 1000000007u); + while (!structNames.insert(name).second) name += "x"; + set_name(id, name); + for (uint32_t m = 0; m < type.member_types.size(); ++m) + if (get_member_name(id, m).empty()) set_member_name(id, m, p + "m" + std::to_string(m)); + }); + } +}; + +} // namespace + +bool spirvHeaderValid(const std::vector& words) { + // Versions 1.0 to 1.6. + return words.size() >= 5 && words[0] == 0x07230203u && words[1] >= 0x00010000u && words[1] <= 0x00010600u; +} + +const std::vector& spirvExtensions() { + static const std::vector extensions = {"SPV_KHR_shader_draw_parameters", "SPV_KHR_storage_buffer_storage_class", + "SPV_KHR_subgroup_vote", "SPV_KHR_no_integer_wrap_decoration"}; + return extensions; +} + +SpirvSpecialization specializeSpirv(const std::vector& words, Stage stage, const std::string& entry, + const std::vector>& constants) { + SpirvSpecialization out; + if (!spirvHeaderValid(words)) { + out.log = "error: not a SPIR-V 1.0-1.6 module\n"; + return out; + } + std::string unknownExtension; + forEachInstruction(words, [&](uint32_t opcode, const uint32_t* operands, uint32_t count) { + if (opcode == spv::OpName || opcode == spv::OpMemberName) out.named = true; + if (opcode == spv::OpExtension) { + const std::string name = literalString(operands, count); + const auto& known = spirvExtensions(); + if (std::none_of(known.begin(), known.end(), [&](const char* e) { return name == e; })) unknownExtension = name; + } + }); + try { + SpirvToGlsl glsl(words); + bool found = false; + for (const spirv_cross::EntryPoint& point : glsl.get_entry_points_and_stages()) + found |= point.name == entry && point.execution_model == executionModel(stage); + if (!found) { + out.status = SpirvSpecialization::kBadEntryPoint; + return out; + } + const spirv_cross::SmallVector specs = glsl.get_specialization_constants(); + for (const auto& [index, value] : constants) { + auto it = std::find_if(specs.begin(), specs.end(), [&](const auto& c) { return c.constant_id == index; }); + if (it == specs.end()) { + out.status = SpirvSpecialization::kBadConstant; + return out; + } + glsl.get_constant(it->id).m.c[0].r[0].u32 = value; + } + if (!unknownExtension.empty()) { + out.log = "error: the module uses SPIR-V extension " + unknownExtension + ", which is not in GL_SPIR_V_EXTENSIONS\n"; + return out; + } + glsl.set_entry_point(entry, executionModel(stage)); + glsl.nameResources(stage); + spirv_cross::CompilerGLSL::Options options = glsl.get_common_options(); + options.version = 460; + options.es = false; + options.vulkan_semantics = false; + options.separate_shader_objects = true; // keeps the locations of stage variables + options.enable_420pack_extension = true; + glsl.set_common_options(options); + out.glsl = glsl.compile(); + out.status = SpirvSpecialization::kOk; + } catch (const std::exception& error) { + out.log = std::string("error: SPIR-V module cannot be used: ") + error.what() + "\n"; + } + return out; +} + +std::string spirvLinkInputs(const std::string& glsl, Stage previous) { + const std::string from = std::string(kSpirvNamePrefix) + "in_", to = std::string(kSpirvNamePrefix) + stageTag(previous) + "_"; + std::string out = glsl; + for (size_t at = out.find(from); at != std::string::npos; at = out.find(from, at + to.size())) out.replace(at, from.size(), to); + return out; +} + +std::string spirvDisplayName(const LinkResult& link, const std::string& name) { + if (!link.spirv) return name; + if (link.spirvUnnamed || name.find(kSpirvNamePrefix) != std::string::npos) return std::string(); + return name; +} + +} // namespace wmgl::shader diff --git a/src/core/shader/spirv_input.h b/src/core/shader/spirv_input.h new file mode 100644 index 00000000..06af186c --- /dev/null +++ b/src/core/shader/spirv_input.h @@ -0,0 +1,50 @@ +// GL 4.6 / GL_ARB_gl_spirv: SPIR-V modules given to glShaderBinary +// (SHADER_BINARY_FORMAT_SPIR_V) are specialized by glSpecializeShader and +// translated to GLSL, which the rest of the compiler takes as it takes an +// application's source. +// +// SPIR-V matches stage interfaces by location and may carry no names; GLSL +// matches them by name. The translation names every inter-stage variable +// after its location, and every resource the module leaves unnamed after its +// location or binding, with kSpirvNamePrefix. Program queries report those +// names, and every name of a module without debug names, as empty +// (spirvDisplayName). +#pragma once + +#include +#include +#include +#include + +#include "core/shader/compiler.h" + +namespace wmgl::shader { + +inline constexpr const char* kSpirvNamePrefix = "wmgl_spv_"; + +struct SpirvSpecialization { + enum Status { kOk, kBadEntryPoint, kBadConstant, kFailed } status = kFailed; + std::string glsl; // kOk: the stage as GLSL + std::string log; // kFailed: why the module cannot be used + bool named = false; // the module carries debug names (OpName, OpMemberName) +}; + +// Specializes `words` for `stage` at entry point `entry` with `constants` +// (SpecId, 32-bit value). +SpirvSpecialization specializeSpirv(const std::vector& words, Stage stage, const std::string& entry, + const std::vector>& constants); + +// A module's header is SPIR-V (magic number, a version WineMetalGL reads). +bool spirvHeaderValid(const std::vector& words); + +// The SPIR-V extensions a module may use (GL_SPIR_V_EXTENSIONS). +const std::vector& spirvExtensions(); + +// A stage's GLSL with its inputs named to match the outputs of `previous`, +// the stage before it in the program. +std::string spirvLinkInputs(const std::string& glsl, Stage previous); + +// The name a program query reports for `name` (see above). +std::string spirvDisplayName(const LinkResult& link, const std::string& name); + +} // namespace wmgl::shader diff --git a/src/core/shader/spirv_passes.cpp b/src/core/shader/spirv_passes.cpp index 67a45aa0..b8f2e1b7 100644 --- a/src/core/shader/spirv_passes.cpp +++ b/src/core/shader/spirv_passes.cpp @@ -1,6 +1,7 @@ #include "core/shader/spirv_passes.h" #include +#include namespace wmgl::shader { namespace { @@ -171,3 +172,255 @@ std::vector cubeStorageImagesAs2DArrays(std::vector spirv) { } } // namespace wmgl::shader + +namespace wmgl::shader { +namespace { + +constexpr uint32_t kOpTypeIntOp = 21, kOpTypeArray = 28, kOpTypePointer = 32, kOpConstantOp = 43, + kOpVariable = 59, kOpDecorate = 71, kOpAccessChain = 65, kOpInBoundsAccessChain = 66, kOpIAdd = 128, + kOpIMul = 132, kOpFunctionOp = 54; +constexpr uint32_t kUniformConstant = 0, kInput = 1, kOutput = 3, kDecorationBuiltIn = 11; + +} // namespace + +std::vector flattenArraysOfArrays(std::vector spirv) { + if (spirv.size() < 5) return spirv; + struct ArrayType { uint32_t element, length; }; + std::map arrays; + std::map> pointers; // id -> (storage, pointee) + std::map> constants; // id -> (type, value) + std::map intTypes; // 32-bit int type -> signed + std::map variables; // id -> instruction index + std::set builtIns; + size_t firstFunction = 0; + for (size_t i = 5; i < spirv.size();) { + const uint32_t count = spirv[i] >> 16, op = spirv[i] & 0xffff; + if (!count || i + count > spirv.size()) return spirv; + if (op == kOpTypeArray && count == 4) arrays[spirv[i + 1]] = {spirv[i + 2], spirv[i + 3]}; + if (op == kOpTypePointer && count == 4) pointers[spirv[i + 1]] = {spirv[i + 2], spirv[i + 3]}; + if (op == kOpConstantOp && count == 4) constants[spirv[i + 2]] = {spirv[i + 1], spirv[i + 3]}; + if (op == kOpTypeIntOp && count == 4 && spirv[i + 2] == 32) intTypes[spirv[i + 1]] = spirv[i + 3] != 0; + if (op == kOpVariable && count >= 4 && !firstFunction) variables[spirv[i + 2]] = i; + if (op == kOpDecorate && count >= 4 && spirv[i + 2] == kDecorationBuiltIn) builtIns.insert(spirv[i + 1]); + if (op == kOpFunctionOp && !firstFunction) firstFunction = i; + i += count; + } + if (!firstFunction) return spirv; + // Candidates: arrays of arrays of opaque types (UniformConstant) or of + // stage inputs and outputs, which MSL cannot declare. + struct Candidate { std::vector dims; uint32_t element; }; + std::map candidates; + for (const auto& [id, at] : variables) { + const uint32_t storage = spirv[at + 3]; + if ((storage != kUniformConstant && storage != kInput && storage != kOutput) || builtIns.count(id)) continue; + auto pointer = pointers.find(spirv[at + 1]); + if (pointer == pointers.end()) continue; + Candidate c; + uint32_t type = pointer->second.second; + while (arrays.count(type)) { + auto length = constants.find(arrays[type].length); + if (length == constants.end()) break; + c.dims.push_back(length->second.second); + type = arrays[type].element; + } + if (c.dims.size() < 2 || arrays.count(type)) continue; + c.element = type; + candidates[id] = c; + } + if (candidates.empty()) return spirv; + // Every use in code must be an access chain through all the dimensions. + for (size_t i = firstFunction; i < spirv.size();) { + const uint32_t count = spirv[i] >> 16, op = spirv[i] & 0xffff; + const bool chain = op == kOpAccessChain || op == kOpInBoundsAccessChain; + for (uint32_t w = 1; w < count; ++w) { + auto c = candidates.find(spirv[i + w]); + if (c == candidates.end()) continue; + if (!(chain && w == 3 && count - 4 >= c->second.dims.size())) candidates.erase(c); + } + i += count; + } + if (candidates.empty()) return spirv; + uint32_t bound = spirv[3]; + std::map, uint32_t> constantIds; // (type, value) -> id + for (const auto& [id, c] : constants) constantIds[{c.first, c.second}] = id; + uint32_t uintType = 0; + for (const auto& [id, isSigned] : intTypes) + if (!isSigned) uintType = id; + std::vector declarations; // new types and constants, before the first variable + auto constant = [&](uint32_t type, uint32_t value) { + auto it = constantIds.find({type, value}); + if (it != constantIds.end()) return it->second; + const uint32_t id = bound++; + declarations.insert(declarations.end(), {(4u << 16) | kOpConstantOp, type, id, value}); + constantIds[{type, value}] = id; + return id; + }; + if (!uintType) { + uintType = bound++; + declarations.insert(declarations.end(), {(4u << 16) | kOpTypeIntOp, uintType, 32u, 0u}); + } + std::map newPointer; // variable -> flattened pointer type + for (const auto& [id, c] : candidates) { + uint32_t total = 1; + for (uint32_t d : c.dims) total *= d; + const uint32_t array = bound++, pointer = bound++; + const uint32_t length = constant(uintType, total); + declarations.insert(declarations.end(), {(4u << 16) | kOpTypeArray, array, c.element, length}); + declarations.insert(declarations.end(), {(4u << 16) | kOpTypePointer, pointer, spirv[variables[id] + 3], array}); + newPointer[id] = pointer; + } + // Result types of code values (for the index arithmetic). + std::map valueType; + for (const auto& [id, c] : constants) valueType[id] = c.first; + for (size_t i = firstFunction; i < spirv.size();) { + const uint32_t count = spirv[i] >> 16; + if (count >= 3) valueType[spirv[i + 2]] = spirv[i + 1]; + i += count; + } + std::vector code; + for (size_t i = firstFunction; i < spirv.size();) { + const uint32_t count = spirv[i] >> 16, op = spirv[i] & 0xffff; + auto c = (op == kOpAccessChain || op == kOpInBoundsAccessChain) ? candidates.find(spirv[i + 3]) : candidates.end(); + if (c == candidates.end()) { + code.insert(code.end(), spirv.begin() + static_cast(i), spirv.begin() + static_cast(i + count)); + i += count; + continue; + } + // ((i0 * d1 + i1) * d2 + i2) ... in the first index's integer type. + const std::vector& dims = c->second.dims; + uint32_t linear = spirv[i + 4]; + uint32_t type = valueType.count(linear) ? valueType[linear] : uintType; + if (!intTypes.count(type)) type = uintType; + for (size_t d = 1; d < dims.size(); ++d) { + const uint32_t scaled = bound++, sum = bound++; + code.insert(code.end(), {(5u << 16) | kOpIMul, type, scaled, linear, constant(type, dims[d])}); + code.insert(code.end(), {(5u << 16) | kOpIAdd, type, sum, scaled, spirv[i + 4 + d]}); + linear = sum; + } + std::vector words = {spirv[i + 1], spirv[i + 2], spirv[i + 3], linear}; + words.insert(words.end(), spirv.begin() + static_cast(i + 4 + dims.size()), spirv.begin() + static_cast(i + count)); + code.push_back((static_cast(words.size() + 1) << 16) | op); + code.insert(code.end(), words.begin(), words.end()); + i += count; + } + // The global section, then the new declarations, then the flattened + // variables (moved after them, with their new pointer types). + std::vector out(spirv.begin(), spirv.begin() + 5), moved; + for (size_t i = 5; i < firstFunction;) { + const uint32_t count = spirv[i] >> 16, op = spirv[i] & 0xffff; + auto p = op == kOpVariable ? newPointer.find(spirv[i + 2]) : newPointer.end(); + if (p != newPointer.end()) { + const size_t start = moved.size(); + moved.insert(moved.end(), spirv.begin() + static_cast(i), spirv.begin() + static_cast(i + count)); + moved[start + 1] = p->second; + } else { + out.insert(out.end(), spirv.begin() + static_cast(i), spirv.begin() + static_cast(i + count)); + } + i += count; + } + out.insert(out.end(), declarations.begin(), declarations.end()); + out.insert(out.end(), moved.begin(), moved.end()); + out.insert(out.end(), code.begin(), code.end()); + out[3] = bound; + return out; +} + +namespace { + +constexpr uint32_t kOpTypeImageOp = 25, kOpTypeArrayOp = 28, kOpTypeRuntimeArray = 29, kOpTypePointerOp = 32, + kOpFunctionFirst = 54, kOpVariableOp = 59, kOpLoadOp = 61, + kOpAccessChainOp = 65, kOpInBoundsAccessChainOp = 66, kOpDecorateOp = 71, kOpMemberDecorate = 72, + kOpDecorationGroup = 73, kOpGroupDecorate = 74, kOpGroupMemberDecorate = 75, kOpCopyObject = 83, + kOpImageRead = 98, kOpImageWrite = 99, kOpImageQuerySizeLod = 103, kOpImageQuerySizeOp = 104, + kOpImageQueryLod = 105, kOpImageQueryLevels = 106, kOpImageQuerySamples = 107, + kOpImageSparseRead = 320, kOpDecorateId = 332, kOpDecorateString = 5632, + kOpMemberDecorateString = 5633; +constexpr uint32_t kDecorationNonWritable = 24, kDecorationNonReadable = 25; +constexpr uint32_t kRead = 1, kWrite = 2; + +bool isAnnotation(uint32_t op) { + return (op >= kOpDecorateOp && op <= kOpGroupMemberDecorate) || op == kOpDecorateId || op == kOpDecorateString || + op == kOpMemberDecorateString; +} + +} // namespace + +std::vector inferStorageImageAccess(std::vector spirv) { + if (spirv.size() < 5) return spirv; + std::set imageTypes, pointerTypes; + std::map access; // storage image variable -> kRead | kWrite + std::map origin; // loads and access chains of one -> the variable + std::map> decorations; + size_t firstFunction = 0, annotationsEnd = 0, firstType = 0; + for (size_t i = 5; i < spirv.size();) { + const uint32_t count = spirv[i] >> 16, op = spirv[i] & 0xffff; + if (!count || i + count > spirv.size()) return spirv; + if (op == kOpFunctionFirst) { + firstFunction = i; + break; + } + if (isAnnotation(op)) annotationsEnd = i + count; + if (op == kOpDecorateOp && count >= 3) decorations[spirv[i + 1]].insert(spirv[i + 2]); + if (op == kOpTypeImageOp && count >= 9 && spirv[i + 7] == 2) imageTypes.insert(spirv[i + 1]); + if ((op == kOpTypeArrayOp || op == kOpTypeRuntimeArray) && count >= 3 && imageTypes.count(spirv[i + 2])) + imageTypes.insert(spirv[i + 1]); + if (op == kOpTypePointerOp && count >= 4 && imageTypes.count(spirv[i + 3])) pointerTypes.insert(spirv[i + 1]); + if (op == kOpVariableOp && count >= 4 && pointerTypes.count(spirv[i + 1])) access[spirv[i + 2]] = 0; + if (!firstType && op >= 19 && op <= 39) firstType = i; + i += count; + } + if (access.empty() || !firstFunction) return spirv; + for (auto& [variable, mask] : access) origin[variable] = variable; + auto mark = [&](uint32_t id, uint32_t bits) { + if (auto it = origin.find(id); it != origin.end()) access[it->second] |= bits; + }; + for (size_t i = firstFunction; i < spirv.size();) { + const uint32_t count = spirv[i] >> 16, op = spirv[i] & 0xffff; + if (!count || i + count > spirv.size()) return spirv; + switch (op) { + case kOpLoadOp: + case kOpAccessChainOp: + case kOpInBoundsAccessChainOp: + case kOpCopyObject: + if (count >= 4) { + if (auto it = origin.find(spirv[i + 3]); it != origin.end()) origin[spirv[i + 2]] = it->second; + for (uint32_t w = 4; w < count; ++w) mark(spirv[i + w], kRead | kWrite); + } + break; + case kOpImageRead: + case kOpImageSparseRead: + if (count >= 4) mark(spirv[i + 3], kRead); + break; + case kOpImageWrite: + if (count >= 2) mark(spirv[i + 1], kWrite); + break; + case kOpImageQuerySizeLod: + case kOpImageQuerySizeOp: + case kOpImageQueryLod: + case kOpImageQueryLevels: + case kOpImageQuerySamples: + break; + default: + // Atomics (OpImageTexelPointer), function arguments and anything + // else: read and written, as without this pass. + for (uint32_t w = 1; w < count; ++w) mark(spirv[i + w], kRead | kWrite); + break; + } + i += count; + } + std::vector added; + for (const auto& [variable, mask] : access) { + const std::set& has = decorations[variable]; + if (!(mask & kWrite) && !has.count(kDecorationNonWritable)) + added.insert(added.end(), {(3u << 16) | kOpDecorateOp, variable, kDecorationNonWritable}); + else if (!(mask & kRead) && !has.count(kDecorationNonReadable)) + added.insert(added.end(), {(3u << 16) | kOpDecorateOp, variable, kDecorationNonReadable}); + } + if (added.empty()) return spirv; + const size_t at = annotationsEnd ? annotationsEnd : firstType; + if (!at) return spirv; + spirv.insert(spirv.begin() + static_cast(at), added.begin(), added.end()); + return spirv; +} + +} // namespace wmgl::shader diff --git a/src/core/shader/spirv_passes.h b/src/core/shader/spirv_passes.h index 44c0747e..6afdddd4 100644 --- a/src/core/shader/spirv_passes.h +++ b/src/core/shader/spirv_passes.h @@ -19,4 +19,16 @@ std::vector lowerGatherOffsets(std::vector spirv); // for cube arrays. std::vector cubeStorageImagesAs2DArrays(std::vector spirv); +// Arrays of arrays of opaque types and of stage inputs and outputs, which +// MSL cannot declare, become one-dimensional arrays: access chains through +// every dimension index them at ((i0 * d1 + i1) * d2 + ...). A variable used +// any other way (a partial access, a whole copy) is left as it is. +std::vector flattenArraysOfArrays(std::vector spirv); + +// Storage images a stage only reads get NonWritable, and those it only +// writes NonReadable, so they become access::read or access::write Metal +// textures: a Metal function takes only 8 read_write textures. Atomics, +// images passed to functions and any other use keep both accesses. +std::vector inferStorageImageAccess(std::vector spirv); + } // namespace wmgl::shader diff --git a/src/core/shader/tessellation.cpp b/src/core/shader/tessellation.cpp index e067e632..4e218f70 100644 --- a/src/core/shader/tessellation.cpp +++ b/src/core/shader/tessellation.cpp @@ -107,7 +107,7 @@ std::string dimsText(const std::vector& dims, size_t from) { // Replaces ARRAY[EXPR].gl_X with PREFIX_x[EXPR] for the gl_in / gl_out built-in arrays. std::string renameArrayedBuiltin(const std::string& text, const std::string& array, const std::string& prefix, bool& pointSize, - bool& clipDistance) { + bool& clipDistance, bool& cullDistance) { std::string out; size_t i = 0; const std::string open = array + "["; @@ -141,6 +141,9 @@ std::string renameArrayedBuiltin(const std::string& text, const std::string& arr } else if (member == "gl_ClipDistance") { clipDistance = true; out += prefix + "clipdistance[" + index + "]"; + } else if (member == "gl_CullDistance") { + cullDistance = true; + out += prefix + "culldistance[" + index + "]"; } else { out += prefix + "position[" + index + "]"; } @@ -236,7 +239,13 @@ bool parseTessellationGlsl(Stage stage, const std::string& glsl, TessStageInfo& info.globals += flat + "\n"; continue; } - if (d.name.rfind("gl_", 0) == 0) continue; + if (d.name.rfind("gl_", 0) == 0) { + // Redeclared built-in arrays ("out float gl_CullDistance[N];"): + // the evaluation stage keeps its outputs' sizes. + if ((d.name == "gl_ClipDistance" || d.name == "gl_CullDistance") && d.direction == "out" && !control) + info.outputDeclarations += flat + "\n"; + continue; + } const bool output = d.direction == "out"; const bool arrayed = !d.patch && (control || !output); if (auto st = structs.find(d.type); st != structs.end()) { @@ -312,6 +321,8 @@ bool parseTessellationGlsl(Stage stage, const std::string& glsl, TessStageInfo& const Declaration m = parseDeclaration(trim(member) + ";"); if (m.name == "gl_ClipDistance" && !m.dims.empty()) (output ? info.outputClipDistances : info.inputClipDistances) = std::atoi(m.dims[0].c_str()); + if (m.name == "gl_CullDistance" && !m.dims.empty()) + (output ? info.outputCullDistances : info.inputCullDistances) = std::atoi(m.dims[0].c_str()); } // The evaluation stage's output block is a vertex shader output. if (!control && output) info.outputDeclarations += flat + "\n"; @@ -350,16 +361,20 @@ bool parseTessellationGlsl(Stage stage, const std::string& glsl, TessStageInfo& } std::string functions; for (size_t i = functionsStart; i < lines.size(); ++i) functions += lines[i] + "\n"; - bool inPointSize = false, inClip = false, outPointSize = false, outClip = false; - functions = renameArrayedBuiltin(functions, "gl_in", "wmgl_in_", inPointSize, inClip); - if (control) functions = renameArrayedBuiltin(functions, "gl_out", "wmgl_out_", outPointSize, outClip); + bool inPointSize = false, inClip = false, inCull = false, outPointSize = false, outClip = false, outCull = false; + functions = renameArrayedBuiltin(functions, "gl_in", "wmgl_in_", inPointSize, inClip, inCull); + if (control) functions = renameArrayedBuiltin(functions, "gl_out", "wmgl_out_", outPointSize, outClip, outCull); info.readsPointSize = inPointSize; info.readsClipDistance = inClip; + info.readsCullDistance = inCull; if (inClip && !info.inputClipDistances) info.inputClipDistances = 8; + if (inCull && !info.inputCullDistances) info.inputCullDistances = 8; if (control) { info.writesPointSize = outPointSize; if (outClip && !info.outputClipDistances) info.outputClipDistances = 8; if (!outClip) info.outputClipDistances = 0; + if (outCull && !info.outputCullDistances) info.outputCullDistances = std::max(1, 8 - info.outputClipDistances); + if (!outCull) info.outputCullDistances = 0; } const std::pair renames[] = { {"gl_InvocationID", "wmgl_invocation_id"}, {"gl_PrimitiveID", "wmgl_primitive_id"}, @@ -382,6 +397,7 @@ std::vector tessellationCaptureVaryings(const TessStageInfo& first) std::vector varyings = {"gl_Position"}; if (first.readsPointSize) varyings.push_back("gl_PointSize"); if (first.readsClipDistance) varyings.push_back("gl_ClipDistance"); + if (first.readsCullDistance) varyings.push_back("gl_CullDistance"); for (const TessVar& v : first.inputs) if (!v.patch) varyings.push_back(varKey(v)); return varyings; @@ -456,6 +472,8 @@ TessControlSources tessControlSources(const TessStageInfo& info, const LinkResul add(out.vertexFields, out.vertexWords, "gl_Position", GL_FLOAT_VEC4, 1, -1); add(out.vertexFields, out.vertexWords, "gl_PointSize", GL_FLOAT, 1, -1); if (info.outputClipDistances) add(out.vertexFields, out.vertexWords, "gl_ClipDistance", GL_FLOAT, info.outputClipDistances, -1); + if (info.outputCullDistances) add(out.vertexFields, out.vertexWords, "gl_CullDistance", GL_FLOAT, info.outputCullDistances, -1); + const size_t builtinFields = out.vertexFields.size(); out.patchWords = 6; // outer levels, inner levels for (const TessVar& v : info.outputs) { const std::string key = varKey(v); @@ -474,9 +492,13 @@ TessControlSources tessControlSources(const TessStageInfo& info, const LinkResul if (info.readsPointSize) c += "float wmgl_in_pointsize[" + maxIn + "];\n"; if (info.readsClipDistance) c += "float wmgl_in_clipdistance[" + maxIn + "][" + std::to_string(info.inputClipDistances) + "];\n"; + if (info.readsCullDistance) + c += "float wmgl_in_culldistance[" + maxIn + "][" + std::to_string(info.inputCullDistances) + "];\n"; c += "shared vec4 wmgl_out_position[" + ns + "];\nshared float wmgl_out_pointsize[" + ns + "];\n"; if (info.outputClipDistances) c += "shared float wmgl_out_clipdistance[" + ns + "][" + std::to_string(info.outputClipDistances) + "];\n"; + if (info.outputCullDistances) + c += "shared float wmgl_out_culldistance[" + ns + "][" + std::to_string(info.outputCullDistances) + "];\n"; c += "shared float wmgl_tess_outer[4];\nshared float wmgl_tess_inner[2];\n" "int wmgl_invocation_id, wmgl_primitive_id, wmgl_patch_vertices_in;\n" "layout(std430) readonly buffer wmgl_TcsInput { uint wmgl_tcs_in[]; };\n" @@ -501,6 +523,10 @@ TessControlSources tessControlSources(const TessStageInfo& info, const LinkResul for (int k = 0; k < std::min(info.inputClipDistances, p->size); ++k) c += " wmgl_in_clipdistance[wmgl_j][" + std::to_string(k) + "] = " + glslLoadValue("float", GL_FLOAT, "wmgl_tcs_in", "wmgl_b + " + std::to_string(p->offset + k) + "u") + ";\n"; + if (const TransformFeedbackVarying* p = info.readsCullDistance ? captured("gl_CullDistance") : nullptr) + for (int k = 0; k < std::min(info.inputCullDistances, p->size); ++k) + c += " wmgl_in_culldistance[wmgl_j][" + std::to_string(k) + "] = " + + glslLoadValue("float", GL_FLOAT, "wmgl_tcs_in", "wmgl_b + " + std::to_string(p->offset + k) + "u") + ";\n"; for (const TessVar& v : info.inputs) { const TransformFeedbackVarying* p = captured(varKey(v)); if (!p) continue; @@ -528,11 +554,14 @@ TessControlSources tessControlSources(const TessStageInfo& info, const LinkResul else if (f.key == "gl_ClipDistance") for (int k = 0; k < f.elements; ++k) c += store("wmgl_o", f.offset + k, "wmgl_out_clipdistance[wmgl_invocation_id][" + std::to_string(k) + "]", GL_FLOAT); + else if (f.key == "gl_CullDistance") + for (int k = 0; k < f.elements; ++k) + c += store("wmgl_o", f.offset + k, "wmgl_out_culldistance[wmgl_invocation_id][" + std::to_string(k) + "]", GL_FLOAT); } size_t field = 0; for (const TessVar& v : info.outputs) { if (v.patch) continue; - const TessRecordField& f = out.vertexFields[(info.outputClipDistances ? 3 : 2) + field++]; + const TessRecordField& f = out.vertexFields[builtinFields + field++]; for (int e = 0; e < f.elements; ++e) c += store("wmgl_o", f.offset + static_cast(e * words(v.type)), access(v, "wmgl_invocation_id", e), v.type); } @@ -563,6 +592,8 @@ std::string tessEvaluationVertexShader(const TessStageInfo& info, const TessCont if (info.readsPointSize) v += "float wmgl_in_pointsize[" + maxIn + "];\n"; if (info.readsClipDistance) v += "float wmgl_in_clipdistance[" + maxIn + "][" + std::to_string(info.inputClipDistances) + "];\n"; + if (info.readsCullDistance) + v += "float wmgl_in_culldistance[" + maxIn + "][" + std::to_string(info.inputCullDistances) + "];\n"; v += "float wmgl_tess_outer[4];\nfloat wmgl_tess_inner[2];\nvec3 wmgl_tess_coord;\n" "int wmgl_primitive_id, wmgl_patch_vertices_in;\n"; v += info.outputDeclarations; @@ -619,13 +650,14 @@ std::string tessEvaluationVertexShader(const TessStageInfo& info, const TessCont for (int k = 0; k < std::min(info.inputClipDistances, f->elements); ++k) v += load("wmgl_in_clipdistance[wmgl_j][" + std::to_string(k) + "]", "float", GL_FLOAT, "wmgl_b", f->offset + static_cast(k)); + if (const TessRecordField* f = info.readsCullDistance ? find(vertexFields, "gl_CullDistance", -1) : nullptr) + for (int k = 0; k < std::min(info.inputCullDistances, f->elements); ++k) + v += load("wmgl_in_culldistance[wmgl_j][" + std::to_string(k) + "]", "float", GL_FLOAT, "wmgl_b", + f->offset + static_cast(k)); for (const TessVar& in : info.inputs) { if (in.patch) continue; const TessRecordField* f = find(vertexFields, varKey(in), in.location); - if (!f) { - log += "error: tessellation evaluation input '" + varKey(in) + "' is not written by the previous stage\n"; - return std::string(); - } + if (!f) continue; // not written by the previous stage: undefined (GL allows it while unread) for (int e = 0; e < std::max(1, std::min(in.arraySize, f->elements)); ++e) v += load(access(in, "wmgl_j", e), in.glslType, in.type, "wmgl_b", f->offset + static_cast(e * words(in.type))); } @@ -641,10 +673,7 @@ std::string tessEvaluationVertexShader(const TessStageInfo& info, const TessCont for (const TessVar& in : info.inputs) { if (!in.patch) continue; const TessRecordField* f = find(control->patchFields, varKey(in), in.location); - if (!f) { - log += "error: tessellation evaluation patch input '" + varKey(in) + "' is not written by the control stage\n"; - return std::string(); - } + if (!f) continue; // not written by the control stage: undefined for (int e = 0; e < std::max(1, std::min(in.arraySize, f->elements)); ++e) v += " " + access(in, "", e) + " = " + glslLoadValue(in.glslType, in.type, "wmgl_tes_p", diff --git a/src/core/shader/tessellation.h b/src/core/shader/tessellation.h index 2249de83..831d793a 100644 --- a/src/core/shader/tessellation.h +++ b/src/core/shader/tessellation.h @@ -52,8 +52,8 @@ struct TessVar { struct TessStageInfo { std::vector inputs, outputs; int verticesOut = 0; // control: layout(vertices = N) - bool readsPointSize = false, readsClipDistance = false; - int inputClipDistances = 0, outputClipDistances = 0; + bool readsPointSize = false, readsClipDistance = false, readsCullDistance = false; + int inputClipDistances = 0, outputClipDistances = 0, inputCullDistances = 0, outputCullDistances = 0; bool writesPointSize = false; std::string globals; // uniforms, constants, structs, extensions std::string outputDeclarations; // evaluation: output declarations (vertex shader outputs) @@ -71,7 +71,7 @@ std::vector tessellationCaptureVaryings(const TessStageInfo& first) // then the patch section: the outer levels (4 words), the inner levels // (2 words), then the per-patch outputs. struct TessRecordField { - std::string key; // "gl_Position", "gl_PointSize", "gl_ClipDistance", "name" or "Block.member" + std::string key; // "gl_Position", "gl_PointSize", "gl_ClipDistance", "gl_CullDistance", "name" or "Block.member" GLenum type = 0; int elements = 1; // array elements uint32_t offset = 0; // words, within the vertex record or the patch section diff --git a/src/core/shader/transform_feedback.cpp b/src/core/shader/transform_feedback.cpp index 8cac8189..4da8d06b 100644 --- a/src/core/shader/transform_feedback.cpp +++ b/src/core/shader/transform_feedback.cpp @@ -1,5 +1,6 @@ #include "core/shader/transform_feedback.h" +#include #include namespace wmgl::shader { @@ -78,7 +79,8 @@ bool layoutTransformFeedback(const std::vector& varyings, GLenum mo return false; } std::set seen; - uint32_t interleavedWords = 0, totalWords = 0; + // Interleaved: the limit applies to each buffer (GL 4.6 section 11.1.2.1). + uint32_t interleavedWords = 0, mostWords = 0; int buffer = 0; // interleaved: the buffer being filled (gl_NextBuffer advances it) for (const std::string& varying : varyings) { // GL_ARB_transform_feedback3: gl_SkipComponents1-4 leaves components @@ -99,8 +101,8 @@ bool layoutTransformFeedback(const std::vector& varyings, GLenum mo v.offset = interleavedWords; if (skip) { interleavedWords += static_cast(v.size); - totalWords += static_cast(v.size); } else { + mostWords = std::max(mostWords, interleavedWords); result.xfbStride[buffer] = interleavedWords; if (++buffer >= TransformFeedbackBufferLimit) { log += "error: too many transform feedback buffers\n"; @@ -153,12 +155,11 @@ bool layoutTransformFeedback(const std::vector& varyings, GLenum mo v.buffer = buffer; v.offset = interleavedWords; interleavedWords += static_cast(v.components); - totalWords += static_cast(v.components); } result.transformFeedback.push_back(v); } if (!separate) { - if (totalWords > maxInterleavedComponents) { + if (std::max(mostWords, interleavedWords) > maxInterleavedComponents) { log += "error: transform feedback varyings exceed MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS\n"; return false; } diff --git a/src/core/state.h b/src/core/state.h index 88b25a46..eee1acb5 100644 --- a/src/core/state.h +++ b/src/core/state.h @@ -127,6 +127,10 @@ struct ContextState { GLenum clipOrigin = GL_LOWER_LEFT, clipDepthMode = GL_NEGATIVE_ONE_TO_ONE; // GL_ARB_clip_control bool primitiveRestart = false; GLuint primitiveRestartIndex = 0; + // GL_ARB/KHR_parallel_shader_compile: the application's thread limit + // (0xFFFFFFFF: the implementation's choice). Compiles finish before the + // call returns, so it changes nothing. + GLuint maxShaderCompilerThreads = 0xFFFFFFFFu; bool primitiveRestartFixedIndex = false; bool textureCubeMapSeamless = false; uint32_t clipDistanceEnables = 0; @@ -186,8 +190,9 @@ struct ContextState { // Bindings. RefPtr arrayBuffer, copyReadBuffer, copyWriteBuffer, pixelPackBuffer, pixelUnpackBuffer, textureBuffer, uniformBuffer, transformFeedbackBuffer, drawIndirectBuffer, dispatchIndirectBuffer, - shaderStorageBuffer, atomicCounterBuffer, queryBuffer; - static constexpr int kMaxUniformBufferBindings = 72; // = limits::kMaxUniformBufferBindings + shaderStorageBuffer, atomicCounterBuffer, queryBuffer, parameterBuffer, + externalVirtualMemoryBuffer; // GL_AMD_pinned_memory + static constexpr int kMaxUniformBufferBindings = 84; // = limits::kMaxUniformBufferBindings (GL 4.5 minimum) std::array uniformBufferBindings; // GL 4.2 atomic counter buffer bindings. static constexpr int kMaxAtomicCounterBufferBindings = 8; // = limits::kMaxAtomicCounterBufferBindings @@ -195,7 +200,7 @@ struct ContextState { // GL 4.3 shader storage buffer bindings. static constexpr int kMaxShaderStorageBufferBindings = 16; // = limits::kMaxShaderStorageBufferBindings std::array shaderStorageBufferBindings; - static constexpr int kMaxImageUnits = 8; // = limits::kMaxImageUnits + static constexpr int kMaxImageUnits = 16; // = limits::kMaxImageUnits std::array imageUnits; static constexpr int kMaxTextureUnits = 80; std::array textureUnits; @@ -212,7 +217,7 @@ struct ContextState { GLenum defaultReadBuffer = GL_BACK; // Active queries by target index. - std::array, 12> activeQueries; // by QueryIndex slot (core/query.h) + std::array, 28> activeQueries; // by QueryIndex slot (core/query.h: kQuerySlotCount) struct { RefPtr query; GLenum mode = 0; diff --git a/src/core/texture_storage.h b/src/core/texture_storage.h index 530975e7..d69797e0 100644 --- a/src/core/texture_storage.h +++ b/src/core/texture_storage.h @@ -4,6 +4,7 @@ #import #include "core/objects.h" +#include "core/shader/border.h" namespace wmgl { @@ -22,6 +23,11 @@ bool imageHasStorage(Context& ctx, Texture& tex, int face, int level); // GL texture completeness (GL 3.3 section 3.8.14) for sampling with `params`. bool textureComplete(Context& ctx, Texture& tex, const SamplerParams* params); +// Whether sampling `tex` with `params` needs clamp-to-border emulated in the +// shader (shader/border.h): a CLAMP_TO_BORDER axis whose border Metal's +// sampler cannot give. Fills `out` (all but its Metal texture index). +bool borderEmulation(const Texture& tex, const SamplerParams& params, bool shadow, shader::BorderSlot& out); + // View for sampling: levels [base, max], composed swizzle, linear view of // stencil/depth as appropriate. id samplingView(Context& ctx, Texture& tex); diff --git a/src/core/texture_storage.mm b/src/core/texture_storage.mm index 45c2c8f1..13ae42ee 100644 --- a/src/core/texture_storage.mm +++ b/src/core/texture_storage.mm @@ -450,6 +450,97 @@ bool textureComplete(Context& ctx, Texture& tex, const SamplerParams* params) { return view; } +bool borderEmulation(const Texture& tex, const SamplerParams& p, bool shadow, shader::BorderSlot& out) { + int dims = 0; + switch (tex.target) { + case GL_TEXTURE_1D: case GL_TEXTURE_1D_ARRAY: dims = 1; break; + case GL_TEXTURE_2D: case GL_TEXTURE_2D_ARRAY: case GL_TEXTURE_RECTANGLE: dims = 2; break; + case GL_TEXTURE_3D: dims = 3; break; + default: return false; // cube maps filter seamlessly; buffers and multisample textures are not filtered + } + const GLenum wraps[3] = {p.wrapS, p.wrapT, p.wrapR}; + out = {}; + for (int a = 0; a < dims; ++a) { + uint32_t mode = 3; + switch (wraps[a]) { + case GL_CLAMP_TO_BORDER: mode = 0; out.axes |= 1u << a; break; + case GL_REPEAT: mode = 1; break; + case GL_MIRRORED_REPEAT: mode = 2; break; + case GL_MIRROR_CLAMP_TO_EDGE: mode = 4; break; + default: break; + } + out.wrap |= mode << (4 * a); + } + if (!out.axes) return false; + const FormatInfo* format = tex.baseImage().format; + if (!format) return false; + const bool stencil = format->isStencil() && (!format->isDepth() || tex.depthStencilMode == GL_STENCIL_INDEX); + out.kind = stencil || format->type == ComponentType::UnsignedInt ? 'u' : format->type == ComponentType::Int ? 'i' : 'f'; + // The border as the texel of the image's base format (GL 4.6 section + // 8.14.2, table 8.11), then the texture's swizzle. + float f[4]; + int32_t n[4]; + for (int k = 0; k < 4; ++k) { + f[k] = p.borderIsInteger ? static_cast(p.borderColor.i[k]) : p.borderColor.f[k]; + n[k] = p.borderIsInteger ? p.borderColor.i[k] : static_cast(p.borderColor.f[k]); + } + int source[4] = {0, 1, 2, 3}; // 0-3: component, 4: zero, 5: one + switch (format->isDepthOrStencil() ? GL_RED : format->baseFormat) { + case GL_RED: source[1] = source[2] = 4; source[3] = 5; break; + case GL_RG: source[2] = 4; source[3] = 5; break; + case GL_RGB: source[3] = 5; break; + case GL_ALPHA: source[0] = source[1] = source[2] = 4; break; + case GL_LUMINANCE: source[1] = source[2] = 0; source[3] = 5; break; + case GL_LUMINANCE_ALPHA: source[1] = source[2] = 0; break; + case GL_INTENSITY: source[1] = source[2] = source[3] = 0; break; + default: break; + } + bool identity = format->baseFormat == GL_RGBA || format->baseFormat == GL_BGRA; + for (int k = 0; k < 4; ++k) { + int s = 4; + switch (tex.swizzle[k]) { + case GL_RED: s = source[0]; break; + case GL_GREEN: s = source[1]; break; + case GL_BLUE: s = source[2]; break; + case GL_ALPHA: s = source[3]; break; + case GL_ONE: s = 5; break; + default: break; + } + identity = identity && s == k; + if (out.kind == 'f') { + const float v = s == 4 ? 0.0f : s == 5 ? 1.0f : f[s]; + std::memcpy(&out.border[k], &v, 4); + } else { + const int32_t v = s == 4 ? 0 : s == 5 ? 1 : n[s]; + std::memcpy(&out.border[k], &v, 4); + } + } + // What the Metal sampler gives (samplerState): exact for these RGBA + // borders, and for shadow lookups whose border depth it has. + const bool rect = tex.target == GL_TEXTURE_RECTANGLE; + const float metal = f[3] == 0.0f ? 0.0f : (f[0] + f[1] + f[2]) > 1.5f ? 1.0f : 0.0f; + if (!rect && out.kind == 'f') { + if (shadow && f[0] == metal) return false; + const bool black = f[0] == 0.0f && f[1] == 0.0f && f[2] == 0.0f, white = f[0] == 1.0f && f[1] == 1.0f && f[2] == 1.0f; + if (!shadow && identity && ((black && (f[3] == 0.0f || f[3] == 1.0f)) || (white && f[3] == 1.0f))) return false; + } + if (!rect && out.kind != 'f' && !shadow && n[0] == 0 && n[1] == 0 && n[2] == 0 && n[3] == 0 && identity) return false; + const auto linear = [](GLenum filter) { + return filter == GL_LINEAR || filter == GL_LINEAR_MIPMAP_NEAREST || filter == GL_LINEAR_MIPMAP_LINEAR; + }; + out.magLinear = linear(p.magFilter); + out.minLinear = linear(p.minFilter); + out.mip = p.minFilter == GL_NEAREST_MIPMAP_NEAREST || p.minFilter == GL_LINEAR_MIPMAP_NEAREST ? 1 + : p.minFilter == GL_NEAREST_MIPMAP_LINEAR || p.minFilter == GL_LINEAR_MIPMAP_LINEAR ? 2 + : 0; + out.rect = rect; + out.minLod = std::max(0.0f, p.minLod); + out.maxLod = std::max(out.minLod, p.maxLod); + out.lodBias = p.lodBias; + out.compare = shadow && p.compareMode == GL_COMPARE_REF_TO_TEXTURE ? p.compareFunc : 0; + return true; +} + id samplerState(Context& ctx, const SamplerParams& p, bool shadow) { SamplerKey key; std::memset(&key, 0, sizeof(key)); diff --git a/src/wine/include/wmgl_gltypes.h b/src/wine/include/wmgl_gltypes.h new file mode 100644 index 00000000..71ea7ee0 --- /dev/null +++ b/src/wine/include/wmgl_gltypes.h @@ -0,0 +1,63 @@ +/* GL types of the Wine module, with Windows sizes (GLhandleARB is an + * unsigned int; pointer-sized types follow the caller), for winemetalgl.dll + * and winemetalgl.so alike. They match src/core/gl/types.h. */ +#ifndef WMGL_GLTYPES_H +#define WMGL_GLTYPES_H + +#include +#include + +#ifndef WMGL_GLAPIENTRY +#ifdef WINE_UNIX_LIB +#define WMGL_GLAPIENTRY +#else +#define WMGL_GLAPIENTRY __stdcall +#endif +#endif + +typedef unsigned int GLenum; +typedef unsigned char GLboolean; +typedef unsigned int GLbitfield; +typedef void GLvoid; +typedef int8_t GLbyte; +typedef uint8_t GLubyte; +typedef int16_t GLshort; +typedef uint16_t GLushort; +typedef int GLint; +typedef unsigned int GLuint; +typedef int GLclampx; +typedef int GLsizei; +typedef float GLfloat; +typedef float GLclampf; +typedef double GLdouble; +typedef double GLclampd; +typedef void *GLeglClientBufferEXT; +typedef void *GLeglImageOES; +typedef char GLchar; +typedef char GLcharARB; +typedef unsigned int GLhandleARB; +typedef uint16_t GLhalf; +typedef uint16_t GLhalfARB; +typedef uint16_t GLhalfNV; +typedef int GLfixed; +typedef intptr_t GLintptr; +typedef intptr_t GLintptrARB; +typedef intptr_t GLsizeiptr; +typedef intptr_t GLsizeiptrARB; +typedef int64_t GLint64; +typedef int64_t GLint64EXT; +typedef uint64_t GLuint64; +typedef uint64_t GLuint64EXT; +typedef struct __GLsync *GLsync; +struct _cl_context; +struct _cl_event; +typedef void (WMGL_GLAPIENTRY *GLDEBUGPROC)(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, + const GLchar *message, const void *userParam); +typedef GLDEBUGPROC GLDEBUGPROCARB; +typedef GLDEBUGPROC GLDEBUGPROCKHR; +typedef void (WMGL_GLAPIENTRY *GLDEBUGPROCAMD)(GLuint id, GLenum category, GLenum severity, GLsizei length, + const GLchar *message, void *userParam); +typedef intptr_t GLvdpauSurfaceNV; +typedef void (WMGL_GLAPIENTRY *GLVULKANPROCNV)(void); + +#endif diff --git a/src/wine/include/wmgl_wine.h b/src/wine/include/wmgl_wine.h new file mode 100644 index 00000000..b988fb68 --- /dev/null +++ b/src/wine/include/wmgl_wine.h @@ -0,0 +1,184 @@ +/* WineMetalGL Wine module: what winemetalgl.dll (PE) and winemetalgl.so + * (unix) share. + * + * The PE side is Wine's opengl32.dll for applications: opengl32.dll forwards + * every export to winemetalgl.dll, whose GL functions make one unix call + * each (generated by tools/gen/wine_gen.py) and whose WGL functions make the + * calls below. The unix side runs the WineMetalGL core and owns contexts, + * drawables and pixel formats; Wine's own opengl32, win32u OpenGL and + * winemac OpenGL code are not used. + * + * The WGL parameter structs hold fixed-width fields only, so 32-bit (wow64) + * and 64-bit callers share their layout. Handles and client pointers travel + * as UINT64, zero-extended from 32-bit callers. */ +#ifndef WMGL_WINE_H +#define WMGL_WINE_H + +#include "wmgl_gltypes.h" +#include "wine_gl_params.gen.h" + +/* Unix call status: the call queued debug messages for this thread, which + * the PE side delivers before returning to the application. */ +#define WMGL_STATUS_DEBUG_MESSAGES 0x00000103 /* STATUS_PENDING */ + +/* Same as include/winemetalgl/driver.h. */ +struct wmgl_pixel_format +{ + int draw_to_window, draw_to_pbuffer, double_buffer, stereo; + int color_bits, red_bits, green_bits, blue_bits, alpha_bits; + int red_shift, green_shift, blue_shift, alpha_shift; + int depth_bits, stencil_bits, accum_bits, aux_buffers; + int sample_buffers, samples, srgb_capable, float_components; + int max_pbuffer_width, max_pbuffer_height, max_pbuffer_pixels; +}; + +/* Where a window DC draws: its window, and the window's client area in + * pixels no DPI awareness scaled (the PE side asks user32, whose answers are + * stable across Wine versions; the unix side keeps no window state of its + * own beyond its layer). */ +struct wmgl_window_placement +{ + UINT64 hwnd; /* 0: not a window DC */ + UINT64 top_level; + INT x, y, width, height; /* the client area within the top-level window's client area */ + INT top_level_width, top_level_height; + UINT screen_width; /* the primary screen's width */ +}; + +struct wmgl_init_params +{ + UINT format_count; /* out: pixel formats */ + UINT onscreen_count; /* out: of which drawable to windows (the first ones) */ + INT core_major, core_minor; + BOOL ok; +}; + +struct wmgl_pixel_formats_params +{ + UINT64 formats; /* client array of struct wmgl_pixel_format */ + UINT max; + UINT count; /* out */ +}; + +struct wmgl_debug_message_params +{ + UINT64 buffer; /* client buffer for the message text */ + UINT size; + UINT64 callback; /* out: the application's callback and user parameter */ + UINT64 user; + UINT source, type, id, severity; + INT length; /* out: the message's length (it may not fit) */ + UINT amd; /* out: GLDEBUGPROCAMD (id, category, severity, length, message, user) */ + BOOL more; /* out: another message follows */ + BOOL got; /* out: a message was returned */ +}; + +struct wmgl_get_pixel_format_params +{ + UINT64 hdc; + UINT64 hwnd; /* the DC's window, or 0 */ + INT ret; +}; + +struct wmgl_set_pixel_format_params +{ + UINT64 hdc; + UINT64 hwnd; /* the DC's window, or 0 */ + INT format; + BOOL internal; /* wglSetPixelFormatWINE: the format may be changed */ + BOOL ret; +}; + +struct wmgl_create_context_params +{ + UINT64 hdc; + UINT64 hwnd; /* the DC's window, or 0 */ + UINT64 share; /* context handle or 0 */ + UINT64 attribs; /* client array of int pairs, 0-terminated, or 0 */ + UINT64 ret; /* out: context handle */ + UINT error; /* out: Win32 error code */ +}; + +struct wmgl_delete_context_params +{ + UINT64 context; + BOOL ret; +}; + +struct wmgl_make_current_params +{ + UINT64 draw_hdc; + UINT64 read_hdc; + struct wmgl_window_placement draw, read; + UINT64 context; /* 0: release the current context */ + BOOL ret; + UINT error; +}; + +struct wmgl_share_lists_params +{ + UINT64 source; + UINT64 dest; + BOOL ret; +}; + +struct wmgl_copy_context_params +{ + UINT64 source; + UINT64 dest; + UINT mask; + BOOL ret; +}; + +struct wmgl_swap_buffers_params +{ + UINT64 hdc; + struct wmgl_window_placement window; + BOOL ret; +}; + +struct wmgl_swap_interval_params +{ + INT interval; + BOOL ret; +}; + +struct wmgl_create_pbuffer_params +{ + UINT64 hdc; + UINT64 hwnd; + INT format, width, height; + UINT64 attribs; /* client array of int pairs or 0 */ + UINT64 ret; /* out: pbuffer handle */ + UINT error; +}; + +struct wmgl_pbuffer_params +{ + UINT64 pbuffer; + UINT64 hdc; /* get/release_pbuffer_dc: the memory DC the PE side made for it */ + INT attribute; /* query_pbuffer; bind/release_tex_image: the buffer */ + INT value; /* query_pbuffer: out */ + UINT64 attribs; /* set_pbuffer_attrib: client array */ + BOOL ret; +}; + +struct wmgl_query_renderer_params +{ + UINT64 hdc; /* 0: the current context's renderer */ + INT renderer; + UINT attribute; + UINT64 value; /* query_renderer_integer: client array of GLuint */ + UINT64 buffer; /* query_renderer_string: client buffer */ + UINT size; + BOOL ret; +}; + +struct wmgl_extensions_string_params +{ + UINT64 buffer; + UINT size; + UINT needed; /* out: the length + 1 */ +}; + +#endif diff --git a/src/wine/pe/wgl_fonts.c b/src/wine/pe/wgl_fonts.c new file mode 100644 index 00000000..d11ed456 --- /dev/null +++ b/src/wine/pe/wgl_fonts.c @@ -0,0 +1,539 @@ +/* wglUseFontBitmaps and wglUseFontOutlines of winemetalgl.dll, adapted from + * Wine 11.0 dlls/opengl32/wgl.c (GLU is loaded from glu32.dll on first use). + * + * Copyright (c) 1999 Lionel Ulmer + * Copyright (c) 2005 Raphael Junqueira + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include + +#include "wmgl_pe.h" + +#define TRACE(...) do { } while (0) +#define FIXME(...) WMGL_TRACE(__VA_ARGS__) +#define WARN(...) WMGL_TRACE(__VA_ARGS__) +#define ERR(...) WMGL_TRACE(__VA_ARGS__) + +#define GL_CCW 0x0901 +#define GL_LINE_LOOP 0x0002 +#define GLU_TESS_BEGIN 100100 +#define GLU_TESS_VERTEX 100101 +#define GLU_TESS_END 100102 + +void WINAPI glBegin( GLenum mode ); +void WINAPI glEnd( void ); +void WINAPI glFrontFace( GLenum mode ); +void WINAPI glNormal3d( GLdouble nx, GLdouble ny, GLdouble nz ); +void WINAPI glTranslated( GLdouble x, GLdouble y, GLdouble z ); +void WINAPI glVertex3d( GLdouble x, GLdouble y, GLdouble z ); +void WINAPI glVertex3dv( const GLdouble *v ); + +static const MAT2 identity = { {0,1},{0,0},{0,0},{0,1} }; + +typedef struct GLUtesselator GLUtesselator; +typedef void (CALLBACK *_GLUfuncptr)(void); +static GLUtesselator *(WINAPI *p_gluNewTess)(void); +static void (WINAPI *p_gluDeleteTess)( GLUtesselator *tess ); +static void (WINAPI *p_gluTessCallback)( GLUtesselator *tess, GLenum which, _GLUfuncptr fn ); +static void (WINAPI *p_gluTessNormal)( GLUtesselator *tess, GLdouble x, GLdouble y, GLdouble z ); +static void (WINAPI *p_gluTessBeginPolygon)( GLUtesselator *tess, void *data ); +static void (WINAPI *p_gluTessBeginContour)( GLUtesselator *tess ); +static void (WINAPI *p_gluTessVertex)( GLUtesselator *tess, GLdouble *location, void *data ); +static void (WINAPI *p_gluTessEndContour)( GLUtesselator *tess ); +static void (WINAPI *p_gluTessEndPolygon)( GLUtesselator *tess ); + +static BOOL load_glu(void) +{ + static HMODULE glu; + if (p_gluTessEndPolygon) return TRUE; + if (!glu && !(glu = LoadLibraryW( L"glu32.dll" ))) return FALSE; +#define LOAD(name) if (!(p_##name = (void *)GetProcAddress( glu, #name ))) return FALSE + LOAD(gluNewTess); + LOAD(gluDeleteTess); + LOAD(gluTessCallback); + LOAD(gluTessNormal); + LOAD(gluTessBeginPolygon); + LOAD(gluTessBeginContour); + LOAD(gluTessVertex); + LOAD(gluTessEndContour); + LOAD(gluTessEndPolygon); +#undef LOAD + return TRUE; +} + +/*********************************************************************** + * wglUseFontBitmaps_common + */ +static BOOL wglUseFontBitmaps_common( HDC hdc, DWORD first, DWORD count, DWORD listBase, BOOL unicode ) +{ + GLYPHMETRICS gm; + unsigned int glyph, size = 0; + void *bitmap = NULL, *gl_bitmap = NULL; + int org_alignment; + BOOL ret = TRUE; + + glGetIntegerv( GL_UNPACK_ALIGNMENT, &org_alignment ); + glPixelStorei( GL_UNPACK_ALIGNMENT, 4 ); + + for (glyph = first; glyph < first + count; glyph++) { + unsigned int needed_size, height, width, width_int; + + if (unicode) + needed_size = GetGlyphOutlineW(hdc, glyph, GGO_BITMAP, &gm, 0, NULL, &identity); + else + needed_size = GetGlyphOutlineA(hdc, glyph, GGO_BITMAP, &gm, 0, NULL, &identity); + + TRACE("Glyph: %3d / List: %ld size %d\n", glyph, listBase, needed_size); + if (needed_size == GDI_ERROR) { + ret = FALSE; + break; + } + + if (needed_size > size) { + size = needed_size; + free( bitmap ); + free( gl_bitmap ); + bitmap = calloc( 1, size ); + gl_bitmap = calloc( 1, size ); + } + if (needed_size != 0) { + if (unicode) + ret = (GetGlyphOutlineW(hdc, glyph, GGO_BITMAP, &gm, + size, bitmap, &identity) != GDI_ERROR); + else + ret = (GetGlyphOutlineA(hdc, glyph, GGO_BITMAP, &gm, + size, bitmap, &identity) != GDI_ERROR); + if (!ret) break; + } + + if (wmgl_debug) + { + unsigned int bitmask; + unsigned char *bitmap_ = bitmap; + + TRACE(" - bbox: %d x %d\n", gm.gmBlackBoxX, gm.gmBlackBoxY); + TRACE(" - origin: (%ld, %ld)\n", gm.gmptGlyphOrigin.x, gm.gmptGlyphOrigin.y); + TRACE(" - increment: %d - %d\n", gm.gmCellIncX, gm.gmCellIncY); + if (needed_size != 0) { + TRACE(" - bitmap:\n"); + for (height = 0; height < gm.gmBlackBoxY; height++) { + TRACE(" "); + for (width = 0, bitmask = 0x80; width < gm.gmBlackBoxX; width++, bitmask >>= 1) { + if (bitmask == 0) { + bitmap_ += 1; + bitmask = 0x80; + } + if (*bitmap_ & bitmask) + TRACE("*"); + else + TRACE(" "); + } + bitmap_ += (4 - ((UINT_PTR)bitmap_ & 0x03)); + TRACE("\n"); + } + } + } + + /* In OpenGL, the bitmap is drawn from the bottom to the top... So we need to invert the + * glyph for it to be drawn properly. + */ + if (needed_size != 0) { + width_int = (gm.gmBlackBoxX + 31) / 32; + for (height = 0; height < gm.gmBlackBoxY; height++) { + for (width = 0; width < width_int; width++) { + ((int *) gl_bitmap)[(gm.gmBlackBoxY - height - 1) * width_int + width] = + ((int *) bitmap)[height * width_int + width]; + } + } + } + + glNewList( listBase++, GL_COMPILE ); + if (needed_size != 0) { + glBitmap( gm.gmBlackBoxX, gm.gmBlackBoxY, 0 - gm.gmptGlyphOrigin.x, + (int)gm.gmBlackBoxY - gm.gmptGlyphOrigin.y, gm.gmCellIncX, gm.gmCellIncY, gl_bitmap ); + } else { + /* This is the case of 'empty' glyphs like the space character */ + glBitmap( 0, 0, 0, 0, gm.gmCellIncX, gm.gmCellIncY, NULL ); + } + glEndList(); + } + + glPixelStorei( GL_UNPACK_ALIGNMENT, org_alignment ); + free( bitmap ); + free( gl_bitmap ); + return ret; +} + +/*********************************************************************** + * wglUseFontBitmapsA (OPENGL32.@) + */ +BOOL WINAPI wglUseFontBitmapsA(HDC hdc, DWORD first, DWORD count, DWORD listBase) +{ + return wglUseFontBitmaps_common( hdc, first, count, listBase, FALSE ); +} + +/*********************************************************************** + * wglUseFontBitmapsW (OPENGL32.@) + */ +BOOL WINAPI wglUseFontBitmapsW(HDC hdc, DWORD first, DWORD count, DWORD listBase) +{ + return wglUseFontBitmaps_common( hdc, first, count, listBase, TRUE ); +} + +static void fixed_to_double(POINTFX fixed, UINT em_size, GLdouble vertex[3]) +{ + vertex[0] = (fixed.x.value + (GLdouble)fixed.x.fract / (1 << 16)) / em_size; + vertex[1] = (fixed.y.value + (GLdouble)fixed.y.fract / (1 << 16)) / em_size; + vertex[2] = 0.0; +} + +typedef struct _bezier_vector { + GLdouble x; + GLdouble y; +} bezier_vector; + +static BOOL bezier_fits_deviation(const bezier_vector *p, FLOAT max_deviation) +{ + bezier_vector deviation; + bezier_vector vertex; + bezier_vector base; + double base_length; + double dot; + + max_deviation *= max_deviation; + + vertex.x = (p[0].x + p[1].x*2 + p[2].x)/4 - p[0].x; + vertex.y = (p[0].y + p[1].y*2 + p[2].y)/4 - p[0].y; + + base.x = p[2].x - p[0].x; + base.y = p[2].y - p[0].y; + + base_length = base.x * base.x + base.y * base.y; + if (base_length <= max_deviation) + { + base.x = 0.0; + base.y = 0.0; + } + else + { + base_length = sqrt(base_length); + base.x /= base_length; + base.y /= base_length; + + dot = base.x*vertex.x + base.y*vertex.y; + dot = min(max(dot, 0.0), base_length); + base.x *= dot; + base.y *= dot; + } + + deviation.x = vertex.x-base.x; + deviation.y = vertex.y-base.y; + + return deviation.x*deviation.x + deviation.y*deviation.y <= max_deviation; +} + +static int bezier_approximate(const bezier_vector *p, bezier_vector *points, FLOAT deviation) +{ + bezier_vector first_curve[3]; + bezier_vector second_curve[3]; + bezier_vector vertex; + int total_vertices; + + if (bezier_fits_deviation(p, deviation)) + { + if(points) + *points = p[2]; + return 1; + } + + vertex.x = (p[0].x + p[1].x*2 + p[2].x)/4; + vertex.y = (p[0].y + p[1].y*2 + p[2].y)/4; + + first_curve[0] = p[0]; + first_curve[1].x = (p[0].x + p[1].x)/2; + first_curve[1].y = (p[0].y + p[1].y)/2; + first_curve[2] = vertex; + + second_curve[0] = vertex; + second_curve[1].x = (p[2].x + p[1].x)/2; + second_curve[1].y = (p[2].y + p[1].y)/2; + second_curve[2] = p[2]; + + total_vertices = bezier_approximate(first_curve, points, deviation); + if(points) + points += total_vertices; + total_vertices += bezier_approximate(second_curve, points, deviation); + return total_vertices; +} + +/*********************************************************************** + * wglUseFontOutlines_common + */ +static BOOL wglUseFontOutlines_common(HDC hdc, + DWORD first, + DWORD count, + DWORD listBase, + FLOAT deviation, + FLOAT extrusion, + int format, + LPGLYPHMETRICSFLOAT lpgmf, + BOOL unicode) +{ + UINT glyph; + GLUtesselator *tess = NULL; + LOGFONTW lf; + HFONT old_font, unscaled_font; + UINT em_size = 1024; + RECT rc; + + TRACE("(%p, %ld, %ld, %ld, %f, %f, %d, %p, %s)\n", hdc, first, count, + listBase, deviation, extrusion, format, lpgmf, unicode ? "W" : "A"); + + if(deviation <= 0.0) + deviation = 1.0/em_size; + + if(format == WGL_FONT_POLYGONS) + { + tess = load_glu() ? p_gluNewTess() : NULL; + if(!tess) + { + ERR("glu32 is required for this function but isn't available\n"); + return FALSE; + } + p_gluTessCallback( tess, GLU_TESS_VERTEX, (_GLUfuncptr)glVertex3dv ); + p_gluTessCallback( tess, GLU_TESS_BEGIN, (_GLUfuncptr)glBegin ); + p_gluTessCallback( tess, GLU_TESS_END, (_GLUfuncptr)glEnd ); + } + + GetObjectW(GetCurrentObject(hdc, OBJ_FONT), sizeof(lf), &lf); + rc.left = rc.right = rc.bottom = 0; + rc.top = em_size; + DPtoLP(hdc, (POINT*)&rc, 2); + lf.lfHeight = -abs(rc.top - rc.bottom); + lf.lfOrientation = lf.lfEscapement = 0; + unscaled_font = CreateFontIndirectW(&lf); + old_font = SelectObject(hdc, unscaled_font); + + for (glyph = first; glyph < first + count; glyph++) + { + DWORD needed; + GLYPHMETRICS gm; + BYTE *buf; + TTPOLYGONHEADER *pph; + TTPOLYCURVE *ppc; + GLdouble *vertices = NULL; + int vertex_total = -1; + + if(unicode) + needed = GetGlyphOutlineW(hdc, glyph, GGO_NATIVE, &gm, 0, NULL, &identity); + else + needed = GetGlyphOutlineA(hdc, glyph, GGO_NATIVE, &gm, 0, NULL, &identity); + + if(needed == GDI_ERROR) + goto error; + + buf = malloc( needed ); + + if(unicode) + GetGlyphOutlineW(hdc, glyph, GGO_NATIVE, &gm, needed, buf, &identity); + else + GetGlyphOutlineA(hdc, glyph, GGO_NATIVE, &gm, needed, buf, &identity); + + TRACE("glyph %d\n", glyph); + + if(lpgmf) + { + lpgmf->gmfBlackBoxX = (float)gm.gmBlackBoxX / em_size; + lpgmf->gmfBlackBoxY = (float)gm.gmBlackBoxY / em_size; + lpgmf->gmfptGlyphOrigin.x = (float)gm.gmptGlyphOrigin.x / em_size; + lpgmf->gmfptGlyphOrigin.y = (float)gm.gmptGlyphOrigin.y / em_size; + lpgmf->gmfCellIncX = (float)gm.gmCellIncX / em_size; + lpgmf->gmfCellIncY = (float)gm.gmCellIncY / em_size; + + TRACE("%fx%f at %f,%f inc %f,%f\n", lpgmf->gmfBlackBoxX, lpgmf->gmfBlackBoxY, + lpgmf->gmfptGlyphOrigin.x, lpgmf->gmfptGlyphOrigin.y, lpgmf->gmfCellIncX, lpgmf->gmfCellIncY); + lpgmf++; + } + + glNewList( listBase++, GL_COMPILE ); + glFrontFace( GL_CCW ); + if(format == WGL_FONT_POLYGONS) + { + glNormal3d( 0.0, 0.0, 1.0 ); + p_gluTessNormal(tess, 0, 0, 1); + p_gluTessBeginPolygon(tess, NULL); + } + + while(!vertices) + { + if (vertex_total != -1) vertices = malloc( vertex_total * 3 * sizeof(GLdouble) ); + vertex_total = 0; + + pph = (TTPOLYGONHEADER*)buf; + while((BYTE*)pph < buf + needed) + { + GLdouble previous[3]; + fixed_to_double(pph->pfxStart, em_size, previous); + + if(vertices) + TRACE("\tstart %d, %d\n", pph->pfxStart.x.value, pph->pfxStart.y.value); + + if (format == WGL_FONT_POLYGONS) p_gluTessBeginContour( tess ); + else glBegin( GL_LINE_LOOP ); + + if(vertices) + { + fixed_to_double(pph->pfxStart, em_size, vertices); + if (format == WGL_FONT_POLYGONS) p_gluTessVertex( tess, vertices, vertices ); + else glVertex3d( vertices[0], vertices[1], vertices[2] ); + vertices += 3; + } + vertex_total++; + + ppc = (TTPOLYCURVE*)((char*)pph + sizeof(*pph)); + while((char*)ppc < (char*)pph + pph->cb) + { + int i, j; + int num; + + switch(ppc->wType) { + case TT_PRIM_LINE: + for(i = 0; i < ppc->cpfx; i++) + { + if(vertices) + { + TRACE("\t\tline to %d, %d\n", + ppc->apfx[i].x.value, ppc->apfx[i].y.value); + fixed_to_double(ppc->apfx[i], em_size, vertices); + if (format == WGL_FONT_POLYGONS) p_gluTessVertex( tess, vertices, vertices ); + else glVertex3d( vertices[0], vertices[1], vertices[2] ); + vertices += 3; + } + fixed_to_double(ppc->apfx[i], em_size, previous); + vertex_total++; + } + break; + + case TT_PRIM_QSPLINE: + for(i = 0; i < ppc->cpfx-1; i++) + { + bezier_vector curve[3]; + bezier_vector *points; + GLdouble curve_vertex[3]; + + if(vertices) + TRACE("\t\tcurve %d,%d %d,%d\n", + ppc->apfx[i].x.value, ppc->apfx[i].y.value, + ppc->apfx[i + 1].x.value, ppc->apfx[i + 1].y.value); + + curve[0].x = previous[0]; + curve[0].y = previous[1]; + fixed_to_double(ppc->apfx[i], em_size, curve_vertex); + curve[1].x = curve_vertex[0]; + curve[1].y = curve_vertex[1]; + fixed_to_double(ppc->apfx[i + 1], em_size, curve_vertex); + curve[2].x = curve_vertex[0]; + curve[2].y = curve_vertex[1]; + if(i < ppc->cpfx-2) + { + curve[2].x = (curve[1].x + curve[2].x)/2; + curve[2].y = (curve[1].y + curve[2].y)/2; + } + num = bezier_approximate(curve, NULL, deviation); + points = malloc( num * sizeof(bezier_vector) ); + num = bezier_approximate(curve, points, deviation); + vertex_total += num; + if(vertices) + { + for(j=0; jwType); + if (format == WGL_FONT_POLYGONS) p_gluTessEndContour( tess ); + else glEnd(); + goto error_in_list; + } + + ppc = (TTPOLYCURVE*)((char*)ppc + sizeof(*ppc) + + (ppc->cpfx - 1) * sizeof(POINTFX)); + } + if (format == WGL_FONT_POLYGONS) p_gluTessEndContour( tess ); + else glEnd(); + pph = (TTPOLYGONHEADER*)((char*)pph + pph->cb); + } + } + +error_in_list: + if (format == WGL_FONT_POLYGONS) p_gluTessEndPolygon( tess ); + glTranslated( (GLdouble)gm.gmCellIncX / em_size, (GLdouble)gm.gmCellIncY / em_size, 0.0 ); + glEndList(); + free( buf ); + free( vertices ); + } + + error: + DeleteObject(SelectObject(hdc, old_font)); + if(format == WGL_FONT_POLYGONS) + p_gluDeleteTess(tess); + return TRUE; + +} + +/*********************************************************************** + * wglUseFontOutlinesA (OPENGL32.@) + */ +BOOL WINAPI wglUseFontOutlinesA(HDC hdc, + DWORD first, + DWORD count, + DWORD listBase, + FLOAT deviation, + FLOAT extrusion, + int format, + LPGLYPHMETRICSFLOAT lpgmf) +{ + return wglUseFontOutlines_common(hdc, first, count, listBase, deviation, extrusion, format, lpgmf, FALSE); +} + +/*********************************************************************** + * wglUseFontOutlinesW (OPENGL32.@) + */ +BOOL WINAPI wglUseFontOutlinesW(HDC hdc, + DWORD first, + DWORD count, + DWORD listBase, + FLOAT deviation, + FLOAT extrusion, + int format, + LPGLYPHMETRICSFLOAT lpgmf) +{ + return wglUseFontOutlines_common(hdc, first, count, listBase, deviation, extrusion, format, lpgmf, TRUE); +} + diff --git a/src/wine/pe/wgl_formats.c b/src/wine/pe/wgl_formats.c new file mode 100644 index 00000000..01b1d6c7 --- /dev/null +++ b/src/wine/pe/wgl_formats.c @@ -0,0 +1,833 @@ +/* Pixel format selection and description (WGL, WGL_ARB_pixel_format) of + * winemetalgl.dll, adapted from Wine 11.0 dlls/opengl32/wgl.c. The formats + * come from the WineMetalGL core once (wmgl_init_pixel_formats); every DC + * sees the same list, its window formats first. + * + * Copyright (c) 1999 Lionel Ulmer + * Copyright (c) 2005 Raphael Junqueira + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include + +#include "wmgl_pe.h" + +#define TRACE(...) do { } while (0) +#define FIXME(...) WMGL_TRACE(__VA_ARGS__) +#define WARN(...) WMGL_TRACE(__VA_ARGS__) +#ifndef ARRAY_SIZE +#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) +#endif + +static struct wgl_pixel_format *wgl_formats_cache; +static UINT wgl_format_count, wgl_onscreen_count; + +/* The core's formats (struct wmgl_pixel_format) as WGL describes them: fully + * accelerated (neither PFD_GENERIC_FORMAT nor PFD_GENERIC_ACCELERATED). */ +void wmgl_init_pixel_formats( const struct wmgl_pixel_format *formats, UINT count, UINT onscreen ) +{ + UINT i; + + if (!(wgl_formats_cache = calloc( count, sizeof(*wgl_formats_cache) ))) return; + for (i = 0; i < count; i++) + { + const struct wmgl_pixel_format *f = &formats[i]; + struct wgl_pixel_format *d = &wgl_formats_cache[i]; + + d->pfd.nSize = sizeof(d->pfd); + d->pfd.nVersion = 1; + d->pfd.dwFlags = PFD_SUPPORT_OPENGL | PFD_SUPPORT_COMPOSITION; + if (f->draw_to_window) d->pfd.dwFlags |= PFD_DRAW_TO_WINDOW; + if (f->double_buffer) d->pfd.dwFlags |= PFD_DOUBLEBUFFER; + if (f->stereo) d->pfd.dwFlags |= PFD_STEREO; + d->pfd.iPixelType = PFD_TYPE_RGBA; + d->pfd.cColorBits = f->color_bits; + d->pfd.cRedBits = f->red_bits; + d->pfd.cRedShift = f->red_shift; + d->pfd.cGreenBits = f->green_bits; + d->pfd.cGreenShift = f->green_shift; + d->pfd.cBlueBits = f->blue_bits; + d->pfd.cBlueShift = f->blue_shift; + d->pfd.cAlphaBits = f->alpha_bits; + d->pfd.cAlphaShift = f->alpha_shift; + d->pfd.cAccumBits = f->accum_bits; + d->pfd.cAccumRedBits = d->pfd.cAccumGreenBits = d->pfd.cAccumBlueBits = d->pfd.cAccumAlphaBits = + f->accum_bits / 4; + d->pfd.cDepthBits = f->depth_bits; + d->pfd.cStencilBits = f->stencil_bits; + d->pfd.cAuxBuffers = f->aux_buffers; + d->pfd.iLayerType = PFD_MAIN_PLANE; + d->swap_method = WGL_SWAP_UNDEFINED_ARB; + d->transparent = FALSE; + d->pixel_type = f->float_components ? WGL_TYPE_RGBA_FLOAT_ARB : WGL_TYPE_RGBA_ARB; + d->draw_to_pbuffer = f->draw_to_pbuffer; + d->max_pbuffer_pixels = f->max_pbuffer_pixels; + d->max_pbuffer_width = f->max_pbuffer_width; + d->max_pbuffer_height = f->max_pbuffer_height; + d->sample_buffers = f->sample_buffers; + d->samples = f->samples; + d->bind_to_texture_rgb = d->bind_to_texture_rgba = f->draw_to_pbuffer; + d->bind_to_texture_rectangle_rgb = d->bind_to_texture_rectangle_rgba = f->draw_to_pbuffer; + d->framebuffer_srgb_capable = f->srgb_capable; + d->float_components = f->float_components; + } + wgl_format_count = count; + wgl_onscreen_count = onscreen; +} + +static BOOL wmgl_is_memdc( HDC hdc, DWORD *is_memdc ) +{ + DWORD type = GetObjectType( hdc ); + if (!type) return FALSE; + *is_memdc = type == OBJ_MEMDC; + return TRUE; +} + +/* A memory DC sees every format; others the window ones. */ +static struct wgl_pixel_format *get_pixel_formats( HDC hdc, UINT *num_formats, UINT *num_onscreen_formats ) +{ + DWORD is_memdc; + + *num_formats = *num_onscreen_formats = 0; + if (!wmgl_is_memdc( hdc, &is_memdc )) + { + SetLastError( ERROR_INVALID_HANDLE ); + return NULL; + } + *num_formats = wgl_format_count; + *num_onscreen_formats = is_memdc ? wgl_format_count : wgl_onscreen_count; + return wgl_formats_cache; +} + +INT WINAPI wglChoosePixelFormat(HDC hdc, const PIXELFORMATDESCRIPTOR* ppfd) +{ + PIXELFORMATDESCRIPTOR format, best; + int i, count, best_format; + int bestDBuffer = -1, bestStereo = -1; + DWORD is_memdc; + + TRACE( "%p %p: size %u version %u flags %lu type %u color %u %u,%u,%u,%u " + "accum %u depth %u stencil %u aux %u\n", + hdc, ppfd, ppfd->nSize, ppfd->nVersion, ppfd->dwFlags, ppfd->iPixelType, + ppfd->cColorBits, ppfd->cRedBits, ppfd->cGreenBits, ppfd->cBlueBits, ppfd->cAlphaBits, + ppfd->cAccumBits, ppfd->cDepthBits, ppfd->cStencilBits, ppfd->cAuxBuffers ); + + count = wglDescribePixelFormat( hdc, 0, 0, NULL ); + if (!count) return 0; + + if (!wmgl_is_memdc( hdc, &is_memdc )) is_memdc = 0; + + best_format = 0; + best.dwFlags = 0; + best.cAlphaBits = -1; + best.cColorBits = -1; + best.cDepthBits = -1; + best.cStencilBits = -1; + best.cAuxBuffers = -1; + best.cAccumBits = -1; + + for (i = 1; i <= count; i++) + { + if (!wglDescribePixelFormat( hdc, i, sizeof(format), &format )) continue; + + if ((ppfd->iPixelType == PFD_TYPE_COLORINDEX) != (format.iPixelType == PFD_TYPE_COLORINDEX)) + { + TRACE( "pixel type mismatch for iPixelFormat=%d\n", i ); + continue; + } + + if ((ppfd->dwFlags & PFD_DRAW_TO_BITMAP) && !(format.dwFlags & PFD_DRAW_TO_BITMAP)) + { + TRACE( "PFD_DRAW_TO_BITMAP required but not found for iPixelFormat=%d\n", i ); + continue; + } + if ((ppfd->dwFlags & PFD_DRAW_TO_WINDOW) && !(format.dwFlags & PFD_DRAW_TO_WINDOW)) + { + TRACE( "PFD_DRAW_TO_WINDOW required but not found for iPixelFormat=%d\n", i ); + continue; + } + + if ((ppfd->dwFlags & PFD_SUPPORT_GDI) && !(format.dwFlags & PFD_SUPPORT_GDI)) + { + TRACE( "PFD_SUPPORT_GDI required but not found for iPixelFormat=%d\n", i ); + continue; + } + if ((ppfd->dwFlags & PFD_SUPPORT_OPENGL) && !(format.dwFlags & PFD_SUPPORT_OPENGL)) + { + TRACE( "PFD_SUPPORT_OPENGL required but not found for iPixelFormat=%d\n", i ); + continue; + } + if (is_memdc && ppfd->cColorBits == 16 && ppfd->cAlphaBits != 1) + { + TRACE( "Ignoring iPixelFormat=%d RGB565 format on memory DC\n", i ); + continue; + } + + /* The behavior of PDF_STEREO/PFD_STEREO_DONTCARE and PFD_DOUBLEBUFFER / PFD_DOUBLEBUFFER_DONTCARE + * is not very clear on MSDN. They specify that ChoosePixelFormat tries to match pixel formats + * with the flag (PFD_STEREO / PFD_DOUBLEBUFFERING) set. Otherwise it says that it tries to match + * formats without the given flag set. + * A test on Windows using a Radeon 9500pro on WinXP (the driver doesn't support Stereo) + * has indicated that a format without stereo is returned when stereo is unavailable. + * So in case PFD_STEREO is set, formats that support it should have priority above formats + * without. In case PFD_STEREO_DONTCARE is set, stereo is ignored. + * + * To summarize the following is most likely the correct behavior: + * stereo not set -> prefer non-stereo formats, but also accept stereo formats + * stereo set -> prefer stereo formats, but also accept non-stereo formats + * stereo don't care -> it doesn't matter whether we get stereo or not + * + * In Wine we will treat non-stereo the same way as don't care because it makes + * format selection even more complicated and second drivers with Stereo advertise + * each format twice anyway. + */ + + /* Doublebuffer, see the comments above */ + if (!(ppfd->dwFlags & PFD_DOUBLEBUFFER_DONTCARE)) + { + if (((ppfd->dwFlags & PFD_DOUBLEBUFFER) != bestDBuffer) && + ((format.dwFlags & PFD_DOUBLEBUFFER) == (ppfd->dwFlags & PFD_DOUBLEBUFFER))) + goto found; + + if (bestDBuffer != -1 && (format.dwFlags & PFD_DOUBLEBUFFER) != bestDBuffer) continue; + } + else if (!best_format) + goto found; + + /* Stereo, see the comments above. */ + if (!(ppfd->dwFlags & PFD_STEREO_DONTCARE)) + { + if (((ppfd->dwFlags & PFD_STEREO) != bestStereo) && + ((format.dwFlags & PFD_STEREO) == (ppfd->dwFlags & PFD_STEREO))) + goto found; + + if (bestStereo != -1 && (format.dwFlags & PFD_STEREO) != bestStereo) continue; + } + else if (!best_format) + goto found; + + /* Below we will do a number of checks to select the 'best' pixelformat. + * We assume the precedence cColorBits > cAlphaBits > cDepthBits > cStencilBits -> cAuxBuffers. + * The code works by trying to match the most important options as close as possible. + * When a reasonable format is found, we will try to match more options. + * It appears (see the opengl32 test) that Windows opengl drivers ignore options + * like cColorBits, cAlphaBits and friends if they are set to 0, so they are considered + * as DONTCARE. At least Serious Sam TSE relies on this behavior. */ + + if (ppfd->cColorBits) + { + if (((ppfd->cColorBits > best.cColorBits) && (format.cColorBits > best.cColorBits)) || + ((format.cColorBits >= ppfd->cColorBits) && (format.cColorBits < best.cColorBits))) + goto found; + + if (best.cColorBits != format.cColorBits) /* Do further checks if the format is compatible */ + { + TRACE( "color mismatch for iPixelFormat=%d\n", i ); + continue; + } + } + if (ppfd->cAlphaBits) + { + if (((ppfd->cAlphaBits > best.cAlphaBits) && (format.cAlphaBits > best.cAlphaBits)) || + ((format.cAlphaBits >= ppfd->cAlphaBits) && (format.cAlphaBits < best.cAlphaBits))) + goto found; + + if (best.cAlphaBits != format.cAlphaBits) + { + TRACE( "alpha mismatch for iPixelFormat=%d\n", i ); + continue; + } + } + if (ppfd->cStencilBits) + { + if (((ppfd->cStencilBits > best.cStencilBits) && (format.cStencilBits > best.cStencilBits)) || + ((format.cStencilBits >= ppfd->cStencilBits) && (format.cStencilBits < best.cStencilBits))) + goto found; + + if (best.cStencilBits != format.cStencilBits) + { + TRACE( "stencil mismatch for iPixelFormat=%d\n", i ); + continue; + } + } + if (ppfd->cDepthBits && !(ppfd->dwFlags & PFD_DEPTH_DONTCARE)) + { + if (((ppfd->cDepthBits > best.cDepthBits) && (format.cDepthBits > best.cDepthBits)) || + ((format.cDepthBits >= ppfd->cDepthBits) && (format.cDepthBits < best.cDepthBits))) + goto found; + + if (best.cDepthBits != format.cDepthBits) + { + TRACE( "depth mismatch for iPixelFormat=%d\n", i ); + continue; + } + } + if (ppfd->cAuxBuffers) + { + if (((ppfd->cAuxBuffers > best.cAuxBuffers) && (format.cAuxBuffers > best.cAuxBuffers)) || + ((format.cAuxBuffers >= ppfd->cAuxBuffers) && (format.cAuxBuffers < best.cAuxBuffers))) + goto found; + + if (best.cAuxBuffers != format.cAuxBuffers) + { + TRACE( "aux mismatch for iPixelFormat=%d\n", i ); + continue; + } + } + if (ppfd->cAccumBits) + { + if (((ppfd->cAccumBits > best.cAccumBits) && (format.cAccumBits > best.cAccumBits)) || + ((format.cAccumBits >= ppfd->cAccumBits) && (format.cAccumBits < best.cAccumBits))) + goto found; + + if (best.cAccumBits != format.cAccumBits) + { + TRACE( "cAccumBits mismatch for iPixelFormat=%d\n", i ); + continue; + } + } + if (ppfd->dwFlags & PFD_DEPTH_DONTCARE) + { + if (format.cDepthBits < best.cDepthBits) + goto found; + continue; + } + + if (!ppfd->cDepthBits && format.cDepthBits > best.cDepthBits) + goto found; + + continue; + + found: + best_format = i; + best = format; + bestDBuffer = format.dwFlags & PFD_DOUBLEBUFFER; + bestStereo = format.dwFlags & PFD_STEREO; + } + + TRACE( "returning %u\n", best_format ); + return best_format; +} + +static BOOL wgl_attrib_uses_layer( int attrib ) +{ + switch (attrib) + { + case WGL_ACCELERATION_ARB: + case WGL_TRANSPARENT_ARB: + case WGL_SHARE_DEPTH_ARB: + case WGL_SHARE_STENCIL_ARB: + case WGL_SHARE_ACCUM_ARB: + case WGL_TRANSPARENT_RED_VALUE_ARB: + case WGL_TRANSPARENT_GREEN_VALUE_ARB: + case WGL_TRANSPARENT_BLUE_VALUE_ARB: + case WGL_TRANSPARENT_ALPHA_VALUE_ARB: + case WGL_TRANSPARENT_INDEX_VALUE_ARB: + case WGL_SUPPORT_GDI_ARB: + case WGL_SUPPORT_OPENGL_ARB: + case WGL_DOUBLE_BUFFER_ARB: + case WGL_STEREO_ARB: + case WGL_PIXEL_TYPE_ARB: + case WGL_COLOR_BITS_ARB: + case WGL_RED_BITS_ARB: + case WGL_RED_SHIFT_ARB: + case WGL_GREEN_BITS_ARB: + case WGL_GREEN_SHIFT_ARB: + case WGL_BLUE_BITS_ARB: + case WGL_BLUE_SHIFT_ARB: + case WGL_ALPHA_BITS_ARB: + case WGL_ALPHA_SHIFT_ARB: + case WGL_ACCUM_BITS_ARB: + case WGL_ACCUM_RED_BITS_ARB: + case WGL_ACCUM_GREEN_BITS_ARB: + case WGL_ACCUM_BLUE_BITS_ARB: + case WGL_ACCUM_ALPHA_BITS_ARB: + case WGL_DEPTH_BITS_ARB: + case WGL_STENCIL_BITS_ARB: + case WGL_AUX_BUFFERS_ARB: + case WGL_SAMPLE_BUFFERS_ARB: + case WGL_SAMPLES_ARB: + case WGL_FRAMEBUFFER_SRGB_CAPABLE_ARB: + case WGL_FLOAT_COMPONENTS_NV: + return TRUE; + default: + return FALSE; + } +} + +static BOOL wgl_pixel_format_get_attrib( const struct wgl_pixel_format *fmt, int attrib, int *value ) +{ + int val = 0; + int valid = -1; + + switch (attrib) + { + case WGL_DRAW_TO_WINDOW_ARB: val = !!(fmt->pfd.dwFlags & PFD_DRAW_TO_WINDOW); break; + case WGL_DRAW_TO_BITMAP_ARB: val = !!(fmt->pfd.dwFlags & PFD_DRAW_TO_BITMAP); break; + case WGL_ACCELERATION_ARB: + if (fmt->pfd.dwFlags & PFD_GENERIC_ACCELERATED) + val = WGL_GENERIC_ACCELERATION_ARB; + else if (fmt->pfd.dwFlags & PFD_GENERIC_FORMAT) + val = WGL_NO_ACCELERATION_ARB; + else + val = WGL_FULL_ACCELERATION_ARB; + break; + case WGL_NEED_PALETTE_ARB: val = !!(fmt->pfd.dwFlags & PFD_NEED_PALETTE); break; + case WGL_NEED_SYSTEM_PALETTE_ARB: val = !!(fmt->pfd.dwFlags & PFD_NEED_SYSTEM_PALETTE); break; + case WGL_SWAP_LAYER_BUFFERS_ARB: val = !!(fmt->pfd.dwFlags & PFD_SWAP_LAYER_BUFFERS); break; + case WGL_SWAP_METHOD_ARB: val = fmt->swap_method; break; + case WGL_NUMBER_OVERLAYS_ARB: + case WGL_NUMBER_UNDERLAYS_ARB: + /* We don't support any overlays/underlays. */ + val = 0; + break; + case WGL_TRANSPARENT_ARB: val = fmt->transparent; break; + case WGL_SHARE_DEPTH_ARB: + case WGL_SHARE_STENCIL_ARB: + case WGL_SHARE_ACCUM_ARB: + /* We support only a main plane at the moment which by definition + * shares the depth/stencil/accum buffers with itself. */ + val = GL_TRUE; + break; + case WGL_SUPPORT_GDI_ARB: val = !!(fmt->pfd.dwFlags & PFD_SUPPORT_GDI); break; + case WGL_SUPPORT_OPENGL_ARB: val = !!(fmt->pfd.dwFlags & PFD_SUPPORT_OPENGL); break; + case WGL_DOUBLE_BUFFER_ARB: val = !!(fmt->pfd.dwFlags & PFD_DOUBLEBUFFER); break; + case WGL_STEREO_ARB: val = !!(fmt->pfd.dwFlags & PFD_STEREO); break; + case WGL_PIXEL_TYPE_ARB: val = fmt->pixel_type; break; + case WGL_COLOR_BITS_ARB: val = fmt->pfd.cColorBits; break; + case WGL_RED_BITS_ARB: val = fmt->pfd.cRedBits; break; + case WGL_RED_SHIFT_ARB: val = fmt->pfd.cRedShift; break; + case WGL_GREEN_BITS_ARB: val = fmt->pfd.cGreenBits; break; + case WGL_GREEN_SHIFT_ARB: val = fmt->pfd.cGreenShift; break; + case WGL_BLUE_BITS_ARB: val = fmt->pfd.cBlueBits; break; + case WGL_BLUE_SHIFT_ARB: val = fmt->pfd.cBlueShift; break; + case WGL_ALPHA_BITS_ARB: val = fmt->pfd.cAlphaBits; break; + case WGL_ALPHA_SHIFT_ARB: val = fmt->pfd.cAlphaShift; break; + case WGL_ACCUM_BITS_ARB: val = fmt->pfd.cAccumBits; break; + case WGL_ACCUM_RED_BITS_ARB: val = fmt->pfd.cAccumRedBits; break; + case WGL_ACCUM_GREEN_BITS_ARB: val = fmt->pfd.cAccumGreenBits; break; + case WGL_ACCUM_BLUE_BITS_ARB: val = fmt->pfd.cAccumBlueBits; break; + case WGL_ACCUM_ALPHA_BITS_ARB: val = fmt->pfd.cAccumAlphaBits; break; + case WGL_DEPTH_BITS_ARB: val = fmt->pfd.cDepthBits; break; + case WGL_STENCIL_BITS_ARB: val = fmt->pfd.cStencilBits; break; + case WGL_AUX_BUFFERS_ARB: val = fmt->pfd.cAuxBuffers; break; + case WGL_DRAW_TO_PBUFFER_ARB: val = fmt->draw_to_pbuffer; break; + case WGL_MAX_PBUFFER_PIXELS_ARB: val = fmt->max_pbuffer_pixels; break; + case WGL_MAX_PBUFFER_WIDTH_ARB: val = fmt->max_pbuffer_width; break; + case WGL_MAX_PBUFFER_HEIGHT_ARB: val = fmt->max_pbuffer_height; break; + case WGL_TRANSPARENT_RED_VALUE_ARB: + val = fmt->transparent_red_value; + valid = !!fmt->transparent_red_value_valid; + break; + case WGL_TRANSPARENT_GREEN_VALUE_ARB: + val = fmt->transparent_green_value; + valid = !!fmt->transparent_green_value_valid; + break; + case WGL_TRANSPARENT_BLUE_VALUE_ARB: + val = fmt->transparent_blue_value; + valid = !!fmt->transparent_blue_value_valid; + break; + case WGL_TRANSPARENT_ALPHA_VALUE_ARB: + val = fmt->transparent_alpha_value; + valid = !!fmt->transparent_alpha_value_valid; + break; + case WGL_TRANSPARENT_INDEX_VALUE_ARB: + val = fmt->transparent_index_value; + valid = !!fmt->transparent_index_value_valid; + break; + case WGL_SAMPLE_BUFFERS_ARB: val = fmt->sample_buffers; break; + case WGL_SAMPLES_ARB: val = fmt->samples; break; + case WGL_BIND_TO_TEXTURE_RGB_ARB: val = fmt->bind_to_texture_rgb; break; + case WGL_BIND_TO_TEXTURE_RGBA_ARB: val = fmt->bind_to_texture_rgba; break; + case WGL_BIND_TO_TEXTURE_RECTANGLE_RGB_NV: val = fmt->bind_to_texture_rectangle_rgb; break; + case WGL_BIND_TO_TEXTURE_RECTANGLE_RGBA_NV: val = fmt->bind_to_texture_rectangle_rgba; break; + case WGL_FRAMEBUFFER_SRGB_CAPABLE_ARB: val = fmt->framebuffer_srgb_capable; break; + case WGL_FLOAT_COMPONENTS_NV: val = fmt->float_components; break; + default: + FIXME( "unsupported 0x%x WGL attribute\n", attrib ); + valid = 0; + break; + } + + /* If we haven't already determined validity, use the default check */ + if (valid == -1) valid = val != -1; + if (valid) *value = val; + + return valid; +} + +enum attrib_match +{ + ATTRIB_MATCH_INVALID = -1, + ATTRIB_MATCH_IGNORE, + ATTRIB_MATCH_EXACT, + ATTRIB_MATCH_MINIMUM, +}; + +static enum attrib_match wgl_attrib_match_criteria( int attrib ) +{ + switch (attrib) + { + case WGL_DRAW_TO_WINDOW_ARB: + case WGL_DRAW_TO_BITMAP_ARB: + case WGL_ACCELERATION_ARB: + case WGL_NEED_PALETTE_ARB: + case WGL_NEED_SYSTEM_PALETTE_ARB: + case WGL_SWAP_LAYER_BUFFERS_ARB: + case WGL_SHARE_DEPTH_ARB: + case WGL_SHARE_STENCIL_ARB: + case WGL_SHARE_ACCUM_ARB: + case WGL_SUPPORT_GDI_ARB: + case WGL_SUPPORT_OPENGL_ARB: + case WGL_DOUBLE_BUFFER_ARB: + case WGL_STEREO_ARB: + case WGL_PIXEL_TYPE_ARB: + case WGL_DRAW_TO_PBUFFER_ARB: + case WGL_BIND_TO_TEXTURE_RGB_ARB: + case WGL_BIND_TO_TEXTURE_RGBA_ARB: + case WGL_BIND_TO_TEXTURE_RECTANGLE_RGB_NV: + case WGL_BIND_TO_TEXTURE_RECTANGLE_RGBA_NV: + case WGL_FRAMEBUFFER_SRGB_CAPABLE_ARB: + case WGL_FLOAT_COMPONENTS_NV: + return ATTRIB_MATCH_EXACT; + case WGL_NUMBER_OVERLAYS_ARB: + case WGL_NUMBER_UNDERLAYS_ARB: + case WGL_COLOR_BITS_ARB: + case WGL_RED_BITS_ARB: + case WGL_GREEN_BITS_ARB: + case WGL_BLUE_BITS_ARB: + case WGL_ALPHA_BITS_ARB: + case WGL_ACCUM_BITS_ARB: + case WGL_ACCUM_RED_BITS_ARB: + case WGL_ACCUM_GREEN_BITS_ARB: + case WGL_ACCUM_BLUE_BITS_ARB: + case WGL_ACCUM_ALPHA_BITS_ARB: + case WGL_DEPTH_BITS_ARB: + case WGL_STENCIL_BITS_ARB: + case WGL_AUX_BUFFERS_ARB: + case WGL_SAMPLE_BUFFERS_ARB: + case WGL_SAMPLES_ARB: + return ATTRIB_MATCH_MINIMUM; + case WGL_NUMBER_PIXEL_FORMATS_ARB: + case WGL_RED_SHIFT_ARB: + case WGL_GREEN_SHIFT_ARB: + case WGL_BLUE_SHIFT_ARB: + case WGL_ALPHA_SHIFT_ARB: + case WGL_TRANSPARENT_ARB: + case WGL_TRANSPARENT_RED_VALUE_ARB: + case WGL_TRANSPARENT_GREEN_VALUE_ARB: + case WGL_TRANSPARENT_BLUE_VALUE_ARB: + case WGL_TRANSPARENT_ALPHA_VALUE_ARB: + case WGL_TRANSPARENT_INDEX_VALUE_ARB: + case WGL_SWAP_METHOD_ARB: + return ATTRIB_MATCH_IGNORE; + default: + return ATTRIB_MATCH_INVALID; + } +} + +static void filter_format_array( const struct wgl_pixel_format **array, + UINT num_formats, int attrib, int value ) +{ + enum attrib_match match = wgl_attrib_match_criteria( attrib ); + int fmt_value; + UINT i; + + assert(match != ATTRIB_MATCH_INVALID); + + if (match == ATTRIB_MATCH_IGNORE && attrib != WGL_SWAP_METHOD_ARB) return; + + for (i = 0; i < num_formats; ++i) + { + if (!array[i]) continue; + if (!wgl_pixel_format_get_attrib( array[i], attrib, &fmt_value ) || + (match == ATTRIB_MATCH_EXACT && fmt_value != value) || + (match == ATTRIB_MATCH_MINIMUM && fmt_value < value) || + (attrib == WGL_SWAP_METHOD_ARB && ((fmt_value == WGL_SWAP_COPY_ARB) ^ (value == WGL_SWAP_COPY_ARB)))) + { + array[i] = NULL; + } + } +} + +static int wgl_attrib_sort_priority( int attrib ) +{ + switch (attrib) + { + case WGL_DRAW_TO_WINDOW_ARB: return 1; + case WGL_DRAW_TO_BITMAP_ARB: return 2; + case WGL_ACCELERATION_ARB: return 3; + case WGL_COLOR_BITS_ARB: return 4; + case WGL_ACCUM_BITS_ARB: return 5; + case WGL_PIXEL_TYPE_ARB: return 6; + case WGL_ALPHA_BITS_ARB: return 7; + case WGL_AUX_BUFFERS_ARB: return 8; + case WGL_DEPTH_BITS_ARB: return 9; + case WGL_STENCIL_BITS_ARB: return 10; + case WGL_DOUBLE_BUFFER_ARB: return 11; + case WGL_SWAP_METHOD_ARB: return 12; + default: return 100; + } +} + +static int compare_attribs( const void *a, const void *b ) +{ + return wgl_attrib_sort_priority( *(int *)a ) - wgl_attrib_sort_priority( *(int *)b ); +} + +static int wgl_attrib_value_priority( int value ) +{ + switch (value) + { + case WGL_SWAP_UNDEFINED_ARB: return 1; + case WGL_SWAP_EXCHANGE_ARB: return 2; + case WGL_SWAP_COPY_ARB: return 3; + + case WGL_FULL_ACCELERATION_ARB: return 1; + case WGL_GENERIC_ACCELERATION_ARB: return 2; + case WGL_NO_ACCELERATION_ARB: return 3; + + case WGL_TYPE_RGBA_ARB: return 1; + case WGL_TYPE_RGBA_FLOAT_ATI: return 2; + case WGL_TYPE_RGBA_UNSIGNED_FLOAT_EXT: return 3; + case WGL_TYPE_COLORINDEX_ARB: return 4; + + default: return 100; + } +} + +struct compare_formats_ctx +{ + int attribs[256]; + UINT num_attribs; +}; + +static int compare_formats( void *arg, const void *a, const void *b ) +{ + const struct wgl_pixel_format *fmt_a = *(void **)a, *fmt_b = *(void **)b; + struct compare_formats_ctx *ctx = arg; + int attrib, val_a, val_b; + UINT i; + + if (!fmt_a) return 1; + if (!fmt_b) return -1; + + for (i = 0; i < ctx->num_attribs; ++i) + { + attrib = ctx->attribs[2 * i]; + if (wgl_pixel_format_get_attrib( fmt_a, attrib, &val_a ) && + wgl_pixel_format_get_attrib( fmt_b, attrib, &val_b ) && + val_a != val_b) + { + switch (attrib) + { + case WGL_ACCELERATION_ARB: + case WGL_SWAP_METHOD_ARB: + case WGL_PIXEL_TYPE_ARB: + return wgl_attrib_value_priority( val_a ) - + wgl_attrib_value_priority( val_b ); + case WGL_COLOR_BITS_ARB: + /* Prefer 32bpp over other values */ + if (val_a >= 32 && val_b >= 32) return val_a - val_b; + else return val_b - val_a; + default: + /* Smaller values first */ + return val_a - val_b; + } + } + } + + /* Maintain pixel format id order */ + return fmt_a - fmt_b; +} + +static void compare_formats_ctx_set_attrib( struct compare_formats_ctx *ctx, + int attrib, int value ) +{ + UINT i; + + /* Overwrite attribute if it exists already */ + for (i = 0; i < ctx->num_attribs; ++i) + if (ctx->attribs[2 * i] == attrib) break; + + assert(i < ARRAY_SIZE(ctx->attribs) / 2); + + ctx->attribs[2 * i] = attrib; + ctx->attribs[2 * i + 1] = value; + if (i == ctx->num_attribs) ++ctx->num_attribs; +} + +/*********************************************************************** + * wglChoosePixelFormatARB (OPENGL32.@) + */ +BOOL WINAPI wglChoosePixelFormatARB( HDC hdc, const int *attribs_int, const FLOAT *attribs_float, + UINT max_formats, int *formats, UINT *num_formats ) +{ + struct wgl_pixel_format *wgl_formats; + UINT i, num_wgl_formats, num_wgl_onscreen_formats; + const struct wgl_pixel_format **format_array; + struct compare_formats_ctx ctx = { 0 }; + DWORD is_memdc; + + TRACE( "hdc %p, attribs_int %p, attribs_float %p, max_formats %u, formats %p, num_formats %p\n", + hdc, attribs_int, attribs_float, max_formats, formats, num_formats ); + + if (!wmgl_is_memdc( hdc, &is_memdc )) is_memdc = 0; + + wgl_formats = get_pixel_formats( hdc, &num_wgl_formats, &num_wgl_onscreen_formats ); + + + /* Gather, validate and deduplicate all attributes */ + for (i = 0; attribs_int && attribs_int[i]; i += 2) + { + if (wgl_attrib_match_criteria( attribs_int[i] ) == ATTRIB_MATCH_INVALID) return FALSE; + compare_formats_ctx_set_attrib( &ctx, attribs_int[i], attribs_int[i + 1] ); + } + for (i = 0; attribs_float && attribs_float[i]; i += 2) + { + if (wgl_attrib_match_criteria( attribs_float[i] ) == ATTRIB_MATCH_INVALID) return FALSE; + compare_formats_ctx_set_attrib( &ctx, attribs_float[i], attribs_float[i + 1] ); + } + + /* Initialize the format_array with (pointers to) all wgl formats */ + format_array = malloc( num_wgl_formats * sizeof(*format_array) ); + if (!format_array) return FALSE; + for (i = 0; i < num_wgl_formats; ++i) + { + struct wgl_pixel_format *format = wgl_formats + i; + if (is_memdc && format->pfd.cColorBits == 16 && format->pfd.cAlphaBits != 1) format_array[i] = NULL; + else format_array[i] = &wgl_formats[i]; + } + + /* Remove formats that are not acceptable */ + for (i = 0; i < ctx.num_attribs; ++i) + filter_format_array( format_array, num_wgl_formats, ctx.attribs[2 * i], + ctx.attribs[2 * i + 1] ); + + /* Some attributes we always want to sort by (values don't matter for sorting) */ + compare_formats_ctx_set_attrib( &ctx, WGL_ACCELERATION_ARB, 0 ); + compare_formats_ctx_set_attrib( &ctx, WGL_COLOR_BITS_ARB, 0 ); + compare_formats_ctx_set_attrib( &ctx, WGL_ACCUM_BITS_ARB, 0 ); + + /* Arrange attributes in the order which we want to check them */ + qsort( ctx.attribs, ctx.num_attribs, 2 * sizeof(*ctx.attribs), compare_attribs ); + + /* Sort pixel formats based on the specified attributes */ + qsort_s( format_array, num_wgl_formats, sizeof(*format_array), compare_formats, &ctx ); + + /* Return the best max_formats format ids */ + *num_formats = 0; + for (i = 0; i < num_wgl_formats && i < max_formats && format_array[i]; ++i) + { + ++*num_formats; + formats[i] = format_array[i] - wgl_formats + 1; + } + + free( format_array ); + return TRUE; +} + +INT WINAPI wglDescribePixelFormat( HDC hdc, int index, UINT size, PIXELFORMATDESCRIPTOR *ppfd ) +{ + struct wgl_pixel_format *formats; + UINT num_formats, num_onscreen_formats; + + TRACE( "hdc %p, index %d, size %u, ppfd %p\n", hdc, index, size, ppfd ); + + if (!(formats = get_pixel_formats( hdc, &num_formats, &num_onscreen_formats ))) return 0; + if (!ppfd) return num_onscreen_formats; + if (size < sizeof(*ppfd)) return 0; + if (index <= 0 || index > num_onscreen_formats) return 0; + + *ppfd = formats[index - 1].pfd; + + return num_onscreen_formats; +} + +/*********************************************************************** + * wglGetPixelFormatAttribivARB (OPENGL32.@) + */ +BOOL WINAPI wglGetPixelFormatAttribivARB( HDC hdc, int index, int plane, UINT count, + const int *attributes, int *values ) +{ + static const DWORD invalid_data_error = 0xC007000D; + struct wgl_pixel_format *formats; + UINT i, num_formats, num_onscreen_formats; + + TRACE( "hdc %p, index %d, plane %d, count %u, attributes %p, values %p\n", + hdc, index, plane, count, attributes, values ); + + formats = get_pixel_formats( hdc, &num_formats, &num_onscreen_formats ); + + + if (!count) return TRUE; + if (count == 1 && attributes[0] == WGL_NUMBER_PIXEL_FORMATS_ARB) + { + values[0] = num_formats; + return TRUE; + } + if (index <= 0 || index > num_formats) + { + SetLastError( invalid_data_error ); + return FALSE; + } + + for (i = 0; i < count; ++i) + { + int attrib = attributes[i]; + + if (attrib == WGL_NUMBER_PIXEL_FORMATS_ARB) + { + values[i] = num_formats; + } + else if ((plane != 0 && wgl_attrib_uses_layer( attrib )) || + !wgl_pixel_format_get_attrib( &formats[index - 1], attrib, &values[i] )) + { + SetLastError( invalid_data_error ); + return FALSE; + } + } + + return TRUE; +} + +/*********************************************************************** + * wglGetPixelFormatAttribfvARB (OPENGL32.@) + */ +BOOL WINAPI wglGetPixelFormatAttribfvARB( HDC hdc, int index, int plane, UINT count, + const int *attributes, FLOAT *values ) +{ + int *ivalues; + BOOL ret; + UINT i; + + TRACE( "hdc %p, index %d, plane %d, count %u, attributes %p, values %p\n", + hdc, index, plane, count, attributes, values ); + + if (!(ivalues = malloc( count * sizeof(int) ))) return FALSE; + + /* For now we can piggy-back on wglGetPixelFormatAttribivARB, since we don't support + * any non-integer attributes. */ + ret = wglGetPixelFormatAttribivARB( hdc, index, plane, count, attributes, ivalues ); + if (ret) + { + for (i = 0; i < count; i++) + values[i] = ivalues[i]; + } + + free( ivalues ); + return ret; +} diff --git a/src/wine/pe/winemetalgl.c b/src/wine/pe/winemetalgl.c new file mode 100644 index 00000000..5ef7f33f --- /dev/null +++ b/src/wine/pe/winemetalgl.c @@ -0,0 +1,643 @@ +/* winemetalgl.dll: WGL and the unix call plumbing (wmgl_wine.h). The GL + * functions are generated (wine_gl_pe.gen.c), the pixel format functions + * adapted from Wine (wgl_formats.c). */ + +#include "wmgl_pe.h" + +BOOL wmgl_debug; + +static unixlib_handle_t unixlib; +static NTSTATUS (WINAPI *unix_call_dispatcher)( unixlib_handle_t, unsigned int, void * ); +static DWORD tls_index = TLS_OUT_OF_INDEXES; +static char *wgl_extensions; + +/* The thread's current context and DCs (wglGetCurrent*). */ +struct thread_state +{ + HGLRC context; + HDC draw, read; + HGLRC extensions_context; /* the context `extensions` lists the GL extensions of */ + char *extensions; +}; + +static struct thread_state *thread_state( BOOL create ) +{ + struct thread_state *state = TlsGetValue( tls_index ); + if (!state && create && (state = calloc( 1, sizeof(*state) ))) TlsSetValue( tls_index, state ); + return state; +} + +static void deliver_debug_messages(void) +{ + char message[4096]; + struct wmgl_debug_message_params params; + + do + { + memset( ¶ms, 0, sizeof(params) ); + params.buffer = (UINT64)(ULONG_PTR)message; + params.size = sizeof(message); + if (unix_call_dispatcher( unixlib, unix_wmgl_debug_message, ¶ms ) || !params.got) return; + if (!params.callback) continue; + if (params.amd) + { + GLDEBUGPROCAMD callback = (GLDEBUGPROCAMD)(ULONG_PTR)params.callback; + callback( params.id, params.type, params.severity, params.length, message, (void *)(ULONG_PTR)params.user ); + } + else + { + GLDEBUGPROC callback = (GLDEBUGPROC)(ULONG_PTR)params.callback; + callback( params.source, params.type, params.id, params.severity, params.length, message, + (const void *)(ULONG_PTR)params.user ); + } + } while (params.more); +} + +NTSTATUS wmgl_unix_call( unsigned int code, void *args ) +{ + NTSTATUS status = unix_call_dispatcher( unixlib, code, args ); + if (status == WMGL_STATUS_DEBUG_MESSAGES) + { + deliver_debug_messages(); + status = 0; + } + return status; +} + +static BOOL init_unix_calls( HINSTANCE instance ) +{ + void **dispatcher = (void **)GetProcAddress( GetModuleHandleW( L"ntdll.dll" ), "__wine_unix_call_dispatcher" ); + /* MemoryWineUnixFuncs: the handle of this builtin module's unix library. */ + if (!dispatcher || NtQueryVirtualMemory( GetCurrentProcess(), instance, 1000, &unixlib, sizeof(unixlib), NULL )) + return FALSE; + unix_call_dispatcher = *dispatcher; + return TRUE; +} + +static BOOL init_pixel_formats(void) +{ + struct wmgl_init_params init = {0}; + struct wmgl_pixel_formats_params params = {0}; + struct wmgl_pixel_format *formats; + + if (wmgl_unix_call( unix_wmgl_init, &init ) || !init.ok || !init.format_count) return FALSE; + if (!(formats = calloc( init.format_count, sizeof(*formats) ))) return FALSE; + params.formats = (UINT64)(ULONG_PTR)formats; + params.max = init.format_count; + if (wmgl_unix_call( unix_wmgl_pixel_formats, ¶ms )) + { + free( formats ); + return FALSE; + } + wmgl_init_pixel_formats( formats, params.count, init.onscreen_count ); + free( formats ); + return TRUE; +} + +BOOL WINAPI DllMain( HINSTANCE instance, DWORD reason, LPVOID reserved ) +{ + switch (reason) + { + case DLL_PROCESS_ATTACH: + wmgl_debug = getenv( "WINEMETALGL_WINE_DEBUG" ) != NULL; + DisableThreadLibraryCalls( instance ); + if ((tls_index = TlsAlloc()) == TLS_OUT_OF_INDEXES) return FALSE; + if (!init_unix_calls( instance )) + { + fprintf( stderr, "winemetalgl: winemetalgl.so is not loaded (is winemetalgl.dll installed as a builtin?)\n" ); + return FALSE; + } + if (!init_pixel_formats()) + { + fprintf( stderr, "winemetalgl: the WineMetalGL core did not start (libwinemetalgl.dylib)\n" ); + return FALSE; + } + break; + case DLL_PROCESS_DETACH: + if (reserved) break; + if (thread_state( FALSE )) free( thread_state( FALSE )->extensions ); + free( thread_state( FALSE ) ); + TlsFree( tls_index ); + free( wgl_extensions ); + break; + } + return TRUE; +} + +/* Where a DC's window is (wmgl_window_placement), in pixels no DPI + * awareness has scaled. */ +static void window_placement( HDC hdc, struct wmgl_window_placement *placement ) +{ + DPI_AWARENESS_CONTEXT awareness; + HWND hwnd, top_level; + RECT client = {0}, around = {0}; + + memset( placement, 0, sizeof(*placement) ); + if (!hdc || !(hwnd = WindowFromDC( hdc ))) return; + awareness = SetThreadDpiAwarenessContext( DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 ); + top_level = GetAncestor( hwnd, GA_ROOT ); + GetClientRect( hwnd, &client ); + GetClientRect( top_level, &around ); + MapWindowPoints( hwnd, top_level, (POINT *)&client, 2 ); + placement->hwnd = (UINT64)(ULONG_PTR)hwnd; + placement->top_level = (UINT64)(ULONG_PTR)top_level; + placement->x = client.left; + placement->y = client.top; + placement->width = client.right - client.left; + placement->height = client.bottom - client.top; + placement->top_level_width = around.right; + placement->top_level_height = around.bottom; + placement->screen_width = GetSystemMetrics( SM_CXSCREEN ); + SetThreadDpiAwarenessContext( awareness ); +} + +/* ------------------------------------------------------------ contexts */ + +static HGLRC create_context( HDC hdc, HGLRC share, const int *attribs ) +{ + struct wmgl_create_context_params params = + { + .hdc = (UINT64)(ULONG_PTR)hdc, + .hwnd = (UINT64)(ULONG_PTR)WindowFromDC( hdc ), + .share = (UINT64)(ULONG_PTR)share, + .attribs = (UINT64)(ULONG_PTR)attribs, + }; + if (wmgl_unix_call( unix_wmgl_create_context, ¶ms ) || !params.ret) + { + SetLastError( params.error ? params.error : ERROR_INVALID_PARAMETER ); + return 0; + } + return (HGLRC)(ULONG_PTR)params.ret; +} + +HGLRC WINAPI wglCreateContext( HDC hdc ) +{ + return create_context( hdc, 0, NULL ); +} + +HGLRC WINAPI wglCreateContextAttribsARB( HDC hdc, HGLRC share, const int *attribs ) +{ + return create_context( hdc, share, attribs ); +} + +HGLRC WINAPI wglCreateLayerContext( HDC hdc, int plane ) +{ + if (plane == 0) return wglCreateContext( hdc ); + SetLastError( ERROR_INVALID_PARAMETER ); + return 0; +} + +static BOOL make_current( HDC draw, HDC read, HGLRC context ) +{ + struct thread_state *state = thread_state( TRUE ); + struct wmgl_make_current_params params = + { + .draw_hdc = (UINT64)(ULONG_PTR)draw, + .read_hdc = (UINT64)(ULONG_PTR)read, + .context = (UINT64)(ULONG_PTR)context, + }; + if (!state) return FALSE; + if (context) + { + window_placement( draw, ¶ms.draw ); + window_placement( read, ¶ms.read ); + } + if (wmgl_unix_call( unix_wmgl_make_current, ¶ms ) || !params.ret) + { + SetLastError( params.error ? params.error : ERROR_INVALID_HANDLE ); + return FALSE; + } + state->context = context; + state->draw = context ? draw : 0; + state->read = context ? read : 0; + return TRUE; +} + +BOOL WINAPI wglMakeCurrent( HDC hdc, HGLRC context ) +{ + if (!context) + { + struct thread_state *state = thread_state( FALSE ); + if (!state || !state->context) return TRUE; + } + return make_current( hdc, hdc, context ); +} + +BOOL WINAPI wglMakeContextCurrentARB( HDC draw, HDC read, HGLRC context ) +{ + return make_current( draw, read, context ); +} + +BOOL WINAPI wglDeleteContext( HGLRC context ) +{ + struct thread_state *state = thread_state( FALSE ); + struct wmgl_delete_context_params params = { .context = (UINT64)(ULONG_PTR)context }; + + if (wmgl_unix_call( unix_wmgl_delete_context, ¶ms ) || !params.ret) + { + SetLastError( ERROR_INVALID_HANDLE ); + return FALSE; + } + /* Deleting this thread's current context releases it. */ + if (state && state->context == context) state->context = 0, state->draw = state->read = 0; + return TRUE; +} + +HGLRC WINAPI wglGetCurrentContext(void) +{ + struct thread_state *state = thread_state( FALSE ); + return state ? state->context : 0; +} + +HDC WINAPI wglGetCurrentDC(void) +{ + struct thread_state *state = thread_state( FALSE ); + return state && state->context ? state->draw : 0; +} + +HDC WINAPI wglGetCurrentReadDCARB(void) +{ + struct thread_state *state = thread_state( FALSE ); + return state && state->context ? state->read : 0; +} + +BOOL WINAPI wglShareLists( HGLRC source, HGLRC dest ) +{ + struct wmgl_share_lists_params params = { .source = (UINT64)(ULONG_PTR)source, .dest = (UINT64)(ULONG_PTR)dest }; + if (wmgl_unix_call( unix_wmgl_share_lists, ¶ms )) return FALSE; + if (!params.ret) SetLastError( ERROR_INVALID_OPERATION ); + return params.ret; +} + +BOOL WINAPI wglCopyContext( HGLRC source, HGLRC dest, UINT mask ) +{ + struct wmgl_copy_context_params params = + { .source = (UINT64)(ULONG_PTR)source, .dest = (UINT64)(ULONG_PTR)dest, .mask = mask }; + if (wmgl_unix_call( unix_wmgl_copy_context, ¶ms )) return FALSE; + return params.ret; +} + +/* ------------------------------------------------------- pixel formats */ + +INT WINAPI wglGetPixelFormat( HDC hdc ) +{ + struct wmgl_get_pixel_format_params params = + { .hdc = (UINT64)(ULONG_PTR)hdc, .hwnd = (UINT64)(ULONG_PTR)WindowFromDC( hdc ) }; + if (wmgl_unix_call( unix_wmgl_get_pixel_format, ¶ms )) return 0; + if (!params.ret) SetLastError( ERROR_INVALID_PIXEL_FORMAT ); + return params.ret; +} + +static BOOL set_pixel_format( HDC hdc, int format, BOOL internal ) +{ + struct wmgl_set_pixel_format_params params = + { + .hdc = (UINT64)(ULONG_PTR)hdc, .hwnd = (UINT64)(ULONG_PTR)WindowFromDC( hdc ), .format = format, + .internal = internal, + }; + if (wmgl_unix_call( unix_wmgl_set_pixel_format, ¶ms )) return FALSE; + if (!params.ret) SetLastError( ERROR_INVALID_PIXEL_FORMAT ); + return params.ret; +} + +BOOL WINAPI wglSetPixelFormat( HDC hdc, int format, const PIXELFORMATDESCRIPTOR *pfd ) +{ + return set_pixel_format( hdc, format, FALSE ); +} + +BOOL WINAPI wglSetPixelFormatWINE( HDC hdc, int format ) +{ + return set_pixel_format( hdc, format, TRUE ); +} + +/* ---------------------------------------------------------------- swaps */ + +BOOL WINAPI wglSwapBuffers( HDC hdc ) +{ + struct wmgl_swap_buffers_params params = { .hdc = (UINT64)(ULONG_PTR)hdc }; + window_placement( hdc, ¶ms.window ); + if (wmgl_unix_call( unix_wmgl_swap_buffers, ¶ms )) return FALSE; + return params.ret; +} + +BOOL WINAPI wglSwapLayerBuffers( HDC hdc, UINT planes ) +{ + if (planes & WGL_SWAP_MAIN_PLANE) return wglSwapBuffers( hdc ); + return TRUE; +} + +BOOL WINAPI wglSwapIntervalEXT( int interval ) +{ + struct wmgl_swap_interval_params params = { .interval = interval }; + if (interval < 0) + { + SetLastError( ERROR_INVALID_DATA ); + return FALSE; + } + if (wmgl_unix_call( unix_wmgl_swap_interval, ¶ms )) return FALSE; + return params.ret; +} + +int WINAPI wglGetSwapIntervalEXT(void) +{ + struct wmgl_swap_interval_params params = {0}; + if (wmgl_unix_call( unix_wmgl_get_swap_interval, ¶ms )) return 0; + return params.interval; +} + +/* -------------------------------------------------------------- pbuffers */ + +HPBUFFERARB WINAPI wglCreatePbufferARB( HDC hdc, int format, int width, int height, const int *attribs ) +{ + struct wmgl_create_pbuffer_params params = + { + .hdc = (UINT64)(ULONG_PTR)hdc, .hwnd = (UINT64)(ULONG_PTR)WindowFromDC( hdc ), + .format = format, .width = width, .height = height, + .attribs = (UINT64)(ULONG_PTR)attribs, + }; + if (wmgl_unix_call( unix_wmgl_create_pbuffer, ¶ms ) || !params.ret) + { + SetLastError( params.error ? params.error : ERROR_INVALID_DATA ); + return 0; + } + return (HPBUFFERARB)(ULONG_PTR)params.ret; +} + +static BOOL pbuffer_call( unsigned int code, struct wmgl_pbuffer_params *params ) +{ + if (wmgl_unix_call( code, params )) return FALSE; + if (!params->ret) SetLastError( ERROR_INVALID_HANDLE ); + return params->ret; +} + +BOOL WINAPI wglDestroyPbufferARB( HPBUFFERARB pbuffer ) +{ + struct wmgl_pbuffer_params params = { .pbuffer = (UINT64)(ULONG_PTR)pbuffer }; + return pbuffer_call( unix_wmgl_destroy_pbuffer, ¶ms ); +} + +/* A pbuffer's DC is a memory DC the unix side knows as the pbuffer's. */ +HDC WINAPI wglGetPbufferDCARB( HPBUFFERARB pbuffer ) +{ + struct wmgl_pbuffer_params params = { .pbuffer = (UINT64)(ULONG_PTR)pbuffer }; + HDC hdc = CreateCompatibleDC( 0 ); + + if (!hdc) return 0; + params.hdc = (UINT64)(ULONG_PTR)hdc; + if (!pbuffer_call( unix_wmgl_get_pbuffer_dc, ¶ms )) + { + DeleteDC( hdc ); + return 0; + } + return hdc; +} + +int WINAPI wglReleasePbufferDCARB( HPBUFFERARB pbuffer, HDC hdc ) +{ + struct wmgl_pbuffer_params params = { .pbuffer = (UINT64)(ULONG_PTR)pbuffer, .hdc = (UINT64)(ULONG_PTR)hdc }; + if (!pbuffer_call( unix_wmgl_release_pbuffer_dc, ¶ms )) return 0; + DeleteDC( hdc ); + return 1; +} + +BOOL WINAPI wglQueryPbufferARB( HPBUFFERARB pbuffer, int attribute, int *value ) +{ + struct wmgl_pbuffer_params params = { .pbuffer = (UINT64)(ULONG_PTR)pbuffer, .attribute = attribute }; + if (!pbuffer_call( unix_wmgl_query_pbuffer, ¶ms )) return FALSE; + *value = params.value; + return TRUE; +} + +BOOL WINAPI wglBindTexImageARB( HPBUFFERARB pbuffer, int buffer ) +{ + struct wmgl_pbuffer_params params = { .pbuffer = (UINT64)(ULONG_PTR)pbuffer, .attribute = buffer }; + return pbuffer_call( unix_wmgl_bind_tex_image, ¶ms ); +} + +BOOL WINAPI wglReleaseTexImageARB( HPBUFFERARB pbuffer, int buffer ) +{ + struct wmgl_pbuffer_params params = { .pbuffer = (UINT64)(ULONG_PTR)pbuffer, .attribute = buffer }; + return pbuffer_call( unix_wmgl_release_tex_image, ¶ms ); +} + +BOOL WINAPI wglSetPbufferAttribARB( HPBUFFERARB pbuffer, const int *attribs ) +{ + struct wmgl_pbuffer_params params = { .pbuffer = (UINT64)(ULONG_PTR)pbuffer, .attribs = (UINT64)(ULONG_PTR)attribs }; + return pbuffer_call( unix_wmgl_set_pbuffer_attrib, ¶ms ); +} + +/* ---------------------------------------------------- renderer queries */ + +static BOOL query_renderer_integer( HDC hdc, GLint renderer, GLenum attribute, GLuint *value ) +{ + struct wmgl_query_renderer_params params = + { .hdc = (UINT64)(ULONG_PTR)hdc, .renderer = renderer, .attribute = attribute, .value = (UINT64)(ULONG_PTR)value }; + if (wmgl_unix_call( unix_wmgl_query_renderer_integer, ¶ms )) return FALSE; + return params.ret; +} + +static const GLchar *query_renderer_string( HDC hdc, GLint renderer, GLenum attribute ) +{ + static char strings[2][256]; /* device, vendor: constant for the process */ + char buffer[256]; + struct wmgl_query_renderer_params params = + { + .hdc = (UINT64)(ULONG_PTR)hdc, .renderer = renderer, .attribute = attribute, + .buffer = (UINT64)(ULONG_PTR)buffer, .size = sizeof(buffer), + }; + int slot = attribute == WGL_RENDERER_VENDOR_ID_WINE ? 1 : 0; + if (wmgl_unix_call( unix_wmgl_query_renderer_string, ¶ms ) || !params.ret) return NULL; + lstrcpynA( strings[slot], buffer, sizeof(strings[slot]) ); + return strings[slot]; +} + +BOOL WINAPI wglQueryCurrentRendererIntegerWINE( GLenum attribute, GLuint *value ) +{ + return query_renderer_integer( 0, 0, attribute, value ); +} + +const GLchar *WINAPI wglQueryCurrentRendererStringWINE( GLenum attribute ) +{ + return query_renderer_string( 0, 0, attribute ); +} + +BOOL WINAPI wglQueryRendererIntegerWINE( HDC dc, GLint renderer, GLenum attribute, GLuint *value ) +{ + return query_renderer_integer( dc, renderer, attribute, value ); +} + +const GLchar *WINAPI wglQueryRendererStringWINE( HDC dc, GLint renderer, GLenum attribute ) +{ + return query_renderer_string( dc, renderer, attribute ); +} + +/* ------------------------------------------------------ extension strings */ + +static const char *extensions_string(void) +{ + struct wmgl_extensions_string_params params = {0}; + char *string; + + if (wgl_extensions) return wgl_extensions; + if (wmgl_unix_call( unix_wmgl_extensions_string, ¶ms ) || !params.needed) return ""; + if (!(string = calloc( 1, params.needed ))) return ""; + params.buffer = (UINT64)(ULONG_PTR)string; + params.size = params.needed; + if (wmgl_unix_call( unix_wmgl_extensions_string, ¶ms )) + { + free( string ); + return ""; + } + if (InterlockedCompareExchangePointer( (void **)&wgl_extensions, string, NULL )) free( string ); + return wgl_extensions; +} + +const char *WINAPI wglGetExtensionsStringARB( HDC hdc ) +{ + return extensions_string(); +} + +const char *WINAPI wglGetExtensionsStringEXT(void) +{ + return extensions_string(); +} + +/* --------------------------------------------------------- proc addresses */ + +static const struct +{ + const char *name; + PROC proc; + const char *extension; +} wgl_procs[] = +{ + {"wglBindTexImageARB", (PROC)wglBindTexImageARB, "WGL_ARB_render_texture"}, + {"wglChoosePixelFormatARB", (PROC)wglChoosePixelFormatARB, "WGL_ARB_pixel_format"}, + {"wglCreateContextAttribsARB", (PROC)wglCreateContextAttribsARB, "WGL_ARB_create_context"}, + {"wglCreatePbufferARB", (PROC)wglCreatePbufferARB, "WGL_ARB_pbuffer"}, + {"wglDestroyPbufferARB", (PROC)wglDestroyPbufferARB, "WGL_ARB_pbuffer"}, + {"wglGetCurrentReadDCARB", (PROC)wglGetCurrentReadDCARB, "WGL_ARB_make_current_read"}, + {"wglGetExtensionsStringARB", (PROC)wglGetExtensionsStringARB, "WGL_ARB_extensions_string"}, + {"wglGetExtensionsStringEXT", (PROC)wglGetExtensionsStringEXT, "WGL_EXT_extensions_string"}, + {"wglGetPbufferDCARB", (PROC)wglGetPbufferDCARB, "WGL_ARB_pbuffer"}, + {"wglGetPixelFormatAttribfvARB", (PROC)wglGetPixelFormatAttribfvARB, "WGL_ARB_pixel_format"}, + {"wglGetPixelFormatAttribivARB", (PROC)wglGetPixelFormatAttribivARB, "WGL_ARB_pixel_format"}, + {"wglGetSwapIntervalEXT", (PROC)wglGetSwapIntervalEXT, "WGL_EXT_swap_control"}, + {"wglMakeContextCurrentARB", (PROC)wglMakeContextCurrentARB, "WGL_ARB_make_current_read"}, + {"wglQueryCurrentRendererIntegerWINE", (PROC)wglQueryCurrentRendererIntegerWINE, "WGL_WINE_query_renderer"}, + {"wglQueryCurrentRendererStringWINE", (PROC)wglQueryCurrentRendererStringWINE, "WGL_WINE_query_renderer"}, + {"wglQueryPbufferARB", (PROC)wglQueryPbufferARB, "WGL_ARB_pbuffer"}, + {"wglQueryRendererIntegerWINE", (PROC)wglQueryRendererIntegerWINE, "WGL_WINE_query_renderer"}, + {"wglQueryRendererStringWINE", (PROC)wglQueryRendererStringWINE, "WGL_WINE_query_renderer"}, + {"wglReleasePbufferDCARB", (PROC)wglReleasePbufferDCARB, "WGL_ARB_pbuffer"}, + {"wglReleaseTexImageARB", (PROC)wglReleaseTexImageARB, "WGL_ARB_render_texture"}, + {"wglSetPbufferAttribARB", (PROC)wglSetPbufferAttribARB, "WGL_ARB_render_texture"}, + {"wglSetPixelFormatWINE", (PROC)wglSetPixelFormatWINE, "WGL_WINE_pixel_format_passthrough"}, + {"wglSwapIntervalEXT", (PROC)wglSwapIntervalEXT, "WGL_EXT_swap_control"}, +}; + +static BOOL has_extension( const char *list, const char *name ) +{ + size_t len = strlen( name ); + const char *p = list; + while ((p = strstr( p, name ))) + { + if ((p == list || p[-1] == ' ') && (p[len] == ' ' || !p[len])) return TRUE; + p += len; + } + return FALSE; +} + +/* The current context's GL extensions (one space-separated list per + * thread, rebuilt when another context becomes current there). */ +static BOOL context_has_extension( const char *name ) +{ + struct thread_state *state = thread_state( FALSE ); + HGLRC context = wglGetCurrentContext(); + GLint count = 0, i; + size_t size = 1; + char *list; + + if (!state || !context) return FALSE; + if (context != state->extensions_context || !state->extensions) + { + glGetIntegerv( GL_NUM_EXTENSIONS, &count ); + for (i = 0; i < count; i++) + { + const char *ext = (const char *)glGetStringi( GL_EXTENSIONS, i ); + if (ext) size += strlen( ext ) + 1; + } + if (!(list = calloc( 1, size ))) return FALSE; + for (i = 0; i < count; i++) + { + const char *ext = (const char *)glGetStringi( GL_EXTENSIONS, i ); + if (!ext) continue; + if (*list) strcat( list, " " ); + strcat( list, ext ); + } + free( state->extensions ); + state->extensions = list; + state->extensions_context = context; + } + return has_extension( state->extensions, name ); +} + +PROC WINAPI wglGetProcAddress( LPCSTR name ) +{ + unsigned int lo = 0, hi = wmgl_proc_count, i; + + if (!name) return NULL; + for (i = 0; i < sizeof(wgl_procs) / sizeof(wgl_procs[0]); i++) + if (!strcmp( wgl_procs[i].name, name )) + return has_extension( extensions_string(), wgl_procs[i].extension ) ? wgl_procs[i].proc : NULL; + if (!wglGetCurrentContext()) return NULL; + while (lo < hi) + { + unsigned int mid = (lo + hi) / 2; + int cmp = strcmp( name, wmgl_procs[mid].name ); + if (!cmp) + { + const struct wmgl_proc *proc = &wmgl_procs[mid]; + /* Commands of a GL version (every context is the newest) always, + * extension commands when the context offers the extension. */ + if (proc->source[0] >= '0' && proc->source[0] <= '9') return proc->proc; + return context_has_extension( proc->source ) ? proc->proc : NULL; + } + if (cmp < 0) hi = mid; + else lo = mid + 1; + } + return NULL; +} + +PROC WINAPI wglGetDefaultProcAddress( LPCSTR name ) +{ + return NULL; +} + +/* ----------------------------------------------------------- layer planes */ + +BOOL WINAPI wglDescribeLayerPlane( HDC hdc, int format, int plane, UINT size, LAYERPLANEDESCRIPTOR *descr ) +{ + return FALSE; +} + +int WINAPI wglGetLayerPaletteEntries( HDC hdc, int plane, int start, int count, COLORREF *colors ) +{ + return 0; +} + +int WINAPI wglSetLayerPaletteEntries( HDC hdc, int plane, int start, int count, const COLORREF *colors ) +{ + return 0; +} + +BOOL WINAPI wglRealizeLayerPalette( HDC hdc, int plane, BOOL realize ) +{ + return FALSE; +} + +/* glDebugEntry (OPENGL32.@): undocumented, kept for the export list. */ +GLint WINAPI glDebugEntry( GLint unknown1, GLint unknown2 ) +{ + return 0; +} diff --git a/src/wine/pe/wmgl_pe.h b/src/wine/pe/wmgl_pe.h new file mode 100644 index 00000000..79475605 --- /dev/null +++ b/src/wine/pe/wmgl_pe.h @@ -0,0 +1,121 @@ +/* winemetalgl.dll: the PE side of the WineMetalGL Wine module (wmgl_wine.h). */ +#ifndef WMGL_PE_H +#define WMGL_PE_H + +/* Windows 10 API level (SetThreadDpiAwarenessContext), before any header. */ +#undef _WIN32_WINNT +#define _WIN32_WINNT 0x0A00 +#undef WINVER +#define WINVER 0x0A00 + +#include +#include +#include +#include + +#define WIN32_LEAN_AND_MEAN +#define _GDI32_ /* wingdi.h: this module defines the wgl functions it declares (no dllimport) */ +#include +#include + +NTSTATUS NTAPI NtQueryVirtualMemory( HANDLE process, const void *address, ULONG info_class, void *buffer, + SIZE_T length, SIZE_T *result ); + +#include "wmgl_wine.h" +#include + +/* WGL_WINE_query_renderer (Wine's wgl.h). */ +#define WGL_RENDERER_VENDOR_ID_WINE 0x8183 +#define WGL_RENDERER_DEVICE_ID_WINE 0x8184 +#define WGL_RENDERER_VERSION_WINE 0x8185 +#define WGL_RENDERER_ACCELERATED_WINE 0x8186 +#define WGL_RENDERER_VIDEO_MEMORY_WINE 0x8187 +#define WGL_RENDERER_UNIFIED_MEMORY_ARCHITECTURE_WINE 0x8188 +#define WGL_RENDERER_PREFERRED_PROFILE_WINE 0x8189 +#define WGL_RENDERER_OPENGL_CORE_PROFILE_VERSION_WINE 0x818A +#define WGL_RENDERER_OPENGL_COMPATIBILITY_PROFILE_VERSION_WINE 0x818B +#define WGL_RENDERER_OPENGL_ES_PROFILE_VERSION_WINE 0x818C +#define WGL_RENDERER_OPENGL_ES2_PROFILE_VERSION_WINE 0x818D + +#ifndef WGL_SWAP_UNDEFINED_ARB +#define WGL_SWAP_UNDEFINED_ARB 0x2063 +#endif +#define GL_FALSE 0 +#define GL_TRUE 1 +#define GL_VERSION 0x1F02 +#define GL_EXTENSIONS 0x1F03 +#define GL_NUM_EXTENSIONS 0x821D +#define GL_COMPILE 0x1300 +#define GL_UNSIGNED_BYTE 0x1401 +#define GL_UNPACK_ALIGNMENT 0x0CF5 +#define GL_UNPACK_ROW_LENGTH 0x0CF2 + +typedef UINT64 unixlib_handle_t; + +/* A unix call (wmgl_wine.h); debug messages it queued reach the + * application's callback before it returns. */ +NTSTATUS wmgl_unix_call( unsigned int code, void *args ); +#define WMGL_CALL(name, args) wmgl_unix_call( unix_##name, (args) ) + +struct wmgl_proc +{ + const char *name; + PROC proc; + const char *source; /* "1.0".."4.6", or the extension that provides it */ +}; +extern const struct wmgl_proc wmgl_procs[]; +extern const unsigned int wmgl_proc_count; + +/* The pixel formats (wgl_formats.c), fetched once. */ +struct wgl_pixel_format +{ + PIXELFORMATDESCRIPTOR pfd; + int swap_method; + int transparent; + int pixel_type; + int draw_to_pbuffer; + int max_pbuffer_pixels; + int max_pbuffer_width; + int max_pbuffer_height; + int transparent_red_value; + int transparent_red_value_valid; + int transparent_green_value; + int transparent_green_value_valid; + int transparent_blue_value; + int transparent_blue_value_valid; + int transparent_alpha_value; + int transparent_alpha_value_valid; + int transparent_index_value; + int transparent_index_value_valid; + int sample_buffers; + int samples; + int bind_to_texture_rgb; + int bind_to_texture_rgba; + int bind_to_texture_rectangle_rgb; + int bind_to_texture_rectangle_rgba; + int framebuffer_srgb_capable; + int float_components; +}; +void wmgl_init_pixel_formats( const struct wmgl_pixel_format *formats, UINT count, UINT onscreen ); +INT WINAPI wglDescribePixelFormat( HDC hdc, int index, UINT size, PIXELFORMATDESCRIPTOR *ppfd ); +BOOL WINAPI wglChoosePixelFormatARB( HDC hdc, const int *attribs_int, const FLOAT *attribs_float, UINT max_formats, + int *formats, UINT *num_formats ); +BOOL WINAPI wglGetPixelFormatAttribivARB( HDC hdc, int index, int plane, UINT count, const int *attributes, int *values ); +BOOL WINAPI wglGetPixelFormatAttribfvARB( HDC hdc, int index, int plane, UINT count, const int *attributes, + FLOAT *values ); + +/* GL entry points of this module the WGL code calls. */ +void WINAPI glGetIntegerv( GLenum pname, GLint *data ); +const GLubyte *WINAPI glGetString( GLenum name ); +const GLubyte *WINAPI glGetStringi( GLenum name, GLuint index ); +GLenum WINAPI glGetError( void ); +void WINAPI glNewList( GLuint list, GLenum mode ); +void WINAPI glEndList( void ); +void WINAPI glBitmap( GLsizei width, GLsizei height, GLfloat xorig, GLfloat yorig, GLfloat xmove, GLfloat ymove, + const GLubyte *bitmap ); +void WINAPI glPixelStorei( GLenum pname, GLint param ); + +#define WMGL_TRACE(...) do { if (wmgl_debug) { fprintf( stderr, "winemetalgl: " __VA_ARGS__ ); } } while (0) +extern BOOL wmgl_debug; + +#endif diff --git a/src/wine/unix/unix_main.c b/src/wine/unix/unix_main.c new file mode 100644 index 00000000..2263bdde --- /dev/null +++ b/src/wine/unix/unix_main.c @@ -0,0 +1,301 @@ +/* winemetalgl.so: loads the WineMetalGL core and carries GL calls to it + * (the call tables are generated: wine_gl_unix.gen.c), with the debug + * callbacks, which the core calls on the unix side where PE code cannot run: + * their messages wait in a per-thread queue that the PE side drains after + * the GL call that produced them (wmgl_after_call). */ + +#include +#include + +#include "wmgl_unix.h" + +#define GL_DEBUG_CALLBACK_FUNCTION 0x8244 +#define GL_DEBUG_CALLBACK_USER_PARAM 0x8245 + +struct wmgl_core wmgl; +BOOL wmgl_trace; +static void *core_handle; + +void *wmgl_core_symbol( const char *name ) +{ + return core_handle ? dlsym( core_handle, name ) : NULL; +} + +/* ------------------------------------------------------------ the core */ + +static BOOL load_core(void) +{ + Dl_info info; + char path[4096], *dir; + void *handle; + unsigned int i; + + wmgl_trace = getenv( "WINEMETALGL_WINE_DEBUG" ) != NULL; + /* libwinemetalgl.dylib sits next to winemetalgl.so. */ + if (!dladdr( (void *)load_core, &info ) || !info.dli_fname) return FALSE; + snprintf( path, sizeof(path), "%s", info.dli_fname ); + dir = dirname( path ); + snprintf( path, sizeof(path), "%s/libwinemetalgl.dylib", dir ); + if (!(handle = dlopen( path, RTLD_NOW | RTLD_LOCAL ))) + { + WMGL_ERR( "cannot load %s: %s\n", path, dlerror() ); + return FALSE; + } + core_handle = handle; +#define LOAD(field, name) if (!(*(void **)&wmgl.field = dlsym( handle, name ))) { WMGL_ERR( "%s missing\n", name ); return FALSE; } + LOAD(initialize, "wmgl_initialize"); + LOAD(get_pixel_formats, "wmgl_get_pixel_formats"); + LOAD(surface_create_window, "wmgl_surface_create_window"); + LOAD(surface_create_pbuffer, "wmgl_surface_create_pbuffer"); + LOAD(surface_destroy, "wmgl_surface_destroy"); + LOAD(surface_flush, "wmgl_surface_flush"); + LOAD(surface_swap, "wmgl_surface_swap"); + LOAD(context_create, "wmgl_context_create"); + LOAD(context_destroy, "wmgl_context_destroy"); + LOAD(make_current, "wmgl_make_current"); + LOAD(get_proc_address, "wmgl_get_proc_address"); + LOAD(renderer_string, "wmgl_renderer_string"); + LOAD(video_memory_mb, "wmgl_video_memory_mb"); + LOAD(max_versions, "wmgl_max_versions"); +#undef LOAD + *(void **)&wmgl.version = dlsym( handle, "wmgl_version" ); + if (wmgl.initialize( WMGL_DRIVER_ABI_VERSION )) + { + WMGL_ERR( "the WineMetalGL core refused to initialise (driver ABI %u)\n", WMGL_DRIVER_ABI_VERSION ); + return FALSE; + } + /* Every command the generated tables call (struct wmgl_core_procs is + * one pointer per name, in wmgl_core_names order). */ + for (i = 0; i < WMGL_CORE_PROC_COUNT; i++) + ((void **)&core)[i] = wmgl.get_proc_address( wmgl_core_names[i] ); + return TRUE; +} + +static pthread_once_t core_once = PTHREAD_ONCE_INIT; +static BOOL core_loaded; + +static void load_core_once(void) +{ + core_loaded = load_core(); +} + +extern NTSTATUS wmgl_unix_init_formats( void *args ); + +/* The first call of the PE side (DllMain): loads the core, then reports the + * pixel formats (unix_wgl.c). */ +NTSTATUS wmgl_unix_init( void *args ) +{ + struct wmgl_init_params *params = args; + pthread_once( &core_once, load_core_once ); + if (!core_loaded) + { + memset( params, 0, sizeof(*params) ); + return STATUS_SUCCESS; + } + return wmgl_unix_init_formats( args ); +} + +/* ------------------------------------------------------- debug messages */ + +#define MAX_QUEUED 64 + +struct queued_message +{ + UINT64 callback, user; + UINT source, type, id, severity; + INT length; + BOOL amd; + char *text; +}; + +static __thread struct queued_message queue[MAX_QUEUED]; +static __thread unsigned int queued, delivered; + +NTSTATUS wmgl_after_call( void ) +{ + return queued > delivered ? WMGL_STATUS_DEBUG_MESSAGES : STATUS_SUCCESS; +} + +static void queue_message( BOOL amd, GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, + const GLchar *message ) +{ + struct wmgl_debug_target *target = wmgl_current_debug_target(); + struct queued_message *slot; + size_t len; + + if (!target || !target->callback || target->amd != amd) return; + if (queued == delivered) queued = delivered = 0; + if (queued >= MAX_QUEUED) return; /* the application gets the first ones */ + slot = &queue[queued]; + len = length >= 0 ? (size_t)length : (message ? strlen( message ) : 0); + if (!(slot->text = malloc( len + 1 ))) return; + if (len) memcpy( slot->text, message, len ); + slot->text[len] = 0; + slot->callback = target->callback; + slot->user = target->user; + slot->source = source; + slot->type = type; + slot->id = id; + slot->severity = severity; + slot->length = (INT)len; + slot->amd = amd; + queued++; +} + +static void debug_callback( GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, + const GLchar *message, const void *user ) +{ + queue_message( FALSE, source, type, id, severity, length, message ); +} + +static void debug_callback_amd( GLuint id, GLenum category, GLenum severity, GLsizei length, const GLchar *message, + void *user ) +{ + queue_message( TRUE, 0, category, id, severity, length, message ); +} + +NTSTATUS wmgl_unix_debug_message( void *args ) +{ + struct wmgl_debug_message_params *params = args; + char *buffer = (char *)(ULONG_PTR)params->buffer; + struct queued_message *message; + + params->got = params->more = FALSE; + if (delivered >= queued) return STATUS_SUCCESS; + message = &queue[delivered++]; + params->callback = message->callback; + params->user = message->user; + params->source = message->source; + params->type = message->type; + params->id = message->id; + params->severity = message->severity; + params->length = message->length; + params->amd = message->amd; + if (buffer && params->size) + { + size_t len = (size_t)message->length < params->size - 1 ? (size_t)message->length : params->size - 1; + memcpy( buffer, message->text, len ); + buffer[len] = 0; + if ((size_t)message->length > len) params->length = (INT)len; + } + free( message->text ); + message->text = NULL; + params->got = TRUE; + params->more = delivered < queued; + if (!params->more) queued = delivered = 0; + return STATUS_SUCCESS; +} + +/* glDebugMessageCallback*: the core calls ours; the application's waits in + * the context for the PE side. */ +static void set_callback( UINT64 callback, UINT64 user, BOOL amd ) +{ + wmgl_set_debug_callback( callback, user, amd ); + if (amd) core.p_glDebugMessageCallbackAMD( callback ? (GLDEBUGPROCAMD)debug_callback_amd : NULL, NULL ); + else core.p_glDebugMessageCallback( callback ? (GLDEBUGPROC)debug_callback : NULL, NULL ); +} + +NTSTATUS manual_glDebugMessageCallback( void *args ) +{ + struct glDebugMessageCallback_params *params = args; + set_callback( (UINT64)(ULONG_PTR)params->callback, (UINT64)(ULONG_PTR)params->userParam, FALSE ); + return wmgl_after_call(); +} + +NTSTATUS manual_glDebugMessageCallbackARB( void *args ) +{ + struct glDebugMessageCallbackARB_params *params = args; + set_callback( (UINT64)(ULONG_PTR)params->callback, (UINT64)(ULONG_PTR)params->userParam, FALSE ); + return wmgl_after_call(); +} + +NTSTATUS manual_glDebugMessageCallbackAMD( void *args ) +{ + struct glDebugMessageCallbackAMD_params *params = args; + set_callback( (UINT64)(ULONG_PTR)params->callback, (UINT64)(ULONG_PTR)params->userParam, TRUE ); + return wmgl_after_call(); +} + +/* glGetPointerv*: the debug callback and its parameter are the application's. */ +static BOOL debug_pointer( GLenum pname, UINT64 *value ) +{ + struct wmgl_debug_target *target = wmgl_current_debug_target(); + if (pname != GL_DEBUG_CALLBACK_FUNCTION && pname != GL_DEBUG_CALLBACK_USER_PARAM) return FALSE; + *value = !target ? 0 : pname == GL_DEBUG_CALLBACK_FUNCTION ? target->callback : target->user; + return TRUE; +} + +static NTSTATUS get_pointer( GLenum pname, void **out, void (*proc)( GLenum, void ** ) ) +{ + UINT64 value; + if (debug_pointer( pname, &value )) + { + if (out) *out = (void *)(ULONG_PTR)value; + } + else proc( pname, out ); + return wmgl_after_call(); +} + +NTSTATUS manual_glGetPointerv( void *args ) +{ + struct glGetPointerv_params *params = args; + return get_pointer( params->pname, params->params, core.p_glGetPointerv ); +} + +NTSTATUS manual_glGetPointervEXT( void *args ) +{ + struct glGetPointervEXT_params *params = args; + return get_pointer( params->pname, params->params, core.p_glGetPointervEXT ); +} + +#ifdef _WIN64 + +NTSTATUS wow64_manual_glDebugMessageCallback( void *args ) +{ + struct glDebugMessageCallback_params32 *params = args; + set_callback( params->callback, params->userParam, FALSE ); + return wmgl_after_call(); +} + +NTSTATUS wow64_manual_glDebugMessageCallbackARB( void *args ) +{ + struct glDebugMessageCallbackARB_params32 *params = args; + set_callback( params->callback, params->userParam, FALSE ); + return wmgl_after_call(); +} + +NTSTATUS wow64_manual_glDebugMessageCallbackAMD( void *args ) +{ + struct glDebugMessageCallbackAMD_params32 *params = args; + set_callback( params->callback, params->userParam, TRUE ); + return wmgl_after_call(); +} + +/* A 32-bit caller's pointers are below 4 GB: what the core reports (the + * application's own pointers) fits, and the debug callback is its own. */ +static NTSTATUS wow64_get_pointer( GLenum pname, UINT out, void (*proc)( GLenum, void ** ) ) +{ + UINT64 value; + void *ptr = NULL; + if (!debug_pointer( pname, &value )) + { + proc( pname, &ptr ); + value = (UINT64)(ULONG_PTR)ptr; + } + if (out) *(UINT *)ULongToPtr( out ) = (UINT)value; + return wmgl_after_call(); +} + +NTSTATUS wow64_manual_glGetPointerv( void *args ) +{ + struct glGetPointerv_params32 *params = args; + return wow64_get_pointer( params->pname, params->params, core.p_glGetPointerv ); +} + +NTSTATUS wow64_manual_glGetPointervEXT( void *args ) +{ + struct glGetPointervEXT_params32 *params = args; + return wow64_get_pointer( params->pname, params->params, core.p_glGetPointervEXT ); +} + +#endif diff --git a/src/wine/unix/unix_wgl.c b/src/wine/unix/unix_wgl.c new file mode 100644 index 00000000..bee3aa1c --- /dev/null +++ b/src/wine/unix/unix_wgl.c @@ -0,0 +1,837 @@ +/* WGL of winemetalgl.so: contexts, pixel formats, drawables and swaps on the + * WineMetalGL core (include/winemetalgl/driver.h). + * + * - Pixel formats belong to windows (by HWND) or, for other DCs, to DCs. + * - A window's drawable is made when a context first becomes current on one + * of its DCs: a core window surface on the layer unix_window.m provides. + * The PE side describes the window on every make-current and swap + * (wmgl_window_placement); a new size resizes the surface. + * - Every context shares objects with a hidden root context, so + * wglShareLists always succeeds (as win32u arranges for its drivers). + * - Handles are small numbers, valid for 32-bit callers too. */ + +#include "wmgl_unix.h" + +#define HANDLE_BASE 0x10000 + +/* WGL constants (wglext.h; WGL_WINE_query_renderer from Wine's wgl.h). */ +#define WGL_TEXTURE_FORMAT_ARB 0x2072 +#define WGL_TEXTURE_TARGET_ARB 0x2073 +#define WGL_MIPMAP_TEXTURE_ARB 0x2074 +#define WGL_TEXTURE_RGB_ARB 0x2075 +#define WGL_TEXTURE_RGBA_ARB 0x2076 +#define WGL_NO_TEXTURE_ARB 0x2077 +#define WGL_TEXTURE_CUBE_MAP_ARB 0x2078 +#define WGL_TEXTURE_1D_ARB 0x2079 +#define WGL_TEXTURE_2D_ARB 0x207A +#define WGL_MIPMAP_LEVEL_ARB 0x207B +#define WGL_CUBE_MAP_FACE_ARB 0x207C +#define WGL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB 0x207D +#define WGL_FRONT_LEFT_ARB 0x2083 +#define WGL_BACK_LEFT_ARB 0x2085 +#define WGL_PBUFFER_LARGEST_ARB 0x2033 +#define WGL_PBUFFER_WIDTH_ARB 0x2034 +#define WGL_PBUFFER_HEIGHT_ARB 0x2035 +#define WGL_PBUFFER_LOST_ARB 0x2036 +#define WGL_TEXTURE_RECTANGLE_NV 0x20A2 +#define WGL_CONTEXT_CORE_PROFILE_BIT_ARB 0x0001 +#define WGL_RENDERER_VENDOR_ID_WINE 0x8183 +#define WGL_RENDERER_DEVICE_ID_WINE 0x8184 +#define WGL_RENDERER_VERSION_WINE 0x8185 +#define WGL_RENDERER_ACCELERATED_WINE 0x8186 +#define WGL_RENDERER_VIDEO_MEMORY_WINE 0x8187 +#define WGL_RENDERER_UNIFIED_MEMORY_ARCHITECTURE_WINE 0x8188 +#define WGL_RENDERER_PREFERRED_PROFILE_WINE 0x8189 +#define WGL_RENDERER_OPENGL_CORE_PROFILE_VERSION_WINE 0x818A +#define WGL_RENDERER_OPENGL_COMPATIBILITY_PROFILE_VERSION_WINE 0x818B +#define WGL_RENDERER_OPENGL_ES_PROFILE_VERSION_WINE 0x818C +#define WGL_RENDERER_OPENGL_ES2_PROFILE_VERSION_WINE 0x818D +#define ERROR_INVALID_VERSION_ARB 0x2095 +#define ERROR_INVALID_PROFILE_ARB 0x2096 + +#define GL_TEXTURE_1D 0x0DE0 +#define GL_TEXTURE_2D 0x0DE1 +#define GL_TEXTURE_RECTANGLE 0x84F5 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_X 0x8515 +#define GL_RGB 0x1907 +#define GL_RGBA 0x1908 + +static const char wgl_extensions[] = + "WGL_ARB_context_flush_control WGL_ARB_create_context WGL_ARB_create_context_no_error " + "WGL_ARB_create_context_profile WGL_ARB_create_context_robustness WGL_ARB_extensions_string " + "WGL_ARB_framebuffer_sRGB WGL_ARB_make_current_read WGL_ARB_multisample WGL_ARB_pbuffer WGL_ARB_pixel_format " + "WGL_ARB_pixel_format_float WGL_ARB_render_texture WGL_ATI_pixel_format_float WGL_EXT_extensions_string " + "WGL_EXT_framebuffer_sRGB WGL_EXT_swap_control WGL_WINE_pixel_format_passthrough WGL_WINE_query_renderer"; + +static pthread_mutex_t wgl_lock = PTHREAD_MUTEX_INITIALIZER; +static struct wmgl_pixel_format *formats; +static unsigned int format_count, onscreen_count; +static void *root_context; +static void *metal_device; + +struct context +{ + void *core; + int format; + BOOL used; + struct wmgl_debug_target debug; +}; + +struct window +{ + UINT64 hwnd; + int format; /* the window's pixel format */ + void *host; /* unix_window.m */ + void *surface; /* core window surface, made on first use */ + int width, height; + int interval; +}; + +struct dc_format +{ + UINT64 hdc; + int format; +}; + +struct pbuffer +{ + void *surface; + int format, width, height; + UINT64 hdc; /* the memory DC the PE side made for it, or 0 */ + int texture_format, texture_target, mipmap_level, cube_face; + BOOL used; +}; + +static struct context *contexts; +static unsigned int context_count; +static struct window *windows; +static unsigned int window_count; +static struct dc_format *dc_formats; +static unsigned int dc_format_count; +static struct pbuffer *pbuffers; +static unsigned int pbuffer_count; + +/* This thread's current context and drawables. */ +static __thread UINT64 current_context; +static __thread void *current_draw, *current_read; +static __thread UINT64 current_draw_hwnd; + +static void *grow( void *array, unsigned int *count, size_t size ) +{ + void *bigger = realloc( array, (*count + 1) * size ); + if (!bigger) return NULL; + memset( (char *)bigger + *count * size, 0, size ); + ++*count; + return bigger; +} + +static struct context *context_from_handle( UINT64 handle ) +{ + if (handle < HANDLE_BASE || handle - HANDLE_BASE >= context_count) return NULL; + return contexts[handle - HANDLE_BASE].used ? &contexts[handle - HANDLE_BASE] : NULL; +} + +static struct pbuffer *pbuffer_from_handle( UINT64 handle ) +{ + if (handle < HANDLE_BASE || handle - HANDLE_BASE >= pbuffer_count) return NULL; + return pbuffers[handle - HANDLE_BASE].used ? &pbuffers[handle - HANDLE_BASE] : NULL; +} + +static struct pbuffer *pbuffer_from_dc( UINT64 hdc ) +{ + unsigned int i; + if (!hdc) return NULL; + for (i = 0; i < pbuffer_count; i++) + if (pbuffers[i].used && pbuffers[i].hdc == hdc) return &pbuffers[i]; + return NULL; +} + +static struct window *find_window( UINT64 hwnd, BOOL create ) +{ + unsigned int i; + struct window *bigger; + + if (!hwnd) return NULL; + for (i = 0; i < window_count; i++) + if (windows[i].hwnd == hwnd) return &windows[i]; + if (!create || !(bigger = grow( windows, &window_count, sizeof(*windows) ))) return NULL; + windows = bigger; + windows[window_count - 1].hwnd = hwnd; + windows[window_count - 1].interval = 1; + return &windows[window_count - 1]; +} + +static struct dc_format *find_dc_format( UINT64 hdc, BOOL create ) +{ + unsigned int i; + struct dc_format *bigger; + + for (i = 0; i < dc_format_count; i++) + if (dc_formats[i].hdc == hdc) return &dc_formats[i]; + if (!create || !(bigger = grow( dc_formats, &dc_format_count, sizeof(*dc_formats) ))) return NULL; + dc_formats = bigger; + dc_formats[dc_format_count - 1].hdc = hdc; + return &dc_formats[dc_format_count - 1]; +} + +/* The pixel format of a DC: its window's, its pbuffer's or its own. */ +static int dc_pixel_format( UINT64 hdc, UINT64 hwnd ) +{ + struct window *window; + struct pbuffer *pbuffer; + struct dc_format *dc; + + if (hwnd) return (window = find_window( hwnd, FALSE )) ? window->format : 0; + if ((pbuffer = pbuffer_from_dc( hdc ))) return pbuffer->format; + return (dc = find_dc_format( hdc, FALSE )) ? dc->format : 0; +} + +static void to_placement( const struct wmgl_window_placement *from, struct wmgl_placement *to ) +{ + to->hwnd = from->hwnd; + to->top_level = from->top_level; + to->x = from->x; + to->y = from->y; + to->width = from->width; + to->height = from->height; + to->top_level_width = from->top_level_width; + to->top_level_height = from->top_level_height; + to->screen_width = from->screen_width; +} + +/* A window's surface, made on first use and kept on its client area. */ +static void *window_surface( const struct wmgl_window_placement *placement ) +{ + struct window *window = find_window( placement->hwnd, FALSE ); + struct wmgl_placement where; + int width = placement->width > 0 ? placement->width : 1, height = placement->height > 0 ? placement->height : 1; + void *layer = NULL; + + if (!window || !window->format) return NULL; + to_placement( placement, &where ); + if (!window->surface) + { + if (!(window->host = wmgl_window_create( &where, metal_device, &layer )) || !layer) + { + WMGL_ERR( "no Cocoa view for window %#llx\n", (unsigned long long)placement->hwnd ); + return NULL; + } + if (!(window->surface = wmgl.surface_create_window( layer, window->format, width, height ))) + { + wmgl_window_destroy( window->host ); + window->host = NULL; + return NULL; + } + window->width = width; + window->height = height; + return window->surface; + } + wmgl_window_place( window->host, &where ); + return window->surface; +} + +/* Resizes a window surface to its client area (the core keeps what fits). */ +static void window_resize( const struct wmgl_window_placement *placement ) +{ + struct window *window = find_window( placement->hwnd, FALSE ); + int width = placement->width > 0 ? placement->width : 1, height = placement->height > 0 ? placement->height : 1; + + if (!window || !window->surface || (window->width == width && window->height == height)) return; + wmgl.surface_flush( window->surface, WMGL_FLUSH_UPDATED, width, height, window->interval ); + window->width = width; + window->height = height; +} + +/* The surface a DC draws to: its window's or its pbuffer's. */ +static void *dc_surface( UINT64 hdc, const struct wmgl_window_placement *placement ) +{ + struct pbuffer *pbuffer; + if (placement->hwnd) return window_surface( placement ); + return (pbuffer = pbuffer_from_dc( hdc )) ? pbuffer->surface : NULL; +} + +struct wmgl_debug_target *wmgl_current_debug_target( void ) +{ + struct context *context = context_from_handle( current_context ); + return context ? &context->debug : NULL; +} + +void wmgl_set_debug_callback( UINT64 callback, UINT64 user, BOOL amd ) +{ + struct context *context = context_from_handle( current_context ); + if (!context) return; + context->debug.callback = callback; + context->debug.user = user; + context->debug.amd = amd; +} + +/* ---------------------------------------------------------- pixel formats */ + +NTSTATUS wmgl_unix_init_formats( void *args ) +{ + struct wmgl_init_params *params = args; + int core_major = 0, core_minor = 0, compat_major = 0, compat_minor = 0; + + pthread_mutex_lock( &wgl_lock ); + if (!formats && wmgl.get_pixel_formats) + { + format_count = wmgl.get_pixel_formats( NULL, 0, &onscreen_count ); + if (format_count && (formats = calloc( format_count, sizeof(*formats) ))) + wmgl.get_pixel_formats( formats, format_count, &onscreen_count ); + metal_device = wmgl_window_default_device(); + } + params->format_count = formats ? format_count : 0; + params->onscreen_count = formats ? onscreen_count : 0; + pthread_mutex_unlock( &wgl_lock ); + if (wmgl.max_versions) wmgl.max_versions( &core_major, &core_minor, &compat_major, &compat_minor ); + params->core_major = core_major; + params->core_minor = core_minor; + params->ok = formats != NULL; + return STATUS_SUCCESS; +} + +NTSTATUS wmgl_unix_pixel_formats( void *args ) +{ + struct wmgl_pixel_formats_params *params = args; + struct wmgl_pixel_format *out = (struct wmgl_pixel_format *)(ULONG_PTR)params->formats; + unsigned int count = format_count < params->max ? format_count : params->max; + + if (out && formats) memcpy( out, formats, count * sizeof(*out) ); + params->count = count; + return STATUS_SUCCESS; +} + +NTSTATUS wmgl_unix_get_pixel_format( void *args ) +{ + struct wmgl_get_pixel_format_params *params = args; + pthread_mutex_lock( &wgl_lock ); + params->ret = dc_pixel_format( params->hdc, params->hwnd ); + pthread_mutex_unlock( &wgl_lock ); + return STATUS_SUCCESS; +} + +NTSTATUS wmgl_unix_set_pixel_format( void *args ) +{ + struct wmgl_set_pixel_format_params *params = args; + int format = params->format; + struct window *window; + struct dc_format *dc; + + params->ret = FALSE; + if (format <= 0 || (unsigned int)format > format_count) return STATUS_SUCCESS; + pthread_mutex_lock( &wgl_lock ); + if (params->hwnd) + { + /* A window takes a window format, once (WGL), or again through + * wglSetPixelFormatWINE before it has a drawable. */ + if ((unsigned int)format <= onscreen_count && (window = find_window( params->hwnd, TRUE ))) + { + if (!window->format || window->format == format) window->format = format, params->ret = TRUE; + else if (params->internal && !window->surface) window->format = format, params->ret = TRUE; + } + } + else if (!pbuffer_from_dc( params->hdc ) && (dc = find_dc_format( params->hdc, TRUE ))) + { + if (!dc->format || dc->format == format) dc->format = format, params->ret = TRUE; + } + pthread_mutex_unlock( &wgl_lock ); + return STATUS_SUCCESS; +} + +/* --------------------------------------------------------------- contexts */ + +NTSTATUS wmgl_unix_create_context( void *args ) +{ + struct wmgl_create_context_params *params = args; + const int *attribs = (const int *)(ULONG_PTR)params->attribs; + struct context *share = NULL, *slot = NULL, *bigger; + void *core, *share_core; + int format, error = 0; + unsigned int i; + + params->ret = 0; + params->error = 0; + pthread_mutex_lock( &wgl_lock ); + if (!(format = dc_pixel_format( params->hdc, params->hwnd ))) + { + params->error = ERROR_INVALID_PIXEL_FORMAT; + goto done; + } + if (params->share && !(share = context_from_handle( params->share ))) + { + params->error = ERROR_INVALID_OPERATION; + goto done; + } + if (!root_context) root_context = wmgl.context_create( format, NULL, NULL, &error ); + share_core = share ? share->core : root_context; + if (!share_core || !(core = wmgl.context_create( format, share_core, attribs, &error ))) + { + params->error = error == 1 ? ERROR_INVALID_VERSION_ARB : error == 2 ? ERROR_INVALID_PROFILE_ARB + : ERROR_INVALID_PARAMETER; + goto done; + } + for (i = 0; i < context_count && !slot; i++) + if (!contexts[i].used) slot = &contexts[i]; + if (!slot) + { + if (!(bigger = grow( contexts, &context_count, sizeof(*contexts) ))) + { + wmgl.context_destroy( core ); + params->error = ERROR_NOT_ENOUGH_MEMORY; + goto done; + } + contexts = bigger; + slot = &contexts[context_count - 1]; + } + memset( slot, 0, sizeof(*slot) ); + slot->core = core; + slot->format = format; + slot->used = TRUE; + params->ret = HANDLE_BASE + (slot - contexts); +done: + pthread_mutex_unlock( &wgl_lock ); + return STATUS_SUCCESS; +} + +NTSTATUS wmgl_unix_delete_context( void *args ) +{ + struct wmgl_delete_context_params *params = args; + struct context *context; + + pthread_mutex_lock( &wgl_lock ); + if ((context = context_from_handle( params->context ))) + { + if (current_context == params->context) + { + wmgl.make_current( NULL, NULL, NULL ); + current_context = 0; + current_draw = current_read = NULL; + } + wmgl.context_destroy( context->core ); + memset( context, 0, sizeof(*context) ); + params->ret = TRUE; + } + pthread_mutex_unlock( &wgl_lock ); + return STATUS_SUCCESS; +} + +NTSTATUS wmgl_unix_make_current( void *args ) +{ + struct wmgl_make_current_params *params = args; + struct context *context; + void *draw, *read; + + params->ret = FALSE; + params->error = 0; + pthread_mutex_lock( &wgl_lock ); + if (!params->context) + { + wmgl.make_current( NULL, NULL, NULL ); + params->ret = TRUE; + current_context = 0; + current_draw = current_read = NULL; + current_draw_hwnd = 0; + goto done; + } + if (!(context = context_from_handle( params->context ))) + { + params->error = ERROR_INVALID_HANDLE; + goto done; + } + if (!(draw = dc_surface( params->draw_hdc, ¶ms->draw )) || + !(read = dc_surface( params->read_hdc, ¶ms->read ))) + { + params->error = ERROR_INVALID_HANDLE; + goto done; + } + if (!wmgl.make_current( context->core, draw, read)) + { + params->error = ERROR_INVALID_OPERATION; + goto done; + } + current_context = params->context; + current_draw = draw; + current_read = read; + current_draw_hwnd = params->draw.hwnd; + if (params->draw.hwnd) window_resize( ¶ms->draw ); + if (params->read.hwnd && params->read.hwnd != params->draw.hwnd) window_resize( ¶ms->read ); + params->ret = TRUE; +done: + pthread_mutex_unlock( &wgl_lock ); + return STATUS_SUCCESS; +} + +NTSTATUS wmgl_unix_share_lists( void *args ) +{ + struct wmgl_share_lists_params *params = args; + /* Every context shares with the root context already. */ + pthread_mutex_lock( &wgl_lock ); + params->ret = context_from_handle( params->source ) && context_from_handle( params->dest ); + pthread_mutex_unlock( &wgl_lock ); + return STATUS_SUCCESS; +} + +NTSTATUS wmgl_unix_copy_context( void *args ) +{ + struct wmgl_copy_context_params *params = args; + /* wglCopyContext: attribute groups between contexts are not copied. */ + params->ret = FALSE; + return STATUS_SUCCESS; +} + +/* ------------------------------------------------------------------ swaps */ + +NTSTATUS wmgl_unix_swap_buffers( void *args ) +{ + struct wmgl_swap_buffers_params *params = args; + struct window *window; + void *surface; + + params->ret = FALSE; + pthread_mutex_lock( &wgl_lock ); + if (!params->window.hwnd) + { + /* A pbuffer or memory DC: nothing to present. */ + params->ret = pbuffer_from_dc( params->hdc ) != NULL || find_dc_format( params->hdc, FALSE ) != NULL; + goto done; + } + if (!(window = find_window( params->window.hwnd, FALSE )) || !window->surface) + { + /* SwapBuffers before any context was current on the window. */ + params->ret = window && window->format; + goto done; + } + window_surface( ¶ms->window ); /* follows the window */ + window_resize( ¶ms->window ); + surface = window->surface; /* window surfaces live as long as the process */ + pthread_mutex_unlock( &wgl_lock ); + /* A frame the core could not present (no drawable) is dropped, as a + * compositor would; SwapBuffers itself succeeds. Presenting may wait for + * a drawable: not under the lock. */ + wmgl.surface_swap( surface ); + params->ret = TRUE; + return STATUS_SUCCESS; +done: + pthread_mutex_unlock( &wgl_lock ); + return STATUS_SUCCESS; +} + +NTSTATUS wmgl_unix_swap_interval( void *args ) +{ + struct wmgl_swap_interval_params *params = args; + struct window *window; + + pthread_mutex_lock( &wgl_lock ); + params->ret = FALSE; + if (current_context && (window = find_window( current_draw_hwnd, FALSE )) && window->surface) + { + window->interval = params->interval; + wmgl.surface_flush( window->surface, WMGL_FLUSH_INTERVAL, window->width, window->height, window->interval ); + params->ret = TRUE; + } + else if (current_context) params->ret = TRUE; /* pbuffers present nothing */ + pthread_mutex_unlock( &wgl_lock ); + return STATUS_SUCCESS; +} + +NTSTATUS wmgl_unix_get_swap_interval( void *args ) +{ + struct wmgl_swap_interval_params *params = args; + struct window *window; + + pthread_mutex_lock( &wgl_lock ); + window = find_window( current_draw_hwnd, FALSE ); + params->interval = window ? window->interval : 1; + params->ret = TRUE; + pthread_mutex_unlock( &wgl_lock ); + return STATUS_SUCCESS; +} + +/* --------------------------------------------------------------- pbuffers */ + +NTSTATUS wmgl_unix_create_pbuffer( void *args ) +{ + struct wmgl_create_pbuffer_params *params = args; + const int *attribs = (const int *)(ULONG_PTR)params->attribs; + struct pbuffer *slot = NULL, *bigger; + int width = params->width, height = params->height, texture_format = WGL_NO_TEXTURE_ARB; + int texture_target = WGL_NO_TEXTURE_ARB; + BOOL largest = FALSE; + unsigned int i; + void *surface; + + params->ret = 0; + params->error = 0; + if (params->format <= 0 || (unsigned int)params->format > format_count || + !formats[params->format - 1].draw_to_pbuffer) + { + params->error = ERROR_INVALID_PIXEL_FORMAT; + return STATUS_SUCCESS; + } + for (i = 0; attribs && attribs[i]; i += 2) + { + switch (attribs[i]) + { + case WGL_PBUFFER_LARGEST_ARB: largest = attribs[i + 1] != 0; break; + case WGL_TEXTURE_FORMAT_ARB: texture_format = attribs[i + 1]; break; + case WGL_TEXTURE_TARGET_ARB: texture_target = attribs[i + 1]; break; + case WGL_MIPMAP_TEXTURE_ARB: break; + default: + params->error = ERROR_INVALID_DATA; + return STATUS_SUCCESS; + } + } + if (width <= 0 || height <= 0) + { + params->error = ERROR_INVALID_DATA; + return STATUS_SUCCESS; + } + if (largest) + { + const struct wmgl_pixel_format *f = &formats[params->format - 1]; + if (f->max_pbuffer_width && width > f->max_pbuffer_width) width = f->max_pbuffer_width; + if (f->max_pbuffer_height && height > f->max_pbuffer_height) height = f->max_pbuffer_height; + } + if (!(surface = wmgl.surface_create_pbuffer( params->format, width, height ))) + { + params->error = ERROR_NO_SYSTEM_RESOURCES; + return STATUS_SUCCESS; + } + pthread_mutex_lock( &wgl_lock ); + for (i = 0; i < pbuffer_count && !slot; i++) + if (!pbuffers[i].used) slot = &pbuffers[i]; + if (!slot && (bigger = grow( pbuffers, &pbuffer_count, sizeof(*pbuffers) ))) + { + pbuffers = bigger; + slot = &pbuffers[pbuffer_count - 1]; + } + if (slot) + { + memset( slot, 0, sizeof(*slot) ); + slot->surface = surface; + slot->format = params->format; + slot->width = width; + slot->height = height; + slot->texture_format = texture_format; + slot->texture_target = texture_target; + slot->cube_face = WGL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB; + slot->used = TRUE; + params->ret = HANDLE_BASE + (slot - pbuffers); + } + else + { + wmgl.surface_destroy( surface ); + params->error = ERROR_NOT_ENOUGH_MEMORY; + } + pthread_mutex_unlock( &wgl_lock ); + return STATUS_SUCCESS; +} + +NTSTATUS wmgl_unix_destroy_pbuffer( void *args ) +{ + struct wmgl_pbuffer_params *params = args; + struct pbuffer *pbuffer; + + pthread_mutex_lock( &wgl_lock ); + if ((params->ret = (pbuffer = pbuffer_from_handle( params->pbuffer )) != NULL)) + { + wmgl.surface_destroy( pbuffer->surface ); + memset( pbuffer, 0, sizeof(*pbuffer) ); + } + pthread_mutex_unlock( &wgl_lock ); + return STATUS_SUCCESS; +} + +NTSTATUS wmgl_unix_get_pbuffer_dc( void *args ) +{ + struct wmgl_pbuffer_params *params = args; + struct pbuffer *pbuffer; + + pthread_mutex_lock( &wgl_lock ); + if ((params->ret = (pbuffer = pbuffer_from_handle( params->pbuffer )) != NULL)) pbuffer->hdc = params->hdc; + pthread_mutex_unlock( &wgl_lock ); + return STATUS_SUCCESS; +} + +NTSTATUS wmgl_unix_release_pbuffer_dc( void *args ) +{ + struct wmgl_pbuffer_params *params = args; + struct pbuffer *pbuffer; + + pthread_mutex_lock( &wgl_lock ); + params->ret = (pbuffer = pbuffer_from_handle( params->pbuffer )) && pbuffer->hdc == params->hdc; + if (params->ret) pbuffer->hdc = 0; + pthread_mutex_unlock( &wgl_lock ); + return STATUS_SUCCESS; +} + +NTSTATUS wmgl_unix_query_pbuffer( void *args ) +{ + struct wmgl_pbuffer_params *params = args; + struct pbuffer *pbuffer; + + pthread_mutex_lock( &wgl_lock ); + params->ret = FALSE; + if ((pbuffer = pbuffer_from_handle( params->pbuffer ))) + { + params->ret = TRUE; + switch (params->attribute) + { + case WGL_PBUFFER_WIDTH_ARB: params->value = pbuffer->width; break; + case WGL_PBUFFER_HEIGHT_ARB: params->value = pbuffer->height; break; + case WGL_PBUFFER_LOST_ARB: params->value = FALSE; break; + case WGL_TEXTURE_FORMAT_ARB: params->value = pbuffer->texture_format; break; + case WGL_TEXTURE_TARGET_ARB: params->value = pbuffer->texture_target; break; + case WGL_MIPMAP_TEXTURE_ARB: params->value = FALSE; break; + case WGL_MIPMAP_LEVEL_ARB: params->value = pbuffer->mipmap_level; break; + case WGL_CUBE_MAP_FACE_ARB: params->value = pbuffer->cube_face; break; + default: params->ret = FALSE; break; + } + } + pthread_mutex_unlock( &wgl_lock ); + return STATUS_SUCCESS; +} + +/* WGL_ARB_render_texture: the pbuffer's colour is copied into the texture + * bound to the pbuffer's texture target, reading from the pbuffer. */ +NTSTATUS wmgl_unix_bind_tex_image( void *args ) +{ + struct wmgl_pbuffer_params *params = args; + struct context *context; + struct pbuffer *pbuffer; + GLenum target, internal; + + pthread_mutex_lock( &wgl_lock ); + params->ret = FALSE; + if (!(pbuffer = pbuffer_from_handle( params->pbuffer )) || pbuffer->texture_format == WGL_NO_TEXTURE_ARB || + !(context = context_from_handle( current_context ))) + goto done; + switch (pbuffer->texture_target) + { + case WGL_TEXTURE_1D_ARB: target = GL_TEXTURE_1D; break; + case WGL_TEXTURE_2D_ARB: target = GL_TEXTURE_2D; break; + case WGL_TEXTURE_RECTANGLE_NV: target = GL_TEXTURE_RECTANGLE; break; + case WGL_TEXTURE_CUBE_MAP_ARB: + target = GL_TEXTURE_CUBE_MAP_POSITIVE_X + (pbuffer->cube_face - WGL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB); + break; + default: goto done; + } + internal = pbuffer->texture_format == WGL_TEXTURE_RGB_ARB ? GL_RGB : GL_RGBA; + if (!wmgl.make_current( context->core, current_draw, pbuffer->surface )) goto done; + if (target == GL_TEXTURE_1D) + core.p_glCopyTexImage1D( target, pbuffer->mipmap_level, internal, 0, 0, pbuffer->width, 0 ); + else + core.p_glCopyTexImage2D( target, pbuffer->mipmap_level, internal, 0, 0, pbuffer->width, pbuffer->height, 0 ); + wmgl.make_current( context->core, current_draw, current_read ); + params->ret = TRUE; +done: + pthread_mutex_unlock( &wgl_lock ); + return STATUS_SUCCESS; +} + +NTSTATUS wmgl_unix_release_tex_image( void *args ) +{ + struct wmgl_pbuffer_params *params = args; + pthread_mutex_lock( &wgl_lock ); + params->ret = pbuffer_from_handle( params->pbuffer ) != NULL; + pthread_mutex_unlock( &wgl_lock ); + return STATUS_SUCCESS; +} + +NTSTATUS wmgl_unix_set_pbuffer_attrib( void *args ) +{ + struct wmgl_pbuffer_params *params = args; + const int *attribs = (const int *)(ULONG_PTR)params->attribs; + struct pbuffer *pbuffer; + unsigned int i; + + pthread_mutex_lock( &wgl_lock ); + if ((params->ret = (pbuffer = pbuffer_from_handle( params->pbuffer )) != NULL)) + { + for (i = 0; attribs && attribs[i]; i += 2) + { + if (attribs[i] == WGL_MIPMAP_LEVEL_ARB) pbuffer->mipmap_level = attribs[i + 1]; + else if (attribs[i] == WGL_CUBE_MAP_FACE_ARB) pbuffer->cube_face = attribs[i + 1]; + else params->ret = FALSE; + } + } + pthread_mutex_unlock( &wgl_lock ); + return STATUS_SUCCESS; +} + +/* ------------------------------------------------------- renderer queries */ + +/* WGL_WINE_query_renderer: one renderer, the Metal device; wined3d reads + * its vendor, device and video memory. */ +NTSTATUS wmgl_unix_query_renderer_integer( void *args ) +{ + struct wmgl_query_renderer_params *params = args; + GLuint *value = (GLuint *)(ULONG_PTR)params->value; + int core_major = 0, core_minor = 0, compat_major = 0, compat_minor = 0, major = 0, minor = 0, patch = 0; + + params->ret = FALSE; + if (params->renderer || !value) return STATUS_SUCCESS; + params->ret = TRUE; + switch (params->attribute) + { + case WGL_RENDERER_ACCELERATED_WINE: *value = 1; break; + case WGL_RENDERER_UNIFIED_MEMORY_ARCHITECTURE_WINE: *value = 1; break; /* Apple silicon */ + case WGL_RENDERER_VENDOR_ID_WINE: *value = 0x106b; break; /* Apple */ + case WGL_RENDERER_DEVICE_ID_WINE: *value = 0xffffffff; break; + case WGL_RENDERER_OPENGL_CORE_PROFILE_VERSION_WINE: + case WGL_RENDERER_OPENGL_COMPATIBILITY_PROFILE_VERSION_WINE: + wmgl.max_versions( &core_major, &core_minor, &compat_major, &compat_minor ); + if (params->attribute == WGL_RENDERER_OPENGL_CORE_PROFILE_VERSION_WINE) + value[0] = core_major, value[1] = core_minor; + else + value[0] = compat_major, value[1] = compat_minor; + break; + case WGL_RENDERER_OPENGL_ES_PROFILE_VERSION_WINE: + case WGL_RENDERER_OPENGL_ES2_PROFILE_VERSION_WINE: + value[0] = value[1] = 0; + break; + case WGL_RENDERER_PREFERRED_PROFILE_WINE: *value = WGL_CONTEXT_CORE_PROFILE_BIT_ARB; break; + case WGL_RENDERER_VERSION_WINE: + if (wmgl.version) wmgl.version( &major, &minor, &patch ); + value[0] = major; + value[1] = minor; + value[2] = patch; + break; + case WGL_RENDERER_VIDEO_MEMORY_WINE: *value = wmgl.video_memory_mb(); break; + default: params->ret = FALSE; break; + } + return STATUS_SUCCESS; +} + +NTSTATUS wmgl_unix_query_renderer_string( void *args ) +{ + struct wmgl_query_renderer_params *params = args; + char *buffer = (char *)(ULONG_PTR)params->buffer; + const char *string = NULL, *vendor; + + params->ret = FALSE; + if (params->renderer || !buffer || !params->size) return STATUS_SUCCESS; + switch (params->attribute) + { + case WGL_RENDERER_DEVICE_ID_WINE: string = wmgl.renderer_string(); break; + case WGL_RENDERER_VENDOR_ID_WINE: + /* The core's GL_VENDOR (WINEMETALGL_VENDOR overrides it). */ + vendor = getenv( "WINEMETALGL_VENDOR" ); + string = vendor && *vendor ? vendor : "WineMetalGL"; + break; + } + if (!string) return STATUS_SUCCESS; + snprintf( buffer, params->size, "%s", string ); + params->ret = TRUE; + return STATUS_SUCCESS; +} + +NTSTATUS wmgl_unix_extensions_string( void *args ) +{ + struct wmgl_extensions_string_params *params = args; + char *buffer = (char *)(ULONG_PTR)params->buffer; + + params->needed = sizeof(wgl_extensions); + if (buffer && params->size >= sizeof(wgl_extensions)) memcpy( buffer, wgl_extensions, sizeof(wgl_extensions) ); + return STATUS_SUCCESS; +} diff --git a/src/wine/unix/unix_window.m b/src/wine/unix/unix_window.m new file mode 100644 index 00000000..178d53df --- /dev/null +++ b/src/wine/unix/unix_window.m @@ -0,0 +1,200 @@ +/* Window layers of winemetalgl.so (wmgl_window.h): where a window DC's + * frames are shown, without changes to winemac. + * + * Two ways in: + * - a winemac that exports macdrv_functions (CrossOver, MetalSharp's Wine), + * for a window that already has a client view (Wine 11 makes one with a + * client surface, for Vulkan or Wine's GL): a metal view in it, which + * follows the window as winemac moves and resizes it; + * - otherwise (WineHQ's winemac exports nothing; a window without a client + * view) a view of ours, with the layer, goes into the content view of the + * top-level Cocoa window whose hwnd matches, placed from the window's + * client area (wmgl_placement), on the main thread. */ + +#import +#import +#import +#include +#include +#include + +#include "wmgl_window.h" + +typedef struct macdrv_opaque_metal_device *macdrv_metal_device; +typedef struct macdrv_opaque_metal_view *macdrv_metal_view; +typedef struct macdrv_opaque_metal_layer *macdrv_metal_layer; +typedef struct macdrv_opaque_view *macdrv_view; +typedef struct macdrv_opaque_window *macdrv_window; +typedef struct opaque_HWND *HWND; + +/* The start of winemac's struct macdrv_win_data (Wine 11.x), up to the + * client area's view. */ +struct macdrv_win_data +{ + HWND hwnd; + macdrv_window cocoa_window; + macdrv_view client_view; +}; + +/* winemac's exported function table (CrossOver, MetalSharp's Wine). */ +struct macdrv_functions_t +{ + void (*macdrv_init_display_devices)( int ); + struct macdrv_win_data *(*get_win_data)( HWND hwnd ); + void (*release_win_data)( struct macdrv_win_data *data ); + macdrv_window (*macdrv_get_cocoa_window)( HWND hwnd, int require_on_screen ); + macdrv_metal_device (*macdrv_create_metal_device)( void ); + void (*macdrv_release_metal_device)( macdrv_metal_device d ); + macdrv_metal_view (*macdrv_view_create_metal_view)( macdrv_view v, macdrv_metal_device d ); + macdrv_metal_layer (*macdrv_view_get_metal_layer)( macdrv_metal_view v ); + void (*macdrv_view_release_metal_view)( macdrv_metal_view v ); + void (*on_main_thread)( dispatch_block_t b ); +}; + +/* Our view: mouse events go to Wine's views under it. */ +@interface WineMetalGLView : NSView +@end + +@implementation WineMetalGLView +- (NSView *)hitTest:(NSPoint)point +{ + return nil; +} +@end + +@interface WineMetalGLHost : NSObject +{ +@public + CAMetalLayer *layer; + NSView *view; /* ours (no winemac metal view) */ + macdrv_metal_view winemac_view; /* winemac's */ + struct wmgl_placement placement; +} +@end + +@implementation WineMetalGLHost +@end + +static struct macdrv_functions_t *winemac_functions(void) +{ + static struct macdrv_functions_t *functions; + static dispatch_once_t once; + dispatch_once( &once, ^{ functions = dlsym( RTLD_DEFAULT, "macdrv_functions" ); } ); + return functions; +} + +static void trace( const char *what, const struct wmgl_placement *placement ) +{ + static int enabled = -1; + if (enabled < 0) enabled = getenv( "WINEMETALGL_WINE_DEBUG" ) != NULL; + if (enabled) fprintf( stderr, "winemetalgl: window %#llx: %s\n", (unsigned long long)placement->hwnd, what ); +} + +void *wmgl_window_default_device( void ) +{ + return (__bridge void *)MTLCreateSystemDefaultDevice(); +} + +/* Puts a host's view over its window's client area (main thread). The + * content view is the top-level window less what Cocoa draws around it; + * what Wine draws there itself is a border as wide below as beside the + * client area, and the rest (caption, menu) above. */ +static void place_on_main_thread( WineMetalGLHost *host, struct wmgl_placement placement ) +{ + for (NSWindow *window in [NSApp windows]) + { + NSView *content; + CGFloat scale, border, above; + NSRect area; + + /* A 32-bit program's handle is the low half of Wine's. */ + if (![window respondsToSelector:@selector(hwnd)] || + (uint32_t)(uintptr_t)((void *(*)(id, SEL))objc_msgSend)( window, @selector(hwnd) ) != (uint32_t)placement.top_level) + continue; + content = window.contentView; + scale = placement.screen_width ? placement.screen_width / NSScreen.screens.firstObject.frame.size.width : 1.0; + if (scale <= 0) scale = 1.0; + border = (content.bounds.size.width - placement.top_level_width / scale) / 2; + above = content.bounds.size.height - placement.top_level_height / scale - border; + area = NSMakeRect( border + placement.x / scale, above + placement.y / scale, placement.width / scale, + placement.height / scale ); + if (!content.isFlipped) area.origin.y = content.bounds.size.height - NSMaxY( area ); + if (!host->view) + { + host->view = [[WineMetalGLView alloc] initWithFrame:area]; + host->view.layer = host->layer; + host->view.wantsLayer = YES; + } + /* A window given another parent takes its view along. */ + if (host->view.superview != content) [content addSubview:host->view positioned:NSWindowAbove relativeTo:nil]; + host->view.frame = area; + host->layer.contentsScale = window.backingScaleFactor; + return; + } +} + +void *wmgl_window_create( const struct wmgl_placement *placement, void *device, void **layer ) +{ + struct macdrv_functions_t *functions = winemac_functions(); + WineMetalGLHost *host = [[WineMetalGLHost alloc] init]; + + *layer = NULL; + host->placement = *placement; + if (functions && functions->get_win_data && functions->macdrv_view_create_metal_view) + { + struct macdrv_win_data *data = functions->get_win_data( (HWND)(uintptr_t)placement->hwnd ); + if (data) + { + if (data->client_view) + host->winemac_view = functions->macdrv_view_create_metal_view( data->client_view, + (macdrv_metal_device)device ); + functions->release_win_data( data ); + } + if (host->winemac_view) + { + host->layer = (__bridge CAMetalLayer *)(void *)functions->macdrv_view_get_metal_layer( host->winemac_view ); + *layer = (__bridge void *)host->layer; + trace( "winemac metal view", placement ); + return (__bridge_retained void *)host; + } + } + if (!objc_getClass( "WineWindow" )) return NULL; + trace( "own view in the Cocoa window", placement ); + /* A layer made outside a committed transaction never shows, and this + * thread has no run loop to commit one. The main thread places the view + * later: this thread must not wait for it, which may be waiting on us. */ + [CATransaction begin]; + host->layer = [[CAMetalLayer alloc] init]; + host->layer.device = (__bridge id)device; + [CATransaction commit]; + [CATransaction flush]; + *layer = (__bridge void *)host->layer; + struct wmgl_placement where = *placement; + dispatch_async( dispatch_get_main_queue(), ^{ place_on_main_thread( host, where ); } ); + return (__bridge_retained void *)host; +} + +void wmgl_window_place( void *handle, const struct wmgl_placement *placement ) +{ + WineMetalGLHost *host = (__bridge WineMetalGLHost *)handle; + if (!host || host->winemac_view) return; /* winemac's view follows its window */ + if (!memcmp( &host->placement, placement, sizeof(*placement) )) return; + host->placement = *placement; + struct wmgl_placement where = *placement; + dispatch_async( dispatch_get_main_queue(), ^{ place_on_main_thread( host, where ); } ); +} + +void wmgl_window_destroy( void *handle ) +{ + WineMetalGLHost *host = (__bridge_transfer WineMetalGLHost *)handle; + struct macdrv_functions_t *functions = winemac_functions(); + + if (!host) return; + if (host->winemac_view) + { + if (functions && functions->macdrv_view_release_metal_view) + functions->macdrv_view_release_metal_view( host->winemac_view ); + return; + } + dispatch_async( dispatch_get_main_queue(), ^{ [host->view removeFromSuperview]; } ); +} diff --git a/src/wine/unix/unix_wow64.c b/src/wine/unix/unix_wow64.c new file mode 100644 index 00000000..288c49c2 --- /dev/null +++ b/src/wine/unix/unix_wow64.c @@ -0,0 +1,399 @@ +/* winemetalgl.so for 32-bit callers (wow64): what the generated wow64 calls + * cannot do by widening and narrowing (tools/gen/wine_gen.py MANUAL_WOW64). + * + * - Buffer storage lies below 4 GB (the core allocates it through + * wmgl_set_buffer_memory here), so mapped pointers reach the application + * as they are, coherent and persistent maps included. + * - Strings the core returns are copied below 4 GB once. + * - Sync objects become 32-bit handles. + * - Arrays of 32-bit pointers are widened; pointers written through + * pointers are narrowed. */ + +#include "wmgl_unix.h" + +#ifdef _WIN64 + +static ULONG_PTR zero_bits; +static pthread_mutex_t wow64_lock = PTHREAD_MUTEX_INITIALIZER; + +static void *low_alloc( size_t size ) +{ + void *memory = NULL; + SIZE_T length = size; + if (NtAllocateVirtualMemory( GetCurrentProcess(), &memory, zero_bits, &length, MEM_COMMIT | MEM_RESERVE, + PAGE_READWRITE )) + return NULL; + return memory; +} + +static void low_free( void *memory, size_t size ) +{ + SIZE_T length = 0; + NtFreeVirtualMemory( GetCurrentProcess(), &memory, &length, MEM_RELEASE ); +} + +extern NTSTATUS wmgl_unix_init( void *args ); + +/* The first call of a 32-bit PE side. */ +NTSTATUS wmgl_unix_init_wow64( void *args ) +{ + void (*set_buffer_memory)( void *(*)(size_t), void (*)(void *, size_t) ); + NTSTATUS status = wmgl_unix_init( args ); + SYSTEM_BASIC_INFORMATION info; + + if (status || !((struct wmgl_init_params *)args)->ok) return status; + NtQuerySystemInformation( SystemEmulationBasicInformation, &info, sizeof(info), NULL ); + zero_bits = (ULONG_PTR)info.HighestUserAddress | 0x7fffffff; + if (!(*(void **)&set_buffer_memory = wmgl_core_symbol( "wmgl_set_buffer_memory" ))) + { + WMGL_ERR( "this WineMetalGL core cannot place buffers for 32-bit applications (it is older than 3.0.0)\n" ); + ((struct wmgl_init_params *)args)->ok = FALSE; + return STATUS_SUCCESS; + } + set_buffer_memory( low_alloc, low_free ); + return STATUS_SUCCESS; +} + +UINT wmgl_wow64_pointer( const void *pointer ) +{ + if ((ULONG_PTR)pointer > 0xffffffff) + { + WMGL_ERR( "pointer %p is beyond a 32-bit application's reach\n", pointer ); + return 0; + } + return PtrToUlong( pointer ); +} + +/* ---------------------------------------------------------------- strings */ + +struct string_copy +{ + const char *source; + char *copy; +}; +static struct string_copy *strings; +static unsigned int string_count, string_capacity; + +/* The core's strings are constant for its lifetime: one copy each. */ +static UINT wow64_string( const GLubyte *source ) +{ + struct string_copy *bigger; + unsigned int i; + size_t len; + char *copy; + + if (!source) return 0; + pthread_mutex_lock( &wow64_lock ); + for (i = 0; i < string_count; i++) + { + if (strings[i].source == (const char *)source && !strcmp( strings[i].copy, (const char *)source )) + { + pthread_mutex_unlock( &wow64_lock ); + return PtrToUlong( strings[i].copy ); + } + } + len = strlen( (const char *)source ) + 1; + if (!(copy = low_alloc( len ))) + { + pthread_mutex_unlock( &wow64_lock ); + return 0; + } + memcpy( copy, source, len ); + if (string_count == string_capacity) + { + string_capacity = string_capacity ? string_capacity * 2 : 64; + if (!(bigger = realloc( strings, string_capacity * sizeof(*strings) ))) + { + pthread_mutex_unlock( &wow64_lock ); + return PtrToUlong( copy ); + } + strings = bigger; + } + strings[string_count].source = (const char *)source; + strings[string_count].copy = copy; + string_count++; + pthread_mutex_unlock( &wow64_lock ); + return PtrToUlong( copy ); +} + +NTSTATUS wow64_manual_glGetString( void *args ) +{ + struct glGetString_params32 *params = args; + params->ret = wow64_string( core.p_glGetString( params->name ) ); + return wmgl_after_call(); +} + +NTSTATUS wow64_manual_glGetStringi( void *args ) +{ + struct glGetStringi_params32 *params = args; + params->ret = wow64_string( core.p_glGetStringi( params->name, params->index ) ); + return wmgl_after_call(); +} + +/* ------------------------------------------------------------ sync objects */ + +static GLsync *syncs; +static unsigned int sync_count; + +static UINT sync_handle( GLsync sync ) +{ + unsigned int i; + GLsync *bigger; + + if (!sync) return 0; + pthread_mutex_lock( &wow64_lock ); + for (i = 0; i < sync_count; i++) + if (!syncs[i]) break; + if (i == sync_count) + { + if (!(bigger = realloc( syncs, (sync_count + 64) * sizeof(*syncs) ))) + { + pthread_mutex_unlock( &wow64_lock ); + return 0; + } + memset( bigger + sync_count, 0, 64 * sizeof(*bigger) ); + syncs = bigger; + sync_count += 64; + } + syncs[i] = sync; + pthread_mutex_unlock( &wow64_lock ); + return i + 1; +} + +/* The sync object of a handle; an unknown handle stays an invalid sync. */ +static GLsync sync_from_handle( UINT handle, BOOL release ) +{ + GLsync sync = (GLsync)(ULONG_PTR)0xdeadbeef00000000ull; /* rejected by the core's lookup */ + + if (!handle) return NULL; + pthread_mutex_lock( &wow64_lock ); + if (handle <= sync_count && syncs[handle - 1]) + { + sync = syncs[handle - 1]; + if (release) syncs[handle - 1] = NULL; + } + pthread_mutex_unlock( &wow64_lock ); + return sync; +} + +NTSTATUS wow64_manual_glFenceSync( void *args ) +{ + struct glFenceSync_params32 *params = args; + params->ret = sync_handle( core.p_glFenceSync( params->condition, params->flags ) ); + return wmgl_after_call(); +} + +NTSTATUS wow64_manual_glDeleteSync( void *args ) +{ + struct glDeleteSync_params32 *params = args; + GLsync sync = sync_from_handle( params->sync, FALSE ); + core.p_glDeleteSync( sync ); + /* The handle goes once the core accepted the sync (a bad one stays invalid). */ + if (sync && core.p_glIsSync( sync ) == 0) sync_from_handle( params->sync, TRUE ); + return wmgl_after_call(); +} + +NTSTATUS wow64_manual_glIsSync( void *args ) +{ + struct glIsSync_params32 *params = args; + params->ret = core.p_glIsSync( sync_from_handle( params->sync, FALSE ) ); + return wmgl_after_call(); +} + +NTSTATUS wow64_manual_glClientWaitSync( void *args ) +{ + struct glClientWaitSync_params32 *params = args; + params->ret = core.p_glClientWaitSync( sync_from_handle( params->sync, FALSE ), params->flags, params->timeout ); + return wmgl_after_call(); +} + +NTSTATUS wow64_manual_glWaitSync( void *args ) +{ + struct glWaitSync_params32 *params = args; + core.p_glWaitSync( sync_from_handle( params->sync, FALSE ), params->flags, params->timeout ); + return wmgl_after_call(); +} + +NTSTATUS wow64_manual_glGetSynciv( void *args ) +{ + struct glGetSynciv_params32 *params = args; + core.p_glGetSynciv( sync_from_handle( params->sync, FALSE ), params->pname, params->count, + (GLsizei *)ULongToPtr( params->length ), (GLint *)ULongToPtr( params->values ) ); + return wmgl_after_call(); +} + +/* Object pointer labels name sync objects. */ +NTSTATUS wow64_manual_glObjectPtrLabel( void *args ) +{ + struct glObjectPtrLabel_params32 *params = args; + core.p_glObjectPtrLabel( (const void *)sync_from_handle( params->ptr, FALSE ), params->length, + (const GLchar *)ULongToPtr( params->label ) ); + return wmgl_after_call(); +} + +NTSTATUS wow64_manual_glGetObjectPtrLabel( void *args ) +{ + struct glGetObjectPtrLabel_params32 *params = args; + core.p_glGetObjectPtrLabel( (const void *)sync_from_handle( params->ptr, FALSE ), params->bufSize, + (GLsizei *)ULongToPtr( params->length ), (GLchar *)ULongToPtr( params->label ) ); + return wmgl_after_call(); +} + +/* ------------------------------------------------------- pointer arrays */ + +/* `count` 32-bit pointers at `address`, widened (NULL for a NULL array). */ +static void **widen_pointers( UINT address, GLsizei count ) +{ + const UINT *source = ULongToPtr( address ); + void **out; + GLsizei i; + + if (!source || count <= 0) return NULL; + if (!(out = malloc( count * sizeof(*out) ))) return NULL; + for (i = 0; i < count; i++) out[i] = ULongToPtr( source[i] ); + return out; +} + +NTSTATUS wow64_manual_glShaderSource( void *args ) +{ + struct glShaderSource_params32 *params = args; + void **strings = widen_pointers( params->string, params->count ); + core.p_glShaderSource( params->shader, params->count, (const GLchar *const *)strings, + (const GLint *)ULongToPtr( params->length ) ); + free( strings ); + return wmgl_after_call(); +} + +NTSTATUS wow64_manual_glShaderSourceARB( void *args ) +{ + struct glShaderSourceARB_params32 *params = args; + void **strings = widen_pointers( params->string, params->count ); + core.p_glShaderSourceARB( params->shaderObj, params->count, (const GLcharARB **)strings, + (const GLint *)ULongToPtr( params->length ) ); + free( strings ); + return wmgl_after_call(); +} + +NTSTATUS wow64_manual_glTransformFeedbackVaryings( void *args ) +{ + struct glTransformFeedbackVaryings_params32 *params = args; + void **names = widen_pointers( params->varyings, params->count ); + core.p_glTransformFeedbackVaryings( params->program, params->count, (const GLchar *const *)names, + params->bufferMode ); + free( names ); + return wmgl_after_call(); +} + +NTSTATUS wow64_manual_glTransformFeedbackVaryingsEXT( void *args ) +{ + struct glTransformFeedbackVaryingsEXT_params32 *params = args; + void **names = widen_pointers( params->varyings, params->count ); + core.p_glTransformFeedbackVaryingsEXT( params->program, params->count, (const GLchar *const *)names, + params->bufferMode ); + free( names ); + return wmgl_after_call(); +} + +NTSTATUS wow64_manual_glGetUniformIndices( void *args ) +{ + struct glGetUniformIndices_params32 *params = args; + void **names = widen_pointers( params->uniformNames, params->uniformCount ); + core.p_glGetUniformIndices( params->program, params->uniformCount, (const GLchar *const *)names, + (GLuint *)ULongToPtr( params->uniformIndices ) ); + free( names ); + return wmgl_after_call(); +} + +NTSTATUS wow64_manual_glCreateShaderProgramv( void *args ) +{ + struct glCreateShaderProgramv_params32 *params = args; + void **strings = widen_pointers( params->strings, params->count ); + params->ret = core.p_glCreateShaderProgramv( params->type, params->count, (const GLchar *const *)strings ); + free( strings ); + return wmgl_after_call(); +} + +NTSTATUS wow64_manual_glMultiDrawElements( void *args ) +{ + struct glMultiDrawElements_params32 *params = args; + void **indices = widen_pointers( params->indices, params->drawcount ); + core.p_glMultiDrawElements( params->mode, (const GLsizei *)ULongToPtr( params->count ), params->type, + (const void *const *)indices, params->drawcount ); + free( indices ); + return wmgl_after_call(); +} + +NTSTATUS wow64_manual_glMultiDrawElementsEXT( void *args ) +{ + struct glMultiDrawElementsEXT_params32 *params = args; + void **indices = widen_pointers( params->indices, params->primcount ); + core.p_glMultiDrawElementsEXT( params->mode, (const GLsizei *)ULongToPtr( params->count ), params->type, + (const void *const *)indices, params->primcount ); + free( indices ); + return wmgl_after_call(); +} + +NTSTATUS wow64_manual_glMultiDrawElementsBaseVertex( void *args ) +{ + struct glMultiDrawElementsBaseVertex_params32 *params = args; + void **indices = widen_pointers( params->indices, params->drawcount ); + core.p_glMultiDrawElementsBaseVertex( params->mode, (const GLsizei *)ULongToPtr( params->count ), params->type, + (const void *const *)indices, params->drawcount, + (const GLint *)ULongToPtr( params->basevertex ) ); + free( indices ); + return wmgl_after_call(); +} + +/* ---------------------------------------------- pointers through pointers */ + +static void narrow_pointer( UINT out, void *pointer ) +{ + if (out) *(UINT *)ULongToPtr( out ) = wmgl_wow64_pointer( pointer ); +} + +NTSTATUS wow64_manual_glGetVertexAttribPointerv( void *args ) +{ + struct glGetVertexAttribPointerv_params32 *params = args; + void *pointer = NULL; + core.p_glGetVertexAttribPointerv( params->index, params->pname, &pointer ); + narrow_pointer( params->pointer, pointer ); + return wmgl_after_call(); +} + +NTSTATUS wow64_manual_glGetVertexAttribPointervARB( void *args ) +{ + struct glGetVertexAttribPointervARB_params32 *params = args; + void *pointer = NULL; + core.p_glGetVertexAttribPointervARB( params->index, params->pname, &pointer ); + narrow_pointer( params->pointer, pointer ); + return wmgl_after_call(); +} + +NTSTATUS wow64_manual_glGetBufferPointerv( void *args ) +{ + struct glGetBufferPointerv_params32 *params = args; + void *pointer = NULL; + core.p_glGetBufferPointerv( params->target, params->pname, &pointer ); + narrow_pointer( params->params, pointer ); + return wmgl_after_call(); +} + +NTSTATUS wow64_manual_glGetBufferPointervARB( void *args ) +{ + struct glGetBufferPointervARB_params32 *params = args; + void *pointer = NULL; + core.p_glGetBufferPointervARB( params->target, params->pname, &pointer ); + narrow_pointer( params->params, pointer ); + return wmgl_after_call(); +} + +NTSTATUS wow64_manual_glGetNamedBufferPointerv( void *args ) +{ + struct glGetNamedBufferPointerv_params32 *params = args; + void *pointer = NULL; + core.p_glGetNamedBufferPointerv( params->buffer, params->pname, &pointer ); + narrow_pointer( params->params, pointer ); + return wmgl_after_call(); +} + +#endif /* _WIN64 */ diff --git a/src/wine/unix/wmgl_unix.h b/src/wine/unix/wmgl_unix.h new file mode 100644 index 00000000..324e053c --- /dev/null +++ b/src/wine/unix/wmgl_unix.h @@ -0,0 +1,73 @@ +/* winemetalgl.so: the unix side of the WineMetalGL Wine module (wmgl_wine.h). */ +#ifndef WMGL_UNIX_H +#define WMGL_UNIX_H + +#include +#include +#include +#include +#include +#include +#include + +#include "ntstatus.h" +#define WIN32_NO_STATUS +#include "windef.h" +#include "winbase.h" +#include "winternl.h" +#include "wingdi.h" +#include "wine/unixlib.h" + +#include "wmgl_wine.h" +#include "wine_gl_procs.gen.h" +#include "wmgl_window.h" + +/* The core (include/winemetalgl/driver.h). */ +struct wmgl_core +{ + int (*initialize)( unsigned int abi_version ); + unsigned int (*get_pixel_formats)( struct wmgl_pixel_format *formats, unsigned int max, unsigned int *onscreen ); + void *(*surface_create_window)( void *layer, int format, int width, int height ); + void *(*surface_create_pbuffer)( int format, int width, int height ); + void (*surface_destroy)( void *surface ); + void (*surface_flush)( void *surface, unsigned int flags, int width, int height, int interval ); + int (*surface_swap)( void *surface ); + void *(*context_create)( int format, void *share, const int *attribs, int *error ); + void (*context_destroy)( void *context ); + int (*make_current)( void *context, void *draw, void *read ); + void *(*get_proc_address)( const char *name ); + const char *(*renderer_string)( void ); + unsigned int (*video_memory_mb)( void ); + void (*max_versions)( int *core_major, int *core_minor, int *compat_major, int *compat_minor ); + void (*version)( int *major, int *minor, int *patch ); /* optional */ +}; +extern struct wmgl_core wmgl; + +#define WMGL_DRIVER_ABI_VERSION 1 +#define WMGL_FLUSH_INTERVAL 0x02 +#define WMGL_FLUSH_UPDATED 0x04 + +/* An optional symbol of the core, or NULL. */ +void *wmgl_core_symbol( const char *name ); +/* A pointer for a 32-bit caller (0, logged, when beyond its reach). */ +UINT wmgl_wow64_pointer( const void *pointer ); + +/* The status a GL unix call returns: STATUS_PENDING when this thread has + * debug messages for the PE side to deliver. */ +NTSTATUS wmgl_after_call( void ); + +/* The current context's debug callback (unix_main.c). */ +struct wmgl_debug_target +{ + UINT64 callback; /* client pointer, 0: none */ + UINT64 user; + BOOL amd; +}; +struct wmgl_debug_target *wmgl_current_debug_target( void ); +void wmgl_set_debug_callback( UINT64 callback, UINT64 user, BOOL amd ); + +#define WMGL_ERR(...) fprintf( stderr, "winemetalgl: " __VA_ARGS__ ) +extern BOOL wmgl_trace; +#define WMGL_TRACE(...) do { if (wmgl_trace) fprintf( stderr, "winemetalgl: " __VA_ARGS__ ); } while (0) + +#endif diff --git a/src/wine/unix/wmgl_window.h b/src/wine/unix/wmgl_window.h new file mode 100644 index 00000000..729a812f --- /dev/null +++ b/src/wine/unix/wmgl_window.h @@ -0,0 +1,30 @@ +/* Window layers of winemetalgl.so (unix_window.m). The Objective-C side + * includes no Wine headers (their BOOL is not Objective-C's); this is its + * interface in plain C types. */ +#ifndef WMGL_WINDOW_H +#define WMGL_WINDOW_H + +#include + +/* struct wmgl_window_placement of wmgl_wine.h, in C types. */ +struct wmgl_placement +{ + uint64_t hwnd; + uint64_t top_level; + int32_t x, y, width, height; + int32_t top_level_width, top_level_height; + uint32_t screen_width; +}; + +/* A CAMetalLayer over the window's client area (*layer), and the host that + * keeps it there (the result, NULL on failure): winemac's metal view where + * winemac exports how to make one (macdrv_functions), which follows the + * window by itself; otherwise a view of ours in the top-level Cocoa window, + * which wmgl_window_place moves. */ +void *wmgl_window_create( const struct wmgl_placement *placement, void *device, void **layer ); +void wmgl_window_place( void *host, const struct wmgl_placement *placement ); +void wmgl_window_destroy( void *host ); +/* The Metal device the core renders with. */ +void *wmgl_window_default_device( void ); + +#endif diff --git a/tests/cts/native/README.md b/tests/cts/native/README.md index 088aa2b6..a2b7f3d9 100644 --- a/tests/cts/native/README.md +++ b/tests/cts/native/README.md @@ -4,7 +4,7 @@ on macOS directly against the WineMetalGL core, without Wine. Each GL context renders to a pbuffer surface created through the driver ABI (`include/winemetalgl/driver.h`), and entry points come from -`wmgl_get_proc_address`, exactly as winemac.drv uses them. +`wmgl_get_proc_address`, exactly as the Wine module uses them. It exists for fast iteration and debugging (lldb, Metal validation, no Rosetta). Conformance claims still come from the Wine runs diff --git a/tests/games/README.md b/tests/games/README.md index c8d9a2e4..76bac328 100644 --- a/tests/games/README.md +++ b/tests/games/README.md @@ -31,8 +31,11 @@ scripts/traces/replay.py --wine /path/to/wine --corpus-root /path/to/corpus \ --template /path/to/prefix-template --path new --html /tmp/corpus.html ``` -`--path` selects the implementation: `new` (WineMetalGL, Wine's default) or -`apple` (`WINEMETALGL=0`, Apple's OpenGL, which records the references). +The template prefix has the Wine module installed +(`scripts/install-wine-module.sh /path/to/wine /path/to/prefix-template`). +`--path` selects the implementation: `new` (WineMetalGL, the prefix's +OpenGL) or `apple` (Wine's own `opengl32`, Apple's OpenGL, which records the +references; it needs a Wine built with OpenGL). `--json tests/games/results.json` records the run that `docs/games.md` reports. The corpus directory is not tracked: `build-demos.sh` builds the demos into diff --git a/tests/gpu/basic_tests.cpp b/tests/gpu/basic_tests.cpp index e7610bf7..e59583c4 100644 --- a/tests/gpu/basic_tests.cpp +++ b/tests/gpu/basic_tests.cpp @@ -17,10 +17,10 @@ void main() { fragColor = color; })"; } // namespace GPU_TEST(gpu_version_string) { - // A 3.3 core request gets the highest core version (4.3), which is + // A 3.3 core request gets the highest core version (4.6), which is // backward compatible with it (WGL_ARB_create_context). const char* version = reinterpret_cast(glGetString(GL_VERSION)); - CHECK(version && std::strncmp(version, "4.3", 3) == 0); + CHECK(version && std::strncmp(version, "4.6", 3) == 0); CHECK_GL(); } diff --git a/tests/gpu/gl44_tests.cpp b/tests/gpu/gl44_tests.cpp new file mode 100644 index 00000000..1e7bb97a --- /dev/null +++ b/tests/gpu/gl44_tests.cpp @@ -0,0 +1,789 @@ +// GL 4.4 features (contexts above 4.3 go through the development override +// until the version is advertised). +#include +#include +#include +#include + +#include "gpu.h" + +// glClearTexImage and glClearTexSubImage (GL_ARB_clear_texture): a whole level +// from float data, a sub-region from bytes, a depth image, and the errors. +GPU_TEST_VERSION(gpu_gl44_clear_texture, 44) { + GLuint tex[2]; + glGenTextures(2, tex); + glBindTexture(GL_TEXTURE_2D, tex[0]); + glTexStorage2D(GL_TEXTURE_2D, 2, GL_RGBA8, 4, 4); + const float orange[4] = {1.0f, 0.5f, 0.0f, 1.0f}; + glClearTexImage(tex[0], 0, GL_RGBA, GL_FLOAT, orange); + const uint8_t blue[4] = {0, 0, 255, 255}; + glClearTexSubImage(tex[0], 0, 1, 2, 0, 2, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, blue); + glClearTexImage(tex[0], 1, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); // zeros + CHECK_GL(); + uint8_t level0[64], level1[16]; + glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, level0); + glGetTexImage(GL_TEXTURE_2D, 1, GL_RGBA, GL_UNSIGNED_BYTE, level1); + for (int i = 0; i < 16; ++i) { + const bool region = i / 4 == 2 && (i % 4 == 1 || i % 4 == 2); + const uint8_t* p = &level0[i * 4]; + if (region) { + CHECK(p[0] == 0 && p[1] == 0 && p[2] == 255 && p[3] == 255); + } else { + CHECK(p[0] == 255 && std::abs(p[1] - 128) <= 1 && p[2] == 0 && p[3] == 255); + } + } + for (int i = 0; i < 16; ++i) CHECK_EQ(static_cast(level1[i]), 0); + glBindTexture(GL_TEXTURE_2D, tex[1]); + glTexStorage2D(GL_TEXTURE_2D, 1, GL_DEPTH_COMPONENT32F, 4, 4); + const float depth = 0.25f; + glClearTexImage(tex[1], 0, GL_DEPTH_COMPONENT, GL_FLOAT, &depth); + CHECK_GL(); + float back[16] = {}; + glGetTexImage(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, GL_FLOAT, back); + for (float v : back) CHECK(std::fabs(v - 0.25f) < 1e-6f); + // A colour format for a depth image, and a region past the image. + glClearTexImage(tex[1], 0, GL_RGBA, GL_FLOAT, orange); + CHECK_EQ(glGetError(), static_cast(GL_INVALID_OPERATION)); + glClearTexSubImage(tex[0], 0, 3, 3, 0, 2, 2, 1, GL_RGBA, GL_FLOAT, orange); + CHECK_EQ(glGetError(), static_cast(GL_INVALID_OPERATION)); + glClearTexSubImage(tex[0], 0, 0, 0, 0, -1, 1, 1, GL_RGBA, GL_FLOAT, orange); + CHECK_EQ(glGetError(), static_cast(GL_INVALID_VALUE)); + glDeleteTextures(2, tex); +} + +// GL_ARB_multi_bind: ranges of texture, sampler, buffer and vertex buffer +// bindings, the indexed binding queries, and a range past the last unit. +GPU_TEST_VERSION(gpu_gl44_multi_bind, 44) { + GLuint tex[2], samplers[2], buffers[3], vao = 0; + glGenTextures(2, tex); + glBindTexture(GL_TEXTURE_2D, tex[0]); + glBindTexture(GL_TEXTURE_3D, tex[1]); + glBindTexture(GL_TEXTURE_2D, 0); + glBindTexture(GL_TEXTURE_3D, 0); + glGenSamplers(2, samplers); + glBindTextures(4, 2, tex); + glBindSamplers(4, 2, samplers); + CHECK_GL(); + GLint binding = -1; + glGetIntegeri_v(GL_TEXTURE_BINDING_2D, 4, &binding); + CHECK_EQ(binding, static_cast(tex[0])); + glGetIntegeri_v(GL_TEXTURE_BINDING_3D, 5, &binding); + CHECK_EQ(binding, static_cast(tex[1])); + glGetIntegeri_v(GL_SAMPLER_BINDING, 5, &binding); + CHECK_EQ(binding, static_cast(samplers[1])); + glBindTextures(4, 2, nullptr); // unbind both units + glGetIntegeri_v(GL_TEXTURE_BINDING_2D, 4, &binding); + CHECK_EQ(binding, 0); + glGenBuffers(3, buffers); + for (GLuint b : buffers) { + glBindBuffer(GL_UNIFORM_BUFFER, b); + glBufferData(GL_UNIFORM_BUFFER, 512, nullptr, GL_STATIC_DRAW); + } + glBindBuffer(GL_UNIFORM_BUFFER, buffers[0]); + const GLintptr offsets[3] = {0, 256, 0}; + const GLsizeiptr sizes[3] = {64, 128, 256}; + glBindBuffersRange(GL_UNIFORM_BUFFER, 1, 3, buffers, offsets, sizes); + CHECK_GL(); + GLint64 start = -1; + glGetIntegeri_v(GL_UNIFORM_BUFFER_BINDING, 2, &binding); + CHECK_EQ(binding, static_cast(buffers[1])); + glGetInteger64i_v(GL_UNIFORM_BUFFER_START, 2, &start); + CHECK_EQ(start, static_cast(256)); + glGetIntegerv(GL_UNIFORM_BUFFER_BINDING, &binding); // the generic binding is unchanged + CHECK_EQ(binding, static_cast(buffers[0])); + glGenVertexArrays(1, &vao); + glBindVertexArray(vao); + const GLintptr vOffsets[2] = {16, 32}; + const GLsizei strides[2] = {12, 24}; + glBindVertexBuffers(0, 2, buffers, vOffsets, strides); + glGetIntegeri_v(GL_VERTEX_BINDING_STRIDE, 1, &binding); + CHECK_EQ(binding, 24); + CHECK_GL(); + // A range past the last unit binds nothing. + glBindTextures(79, 2, tex); + CHECK_EQ(glGetError(), static_cast(GL_INVALID_OPERATION)); + glDeleteVertexArrays(1, &vao); + glDeleteBuffers(3, buffers); + glDeleteSamplers(2, samplers); + glDeleteTextures(2, tex); +} + +// GL_ARB_query_buffer_object: query results written into a buffer at an +// offset, as 32-bit and 64-bit values, and the availability flag. +GPU_TEST_VERSION(gpu_gl44_query_buffer, 44) { + GLuint p = gpu::program("#version 440 core\nin vec2 position;\nvoid main() { gl_Position = vec4(position, 0.0, 1.0); }\n", + "#version 440 core\nout vec4 o;\nvoid main() { o = vec4(1.0); }\n"); + glUseProgram(p); + GLuint vao = gpu::quad(), query = 0, buffer = 0; + glGenQueries(1, &query); + glBeginQuery(GL_SAMPLES_PASSED, query); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + glEndQuery(GL_SAMPLES_PASSED); + glGenBuffers(1, &buffer); + glBindBuffer(GL_QUERY_BUFFER, buffer); + const uint32_t fill[6] = {0xdeadbeef, 0xdeadbeef, 0xdeadbeef, 0xdeadbeef, 0xdeadbeef, 0xdeadbeef}; + glBufferData(GL_QUERY_BUFFER, sizeof(fill), fill, GL_DYNAMIC_READ); + glGetQueryObjectuiv(query, GL_QUERY_RESULT, reinterpret_cast(4)); + glGetQueryObjectui64v(query, GL_QUERY_RESULT, reinterpret_cast(8)); + glGetQueryObjectuiv(query, GL_QUERY_RESULT_AVAILABLE, reinterpret_cast(16)); + CHECK_GL(); + GLint bound = 0; + glGetIntegerv(GL_QUERY_BUFFER_BINDING, &bound); + CHECK_EQ(bound, static_cast(buffer)); + uint32_t back[6] = {}; + glGetBufferSubData(GL_QUERY_BUFFER, 0, sizeof(back), back); + CHECK_EQ(back[0], 0xdeadbeefu); + CHECK_EQ(back[1], 64u * 64u); + CHECK_EQ(back[2], 64u * 64u); + CHECK_EQ(back[3], 0u); + CHECK_EQ(back[4], 1u); + // An offset past the buffer is INVALID_OPERATION. + glGetQueryObjectuiv(query, GL_QUERY_RESULT, reinterpret_cast(24)); + CHECK_EQ(glGetError(), static_cast(GL_INVALID_OPERATION)); + glBindBuffer(GL_QUERY_BUFFER, 0); + glDeleteBuffers(1, &buffer); + glDeleteQueries(1, &query); + glDeleteVertexArrays(1, &vao); + glDeleteProgram(p); +} + +// GL_ARB_texture_stencil8: a STENCIL_INDEX8 texture uploaded, read back and +// sampled as unsigned integers; GL_MIRROR_CLAMP_TO_EDGE is a wrap mode. +GPU_TEST_VERSION(gpu_gl44_stencil8_texture, 44) { + GLuint tex = 0; + glGenTextures(1, &tex); + glBindTexture(GL_TEXTURE_2D, tex); + const uint8_t stencil[4] = {7, 42, 200, 255}; + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + glTexImage2D(GL_TEXTURE_2D, 0, GL_STENCIL_INDEX8, 2, 2, 0, GL_STENCIL_INDEX, GL_UNSIGNED_BYTE, stencil); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_MIRROR_CLAMP_TO_EDGE); + CHECK_GL(); + uint8_t back[4] = {}; + glPixelStorei(GL_PACK_ALIGNMENT, 1); + glGetTexImage(GL_TEXTURE_2D, 0, GL_STENCIL_INDEX, GL_UNSIGNED_BYTE, back); + CHECK_GL(); + CHECK(std::memcmp(back, stencil, 4) == 0); + GLuint p = gpu::program("#version 440 core\nin vec2 position;\nvoid main() { gl_Position = vec4(position, 0.0, 1.0); }\n", + "#version 440 core\nuniform usampler2D s;\nout vec4 o;\n" + "void main() { o = vec4(float(texelFetch(s, ivec2(1, 0), 0).r) / 255.0, 0.0, 0.0, 1.0); }\n"); + glUseProgram(p); + GLuint vao = gpu::quad(); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + CHECK_GL(); + const uint8_t want[4] = {42, 0, 0, 255}; + CHECK(gpu::near(gpu::readRGBA8(32, 32, 1, 1).data(), want)); + glDeleteVertexArrays(1, &vao); + glDeleteProgram(p); + glDeleteTextures(1, &tex); +} + +// GL_ARB_enhanced_layouts: transform feedback declared in the shader (two +// buffers, an explicit stride with a gap) without glTransformFeedbackVaryings, +// and layout(offset) in a uniform block. +GPU_TEST_VERSION(gpu_gl44_enhanced_layouts, 44) { + const char* vs = R"(#version 440 core +layout(xfb_buffer = 0, xfb_offset = 0) out vec4 a; +layout(xfb_buffer = 0, xfb_offset = 16) out float b; +layout(xfb_buffer = 1, xfb_stride = 16, xfb_offset = 4) out vec2 c; +layout(std140) uniform Block { layout(offset = 32) vec4 shift; }; +void main() { + a = vec4(gl_VertexID, 1.0, 2.0, 3.0) + shift; + b = 10.0 + float(gl_VertexID); + c = vec2(20.0, 21.0) + float(gl_VertexID); + gl_Position = vec4(0.0); +})"; + const char* fs = "#version 440 core\nout vec4 o;\nvoid main() { o = vec4(1.0); }\n"; + GLuint p = gpu::program(vs, fs); + glUseProgram(p); + GLuint block = glGetUniformBlockIndex(p, "Block"); + GLint dataSize = 0; + glGetActiveUniformBlockiv(p, block, GL_UNIFORM_BLOCK_DATA_SIZE, &dataSize); + CHECK_EQ(dataSize, 48); + float ubo[12] = {0, 0, 0, 0, 0, 0, 0, 0, 0.5f, 0.5f, 0.5f, 0.5f}; + GLuint buffers[3], vao = 0; + glGenBuffers(3, buffers); + glBindBufferBase(GL_UNIFORM_BUFFER, 0, buffers[2]); + glBufferData(GL_UNIFORM_BUFFER, sizeof(ubo), ubo, GL_STATIC_DRAW); + glUniformBlockBinding(p, block, 0); + std::vector zeros(64, -1.0f); + for (int i = 0; i < 2; ++i) { + glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, static_cast(i), buffers[i]); + glBufferData(GL_TRANSFORM_FEEDBACK_BUFFER, 64 * sizeof(float), zeros.data(), GL_DYNAMIC_READ); + } + glGenVertexArrays(1, &vao); + glBindVertexArray(vao); + glEnable(GL_RASTERIZER_DISCARD); + glBeginTransformFeedback(GL_POINTS); + glDrawArrays(GL_POINTS, 0, 2); + glEndTransformFeedback(); + glDisable(GL_RASTERIZER_DISCARD); + CHECK_GL(); + float out0[10] = {}, out1[8] = {}; + glBindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, buffers[0]); + glGetBufferSubData(GL_TRANSFORM_FEEDBACK_BUFFER, 0, sizeof(out0), out0); + glBindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, buffers[1]); + glGetBufferSubData(GL_TRANSFORM_FEEDBACK_BUFFER, 0, sizeof(out1), out1); + // Buffer 0: stride 20 bytes (a, b); buffer 1: stride 16 bytes, c at byte 4. + const float want0[10] = {0.5f, 1.5f, 2.5f, 3.5f, 10.0f, 1.5f, 1.5f, 2.5f, 3.5f, 11.0f}; + for (int i = 0; i < 10; ++i) CHECK(std::fabs(out0[i] - want0[i]) < 1e-6f); + const float want1[8] = {-1.0f, 20.0f, 21.0f, -1.0f, -1.0f, 21.0f, 22.0f, -1.0f}; + for (int i = 0; i < 8; ++i) { + if (std::fabs(out1[i] - want1[i]) >= 1e-6f) std::fprintf(stderr, " buffer 1 word %d: %g, expected %g\n", i, out1[i], want1[i]); + CHECK(std::fabs(out1[i] - want1[i]) < 1e-6f); + } + glDeleteVertexArrays(1, &vao); + glDeleteBuffers(3, buffers); + glDeleteProgram(p); +} + +// GL 4.5 direct state access: a frame set up without binding anything to +// edit it (buffers, a texture, a framebuffer and a vertex array), drawn into +// the framebuffer and read back through the texture. +GPU_TEST_VERSION(gpu_gl45_direct_state_access, 45) { + const float quad[8] = {-1, -1, 1, -1, -1, 1, 1, 1}; + GLuint vbo = 0, tex = 0, src = 0, fbo = 0, vao = 0; + glCreateBuffers(1, &vbo); + glNamedBufferData(vbo, sizeof(quad), nullptr, GL_STATIC_DRAW); + glNamedBufferSubData(vbo, 0, sizeof(quad), quad); + float back[8] = {}; + glGetNamedBufferSubData(vbo, 0, sizeof(back), back); + CHECK(std::memcmp(back, quad, sizeof(quad)) == 0); + glCreateTextures(GL_TEXTURE_2D, 1, &tex); + glTextureStorage2D(tex, 1, GL_RGBA8, 4, 4); + glCreateTextures(GL_TEXTURE_2D, 1, &src); + glTextureStorage2D(src, 1, GL_RGBA8, 1, 1); + const uint8_t green[4] = {0, 255, 0, 255}; + glTextureSubImage2D(src, 0, 0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, green); + glTextureParameteri(src, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glCreateFramebuffers(1, &fbo); + glNamedFramebufferTexture(fbo, GL_COLOR_ATTACHMENT0, tex, 0); + CHECK_EQ(glCheckNamedFramebufferStatus(fbo, GL_FRAMEBUFFER), static_cast(GL_FRAMEBUFFER_COMPLETE)); + const float red[4] = {1, 0, 0, 1}; + glClearNamedFramebufferfv(fbo, GL_COLOR, 0, red); + glCreateVertexArrays(1, &vao); + glVertexArrayVertexBuffer(vao, 0, vbo, 0, 8); + glVertexArrayAttribFormat(vao, 0, 2, GL_FLOAT, GL_FALSE, 0); + glVertexArrayAttribBinding(vao, 0, 0); + glEnableVertexArrayAttrib(vao, 0); + GLuint p = gpu::program("#version 450 core\nlayout(location = 0) in vec2 position;\n" + "void main() { gl_Position = vec4(position * 0.5 - 0.5, 0.0, 1.0); }\n", + "#version 450 core\nlayout(binding = 3) uniform sampler2D s;\nout vec4 o;\n" + "void main() { o = texture(s, vec2(0.5)); }\n"); + glBindTextureUnit(3, src); + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + glViewport(0, 0, 4, 4); + glUseProgram(p); + glBindVertexArray(vao); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); // the lower-left quarter + glBindFramebuffer(GL_FRAMEBUFFER, 0); + glViewport(0, 0, 64, 64); + CHECK_GL(); + uint8_t pixels[64] = {}; + glGetTextureImage(tex, 0, GL_RGBA, GL_UNSIGNED_BYTE, sizeof(pixels), pixels); + CHECK_GL(); + CHECK(pixels[0] == 0 && pixels[1] == 255); // (0, 0): drawn + CHECK(pixels[15 * 4] == 255 && pixels[15 * 4 + 1] == 0); // (3, 3): cleared + // A name that is not an object is INVALID_OPERATION. + glNamedBufferData(4321, 4, nullptr, GL_STATIC_DRAW); + CHECK_EQ(glGetError(), static_cast(GL_INVALID_OPERATION)); + glDeleteProgram(p); + glDeleteVertexArrays(1, &vao); + glDeleteFramebuffers(1, &fbo); + glDeleteTextures(1, &src); + glDeleteTextures(1, &tex); + glDeleteBuffers(1, &vbo); +} + +// GL 4.5 cull distances (GL_ARB_cull_distance): a primitive is dropped only +// when one distance is negative at every vertex; a negative vertex alone +// leaves it whole (a clip distance would clip it). Triangles, lines and +// points, then the triangles again through a passthrough geometry shader. +GPU_TEST_VERSION(gpu_gl45_cull_distance, 45) { + const char* vs = R"(#version 450 core +layout(location = 0) in vec2 pos; +layout(location = 1) in vec2 cull; +out float gl_CullDistance[2]; +void main() { + gl_CullDistance[0] = cull.x; + gl_CullDistance[1] = cull.y; + gl_Position = vec4(pos, 0.0, 1.0); +})"; + const char* gs = R"(#version 450 core +layout(triangles) in; +layout(triangle_strip, max_vertices = 3) out; +out float gl_CullDistance[2]; +void main() { + for (int i = 0; i < 3; ++i) { + gl_Position = gl_in[i].gl_Position; + gl_CullDistance[0] = gl_in[i].gl_CullDistance[0]; + gl_CullDistance[1] = gl_in[i].gl_CullDistance[1]; + EmitVertex(); + } +})"; + const char* fs = "#version 450 core\nout vec4 o;\nvoid main() { o = vec4(1.0); }\n"; + GLuint p = gpu::program(vs, fs); + GLuint withGs = glCreateProgram(); + { + const GLenum types[3] = {GL_VERTEX_SHADER, GL_GEOMETRY_SHADER, GL_FRAGMENT_SHADER}; + const char* sources[3] = {vs, gs, fs}; + for (int i = 0; i < 3; ++i) { + GLuint s = glCreateShader(types[i]); + glShaderSource(s, 1, &sources[i], nullptr); + glCompileShader(s); + glAttachShader(withGs, s); + glDeleteShader(s); + } + glLinkProgram(withGs); + GLint linked = 0; + glGetProgramiv(withGs, GL_LINK_STATUS, &linked); + CHECK(linked); + } + GLint limit = 0; + glGetIntegerv(GL_MAX_CULL_DISTANCES, &limit); + CHECK_EQ(limit, 8); + glGetIntegerv(GL_MAX_COMBINED_CLIP_AND_CULL_DISTANCES, &limit); + CHECK_EQ(limit, 8); + // x, y, cull0, cull1 per vertex. Pixel (px, py) has its centre at (px + 0.5) / 32 - 1. + const float tris[] = { + // A, bottom left: distance 0 negative at vertex 0 only -> drawn whole. + -1, -1, -1, 1, 0, -1, 1, 1, -1, 0, 1, 1, + // B, bottom right: distance 1 negative everywhere -> culled. + 0, -1, 5, -1, 1, -1, 5, -2, 0, 0, 5, -0.5f, + // C, top left: each distance negative somewhere, neither everywhere -> drawn. + -1, 0, -1, 1, 0, 0, -1, -1, -1, 1, 1, -1, + }; + const float lines[] = { + // Row 40: one end negative -> drawn. Row 44: distance 0 negative at both ends -> culled. + 0.0f, 40.5f / 32 - 1, -1, 1, 1.0f, 40.5f / 32 - 1, 1, 1, + 0.0f, 44.5f / 32 - 1, -1, 1, 1.0f, 44.5f / 32 - 1, -2, 1, + }; + const float points[] = { + 48.5f / 32 - 1, 56.5f / 32 - 1, 1, -1, // culled + 56.5f / 32 - 1, 56.5f / 32 - 1, 1, 1, // drawn + }; + GLuint vao = 0, vbo = 0; + glGenVertexArrays(1, &vao); + glBindVertexArray(vao); + glGenBuffers(1, &vbo); + glBindBuffer(GL_ARRAY_BUFFER, vbo); + glEnableVertexAttribArray(0); + glEnableVertexAttribArray(1); + const auto draw = [&](GLuint program, GLenum mode, const float* data, size_t bytes) { + glBufferData(GL_ARRAY_BUFFER, static_cast(bytes), data, GL_STATIC_DRAW); + glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 16, nullptr); + glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 16, reinterpret_cast(8)); + glUseProgram(program); + glDrawArrays(mode, 0, static_cast(bytes / 16)); + }; + const uint8_t white[4] = {255, 255, 255, 255}, black[4] = {0, 0, 0, 0}; + for (GLuint program : {p, withGs}) { + glClearColor(0, 0, 0, 0); + glClear(GL_COLOR_BUFFER_BIT); + draw(program, GL_TRIANGLES, tris, sizeof(tris)); + if (program == p) { + draw(p, GL_LINES, lines, sizeof(lines)); + draw(p, GL_POINTS, points, sizeof(points)); + } + CHECK_GL(); + const std::vector px = gpu::readRGBA8(0, 0, 64, 64); + const auto at = [&](int x, int y) { return &px[static_cast(y * 64 + x) * 4]; }; + CHECK(gpu::near(at(1, 1), white)); // A, next to its negative vertex + CHECK(gpu::near(at(20, 8), white)); // A + CHECK(gpu::near(at(34, 2), black)); // B + CHECK(gpu::near(at(2, 34), white)); // C + CHECK(gpu::near(at(20, 40), white)); // C + if (program == p) { + CHECK(gpu::near(at(33, 40), white)); // line, next to its negative end + CHECK(gpu::near(at(48, 40), white)); + CHECK(gpu::near(at(48, 44), black)); + CHECK(gpu::near(at(48, 56), black)); + CHECK(gpu::near(at(56, 56), white)); + } + } + glDeleteBuffers(1, &vbo); + glDeleteVertexArrays(1, &vao); + glDeleteProgram(p); + glDeleteProgram(withGs); +} + +// GL 4.5 glGetTextureSubImage: a region of a 2D array (two layers) and of a +// cube map (faces as z), its errors, and glGetCompressedTextureSubImage's +// block alignment. +GPU_TEST_VERSION(gpu_gl45_get_texture_sub_image, 45) { + GLuint tex[3]; + glCreateTextures(GL_TEXTURE_2D_ARRAY, 1, &tex[0]); + glTextureStorage3D(tex[0], 1, GL_RGBA8, 4, 4, 3); + std::vector texels(4 * 4 * 3 * 4); + for (size_t i = 0; i < texels.size(); ++i) texels[i] = static_cast(i); + glTextureSubImage3D(tex[0], 0, 0, 0, 0, 4, 4, 3, GL_RGBA, GL_UNSIGNED_BYTE, texels.data()); + uint8_t region[2 * 2 * 2 * 4] = {}; + glGetTextureSubImage(tex[0], 0, 1, 2, 1, 2, 2, 2, GL_RGBA, GL_UNSIGNED_BYTE, sizeof(region), region); + CHECK_GL(); + for (int z = 0; z < 2; ++z) + for (int y = 0; y < 2; ++y) + for (int x = 0; x < 2; ++x) + for (int c = 0; c < 4; ++c) { + const size_t src = (((static_cast(1 + z) * 4 + 2 + y) * 4) + 1 + x) * 4 + c; + CHECK_EQ(static_cast(region[((z * 2 + y) * 2 + x) * 4 + c]), static_cast(texels[src])); + } + // Past the image, a negative offset, and a buffer too small. + glGetTextureSubImage(tex[0], 0, 3, 0, 0, 2, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, sizeof(region), region); + CHECK_EQ(glGetError(), static_cast(GL_INVALID_VALUE)); + glGetTextureSubImage(tex[0], 0, -1, 0, 0, 1, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, sizeof(region), region); + CHECK_EQ(glGetError(), static_cast(GL_INVALID_VALUE)); + glGetTextureSubImage(tex[0], 0, 0, 0, 0, 2, 2, 2, GL_RGBA, GL_UNSIGNED_BYTE, 4, region); + CHECK_EQ(glGetError(), static_cast(GL_INVALID_OPERATION)); + // Cube map faces 2 and 3 (one texel each). + glCreateTextures(GL_TEXTURE_CUBE_MAP, 1, &tex[1]); + glTextureStorage2D(tex[1], 1, GL_RGBA8, 2, 2); + for (int f = 0; f < 6; ++f) { + const uint8_t face[16] = {static_cast(f), 0, 0, 255, static_cast(f), 0, 0, 255, + static_cast(f), 0, 0, 255, static_cast(f), 0, 0, 255}; + glTextureSubImage3D(tex[1], 0, 0, 0, f, 2, 2, 1, GL_RGBA, GL_UNSIGNED_BYTE, face); + } + uint8_t faces[8] = {}; + glGetTextureSubImage(tex[1], 0, 1, 1, 2, 1, 1, 2, GL_RGBA, GL_UNSIGNED_BYTE, sizeof(faces), faces); + CHECK_GL(); + CHECK_EQ(static_cast(faces[0]), 2); + CHECK_EQ(static_cast(faces[4]), 3); + // Compressed: whole 4x4 blocks only. + glCreateTextures(GL_TEXTURE_2D, 1, &tex[2]); + glTextureStorage2D(tex[2], 1, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, 8, 8); + uint8_t blocks[64] = {}; + glGetCompressedTextureSubImage(tex[2], 0, 4, 0, 0, 4, 8, 1, sizeof(blocks), blocks); + CHECK_GL(); + glGetCompressedTextureSubImage(tex[2], 0, 2, 0, 0, 4, 4, 1, sizeof(blocks), blocks); + CHECK_EQ(glGetError(), static_cast(GL_INVALID_OPERATION)); + glDeleteTextures(3, tex); +} + +// GL 4.5 conditional rendering inverted: a draw runs only if the query +// passed no samples; and GL 4.5 range rules for a mapped buffer's sub-data. +GPU_TEST_VERSION(gpu_gl45_conditional_render_inverted, 45) { + GLuint p = gpu::program("#version 450 core\nlayout(location = 0) in vec2 pos;\nvoid main() { gl_Position = vec4(pos, 0.0, 1.0); }\n", + "#version 450 core\nout vec4 o;\nvoid main() { o = vec4(1.0); }\n"); + GLuint vao = gpu::quad(); + glUseProgram(p); + GLuint queries[2]; + glGenQueries(2, queries); + glBeginQuery(GL_ANY_SAMPLES_PASSED, queries[0]); // passes samples + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + glEndQuery(GL_ANY_SAMPLES_PASSED); + glBeginQuery(GL_ANY_SAMPLES_PASSED, queries[1]); // passes none + glEndQuery(GL_ANY_SAMPLES_PASSED); + const uint8_t white[4] = {255, 255, 255, 255}, black[4] = {0, 0, 0, 0}; + for (int i = 0; i < 2; ++i) { + glClearColor(0, 0, 0, 0); + glClear(GL_COLOR_BUFFER_BIT); + glBeginConditionalRender(queries[i], GL_QUERY_WAIT_INVERTED); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + glEndConditionalRender(); + CHECK_GL(); + const std::vector px = gpu::readRGBA8(8, 8, 1, 1); + CHECK(gpu::near(px.data(), i == 0 ? black : white)); + } + GLuint buffer = 0; + glGenBuffers(1, &buffer); + glBindBuffer(GL_ARRAY_BUFFER, buffer); + glBufferData(GL_ARRAY_BUFFER, 64, nullptr, GL_DYNAMIC_DRAW); + glMapBufferRange(GL_ARRAY_BUFFER, 16, 16, GL_MAP_WRITE_BIT); + const uint8_t bytes[16] = {}; + glBufferSubData(GL_ARRAY_BUFFER, 32, 16, bytes); // outside the mapping + CHECK_GL(); + glBufferSubData(GL_ARRAY_BUFFER, 24, 16, bytes); // across it + CHECK_EQ(glGetError(), static_cast(GL_INVALID_OPERATION)); + glUnmapBuffer(GL_ARRAY_BUFFER); + glDeleteBuffers(1, &buffer); + glDeleteQueries(2, queries); + glDeleteVertexArrays(1, &vao); + glDeleteProgram(p); +} + +// Sampler border colours Metal does not have, and integer, shadow and +// rectangle textures' borders (clamp-to-border emulated in the shader): +// nearest and linear samples, a gather and depth comparisons, from a +// compute shader. +GPU_TEST_VERSION(gpu_gl44_border_colors, 44) { + const char* cs = R"(#version 450 core +layout(local_size_x = 1) in; +layout(binding = 0) uniform sampler2D colorTex; +layout(binding = 1) uniform isampler2D intTex; +layout(binding = 2) uniform sampler2D linearTex; +layout(binding = 3) uniform sampler2DShadow depthTex; +layout(binding = 4) uniform sampler2DRect rectTex; +layout(std430, binding = 0) buffer Out { vec4 f[6]; ivec4 i[2]; }; +void main() { + f[0] = textureLod(colorTex, vec2(-0.5, 0.5), 0.0); + f[1] = textureLod(colorTex, vec2(0.25, 0.25), 0.0); + f[2] = textureLod(linearTex, vec2(0.0, 0.5), 0.0); + f[3] = vec4(texture(depthTex, vec3(-0.5, 0.5, 0.25))); + f[4] = vec4(texture(depthTex, vec3(-0.5, 0.5, 0.75))); + f[5] = texture(rectTex, vec2(-1.0, 0.5)); + i[0] = texture(intTex, vec2(1.5, 0.5)); + i[1] = textureGather(intTex, vec2(0.0, 0.5), 0); +})"; + GLuint shader = glCreateShader(GL_COMPUTE_SHADER); + glShaderSource(shader, 1, &cs, nullptr); + glCompileShader(shader); + GLuint p = glCreateProgram(); + glAttachShader(p, shader); + glLinkProgram(p); + glDeleteShader(shader); + GLint linked = 0; + glGetProgramiv(p, GL_LINK_STATUS, &linked); + CHECK(linked); + GLuint tex[5]; + glGenTextures(5, tex); + const auto setup = [&](int unit, GLenum target, GLenum filter) { + glActiveTexture(GL_TEXTURE0 + unit); + glBindTexture(target, tex[unit]); + glTexParameteri(target, GL_TEXTURE_MIN_FILTER, filter); + glTexParameteri(target, GL_TEXTURE_MAG_FILTER, filter); + glTexParameteri(target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); + glTexParameteri(target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); + }; + const uint8_t rgba[16] = {255, 0, 0, 255, 0, 255, 0, 255, 0, 0, 255, 255, 255, 255, 255, 255}; + const uint8_t white[16] = {255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}; + setup(0, GL_TEXTURE_2D, GL_NEAREST); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE, rgba); + const float color[4] = {0.25f, 0.5f, 0.75f, 1.0f}; + glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, color); + setup(1, GL_TEXTURE_2D, GL_NEAREST); + int32_t ints[16] = {}; + for (int y = 0; y < 2; ++y) + for (int x = 0; x < 2; ++x) ints[(y * 2 + x) * 4] = 10 * y + x + 1; + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32I, 2, 2, 0, GL_RGBA_INTEGER, GL_INT, ints); + const GLint intBorder[4] = {-5, 7, -9, 11}; + glTexParameterIiv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, intBorder); + setup(2, GL_TEXTURE_2D, GL_LINEAR); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE, white); + const float grey[4] = {0.5f, 0.5f, 0.5f, 0.5f}; + glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, grey); + setup(3, GL_TEXTURE_2D, GL_NEAREST); + const float depths[4] = {0.9f, 0.9f, 0.9f, 0.9f}; + glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT32F, 2, 2, 0, GL_DEPTH_COMPONENT, GL_FLOAT, depths); + const float depthBorder[4] = {0.5f, 0.0f, 0.0f, 0.0f}; + glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, depthBorder); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL); + setup(4, GL_TEXTURE_RECTANGLE, GL_NEAREST); + glTexImage2D(GL_TEXTURE_RECTANGLE, 0, GL_RGBA8, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE, white); + CHECK_GL(); + GLuint buffer = 0; + glGenBuffers(1, &buffer); + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, buffer); + std::vector init(32, -1.0f); + glBufferData(GL_SHADER_STORAGE_BUFFER, init.size() * 4, init.data(), GL_DYNAMIC_READ); + glUseProgram(p); + glDispatchCompute(1, 1, 1); + glMemoryBarrier(GL_BUFFER_UPDATE_BARRIER_BIT); + CHECK_GL(); + float out[32]; + glGetBufferSubData(GL_SHADER_STORAGE_BUFFER, 0, sizeof(out), out); + const auto close = [](const float* got, std::initializer_list want) { + int k = 0; + bool ok = true; + for (float w : want) ok = ok && std::fabs(got[k++] - w) < 0.02f; + if (!ok) std::fprintf(stderr, " got (%g, %g, %g, %g)\n", got[0], got[1], got[2], got[3]); + return ok; + }; + CHECK(close(&out[0], {0.25f, 0.5f, 0.75f, 1.0f})); // nearest, outside + CHECK(close(&out[4], {1.0f, 0.0f, 0.0f, 1.0f})); // nearest, inside + CHECK(close(&out[8], {0.75f, 0.75f, 0.75f, 0.75f})); // linear, half outside + CHECK(close(&out[12], {1.0f, 1.0f, 1.0f, 1.0f})); // 0.25 <= border depth 0.5 + CHECK(close(&out[16], {0.0f, 0.0f, 0.0f, 0.0f})); // 0.75 > 0.5 + CHECK(close(&out[20], {0.0f, 0.0f, 0.0f, 0.0f})); // rectangle, outside + int32_t is[8]; + std::memcpy(is, &out[24], sizeof(is)); + CHECK(is[0] == -5 && is[1] == 7 && is[2] == -9 && is[3] == 11); + CHECK(is[4] == -5 && is[5] == 11 && is[6] == 1 && is[7] == -5); + if (!(is[4] == -5 && is[5] == 11 && is[6] == 1 && is[7] == -5)) + std::fprintf(stderr, " gather (%d, %d, %d, %d)\n", is[4], is[5], is[6], is[7]); + glDeleteBuffers(1, &buffer); + glDeleteTextures(5, tex); + glDeleteProgram(p); +} + +// glClearTexSubImage of a multisample texture: the whole image, then a +// region, read back through a resolving blit. +GPU_TEST_VERSION(gpu_gl44_clear_texture_multisample, 44) { + GLuint tex[2], fbo[2]; + glGenTextures(2, tex); + glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, tex[0]); + glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, 4, GL_RGBA8, 4, 4, GL_TRUE); + glBindTexture(GL_TEXTURE_2D, tex[1]); + glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, 4, 4); + const uint8_t red[4] = {255, 0, 0, 255}, blue[4] = {0, 0, 255, 255}; + glClearTexImage(tex[0], 0, GL_RGBA, GL_UNSIGNED_BYTE, red); + glClearTexSubImage(tex[0], 0, 1, 1, 0, 2, 2, 1, GL_RGBA, GL_UNSIGNED_BYTE, blue); + CHECK_GL(); + glGenFramebuffers(2, fbo); + glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo[0]); + glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D_MULTISAMPLE, tex[0], 0); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo[1]); + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex[1], 0); + glBlitFramebuffer(0, 0, 4, 4, 0, 0, 4, 4, GL_COLOR_BUFFER_BIT, GL_NEAREST); + CHECK_GL(); + glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo[1]); + const std::vector px = gpu::readRGBA8(0, 0, 4, 4); + for (int y = 0; y < 4; ++y) + for (int x = 0; x < 4; ++x) { + const bool inside = x >= 1 && x <= 2 && y >= 1 && y <= 2; + CHECK(gpu::near(&px[static_cast(y * 4 + x) * 4], inside ? blue : red)); + } + glBindFramebuffer(GL_FRAMEBUFFER, 0); + glDeleteFramebuffers(2, fbo); + glDeleteTextures(2, tex); +} + +// GL 4.4 vertex inputs that alias one location (whole or by component): +// each reads its components of the same attribute. +GPU_TEST_VERSION(gpu_gl44_vertex_input_aliasing, 44) { + const char* vs = R"(#version 440 core +layout(location = 0) in vec2 pos; +layout(location = 1, component = 0) in float a; +layout(location = 1, component = 0) in float b; +layout(location = 1, component = 2) in vec2 zw; +flat out vec4 v; +void main() { + v = vec4(a, b, zw); + gl_Position = vec4(pos, 0.0, 1.0); +})"; + const char* fs = "#version 440 core\nflat in vec4 v;\nout vec4 o;\nvoid main() { o = v; }\n"; + GLuint p = gpu::program(vs, fs); + GLuint vao = gpu::quad(); + const float values[4] = {0.25f, 0.5f, 0.75f, 1.0f}; + glVertexAttrib4fv(1, values); + glUseProgram(p); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + CHECK_GL(); + const std::vector px = gpu::readRGBA8(8, 8, 1, 1); + const uint8_t want[4] = {64, 64, 191, 255}; + if (!gpu::near(px.data(), want, 2)) std::fprintf(stderr, " got (%d, %d, %d, %d)\n", px[0], px[1], px[2], px[3]); + CHECK(gpu::near(px.data(), want, 2)); + // The same from a vertex buffer. + GLuint vbo = 0; + glGenBuffers(1, &vbo); + glBindBuffer(GL_ARRAY_BUFFER, vbo); + const float array[16] = {0.25f, 0.5f, 0.75f, 1.0f, 0.25f, 0.5f, 0.75f, 1.0f, + 0.25f, 0.5f, 0.75f, 1.0f, 0.25f, 0.5f, 0.75f, 1.0f}; + glBufferData(GL_ARRAY_BUFFER, sizeof(array), array, GL_STATIC_DRAW); + glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, 0, nullptr); + glEnableVertexAttribArray(1); + glClearColor(0, 0, 0, 0); + glClear(GL_COLOR_BUFFER_BIT); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + CHECK_GL(); + const std::vector fromArray = gpu::readRGBA8(8, 8, 1, 1); + if (!gpu::near(fromArray.data(), want, 2)) + std::fprintf(stderr, " array: got (%d, %d, %d, %d)\n", fromArray[0], fromArray[1], fromArray[2], fromArray[3]); + CHECK(gpu::near(fromArray.data(), want, 2)); + glDeleteBuffers(1, &vbo); + glDeleteVertexArrays(1, &vao); + glDeleteProgram(p); +} + +// GL 4.4 enhanced layouts: an array of double matrices captured by +// xfb_offset (each element's columns in order, low word first). +GPU_TEST_VERSION(gpu_gl44_xfb_double_matrices, 44) { + const char* vs = R"(#version 440 core +layout(std140, binding = 0) uniform Values { dmat2 values[3]; }; +layout(location = 0, xfb_offset = 0) flat out dmat2 goku[3]; +void main() { + goku = values; + gl_Position = vec4(0.0); +})"; + const char* fs = "#version 440 core\nout vec4 o;\nvoid main() { o = vec4(1.0); }\n"; + GLuint p = gpu::program(vs, fs); + GLuint buffers[2], vao = 0; + glGenBuffers(2, buffers); + // std140: each dmat2 column is a dvec2 at a 16-byte stride (no padding to 32). + double ubo[12]; + for (int i = 0; i < 12; ++i) ubo[i] = 1.5 + i; + glBindBufferBase(GL_UNIFORM_BUFFER, 0, buffers[0]); + glBufferData(GL_UNIFORM_BUFFER, sizeof(ubo), ubo, GL_STATIC_DRAW); + glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, buffers[1]); + std::vector init(12, -1.0); + glBufferData(GL_TRANSFORM_FEEDBACK_BUFFER, init.size() * 8, init.data(), GL_DYNAMIC_READ); + glGenVertexArrays(1, &vao); + glBindVertexArray(vao); + glUseProgram(p); + glEnable(GL_RASTERIZER_DISCARD); + glBeginTransformFeedback(GL_POINTS); + glDrawArrays(GL_POINTS, 0, 1); + glEndTransformFeedback(); + glDisable(GL_RASTERIZER_DISCARD); + CHECK_GL(); + double out[12] = {}; + glGetBufferSubData(GL_TRANSFORM_FEEDBACK_BUFFER, 0, sizeof(out), out); + for (int i = 0; i < 12; ++i) { + if (out[i] != ubo[i]) std::fprintf(stderr, " element %d: %g, expected %g\n", i, out[i], ubo[i]); + CHECK(out[i] == ubo[i]); + } + glDeleteVertexArrays(1, &vao); + glDeleteBuffers(2, buffers); + glDeleteProgram(p); +} + +// GL_AMD_pinned_memory: page-aligned application memory as a buffer's +// storage (Wine maps WoW64 guests' buffers this way): the GPU reads what +// the application writes there, and the buffer reads back through it. +GPU_TEST_VERSION(gpu_gl44_amd_pinned_memory, 44) { + const size_t page = static_cast(getpagesize()); + void* memory = nullptr; + CHECK(posix_memalign(&memory, page, page) == 0); + auto* values = static_cast(memory); + for (int i = 0; i < 4; ++i) values[i] = 0.25f * static_cast(i + 1); + GLuint buffer = 0; + glGenBuffers(1, &buffer); + glBindBuffer(GL_EXTERNAL_VIRTUAL_MEMORY_BUFFER_AMD, buffer); + glBufferData(GL_EXTERNAL_VIRTUAL_MEMORY_BUFFER_AMD, 16, memory, GL_DYNAMIC_COPY); + CHECK_GL(); + // Unaligned memory is refused. + GLuint other = 0; + glGenBuffers(1, &other); + glBindBuffer(GL_EXTERNAL_VIRTUAL_MEMORY_BUFFER_AMD, other); + glBufferData(GL_EXTERNAL_VIRTUAL_MEMORY_BUFFER_AMD, 16, static_cast(memory) + 4, GL_DYNAMIC_COPY); + CHECK_EQ(glGetError(), static_cast(GL_INVALID_OPERATION)); + // The pinned buffer as a uniform buffer. + GLuint p = gpu::program("#version 440 core\nlayout(location = 0) in vec2 pos;\nvoid main() { gl_Position = vec4(pos, 0.0, 1.0); }\n", + "#version 440 core\nlayout(std140, binding = 3) uniform U { vec4 color; };\nout vec4 o;\n" + "void main() { o = color; }\n"); + GLuint vao = gpu::quad(); + glBindBufferBase(GL_UNIFORM_BUFFER, 3, buffer); + glUseProgram(p); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + CHECK_GL(); + const uint8_t want[4] = {64, 128, 191, 255}; + CHECK(gpu::near(gpu::readRGBA8(4, 4, 1, 1).data(), want, 2)); + // A write through the GL lands in the application's memory. + const float zero[4] = {0, 0, 0, 0}; + glBindBuffer(GL_UNIFORM_BUFFER, buffer); + glBufferSubData(GL_UNIFORM_BUFFER, 0, 16, zero); + glFinish(); + CHECK(values[0] == 0.0f && values[3] == 0.0f); + glDeleteVertexArrays(1, &vao); + glDeleteProgram(p); + glDeleteBuffers(1, &buffer); + glDeleteBuffers(1, &other); + free(memory); +} + +// GL 4.5 derivative control (GL_ARB_derivative_control): fine and coarse +// derivatives of the window position are one pixel per pixel. +GPU_TEST_VERSION(gpu_gl45_derivative_control, 45) { + GLuint p = gpu::program("#version 450 core\nlayout(location = 0) in vec2 pos;\nvoid main() { gl_Position = vec4(pos, 0.0, 1.0); }\n", + "#version 450 core\nout vec4 o;\nvoid main() {\n" + " o = vec4(dFdxFine(gl_FragCoord.x), dFdyCoarse(gl_FragCoord.y), fwidthFine(gl_FragCoord.x) * 0.5,\n" + " dFdxCoarse(gl_FragCoord.y) + 1.0);\n}\n"); + GLuint vao = gpu::quad(); + glUseProgram(p); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + CHECK_GL(); + const uint8_t want[4] = {255, 255, 128, 255}; + CHECK(gpu::near(gpu::readRGBA8(9, 9, 1, 1).data(), want, 2)); + glDeleteVertexArrays(1, &vao); + glDeleteProgram(p); +} diff --git a/tests/gpu/gl46_tests.cpp b/tests/gpu/gl46_tests.cpp new file mode 100644 index 00000000..49f3776a --- /dev/null +++ b/tests/gpu/gl46_tests.cpp @@ -0,0 +1,419 @@ +// GL 4.6 features (contexts above 4.5 go through the development override +// until the version is advertised). +#include +#include + +#include "gpu.h" + +namespace { + +// A point at the centre of pixel (x, y) of the 64x64 target, its colour the +// draw parameters over 255 (exact in RGBA8). +const char* const kDrawParametersVs = + "#version 460 core\n" + "uniform int row;\n" + "out vec4 color;\n" + "void main() {\n" + " vec2 pixel = vec2(8 + 16 * gl_DrawID, 8 + 16 * row) + 0.5;\n" + " gl_Position = vec4(pixel / 32.0 - 1.0, 0.0, 1.0);\n" + " color = vec4(gl_DrawID, gl_BaseVertex, gl_BaseInstance, 255) / 255.0;\n" + "}\n"; +const char* const kColorFs = "#version 460 core\nin vec4 color;\nout vec4 o;\nvoid main() { o = color; }\n"; + +bool pixelIs(int x, int y, uint8_t r, uint8_t g, uint8_t b, uint8_t a = 255) { + const uint8_t want[4] = {r, g, b, a}; + return gpu::near(gpu::readRGBA8(x, y, 1, 1).data(), want, 0); +} + +} // namespace + +// GL_ARB_shader_draw_parameters: gl_DrawID in multi-draws, gl_BaseVertex of +// an elements draw (0 for array draws) and gl_BaseInstance. +GPU_TEST_VERSION(gpu_gl46_draw_parameters, 46) { + GLuint p = gpu::program(kDrawParametersVs, kColorFs); + glUseProgram(p); + GLuint vao = 0, elements = 0; + glGenVertexArrays(1, &vao); + glBindVertexArray(vao); + glClearColor(0, 0, 0, 0); + glClear(GL_COLOR_BUFFER_BIT); + const GLint row = glGetUniformLocation(p, "row"); + glUniform1i(row, 0); + const GLint first[3] = {0, 0, 0}; + const GLsizei count[3] = {1, 1, 1}; + glMultiDrawArrays(GL_POINTS, first, count, 3); + glUniform1i(row, 1); + glGenBuffers(1, &elements); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elements); + const uint32_t index = 0; + glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(index), &index, GL_STATIC_DRAW); + glDrawElementsBaseVertex(GL_POINTS, 1, GL_UNSIGNED_INT, nullptr, 5); + glUniform1i(row, 2); + glDrawArraysInstancedBaseInstance(GL_POINTS, 0, 1, 1, 3); + CHECK_GL(); + for (int i = 0; i < 3; ++i) CHECK(pixelIs(8 + 16 * i, 8, static_cast(i), 0, 0)); + CHECK(pixelIs(8, 24, 0, 5, 0)); + CHECK(pixelIs(8, 40, 0, 0, 3)); + glDeleteBuffers(1, &elements); + glDeleteVertexArrays(1, &vao); + glDeleteProgram(p); +} + +// GL_ARB_indirect_parameters: the draw count from PARAMETER_BUFFER, clamped +// to maxdrawcount, and the errors. +GPU_TEST_VERSION(gpu_gl46_indirect_count, 46) { + GLuint p = gpu::program(kDrawParametersVs, kColorFs); + glUseProgram(p); + GLuint vao = 0, buffers[2]; + glGenVertexArrays(1, &vao); + glBindVertexArray(vao); + glClearColor(0, 0, 0, 0); + glClear(GL_COLOR_BUFFER_BIT); + glUniform1i(glGetUniformLocation(p, "row"), 0); + glGenBuffers(2, buffers); + const uint32_t commands[4][4] = {{1, 1, 0, 0}, {1, 1, 0, 0}, {1, 1, 0, 0}, {1, 1, 0, 0}}; // count, instances, first, base instance + glBindBuffer(GL_DRAW_INDIRECT_BUFFER, buffers[0]); + glBufferData(GL_DRAW_INDIRECT_BUFFER, sizeof(commands), commands, GL_STATIC_DRAW); + const uint32_t counts[2] = {7, 2}; + glBindBuffer(GL_PARAMETER_BUFFER, buffers[1]); + glBufferData(GL_PARAMETER_BUFFER, sizeof(counts), counts, GL_STATIC_DRAW); + GLint bound = 0; + glGetIntegerv(GL_PARAMETER_BUFFER_BINDING, &bound); + CHECK_EQ(bound, static_cast(buffers[1])); + glMultiDrawArraysIndirectCount(GL_POINTS, nullptr, 4, 3, 0); // 2 draws + CHECK_GL(); + CHECK(pixelIs(8, 8, 0, 0, 0)); + CHECK(pixelIs(24, 8, 1, 0, 0)); + CHECK(pixelIs(40, 8, 0, 0, 0, 0)); + glClear(GL_COLOR_BUFFER_BIT); + glMultiDrawArraysIndirectCount(GL_POINTS, nullptr, 0, 3, 0); // 7 clamped to 3 + CHECK(pixelIs(40, 8, 2, 0, 0)); + CHECK(pixelIs(56, 8, 0, 0, 0, 0)); + glMultiDrawArraysIndirectCount(GL_POINTS, nullptr, 2, 3, 0); + CHECK_EQ(glGetError(), static_cast(GL_INVALID_VALUE)); + glMultiDrawArraysIndirectCount(GL_POINTS, nullptr, 0, 5, 0); // 5 commands overrun the indirect buffer + CHECK_EQ(glGetError(), static_cast(GL_INVALID_OPERATION)); + glBindBuffer(GL_PARAMETER_BUFFER, 0); + glMultiDrawArraysIndirectCount(GL_POINTS, nullptr, 0, 3, 0); + CHECK_EQ(glGetError(), static_cast(GL_INVALID_OPERATION)); + glDeleteBuffers(2, buffers); + glDeleteVertexArrays(1, &vao); + glDeleteProgram(p); +} + +// GL_ARB_transform_feedback_overflow_query: overflow only when a primitive +// does not fit. +GPU_TEST_VERSION(gpu_gl46_transform_feedback_overflow, 46) { + GLuint p = gpu::program("#version 460 core\nlayout(xfb_buffer = 0, xfb_offset = 0) out float v;\n" + "void main() { v = float(gl_VertexID); gl_Position = vec4(0.0, 0.0, 0.0, 1.0); }\n", + "#version 460 core\nout vec4 o;\nvoid main() { o = vec4(1.0); }\n"); + glUseProgram(p); + GLuint vao = 0, buffer = 0, queries[2]; + glGenVertexArrays(1, &vao); + glBindVertexArray(vao); + glGenBuffers(1, &buffer); + glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, buffer); + glBufferData(GL_TRANSFORM_FEEDBACK_BUFFER, 12, nullptr, GL_DYNAMIC_COPY); // room for 3 points + glGenQueries(2, queries); + glEnable(GL_RASTERIZER_DISCARD); + glBeginTransformFeedback(GL_POINTS); + glBeginQuery(GL_TRANSFORM_FEEDBACK_OVERFLOW, queries[0]); + glDrawArrays(GL_POINTS, 0, 2); + glEndQuery(GL_TRANSFORM_FEEDBACK_OVERFLOW); + glBeginQueryIndexed(GL_TRANSFORM_FEEDBACK_STREAM_OVERFLOW, 0, queries[1]); + glDrawArrays(GL_POINTS, 0, 2); // one of the two fits + glEndQueryIndexed(GL_TRANSFORM_FEEDBACK_STREAM_OVERFLOW, 0); + glEndTransformFeedback(); + glDisable(GL_RASTERIZER_DISCARD); + CHECK_GL(); + GLuint overflow[2] = {9, 9}; + glGetQueryObjectuiv(queries[0], GL_QUERY_RESULT, &overflow[0]); + glGetQueryObjectuiv(queries[1], GL_QUERY_RESULT, &overflow[1]); + CHECK_EQ(overflow[0], 0u); + CHECK_EQ(overflow[1], 1u); + glBeginQueryIndexed(GL_TRANSFORM_FEEDBACK_OVERFLOW, 1, queries[0]); + CHECK_EQ(glGetError(), static_cast(GL_INVALID_VALUE)); + glDeleteQueries(2, queries); + glDeleteBuffers(1, &buffer); + glDeleteVertexArrays(1, &vao); + glDeleteProgram(p); +} + +// GL_ARB_pipeline_statistics_query: submitted vertices and primitives, +// clipping input, fragment shader invocations of a fullscreen quad, compute +// shader invocations of a dispatch. +GPU_TEST_VERSION(gpu_gl46_pipeline_statistics, 46) { + GLuint p = gpu::program("#version 460 core\nlayout(location = 0) in vec2 position;\n" + "void main() { gl_Position = vec4(position, 0.0, 1.0); }\n", + "#version 460 core\nout vec4 o;\nvoid main() { o = vec4(1.0); }\n"); + glUseProgram(p); + GLuint vao = gpu::quad(); + const GLenum targets[5] = {GL_VERTICES_SUBMITTED, GL_PRIMITIVES_SUBMITTED, GL_VERTEX_SHADER_INVOCATIONS, + GL_CLIPPING_INPUT_PRIMITIVES, GL_FRAGMENT_SHADER_INVOCATIONS}; + const GLuint64 want[5] = {4, 2, 4, 2, 64 * 64}; + for (int i = 0; i < 5; ++i) { + GLuint query = 0; + glGenQueries(1, &query); + glBeginQuery(targets[i], query); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + glEndQuery(targets[i]); + GLuint64 result = 0; + glGetQueryObjectui64v(query, GL_QUERY_RESULT, &result); + CHECK_EQ(result, want[i]); + glDeleteQueries(1, &query); + } + CHECK_GL(); + GLuint cs = glCreateShader(GL_COMPUTE_SHADER); + const char* source = "#version 460 core\nlayout(local_size_x = 8, local_size_y = 2) in;\nvoid main() {}\n"; + glShaderSource(cs, 1, &source, nullptr); + glCompileShader(cs); + GLuint compute = glCreateProgram(); + glAttachShader(compute, cs); + glLinkProgram(compute); + glUseProgram(compute); + GLuint query = 0; + glGenQueries(1, &query); + glBeginQuery(GL_COMPUTE_SHADER_INVOCATIONS, query); + glDispatchCompute(3, 1, 1); + glEndQuery(GL_COMPUTE_SHADER_INVOCATIONS); + GLuint64 result = 0; + glGetQueryObjectui64v(query, GL_QUERY_RESULT, &result); + CHECK_EQ(result, static_cast(3 * 8 * 2)); + CHECK_GL(); + glDeleteQueries(1, &query); + glDeleteProgram(compute); + glDeleteShader(cs); + glDeleteVertexArrays(1, &vao); + glDeleteProgram(p); +} + +// GL_ARB_shader_atomic_counter_ops in a vertex shader with two counter +// buffers (the vertex stage keeps them in its argument buffer). +GPU_TEST_VERSION(gpu_gl46_atomic_counter_ops, 46) { + GLuint p = gpu::program("#version 460 core\n" + "layout(binding = 0) uniform atomic_uint sum;\n" + "layout(binding = 1, offset = 4) uniform atomic_uint highest;\n" + "void main() {\n" + " atomicCounterAdd(sum, 2u);\n" + " atomicCounterMax(highest, uint(gl_VertexID) * 10u);\n" + " gl_Position = vec4(0.0, 0.0, 0.0, 1.0);\n" + "}\n", + "#version 460 core\nout vec4 o;\nvoid main() { o = vec4(1.0); }\n"); + glUseProgram(p); + GLuint vao = 0, buffers[2]; + glGenVertexArrays(1, &vao); + glBindVertexArray(vao); + glGenBuffers(2, buffers); + const uint32_t zero[2] = {0, 0}; + for (int i = 0; i < 2; ++i) { + glBindBufferBase(GL_ATOMIC_COUNTER_BUFFER, static_cast(i), buffers[i]); + glBufferData(GL_ATOMIC_COUNTER_BUFFER, sizeof(zero), zero, GL_DYNAMIC_COPY); + } + GLint limit = 0; + glGetIntegerv(GL_MAX_VERTEX_ATOMIC_COUNTER_BUFFERS, &limit); + CHECK(limit >= 2); + glDrawArrays(GL_POINTS, 0, 4); + CHECK_GL(); + uint32_t sum = 0, highest[2] = {}; + glBindBuffer(GL_ATOMIC_COUNTER_BUFFER, buffers[0]); + glGetBufferSubData(GL_ATOMIC_COUNTER_BUFFER, 0, 4, &sum); + glBindBuffer(GL_ATOMIC_COUNTER_BUFFER, buffers[1]); + glGetBufferSubData(GL_ATOMIC_COUNTER_BUFFER, 0, 8, highest); + CHECK_EQ(sum, 8u); + CHECK_EQ(highest[1], 30u); + glDeleteBuffers(2, buffers); + glDeleteVertexArrays(1, &vao); + glDeleteProgram(p); +} + +// GL_ARB_shader_group_vote: votes every invocation agrees on. +GPU_TEST_VERSION(gpu_gl46_group_vote, 46) { + GLuint p = gpu::program("#version 460 core\nlayout(location = 0) in vec2 position;\n" + "void main() { gl_Position = vec4(position, 0.0, 1.0); }\n", + "#version 460 core\nout vec4 o;\nvoid main() {\n" + " bool inside = gl_FragCoord.x >= 0.0;\n" + " o = vec4(anyInvocation(inside), allInvocations(inside), allInvocationsEqual(inside),\n" + " anyInvocation(!inside) ? 0.0 : 1.0);\n}\n"); + glUseProgram(p); + GLuint vao = gpu::quad(); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + CHECK_GL(); + CHECK(pixelIs(5, 50, 255, 255, 255)); + glDeleteVertexArrays(1, &vao); + glDeleteProgram(p); +} + +// Atomic counter buffers report the stages that use them, the geometry +// stage included (its emulation runs as a compute kernel). +GPU_TEST_VERSION(gpu_gl46_atomic_counter_buffer_stages, 46) { + const char* const common = "layout(binding = 0) uniform atomic_uint horizontal;\n" + "layout(binding = 1) uniform atomic_uint vertical;\n"; + const std::string vs = std::string("#version 460 core\n") + common + + "void main() { atomicCounterIncrement(vertical); atomicCounterIncrement(horizontal);\n" + " gl_Position = vec4(0.0, 0.0, 0.0, 1.0); }\n"; + const std::string gs = std::string("#version 460 core\nlayout(points) in;\nlayout(points, max_vertices = 1) out;\n") + common + + "out vec4 color;\n" + "void main() { color = vec4(atomicCounter(vertical)); gl_Position = gl_in[0].gl_Position; EmitVertex(); }\n"; + const char* const fs = "#version 460 core\nin vec4 color;\nout vec4 o;\nvoid main() { o = color; }\n"; + GLuint p = glCreateProgram(); + const GLenum types[3] = {GL_VERTEX_SHADER, GL_GEOMETRY_SHADER, GL_FRAGMENT_SHADER}; + const char* sources[3] = {vs.c_str(), gs.c_str(), fs}; + for (int i = 0; i < 3; ++i) { + GLuint s = glCreateShader(types[i]); + glShaderSource(s, 1, &sources[i], nullptr); + glCompileShader(s); + glAttachShader(p, s); + glDeleteShader(s); + } + glLinkProgram(p); + GLint linked = 0; + glGetProgramiv(p, GL_LINK_STATUS, &linked); + CHECK(linked); + GLint buffers = 0; + glGetProgramInterfaceiv(p, GL_ATOMIC_COUNTER_BUFFER, GL_ACTIVE_RESOURCES, &buffers); + CHECK_EQ(buffers, 2); + for (GLint i = 0; i < buffers; ++i) { + const GLenum props[3] = {GL_BUFFER_BINDING, GL_REFERENCED_BY_VERTEX_SHADER, GL_REFERENCED_BY_GEOMETRY_SHADER}; + GLint values[3] = {-1, -1, -1}; + glGetProgramResourceiv(p, GL_ATOMIC_COUNTER_BUFFER, static_cast(i), 3, props, 3, nullptr, values); + CHECK_EQ(values[1], 1); + CHECK_EQ(values[2], values[0] == 1 ? 1 : 0); // the geometry shader reads only `vertical` + } + CHECK_GL(); + glDeleteProgram(p); +} + +// More images in one stage than Metal's 8 read_write textures: images the +// shader only reads or only writes (no qualifiers) are read-only or +// write-only Metal textures; one read and written atomically is read_write. +GPU_TEST_VERSION(gpu_gl46_many_images, 46) { + const char* const source = "#version 460 core\n" + "layout(local_size_x = 1) in;\n" + "layout(binding = 0, r32ui) uniform uimage2D sources[6];\n" + "layout(binding = 6, r32ui) uniform uimage2D results[6];\n" + "layout(binding = 12, r32ui) uniform uimage2D total;\n" + "void main() {\n" + " uint sum = 0u;\n" + " for (int i = 0; i < 6; ++i) {\n" + " uint v = imageLoad(sources[i], ivec2(0)).x;\n" + " imageStore(results[i], ivec2(0), uvec4(v * 2u));\n" + " sum += v;\n" + " }\n" + " imageAtomicAdd(total, ivec2(0), sum);\n" + "}\n"; + GLint units = 0, perStage = 0; + glGetIntegerv(GL_MAX_IMAGE_UNITS, &units); + glGetIntegerv(GL_MAX_COMPUTE_IMAGE_UNIFORMS, &perStage); + CHECK(units >= 13); + CHECK_EQ(perStage, 8); + GLuint p = glCreateShaderProgramv(GL_COMPUTE_SHADER, 1, &source); + GLint linked = 0; + glGetProgramiv(p, GL_LINK_STATUS, &linked); + if (!linked) { + char log[2048] = {}; + glGetProgramInfoLog(p, sizeof(log), nullptr, log); + std::fprintf(stderr, " link failed:\n%s\n", log); + } + CHECK(linked); + GLuint textures[13]; + glGenTextures(13, textures); + for (GLuint i = 0; i < 13; ++i) { + glBindTexture(GL_TEXTURE_2D, textures[i]); + glTexStorage2D(GL_TEXTURE_2D, 1, GL_R32UI, 1, 1); + const GLuint value = i < 6 ? i + 1 : i == 12 ? 5 : 0; + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 1, 1, GL_RED_INTEGER, GL_UNSIGNED_INT, &value); + } + glBindImageTextures(0, 13, textures); + glUseProgram(p); + glDispatchCompute(1, 1, 1); + glMemoryBarrier(GL_TEXTURE_UPDATE_BARRIER_BIT); + CHECK_GL(); + for (GLuint i = 6; i < 13; ++i) { + GLuint value = 0; + glBindTexture(GL_TEXTURE_2D, textures[i]); + glGetTexImage(GL_TEXTURE_2D, 0, GL_RED_INTEGER, GL_UNSIGNED_INT, &value); + CHECK_EQ(value, i < 12 ? 2 * (i - 5) : 26u); + } + glDeleteTextures(13, textures); + glDeleteProgram(p); +} + +// GL_ARB_shader_viewport_layer_array: a vertex shader picks the layer of a +// layered framebuffer and the viewport. +GPU_TEST_VERSION(gpu_gl46_vertex_layer_viewport, 46) { + GLuint p = gpu::program("#version 460 core\n#extension GL_ARB_shader_viewport_layer_array : require\n" + "layout(location = 0) in vec2 position;\n" + "uniform int layer;\n" + "void main() {\n" + " gl_Position = vec4(position, 0.0, 1.0);\n" + " gl_Layer = layer;\n" + " gl_ViewportIndex = layer;\n" + "}\n", + "#version 460 core\nuniform vec4 color;\nout vec4 o;\nvoid main() { o = color; }\n"); + glUseProgram(p); + GLuint tex = 0, fbo = 0; + glGenTextures(1, &tex); + glBindTexture(GL_TEXTURE_2D_ARRAY, tex); + glTexStorage3D(GL_TEXTURE_2D_ARRAY, 1, GL_RGBA8, 64, 64, 2); + glGenFramebuffers(1, &fbo); + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex, 0); + CHECK_EQ(glCheckFramebufferStatus(GL_FRAMEBUFFER), static_cast(GL_FRAMEBUFFER_COMPLETE)); + glClearColor(0, 0, 0, 0); + glClear(GL_COLOR_BUFFER_BIT); + // Viewport 0 is the left half, viewport 1 the right half. + glViewportIndexedf(0, 0, 0, 32, 64); + glViewportIndexedf(1, 32, 0, 32, 64); + GLuint vao = gpu::quad(); + const GLint layer = glGetUniformLocation(p, "layer"), color = glGetUniformLocation(p, "color"); + glUniform1i(layer, 0); + glUniform4f(color, 1, 0, 0, 1); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + glUniform1i(layer, 1); + glUniform4f(color, 0, 1, 0, 1); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + CHECK_GL(); + for (int l = 0; l < 2; ++l) { + glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex, 0, l); + CHECK(pixelIs(16, 32, l == 0 ? 255 : 0, 0, 0, l == 0 ? 255 : 0)); + CHECK(pixelIs(48, 32, 0, l == 1 ? 255 : 0, 0, l == 1 ? 255 : 0)); + } + glBindFramebuffer(GL_FRAMEBUFFER, 0); + glDeleteFramebuffers(1, &fbo); + glDeleteTextures(1, &tex); + glDeleteVertexArrays(1, &vao); + glDeleteProgram(p); +} + +// GL_EXT_texture_shadow_lod: textureLod and texture with a bias on a 2D +// array shadow sampler. +GPU_TEST_VERSION(gpu_gl46_texture_shadow_lod, 46) { + GLuint p = gpu::program("#version 460 core\nlayout(location = 0) in vec2 position;\n" + "void main() { gl_Position = vec4(position, 0.0, 1.0); }\n", + "#version 460 core\n#extension GL_EXT_texture_shadow_lod : require\n" + "uniform sampler2DArrayShadow t;\nout vec4 o;\n" + "void main() {\n" + " float lod = textureLod(t, vec4(0.5, 0.5, 1.0, 0.5), 0.0);\n" + " float biased = texture(t, vec4(0.5, 0.5, 0.0, 0.5), 0.0);\n" + " o = vec4(lod, biased, 0.0, 1.0);\n" + "}\n"); + glUseProgram(p); + GLuint tex = 0; + glGenTextures(1, &tex); + glBindTexture(GL_TEXTURE_2D_ARRAY, tex); + // Layer 0 at depth 0.25, layer 1 at 0.75: a reference of 0.5 passes + // GL_LEQUAL against layer 1 only. + const float depths[2] = {0.25f, 0.75f}; + glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_DEPTH_COMPONENT32F, 1, 1, 2, 0, GL_DEPTH_COMPONENT, GL_FLOAT, depths); + glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE); + glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL); + GLuint vao = gpu::quad(); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + CHECK_GL(); + CHECK(pixelIs(32, 32, 255, 0, 0)); + glDeleteVertexArrays(1, &vao); + glDeleteTextures(1, &tex); + glDeleteProgram(p); +} diff --git a/tests/gpu/gpu.h b/tests/gpu/gpu.h index d3f8ea2e..1b4841d1 100644 --- a/tests/gpu/gpu.h +++ b/tests/gpu/gpu.h @@ -61,7 +61,20 @@ X(glClearBufferData) X(glClearBufferSubData) X(glFramebufferParameteri) X(glGetFramebufferParameteriv) \ X(glGetInternalformativ) X(glGetInternalformati64v) X(glBlendFunc) X(glGetSubroutineIndex) \ X(glUniformSubroutinesuiv) X(glGetSubroutineUniformLocation) X(glGetProgramResourceIndex) \ - X(glGetProgramResourceiv) X(glTexParameterIiv) X(glTexParameterIuiv) + X(glGetProgramResourceiv) X(glTexParameterIiv) X(glTexParameterIuiv) WMGL_GPU_FUNCS_GL44(X) + +// GL 4.4-4.6 entry points (gl44_tests.cpp and later). +#define WMGL_GPU_FUNCS_GL44(X) \ + X(glClearTexImage) X(glClearTexSubImage) X(glBindBuffersBase) X(glBindBuffersRange) X(glBindTextures) \ + X(glBindSamplers) X(glBindImageTextures) X(glBindVertexBuffers) X(glGetInteger64i_v) X(glGetActiveUniformBlockiv) \ + X(glCreateBuffers) X(glNamedBufferData) X(glNamedBufferSubData) X(glCreateTextures) X(glTextureStorage2D) \ + X(glTextureSubImage2D) X(glGetTextureImage) X(glCreateFramebuffers) X(glNamedFramebufferTexture) \ + X(glCheckNamedFramebufferStatus) X(glClearNamedFramebufferfv) X(glCreateVertexArrays) X(glVertexArrayVertexBuffer) \ + X(glVertexArrayAttribFormat) X(glVertexArrayAttribBinding) X(glEnableVertexArrayAttrib) X(glBindTextureUnit) \ + X(glTextureParameteri) X(glGetNamedBufferSubData) X(glBlitNamedFramebuffer) X(glGetTextureSubImage) \ + X(glGetCompressedTextureSubImage) X(glTextureStorage3D) X(glTextureSubImage3D) X(glVertexAttrib4fv) \ + X(glMultiDrawArrays) X(glMultiDrawArraysIndirectCount) X(glDrawElementsBaseVertex) X(glBeginQueryIndexed) \ + X(glEndQueryIndexed) X(glGetProgramInterfaceiv) #define WMGL_DECLARE_FUNC(name) extern PFN_##name name; WMGL_GPU_FUNCS(WMGL_DECLARE_FUNC) @@ -70,7 +83,7 @@ WMGL_GPU_FUNCS(WMGL_DECLARE_FUNC) namespace gpu { // A context current on a pbuffer for the lifetime of the fixture, requesting -// `version` (10 * major + minor); versions above 4.3 go through the +// `version` (10 * major + minor); versions above 4.6 go through the // development override (WINEMETALGL_DEV_MAX_VERSION). struct Fixture { explicit Fixture(int width = 64, int height = 64, bool core = true, int version = 33); diff --git a/tests/gpu/main.cpp b/tests/gpu/main.cpp index d0f89592..916f6fa6 100644 --- a/tests/gpu/main.cpp +++ b/tests/gpu/main.cpp @@ -53,13 +53,13 @@ Fixture::Fixture(int width, int height, bool core, int version) { const int attribs[] = {0x2091 /* MAJOR */, version / 10, 0x2092 /* MINOR */, version % 10, 0x9126 /* PROFILE_MASK */, core ? 1 : 2, 0}; int error = 0; - if (version > 43) { + if (version > 46) { char value[8]; std::snprintf(value, sizeof(value), "%d.%d", version / 10, version % 10); setenv("WINEMETALGL_DEV_MAX_VERSION", value, 1); } context = wmgl_context_create(format, nullptr, attribs, &error); - if (version > 43) unsetenv("WINEMETALGL_DEV_MAX_VERSION"); + if (version > 46) unsetenv("WINEMETALGL_DEV_MAX_VERSION"); ok = surface && context && wmgl_make_current(context, surface, surface); if (ok) glViewport(0, 0, width, height); } diff --git a/tests/host/shader_tests.cpp b/tests/host/shader_tests.cpp index 0082b38f..933999be 100644 --- a/tests/host/shader_tests.cpp +++ b/tests/host/shader_tests.cpp @@ -398,13 +398,13 @@ TEST(shader_glsl420_pack_rules) { "out vec4 o; void main() { o = v; }\n").ok); CHECK(!compile(Stage::Vertex, "#version 420\ninvariant in vec4 v; void main() { gl_Position = v; }\n").ok); // Image bindings name image units; block bindings stay below GL_MAX_UNIFORM_BUFFER_BINDINGS. - CHECK(compile(Stage::Fragment, "#version 420\nlayout(binding = 7, rgba8) readonly uniform image2D i;\n" + CHECK(compile(Stage::Fragment, "#version 420\nlayout(binding = 15, rgba8) readonly uniform image2D i;\n" "out vec4 o; void main() { o = imageLoad(i, ivec2(0)); }\n").ok); - CHECK(!compile(Stage::Fragment, "#version 420\nlayout(binding = 8, rgba8) readonly uniform image2D i;\n" + CHECK(!compile(Stage::Fragment, "#version 420\nlayout(binding = 16, rgba8) readonly uniform image2D i;\n" "out vec4 o; void main() { o = imageLoad(i, ivec2(0)); }\n").ok); - CHECK(compile(Stage::Fragment, "#version 420\nlayout(std140, binding = 70) uniform B { vec4 a; } b[2];\n" + CHECK(compile(Stage::Fragment, "#version 420\nlayout(std140, binding = 82) uniform B { vec4 a; } b[2];\n" "out vec4 o; void main() { o = b[1].a; }\n").ok); - CHECK(!compile(Stage::Fragment, "#version 420\nlayout(std140, binding = 70) uniform B { vec4 a; } b[3];\n" + CHECK(!compile(Stage::Fragment, "#version 420\nlayout(std140, binding = 82) uniform B { vec4 a; } b[3];\n" "out vec4 o; void main() { o = b[2].a; }\n").ok); } @@ -550,3 +550,16 @@ TEST(shader_vertex_input_matrix_array) { CHECK(r.ok); CHECK(r.stages[0].msl.find("m_3 [[attribute(") != std::string::npos); } + +// GLSL 4.30: an array of arrays with an initializer may leave any dimension +// unsized; the initializer sizes them. +TEST(shader_unsized_arrays_of_arrays_with_initializer) { + const char* src = + "#version 430 core\n" + "uniform int x[2][][2][] = int[2][2][2][2](int[2][2][2](int[2][2](int[2](1, 2), int[2](3, 4)), int[2][2](int[2](5, 6), " + "int[2](7, 8))), int[2][2][2](int[2][2](int[2](11, 12), int[2](13, 14)), int[2][2](int[2](15, 16), int[2](17, 18))));\n" + "void main() { gl_Position = vec4(float(x[1][1][1][1])); }\n"; + const CompileResult r = compile(Stage::Vertex, src); + if (!r.ok) std::fprintf(stderr, "%s\n", r.log.c_str()); + CHECK(r.ok); +} diff --git a/tests/wine/fixture_version.h b/tests/wine/fixture_version.h new file mode 100644 index 00000000..ad87645e --- /dev/null +++ b/tests/wine/fixture_version.h @@ -0,0 +1,5 @@ +/* The version every context gets: the highest WineMetalGL reports, which is + backward compatible with any request (WGL_ARB_create_context). Raised with + each version flip. */ +#define WINEMETALGL_FIXTURE_GL_VERSION "4.6" +#define WINEMETALGL_FIXTURE_GLSL_VERSION "4.60" diff --git a/tests/wine/fixtures.txt b/tests/wine/fixtures.txt index 01e35ee2..49cab6f2 100644 --- a/tests/wine/fixtures.txt +++ b/tests/wine/fixtures.txt @@ -1,8 +1,7 @@ # Wine fixtures: run by tests/wine/probe-opengl-wow64.sh in one WoW64 prefix, x86_64 and i386 guests. # -# scope 'gate': part of the merge gate (contexts up to GL 4.3). 'gl4': features of GL 4.4 and -# later, kept for the later milestones and run only with --all. Gate fixtures load the entry -# points of those versions optionally; built with -DWINEMETALGL_GL4 they require them. +# scope 'gate': part of the merge gate. Gate fixtures load the entry points of GL 4.4 and later +# optionally; built with -DWINEMETALGL_GL4 they require them. # opengl_load_probe and opengl_runtime write their marker to the file named by their argument. opengl_load_probe - gate opengl_runtime - gate OPENGL_RUNTIME_ALL_OK @@ -13,35 +12,36 @@ opengl_pipeline - gate WINEMETALGL_PROGRAM_PIPELINE_OK WINEMETALGL_PROGRAM_PIPEL opengl_interface - gate WINEMETALGL_INTERFACE_REJECT_OK opengl_multi_draw - gate WINEMETALGL_MULTI_DRAW_OK opengl_gl33_metal - gate OPENGL_GL330_METAL_DRAW_READBACK_OK WINEMETALGL_RESIZE_PRESENT_OK WINEMETALGL_SHADING_LANGUAGE_VERSION_OK WINEMETALGL_STATE_QUERY_OK WINEMETALGL_ROBUSTNESS_OK WINEMETALGL_FRAG_DATA_LOCATION_OK WINEMETALGL_RASTER_STATE_OK WINEMETALGL_CLIP_CONTROL_OK -opengl_gl33_metal -DWINEMETALGL_GLSL450 gl4 OPENGL_GL450_METAL_DRAW_READBACK_OK +opengl_gl33_metal -DWINEMETALGL_GLSL450 gate OPENGL_GL450_METAL_DRAW_READBACK_OK opengl_glsl120 - gate WINEMETALGL_GLSL120_OK opengl_glsl110 - gate WINEMETALGL_GLSL110_OK opengl_gl33_resources - gate WINEMETALGL_GL33_RESOURCES_OK WINEMETALGL_BUFFER_MAP_RANGE_OK WINEMETALGL_BUFFER_STORAGE_OK WINEMETALGL_BUFFER_POINTER_QUERY_OK WINEMETALGL_UNIFORM_ARRAY_REFLECTION_OK WINEMETALGL_VERTEX_ATTRIB_QUERY_OK WINEMETALGL_PBO_READBACK_OK -opengl_gl33_resources -DWINEMETALGL_GL4 gl4 WINEMETALGL_MULTI_BIND_VERTEX_BUFFERS_OK +opengl_gl33_resources -DWINEMETALGL_GL4 gate WINEMETALGL_MULTI_BIND_VERTEX_BUFFERS_OK opengl_gl33_texture - gate WINEMETALGL_UNPACK_ALIGNMENT_OK WINEMETALGL_INTERNAL_FORMAT_QUERY_OK WINEMETALGL_MIPMAP_OK WINEMETALGL_GL33_TEXTURE_OK WINEMETALGL_TEXTURE_PACKED_OK WINEMETALGL_TEXTURE_USHORT_OK WINEMETALGL_TEXTURE_HALF_FLOAT_OK WINEMETALGL_TEXTURE_RGBA16F_OK WINEMETALGL_TEXTURE_RGBA32F_OK WINEMETALGL_TEXTURE_RGBA8UI_OK WINEMETALGL_TEXTURE_SRGB_OK WINEMETALGL_TEXTURE_RED_OK WINEMETALGL_TEXTURE_RG_OK WINEMETALGL_TEXTURE_LUMINANCE_OK WINEMETALGL_READBACK_PACKED_OK WINEMETALGL_READBACK_HALF_FLOAT_OK WINEMETALGL_PACK_ALIGNMENT_OK WINEMETALGL_TEXTURE_BGRA_OK WINEMETALGL_TEXTURE_SAMPLER_PARAMS_OK WINEMETALGL_TEXTURE_SAMPLER_QUERY_OK WINEMETALGL_TEXTURE_SWIZZLE_OK WINEMETALGL_COPY_IMAGE_OK WINEMETALGL_COPY_TEX_OK WINEMETALGL_TEX_STORAGE_OK WINEMETALGL_SAMPLER_ADVANCED_OK -opengl_gl33_texture -DWINEMETALGL_GL4 gl4 WINEMETALGL_MULTI_BIND_TEXTURES_OK WINEMETALGL_DSA_TEXTURE_OK WINEMETALGL_TEXTURE_VIEW_OK +opengl_gl33_texture -DWINEMETALGL_GL4 gate WINEMETALGL_MULTI_BIND_TEXTURES_OK WINEMETALGL_DSA_TEXTURE_OK WINEMETALGL_TEXTURE_VIEW_OK opengl_texture_integer - gate WINEMETALGL_INTEGER_TEXTURE_SAMPLE_OK opengl_texture_half_sample - gate WINEMETALGL_HALF_FLOAT_TEXTURE_SAMPLE_OK opengl_texture_float_sample - gate WINEMETALGL_FLOAT_TEXTURE_SAMPLE_OK opengl_texture_depth_sample - gate WINEMETALGL_DEPTH_TEXTURE_SAMPLE_OK opengl_texture_r32ui - gate WINEMETALGL_R32UI_TEXTURE_SAMPLE_OK opengl_gl33_fbo - gate WINEMETALGL_FBO_OK WINEMETALGL_FBO_ATTACHMENT_QUERY_OK WINEMETALGL_FBO_DEPTH_TEXTURE_OK WINEMETALGL_FBO_MIXED_DIMENSIONS_OK WINEMETALGL_FBO_DEPTH_READBACK_OK WINEMETALGL_FBO_DEPTH_TEXTURE_READBACK_OK WINEMETALGL_FBO_STENCIL_READBACK_OK WINEMETALGL_FBO_STENCIL_TEXTURE_READBACK_OK WINEMETALGL_FBO_ARRAY_LAYER_OK WINEMETALGL_BLIT_OK WINEMETALGL_INVALIDATE_FBO_OK -opengl_gl33_fbo -DWINEMETALGL_GL4 gl4 WINEMETALGL_FBO_DSA_OK +opengl_gl33_fbo -DWINEMETALGL_GL4 gate WINEMETALGL_FBO_DSA_OK opengl_renderbuffer - gate WINEMETALGL_RENDERBUFFER_OK opengl_multisample - gate WINEMETALGL_MULTISAMPLE_OK WINEMETALGL_MULTISAMPLE_STORAGE_OK opengl_sync - gate WINEMETALGL_SYNC_OK +opengl_buffer_storage - gate WINEMETALGL_BUFFER_STORAGE_OK WINEMETALGL_PINNED_MEMORY_OK opengl_later_entry_points - gate WINEMETALGL_LATER_ENTRY_POINTS_OK opengl_ubo - gate WINEMETALGL_UBO_OK WINEMETALGL_BUFFER_SIZE_OK WINEMETALGL_UBO_RANGE_OK WINEMETALGL_UBO_REFLECTION_OK -opengl_ubo -DWINEMETALGL_GL4 gl4 WINEMETALGL_MULTI_BIND_BUFFERS_OK +opengl_ubo -DWINEMETALGL_GL4 gate WINEMETALGL_MULTI_BIND_BUFFERS_OK opengl_uniform_matrix - gate WINEMETALGL_UNIFORM_MATRIX_ARRAY_OK WINEMETALGL_UNIFORM_MATRIX_DRAW_OK opengl_query - gate WINEMETALGL_QUERY_OK -opengl_query -DWINEMETALGL_GL4 gl4 WINEMETALGL_QUERY_BUFFER_OK +opengl_query -DWINEMETALGL_GL4 gate WINEMETALGL_QUERY_BUFFER_OK opengl_transform - gate WINEMETALGL_TRANSFORM_FIXED_OK opengl_transform_shader - gate WINEMETALGL_TRANSFORM_SHADER_ELEMENTS_OK WINEMETALGL_TRANSFORM_SHADER_RANGE_OK opengl_transform_varying - gate WINEMETALGL_TRANSFORM_SHADER_VARYING_OK opengl_transform_separate - gate WINEMETALGL_TRANSFORM_SHADER_SEPARATE_OK WINEMETALGL_TRANSFORM_SHADER_SEPARATE_ELEMENTS_OK opengl_shader_binary - gate WINEMETALGL_PROGRAM_BINARY_OK WINEMETALGL_PROGRAM_BINARY_DRAW_OK -opengl_shader_binary -DWINEMETALGL_GL4 gl4 WINEMETALGL_SHADER_BINARY_OK WINEMETALGL_SHADER_SPECIALIZE_OK +opengl_shader_binary -DWINEMETALGL_GL4 gate WINEMETALGL_SHADER_BINARY_OK WINEMETALGL_SHADER_SPECIALIZE_OK opengl_debug - gate WINEMETALGL_DEBUG_OUTPUT_OK opengl_gl42 - gate WINEMETALGL_GL42_CONTEXT_OK WINEMETALGL_GL40_DRAW_INDIRECT_OK WINEMETALGL_GL40_SUBROUTINE_OK WINEMETALGL_GL40_FP64_OK WINEMETALGL_GL41_VIEWPORT_ARRAY_OK WINEMETALGL_GL42_IMAGE_ATOMIC_OK opengl_gl43 - gate WINEMETALGL_GL43_CONTEXT_OK WINEMETALGL_GL43_MULTI_DRAW_INDIRECT_OK WINEMETALGL_GL43_SSBO_FRAGMENT_OK WINEMETALGL_GL43_TEXTURE_VIEW_OK WINEMETALGL_GL43_ES3_SHADER_OK @@ -58,14 +58,14 @@ opengl_clear_metal - gate WINEMETALGL_CLEAR_OK WINEMETALGL_CLEAR_BUFFER_OK opengl_gl33_instanced - gate WINEMETALGL_INSTANCED_OK opengl_compute - gate WINEMETALGL_COMPUTE_OK WINEMETALGL_SSBO_REFLECTION_OK WINEMETALGL_RESOURCE_REFLECTION_OK WINEMETALGL_COMPUTE_RANGE_OK WINEMETALGL_COMPUTE_INDIRECT_OK opengl_image - gate WINEMETALGL_IMAGE_OK -opengl_image_formats - gl4 WINEMETALGL_MULTI_BIND_IMAGES_OK WINEMETALGL_IMAGE_R32UI_OK +opengl_image_formats - gate WINEMETALGL_MULTI_BIND_IMAGES_OK WINEMETALGL_IMAGE_R32UI_OK opengl_image_r32f - gate WINEMETALGL_IMAGE_R32F_OK -opengl_image_rgba8ui - gl4 WINEMETALGL_IMAGE_RGBA8UI_OK +opengl_image_rgba8ui - gate WINEMETALGL_IMAGE_RGBA8UI_OK opengl_image_rgba16f - gate WINEMETALGL_IMAGE_RGBA16F_OK opengl_cube - gate WINEMETALGL_TEXTURE_CUBE_OK WINEMETALGL_TEXTURE_CUBE_QUERY_OK opengl_rectangle - gate WINEMETALGL_TEXTURE_RECTANGLE_OK opengl_1d - gate WINEMETALGL_TEXTURE_1D_OK -opengl_indirect - gl4 WINEMETALGL_INDIRECT_OK WINEMETALGL_INDIRECT_COUNT_OK WINEMETALGL_MULTI_INDIRECT_OK +opengl_indirect - gate WINEMETALGL_INDIRECT_OK WINEMETALGL_INDIRECT_COUNT_OK WINEMETALGL_MULTI_INDIRECT_OK opengl_indirect_elements - gate WINEMETALGL_MULTI_ELEMENTS_INDIRECT_OK opengl_fixed - gate WINEMETALGL_FIXED_OK WINEMETALGL_FIXED_LIST_MATRIX_OK WINEMETALGL_FIXED_LIGHTING_OK WINEMETALGL_FIXED_SPECULAR_OK WINEMETALGL_FIXED_FOG_OK WINEMETALGL_CLIP_PLANE_OK opengl_fixed_point - gate WINEMETALGL_FIXED_POINT_SIZE_OK diff --git a/tests/wine/opengl_buffer_storage.c b/tests/wine/opengl_buffer_storage.c new file mode 100644 index 00000000..476ca986 --- /dev/null +++ b/tests/wine/opengl_buffer_storage.c @@ -0,0 +1,177 @@ +/* Buffer storage for 32-bit and 64-bit programs: the context is above GL 4.3 + * with GL_ARB_buffer_storage and GL_AMD_pinned_memory (Wine limits 32-bit + * programs to 4.3 without them). A persistent, coherent mapping takes vertex + * writes the next draw sees without a flush or unmap, and a pinned-memory + * buffer (GL_EXTERNAL_VIRTUAL_MEMORY_BUFFER_AMD) draws from the program's own + * memory, again after it changes. Prints WINEMETALGL_BUFFER_STORAGE_OK and + * WINEMETALGL_PINNED_MEMORY_OK. */ +#include +#include +#include +#include +#include + +#ifndef GL_EXTERNAL_VIRTUAL_MEMORY_BUFFER_AMD +#define GL_EXTERNAL_VIRTUAL_MEMORY_BUFFER_AMD 0x9160 +#endif + +#define FUNCS(X) \ + X(PFNGLGENBUFFERSPROC, glGenBuffers) \ + X(PFNGLBINDBUFFERPROC, glBindBuffer) \ + X(PFNGLBUFFERDATAPROC, glBufferData) \ + X(PFNGLBUFFERSTORAGEPROC, glBufferStorage) \ + X(PFNGLMAPBUFFERRANGEPROC, glMapBufferRange) \ + X(PFNGLGETSTRINGIPROC, glGetStringi) \ + X(PFNGLGENVERTEXARRAYSPROC, glGenVertexArrays) \ + X(PFNGLBINDVERTEXARRAYPROC, glBindVertexArray) \ + X(PFNGLVERTEXATTRIBPOINTERPROC, glVertexAttribPointer) \ + X(PFNGLENABLEVERTEXATTRIBARRAYPROC, glEnableVertexAttribArray) \ + X(PFNGLCREATESHADERPROC, glCreateShader) \ + X(PFNGLSHADERSOURCEPROC, glShaderSource) \ + X(PFNGLCOMPILESHADERPROC, glCompileShader) \ + X(PFNGLCREATEPROGRAMPROC, glCreateProgram) \ + X(PFNGLATTACHSHADERPROC, glAttachShader) \ + X(PFNGLLINKPROGRAMPROC, glLinkProgram) \ + X(PFNGLGETPROGRAMIVPROC, glGetProgramiv) \ + X(PFNGLUSEPROGRAMPROC, glUseProgram) \ + X(PFNGLGETUNIFORMLOCATIONPROC, glGetUniformLocation) \ + X(PFNGLUNIFORM4FPROC, glUniform4f) \ + X(PFNGLGENFRAMEBUFFERSPROC, glGenFramebuffers) \ + X(PFNGLBINDFRAMEBUFFERPROC, glBindFramebuffer) \ + X(PFNGLGENRENDERBUFFERSPROC, glGenRenderbuffers) \ + X(PFNGLBINDRENDERBUFFERPROC, glBindRenderbuffer) \ + X(PFNGLRENDERBUFFERSTORAGEPROC, glRenderbufferStorage) \ + X(PFNGLFRAMEBUFFERRENDERBUFFERPROC, glFramebufferRenderbuffer) + +#define DECLARE(type, name) static type p_##name; +FUNCS(DECLARE) + +static LRESULT CALLBACK proc(HWND h, UINT m, WPARAM w, LPARAM l) { return DefWindowProcA(h, m, w, l); } + +static int has_extension(const char *name) { + GLint count = 0, i; + glGetIntegerv(GL_NUM_EXTENSIONS, &count); + for (i = 0; i < count; i++) + if (!strcmp((const char *)p_glGetStringi(GL_EXTENSIONS, i), name)) return 1; + return 0; +} + +/* A quad over columns [x0, x1) of the 64x64 target, as a 4-vertex strip. */ +static void quad(float *v, int x0, int x1) { + const float l = x0 / 32.0f - 1.0f, r = x1 / 32.0f - 1.0f; + const float q[8] = {l, -1.0f, r, -1.0f, l, 1.0f, r, 1.0f}; + memcpy(v, q, sizeof(q)); +} + +static int pixel_is(int x, unsigned int want) { + unsigned int got = 0; + glReadPixels(x, 32, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, &got); + if (got != want) printf("pixel %d: 0x%08x, want 0x%08x\n", x, got, want); + return got == want; +} + +static void draw(GLint color, float r, float g, float b) { + p_glUniform4f(color, r, g, b, 1.0f); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); +} + +int main(void) { + static const char *vs = "#version 330 core\nlayout(location = 0) in vec2 p;\n" + "void main() { gl_Position = vec4(p, 0.0, 1.0); }\n"; + static const char *fs = "#version 330 core\nuniform vec4 color;\nout vec4 o;\nvoid main() { o = color; }\n"; + const GLbitfield flags = GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_COHERENT_BIT; + PIXELFORMATDESCRIPTOR pfd = {sizeof(pfd), 1, PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER, + PFD_TYPE_RGBA, 32}; + WNDCLASSA wc = {0}; + GLuint vao, buffers[2], fbo, rb, vsh, fsh, program; + GLint linked = 0, color, major = 0, minor = 0; + float *mapped, *pinned; + HWND hwnd; + HDC dc; + HGLRC rc; + + wc.lpfnWndProc = proc; + wc.hInstance = GetModuleHandleA(NULL); + wc.lpszClassName = "wmgl"; + wc.style = CS_OWNDC; + RegisterClassA(&wc); + hwnd = CreateWindowA("wmgl", "wmgl", WS_POPUP, 0, 0, 64, 64, NULL, NULL, wc.hInstance, NULL); + dc = GetDC(hwnd); + if (!SetPixelFormat(dc, ChoosePixelFormat(dc, &pfd), &pfd)) return 11; + rc = wglCreateContext(dc); + if (!rc || !wglMakeCurrent(dc, rc)) return 12; +#define LOAD(type, name) if (!(p_##name = (type)wglGetProcAddress(#name))) { printf("missing %s\n", #name); return 13; } + FUNCS(LOAD) + + glGetIntegerv(GL_MAJOR_VERSION, &major); + glGetIntegerv(GL_MINOR_VERSION, &minor); + printf("GL_VERSION %s (%u-bit)\n", (const char *)glGetString(GL_VERSION), (unsigned)sizeof(void *) * 8); + if (major * 10 + minor <= 43) { printf("version %d.%d is not above 4.3\n", major, minor); return 14; } + if (!has_extension("GL_ARB_buffer_storage")) { printf("no GL_ARB_buffer_storage\n"); return 15; } + if (!has_extension("GL_AMD_pinned_memory")) { printf("no GL_AMD_pinned_memory\n"); return 16; } + + vsh = p_glCreateShader(GL_VERTEX_SHADER); + p_glShaderSource(vsh, 1, &vs, NULL); + p_glCompileShader(vsh); + fsh = p_glCreateShader(GL_FRAGMENT_SHADER); + p_glShaderSource(fsh, 1, &fs, NULL); + p_glCompileShader(fsh); + program = p_glCreateProgram(); + p_glAttachShader(program, vsh); + p_glAttachShader(program, fsh); + p_glLinkProgram(program); + p_glGetProgramiv(program, GL_LINK_STATUS, &linked); + if (!linked) return 17; + p_glUseProgram(program); + color = p_glGetUniformLocation(program, "color"); + + p_glGenFramebuffers(1, &fbo); + p_glBindFramebuffer(GL_FRAMEBUFFER, fbo); + p_glGenRenderbuffers(1, &rb); + p_glBindRenderbuffer(GL_RENDERBUFFER, rb); + p_glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, 64, 64); + p_glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, rb); + glViewport(0, 0, 64, 64); + glClearColor(0, 0, 0, 1); + glClear(GL_COLOR_BUFFER_BIT); + p_glGenVertexArrays(1, &vao); + p_glBindVertexArray(vao); + p_glGenBuffers(2, buffers); + + /* Persistent, coherent mapping: the second draw sees the rewritten quad. */ + p_glBindBuffer(GL_ARRAY_BUFFER, buffers[0]); + p_glBufferStorage(GL_ARRAY_BUFFER, 4096, NULL, flags); + mapped = p_glMapBufferRange(GL_ARRAY_BUFFER, 0, 4096, flags); + if (!mapped || glGetError()) { printf("persistent map failed\n"); return 18; } + p_glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, NULL); + p_glEnableVertexAttribArray(0); + quad(mapped, 0, 16); + draw(color, 1, 0, 0); + glFinish(); + quad(mapped, 16, 32); + draw(color, 0, 1, 0); + if (!pixel_is(8, 0xff0000ff) || !pixel_is(24, 0xff00ff00)) return 19; + printf("WINEMETALGL_BUFFER_STORAGE_OK\n"); + + /* Pinned memory: the program's pages are the buffer. */ + pinned = VirtualAlloc(NULL, 4096, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); + if (!pinned) return 20; + quad(pinned, 32, 48); + p_glBindBuffer(GL_EXTERNAL_VIRTUAL_MEMORY_BUFFER_AMD, buffers[1]); + p_glBufferData(GL_EXTERNAL_VIRTUAL_MEMORY_BUFFER_AMD, 4096, pinned, GL_STREAM_DRAW); + if (glGetError()) { printf("pinned buffer failed\n"); return 21; } + p_glBindBuffer(GL_ARRAY_BUFFER, buffers[1]); + p_glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, NULL); + draw(color, 0, 0, 1); + glFinish(); + quad(pinned, 48, 64); + draw(color, 1, 1, 1); + if (!pixel_is(40, 0xffff0000) || !pixel_is(56, 0xffffffff)) return 22; + printf("WINEMETALGL_PINNED_MEMORY_OK\n"); + + wglMakeCurrent(NULL, NULL); + wglDeleteContext(rc); + ReleaseDC(hwnd, dc); + DestroyWindow(hwnd); + return 0; +} diff --git a/tests/wine/opengl_gl33_metal.c b/tests/wine/opengl_gl33_metal.c index df39b299..44623409 100644 --- a/tests/wine/opengl_gl33_metal.c +++ b/tests/wine/opengl_gl33_metal.c @@ -3,6 +3,8 @@ #include #include +#include "fixture_version.h" + #define GL_VERTEX_SHADER 0x8b31 #define GL_FRAGMENT_SHADER 0x8b30 #define GL_COMPILE_STATUS 0x8b81 @@ -58,13 +60,9 @@ static int compile_one(GLuint shader, const char *source, int main(void) { -/* GL_SHADING_LANGUAGE_VERSION of the context the fixture targets (a legacy - context gets the highest version: 4.3, GLSL 4.30). */ -#ifdef WINEMETALGL_GLSL450 -#define EXPECTED_GLSL_VERSION "4.50" -#else -#define EXPECTED_GLSL_VERSION "4.30" -#endif +/* GL_SHADING_LANGUAGE_VERSION of the context the fixture targets (every + context gets the highest version, fixture_version.h). */ +#define EXPECTED_GLSL_VERSION WINEMETALGL_FIXTURE_GLSL_VERSION #ifdef WINEMETALGL_GLSL450 static const char version_name[] = "GLSL450"; static const char success_marker[] = "OPENGL_GL450_METAL_DRAW_READBACK_OK"; diff --git a/tests/wine/opengl_gl33_resources.c b/tests/wine/opengl_gl33_resources.c index 00f9b558..1f96c6c5 100644 --- a/tests/wine/opengl_gl33_resources.c +++ b/tests/wine/opengl_gl33_resources.c @@ -152,7 +152,7 @@ int main(void) if (location < 0) return 18; uniform4f(location, 0.2f, 0.4f, 0.6f, 1.0f); - gen_buffers(1, &vbo); bind_buffer(GL_ARRAY_BUFFER, vbo); buffer_data(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); if(!map_buffer_range(GL_ARRAY_BUFFER,0,sizeof(vertices),0x0002)||!unmap_buffer(GL_ARRAY_BUFFER))return 23;printf("WINEMETALGL_BUFFER_MAP_RANGE_OK\n"); if(!buffer_storage){ /* Wine removes GL_ARB_buffer_storage for 32-bit applications on wow64 (no persistent mappings there) */ const char*ext=(const char*)glGetString(GL_EXTENSIONS); if(sizeof(void*)==8||(ext&&strstr(ext,"GL_ARB_buffer_storage")))return 27; void*mapped=map_buffer_range(GL_ARRAY_BUFFER,0,sizeof(vertices),0x0002); void*queried=NULL; get_buffer_pointer(GL_ARRAY_BUFFER,0x88BD,&queried); if(!mapped||queried!=mapped||!unmap_buffer(GL_ARRAY_BUFFER))return 25; printf("WINEMETALGL_BUFFER_POINTER_QUERY_OK\n"); printf("WINEMETALGL_BUFFER_STORAGE_OK (not advertised to 32-bit applications by Wine)\n"); } else { buffer_storage(GL_ARRAY_BUFFER,sizeof(vertices),vertices,0x00c2/* MAP_WRITE|MAP_PERSISTENT|MAP_COHERENT: persistent maps need read or write */); GLint immutable=0,storage_flags=0; get_buffer_parameter(GL_ARRAY_BUFFER,0x821F,&immutable); get_buffer_parameter(GL_ARRAY_BUFFER,0x8220,&storage_flags); if(immutable!=1||storage_flags!=0x00c2)return 24; float *persistent=(float*)map_buffer_range(GL_ARRAY_BUFFER,0,sizeof(vertices),0x00c2); if(!persistent)return 24; void *queried_pointer=NULL; get_buffer_pointer(GL_ARRAY_BUFFER,0x88BD,&queried_pointer); if(queried_pointer!=persistent)return 25; printf("WINEMETALGL_BUFFER_POINTER_QUERY_OK\\n"); persistent[0]=0.0f; flush_mapped_buffer_range(GL_ARRAY_BUFFER,0,sizeof(float)); if(!unmap_buffer(GL_ARRAY_BUFFER))return 25; float storage_value=0.0f; get_buffer_sub_data(GL_ARRAY_BUFFER,0,sizeof(storage_value),&storage_value); if(storage_value!=0.0f)return 26; printf("WINEMETALGL_BUFFER_STORAGE_OK\n"); } + gen_buffers(1, &vbo); bind_buffer(GL_ARRAY_BUFFER, vbo); buffer_data(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); if(!map_buffer_range(GL_ARRAY_BUFFER,0,sizeof(vertices),0x0002)||!unmap_buffer(GL_ARRAY_BUFFER))return 23;printf("WINEMETALGL_BUFFER_MAP_RANGE_OK\n"); if(!buffer_storage){ /* Wine removes GL_ARB_buffer_storage for 32-bit applications on wow64 (no persistent mappings there) */ const char*ext=(const char*)glGetString(GL_EXTENSIONS); if(sizeof(void*)==8||(ext&&strstr(ext,"GL_ARB_buffer_storage")))return 27; void*mapped=map_buffer_range(GL_ARRAY_BUFFER,0,sizeof(vertices),0x0002); void*queried=NULL; get_buffer_pointer(GL_ARRAY_BUFFER,0x88BD,&queried); if(!mapped||queried!=mapped||!unmap_buffer(GL_ARRAY_BUFFER))return 25; printf("WINEMETALGL_BUFFER_POINTER_QUERY_OK\n"); printf("WINEMETALGL_BUFFER_STORAGE_OK (not advertised to 32-bit applications by Wine)\n"); } else { buffer_storage(GL_ARRAY_BUFFER,sizeof(vertices),vertices,0x00c2/* MAP_WRITE|MAP_PERSISTENT|MAP_COHERENT: persistent maps need read or write */); GLint immutable=0,storage_flags=0; get_buffer_parameter(GL_ARRAY_BUFFER,0x821F,&immutable); get_buffer_parameter(GL_ARRAY_BUFFER,0x8220,&storage_flags); if((immutable!=1||storage_flags!=0x00c2)&&!(sizeof(void*)==4&&immutable==0&&storage_flags==0))return 24;/* 32-bit (WoW64): Wine backs persistent storage with GL_AMD_pinned_memory buffer data, which is mutable */ float *persistent=(float*)map_buffer_range(GL_ARRAY_BUFFER,0,sizeof(vertices),0x00c2); if(!persistent)return 24; void *queried_pointer=NULL; get_buffer_pointer(GL_ARRAY_BUFFER,0x88BD,&queried_pointer); if(queried_pointer!=persistent)return 25; printf("WINEMETALGL_BUFFER_POINTER_QUERY_OK\\n"); persistent[0]=0.0f; flush_mapped_buffer_range(GL_ARRAY_BUFFER,0,sizeof(float)); if(!unmap_buffer(GL_ARRAY_BUFFER))return 25; float storage_value=0.0f; get_buffer_sub_data(GL_ARRAY_BUFFER,0,sizeof(storage_value),&storage_value); if(storage_value!=0.0f)return 26; printf("WINEMETALGL_BUFFER_STORAGE_OK\n"); } gen_buffers(1, &ibo); bind_buffer(GL_ELEMENT_ARRAY_BUFFER, ibo); buffer_data(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW); enable_attrib(0); attrib_pointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), (const void *)0); if(bind_vertex_buffer&&bind_vertex_buffers&&attrib_binding&&attrib_format){ bind_vertex_buffer(0,vbo,0,2*sizeof(float)); GLuint multi_vbos[1]={vbo}; intptr_t multi_offsets[1]={0}; GLsizei multi_strides[1]={2*sizeof(float)}; bind_vertex_buffers(0,1,multi_vbos,multi_offsets,multi_strides); printf("WINEMETALGL_MULTI_BIND_VERTEX_BUFFERS_OK\n"); attrib_binding(0,0); attrib_format(0,2,GL_FLOAT,GL_FALSE,0); } else printf("SKIP vertex buffer bindings (GL_ARB_vertex_attrib_binding, GL_ARB_multi_bind)\n"); GLint attrib_enabled=0,attrib_stride=0;void* attrib_offset=(void*)-1;get_attrib_iv(0,0x8622,&attrib_enabled);get_attrib_iv(0,0x8624,&attrib_stride);get_attrib_pointer(0,0x8645,&attrib_offset);if(!attrib_enabled||attrib_stride!=2*(int)sizeof(float)||attrib_offset!=(void*)0)return 22;printf("WINEMETALGL_VERTEX_ATTRIB_QUERY_OK\\n"); glViewport(0, 0, 64, 64); draw_elements_base(GL_TRIANGLES, 3, GL_UNSIGNED_SHORT, (const void *)0, 1); diff --git a/tests/wine/opengl_gl33_texture.c b/tests/wine/opengl_gl33_texture.c index fafe3ffb..331a5746 100644 --- a/tests/wine/opengl_gl33_texture.c +++ b/tests/wine/opengl_gl33_texture.c @@ -106,6 +106,6 @@ int main(void) uint16_t packed_pixel = 0; glReadPixels(32, 32, 1, 1, 0x1907, 0x8363, &packed_pixel); if (packed_pixel < 0x2400 || packed_pixel > 0x3800) return 19; printf("WINEMETALGL_READBACK_PACKED_OK\\n"); - glPixelStorei(0x0D05,8); unsigned char aligned[16]={0}; glReadPixels(32,31,1,2,0x1907,GL_UNSIGNED_BYTE,aligned); if(aligned[0]<47||aligned[1]<98||aligned[2]<149||aligned[8]<47||aligned[9]<98||aligned[10]<149)return 19; printf("WINEMETALGL_PACK_ALIGNMENT_OK\\n"); glPixelStorei(0x0D05,4); glCopyTexImage2D(GL_TEXTURE_2D,0,GL_RGBA,0,0,2,2,0); get_tex_image(GL_TEXTURE_2D,0,GL_RGBA,GL_UNSIGNED_BYTE,texture_readback); if(texture_readback[0]<47||texture_readback[0]>55||texture_readback[1]<98||texture_readback[1]>110||texture_readback[2]<149||texture_readback[2]>160||texture_readback[3]<250)return 19; printf("WINEMETALGL_COPY_TEX_OK\\n"); tex_storage(GL_TEXTURE_2D,1,GL_RGBA,2,2); printf("WINEMETALGL_TEX_STORAGE_OK\\n"); if(create_textures&&texture_storage&&texture_parameteri&&texture_subimage&&bind_texture_unit&&get_texture_level&&texture_view&&get_texture_image){GLuint dsa_texture; create_textures(GL_TEXTURE_2D,1,&dsa_texture); texture_storage(dsa_texture,1,GL_RGBA,2,2); texture_parameteri(dsa_texture,GL_TEXTURE_MIN_FILTER,GL_LINEAR); bind_texture_unit(0,dsa_texture); GLint dsa_width=0; get_texture_level(dsa_texture,0,0x1000,&dsa_width); if(dsa_width!=2)return 19; texture_subimage(dsa_texture,0,0,0,2,2,0x80E1,GL_UNSIGNED_BYTE,bgra); bind_texture(GL_TEXTURE_2D,dsa_texture); get_tex_image(GL_TEXTURE_2D,0,GL_RGBA,GL_UNSIGNED_BYTE,texture_readback); if(texture_readback[0]!=51||texture_readback[1]!=102||texture_readback[2]!=153||texture_readback[3]!=255)return 19; get_texture_image(dsa_texture,0,GL_RGBA,GL_UNSIGNED_BYTE,sizeof(texture_readback),texture_readback); if(texture_readback[0]!=51||texture_readback[1]!=102||texture_readback[2]!=153||texture_readback[3]!=255)return 19; GLuint view_texture; create_textures(GL_TEXTURE_2D,1,&view_texture); texture_view(view_texture,GL_TEXTURE_2D,dsa_texture,GL_RGBA,0,1,0,1); get_texture_image(view_texture,0,GL_RGBA,GL_UNSIGNED_BYTE,sizeof(texture_readback),texture_readback); if(texture_readback[0]!=51||texture_readback[1]!=102||texture_readback[2]!=153||texture_readback[3]!=255)return 19; printf("WINEMETALGL_TEXTURE_VIEW_OK\\n"); printf("WINEMETALGL_DSA_TEXTURE_OK\\n");}else printf("SKIP texture DSA and views (GL_ARB_direct_state_access, GL_ARB_texture_view)\n"); + glPixelStorei(0x0D05,8); unsigned char aligned[16]={0}; glReadPixels(32,31,1,2,0x1907,GL_UNSIGNED_BYTE,aligned); if(aligned[0]<47||aligned[1]<98||aligned[2]<149||aligned[8]<47||aligned[9]<98||aligned[10]<149)return 19; printf("WINEMETALGL_PACK_ALIGNMENT_OK\\n"); glPixelStorei(0x0D05,4); glCopyTexImage2D(GL_TEXTURE_2D,0,GL_RGBA,0,0,2,2,0); get_tex_image(GL_TEXTURE_2D,0,GL_RGBA,GL_UNSIGNED_BYTE,texture_readback); if(texture_readback[0]<47||texture_readback[0]>55||texture_readback[1]<98||texture_readback[1]>110||texture_readback[2]<149||texture_readback[2]>160||texture_readback[3]<250)return 19; printf("WINEMETALGL_COPY_TEX_OK\\n"); tex_storage(GL_TEXTURE_2D,1,GL_RGBA,2,2); printf("WINEMETALGL_TEX_STORAGE_OK\\n"); if(create_textures&&texture_storage&&texture_parameteri&&texture_subimage&&bind_texture_unit&&get_texture_level&&texture_view&&get_texture_image){GLuint dsa_texture; create_textures(GL_TEXTURE_2D,1,&dsa_texture); texture_storage(dsa_texture,1,0x8058/*GL_RGBA8: storage takes sized formats*/,2,2); texture_parameteri(dsa_texture,GL_TEXTURE_MIN_FILTER,GL_LINEAR); bind_texture_unit(0,dsa_texture); GLint dsa_width=0; get_texture_level(dsa_texture,0,0x1000,&dsa_width); if(dsa_width!=2)return 19; texture_subimage(dsa_texture,0,0,0,2,2,0x80E1,GL_UNSIGNED_BYTE,bgra); bind_texture(GL_TEXTURE_2D,dsa_texture); get_tex_image(GL_TEXTURE_2D,0,GL_RGBA,GL_UNSIGNED_BYTE,texture_readback); if(texture_readback[0]!=51||texture_readback[1]!=102||texture_readback[2]!=153||texture_readback[3]!=255)return 19; get_texture_image(dsa_texture,0,GL_RGBA,GL_UNSIGNED_BYTE,sizeof(texture_readback),texture_readback); if(texture_readback[0]!=51||texture_readback[1]!=102||texture_readback[2]!=153||texture_readback[3]!=255)return 19; GLuint view_texture; create_textures(GL_TEXTURE_2D,1,&view_texture); texture_view(view_texture,GL_TEXTURE_2D,dsa_texture,0x8058,0,1,0,1); get_texture_image(view_texture,0,GL_RGBA,GL_UNSIGNED_BYTE,sizeof(texture_readback),texture_readback); if(texture_readback[0]!=51||texture_readback[1]!=102||texture_readback[2]!=153||texture_readback[3]!=255)return 19; printf("WINEMETALGL_TEXTURE_VIEW_OK\\n"); printf("WINEMETALGL_DSA_TEXTURE_OK\\n");}else printf("SKIP texture DSA and views (GL_ARB_direct_state_access, GL_ARB_texture_view)\n"); printf("WINEMETALGL_GL33_TEXTURE_OK\n"); SwapBuffers(dc); GLuint textures_to_delete[2]={texture,copied_texture}; delete_textures(2, textures_to_delete); wglMakeCurrent(NULL, NULL); wglDeleteContext(context); ReleaseDC(window, dc); DestroyWindow(window); return 0; } diff --git a/tests/wine/opengl_gl42.c b/tests/wine/opengl_gl42.c index 677758a6..f83b4498 100644 --- a/tests/wine/opengl_gl42.c +++ b/tests/wine/opengl_gl42.c @@ -7,6 +7,8 @@ #include #include +#include "fixture_version.h" + #define VERTEX_SHADER 0x8b31 #define FRAGMENT_SHADER 0x8b30 #define COMPILE_STATUS 0x8b81 @@ -141,8 +143,10 @@ int main(void) { const char *version = (const char *)glGetString(GL_VERSION); const char *glsl = (const char *)glGetString(SHADING_LANGUAGE_VERSION); printf("GL_VERSION=%s GLSL=%s\n", version ? version : "(null)", glsl ? glsl : "(null)"); - /* The highest compatible version: 4.3, GLSL 4.30 (WGL_ARB_create_context). */ - if (!version || strncmp(version, "4.3", 3) != 0 || !glsl || strncmp(glsl, "4.30", 4) != 0) return 16; + /* The highest compatible version (WGL_ARB_create_context). */ + if (!version || strncmp(version, WINEMETALGL_FIXTURE_GL_VERSION, 3) != 0 || !glsl || + strncmp(glsl, WINEMETALGL_FIXTURE_GLSL_VERSION, 4) != 0) + return 16; printf("WINEMETALGL_GL42_CONTEXT_OK\n"); createShader = (CreateShader)proc("glCreateShader"); diff --git a/tests/wine/opengl_gl43.c b/tests/wine/opengl_gl43.c index 339fa6ff..58244e38 100644 --- a/tests/wine/opengl_gl43.c +++ b/tests/wine/opengl_gl43.c @@ -8,6 +8,8 @@ #include #include +#include "fixture_version.h" + #define VERTEX_SHADER 0x8b31 #define FRAGMENT_SHADER 0x8b30 #define COMPILE_STATUS 0x8b81 @@ -136,7 +138,10 @@ int main(void) { const char *version = (const char *)glGetString(GL_VERSION); const char *glsl = (const char *)glGetString(SHADING_LANGUAGE_VERSION); printf("GL_VERSION=%s GLSL=%s\n", version ? version : "(null)", glsl ? glsl : "(null)"); - if (!version || strncmp(version, "4.3", 3) != 0 || !glsl || strncmp(glsl, "4.30", 4) != 0) return 16; + /* The highest compatible version (WGL_ARB_create_context). */ + if (!version || strncmp(version, WINEMETALGL_FIXTURE_GL_VERSION, 3) != 0 || !glsl || + strncmp(glsl, WINEMETALGL_FIXTURE_GLSL_VERSION, 4) != 0) + return 16; GetStringi getStringi = (GetStringi)proc("glGetStringi"); if (!getStringi) return 17; GLint extensions = 0, compute = 0; diff --git a/tests/wine/opengl_later_entry_points.c b/tests/wine/opengl_later_entry_points.c index e0e5046c..7dc5bcc9 100644 --- a/tests/wine/opengl_later_entry_points.c +++ b/tests/wine/opengl_later_entry_points.c @@ -1,13 +1,9 @@ /* Entry points of later GL versions on a 3.2 core context. HashLink's GL * loader (Dead Cells) refuses to start without glBindImageTexture, * glMemoryBarrier, glDispatchCompute and glMultiDrawElementsIndirect, which - * Windows drivers return whatever the context's version; the 4.3 context a - * 3.2 request gets offers them. Entry points of versions above the context's - * (glCreateTextures, GL 4.5) Wine returns only for applications with - * HKCU\Software\Wine\AppDefaults\\OpenGL LaterEntryPoints = "Y" - * (patch 0001), so this checks both: NULL without the setting, the entry - * point with it. - * Prints WINEMETALGL_LATER_ENTRY_POINTS_OK. */ + * Windows drivers return whatever the context's version; the 4.6 context a + * 3.2 request gets offers them, and every entry point up to GL 4.6 + * (glSpecializeShader). Prints WINEMETALGL_LATER_ENTRY_POINTS_OK. */ #include #include #include @@ -38,25 +34,15 @@ int main(void) { HGLRC core = create(dc, NULL, attribs); if (!core || !wglMakeCurrent(dc, core)) { printf("3.2 core context failed %lu\n", GetLastError()); return 14; } printf("GL_VERSION %s\n", (const char *)glGetString(GL_VERSION)); - /* The setting belongs to this executable's name. */ - char module[MAX_PATH], key[MAX_PATH + 64]; - const char *app = module; - GetModuleFileNameA(NULL, module, sizeof(module)); - for (const char *p = module; *p; p++) if (*p == '\\' || *p == '/') app = p + 1; - snprintf(key, sizeof(key), "Software\\Wine\\AppDefaults\\%s\\OpenGL", app); - RegDeleteKeyValueA(HKEY_CURRENT_USER, key, "LaterEntryPoints"); for (int i = 0; i < 4; i++) { if (!wglGetProcAddress(names[i])) { printf("missing %s\n", names[i]); return 15; } } - if (wglGetProcAddress("glCreateTextures")) { printf("glCreateTextures without LaterEntryPoints\n"); return 17; } - if (RegSetKeyValueA(HKEY_CURRENT_USER, key, "LaterEntryPoints", REG_SZ, "Y", 2)) return 18; - if (!wglGetProcAddress("glCreateTextures")) { printf("missing glCreateTextures\n"); return 15; } + if (!wglGetProcAddress("glSpecializeShader")) { printf("missing glSpecializeShader\n"); return 17; } PFNGLMEMORYBARRIERPROC barrier = (PFNGLMEMORYBARRIERPROC)wglGetProcAddress("glMemoryBarrier"); while (glGetError() != GL_NO_ERROR) {} barrier(GL_ALL_BARRIER_BITS); GLenum error = glGetError(); - if (error != GL_NO_ERROR && error != GL_INVALID_OPERATION) { printf("glMemoryBarrier error 0x%x\n", error); return 16; } - RegDeleteKeyValueA(HKEY_CURRENT_USER, key, "LaterEntryPoints"); + if (error != GL_NO_ERROR) { printf("glMemoryBarrier error 0x%x\n", error); return 16; } printf("WINEMETALGL_LATER_ENTRY_POINTS_OK\n"); wglMakeCurrent(NULL, NULL); wglDeleteContext(core); diff --git a/tests/wine/opengl_wgl_modern.c b/tests/wine/opengl_wgl_modern.c index f3dc88cc..bdf22511 100644 --- a/tests/wine/opengl_wgl_modern.c +++ b/tests/wine/opengl_wgl_modern.c @@ -2,4 +2,4 @@ #include #include typedef HGLRC (WINAPI *CreateAttribs)(HDC,HGLRC,const int*); -int main(void){PIXELFORMATDESCRIPTOR p={0};HWND w=CreateWindowA("STATIC","WineMetalGL modern WGL",WS_POPUP,0,0,64,64,0,0,0,0);HDC d=GetDC(w);p.nSize=sizeof(p);p.nVersion=1;p.dwFlags=PFD_DRAW_TO_WINDOW|PFD_SUPPORT_OPENGL;p.iPixelType=PFD_TYPE_RGBA;p.cColorBits=32;int f=ChoosePixelFormat(d,&p);if(!f||!SetPixelFormat(d,f,&p))return 12;HGLRC legacy=wglCreateContext(d);if(!legacy||!wglMakeCurrent(d,legacy))return 13;CreateAttribs create=(CreateAttribs)wglGetProcAddress("wglCreateContextAttribsARB");if(!create)return 14;const int too_new[]={0x2091,4,0x2092,6,0x9126,0x00000001,0x2094,0x00000002,0};/* WineMetalGL implements OpenGL 4.2: a 4.6 request must fail with ERROR_INVALID_VERSION_ARB (0x2095). */SetLastError(0);if(create(d,NULL,too_new)){printf("WGL 4.6 request unexpectedly succeeded\n");return 15;}if(GetLastError()!=0xc0072095&&GetLastError()!=0x2095){printf("WGL 4.6 request error=%lx\n",GetLastError());return 15;}const int attribs[]={0x2091,3,0x2092,3,0x9126,0x00000001,0x2094,0x00000002,0};HGLRC modern=create(d,NULL,attribs);if(!modern){printf("WGL modern create failed error=%lu\n",GetLastError());return 15;}if(!wglMakeCurrent(d,modern))return 16;printf("WINEMETALGL_WGL_MODERN_CONTEXT_OK\n");wglMakeCurrent(NULL,NULL);wglDeleteContext(modern);wglDeleteContext(legacy);ReleaseDC(w,d);DestroyWindow(w);return 0;} +int main(void){PIXELFORMATDESCRIPTOR p={0};HWND w=CreateWindowA("STATIC","WineMetalGL modern WGL",WS_POPUP,0,0,64,64,0,0,0,0);HDC d=GetDC(w);p.nSize=sizeof(p);p.nVersion=1;p.dwFlags=PFD_DRAW_TO_WINDOW|PFD_SUPPORT_OPENGL;p.iPixelType=PFD_TYPE_RGBA;p.cColorBits=32;int f=ChoosePixelFormat(d,&p);if(!f||!SetPixelFormat(d,f,&p))return 12;HGLRC legacy=wglCreateContext(d);if(!legacy||!wglMakeCurrent(d,legacy))return 13;CreateAttribs create=(CreateAttribs)wglGetProcAddress("wglCreateContextAttribsARB");if(!create)return 14;const int too_new[]={0x2091,4,0x2092,7,0x9126,0x00000001,0x2094,0x00000002,0};/* WineMetalGL implements OpenGL 4.6: a 4.7 request must fail with ERROR_INVALID_VERSION_ARB (0x2095). */SetLastError(0);if(create(d,NULL,too_new)){printf("WGL 4.7 request unexpectedly succeeded\n");return 15;}if(GetLastError()!=0xc0072095&&GetLastError()!=0x2095){printf("WGL 4.7 request error=%lx\n",GetLastError());return 15;}const int newest[]={0x2091,4,0x2092,6,0x9126,0x00000001,0x2094,0x00000002,0};HGLRC gl46=create(d,NULL,newest);if(!gl46){printf("WGL 4.6 create failed error=%lu\n",GetLastError());return 15;}wglDeleteContext(gl46);const int attribs[]={0x2091,3,0x2092,3,0x9126,0x00000001,0x2094,0x00000002,0};HGLRC modern=create(d,NULL,attribs);if(!modern){printf("WGL modern create failed error=%lu\n",GetLastError());return 15;}if(!wglMakeCurrent(d,modern))return 16;printf("WINEMETALGL_WGL_MODERN_CONTEXT_OK\n");wglMakeCurrent(NULL,NULL);wglDeleteContext(modern);wglDeleteContext(legacy);ReleaseDC(w,d);DestroyWindow(w);return 0;} diff --git a/tests/wine/probe-opengl-wow64.sh b/tests/wine/probe-opengl-wow64.sh index d70be61d..3d94cd9e 100755 --- a/tests/wine/probe-opengl-wow64.sh +++ b/tests/wine/probe-opengl-wow64.sh @@ -4,10 +4,11 @@ # # WINEMETALGL_WINE_RUNTIME=/path/to/wine-install tests/wine/probe-opengl-wow64.sh [--all] [fixture...] # -# The Wine installation must carry the patches in patches/wine and -# libwinemetalgl.dylib next to winemac.so (scripts/stage-wine.sh). --all also -# runs the GL 4.x fixtures. Exit status is non-zero if any fixture misses a -# marker; a summary lists every fixture. +# The Wine installation must have the Wine module and the core installed +# (scripts/install-wine-module.sh WINE); the prefix gets the forwarding +# opengl32.dll from WINEMETALGL_MODULE_DIR (default build/wine-module). --all also +# runs fixtures outside the gate scope (none since GL 4.6). Exit status is +# non-zero if any fixture misses a marker; a summary lists every fixture. set -uo pipefail PROJECT_ROOT=$(cd "$(dirname "$0")/../.." && pwd) @@ -58,13 +59,21 @@ for arg in "$@"; do done core_dir="$WINE_RUNTIME/lib/wine/x86_64-unix" -for required in "$WINE" "$WINESERVER" "$core_dir/winemac.so" "$core_dir/libwinemetalgl.dylib"; do +MODULE=${WINEMETALGL_MODULE_DIR:-$PROJECT_ROOT/build/wine-module} +for required in "$WINE" "$WINESERVER" "$core_dir/winemetalgl.so" "$core_dir/libwinemetalgl.dylib" \ + "$MODULE/x86_64-windows/opengl32.dll" "$MODULE/i386-windows/opengl32.dll"; do test -e "$required" || { echo "Missing WineMetalGL input: $required" >&2; exit 1; } done export WINEPREFIX="$PREFIX" WINEARCH=wow64 WINEDEBUG=${WINEDEBUG:--all} unset WINEMETALGL -${TIMEOUT[@]+"${TIMEOUT[@]}"} "$WINE" wineboot -u >"$RUN_ROOT/wineboot.log" 2>&1 +# A stock Wine offers to install Wine Mono and Gecko on first boot and waits +# on that dialog: the fixtures need neither. +WINEDLLOVERRIDES="mscoree,mshtml=${WINEDLLOVERRIDES:+;$WINEDLLOVERRIDES}" \ + ${TIMEOUT[@]+"${TIMEOUT[@]}"} "$WINE" wineboot -u >"$RUN_ROOT/wineboot.log" 2>&1 +cp "$MODULE/x86_64-windows/opengl32.dll" "$PREFIX/drive_c/windows/system32/opengl32.dll" +cp "$MODULE/i386-windows/opengl32.dll" "$PREFIX/drive_c/windows/syswow64/opengl32.dll" +"$WINE" reg add 'HKCU\Software\Wine\DllOverrides' /v opengl32 /d native /f >>"$RUN_ROOT/wineboot.log" 2>&1 failed=0 summary=() diff --git a/tools/gen/wine_gen.py b/tools/gen/wine_gen.py new file mode 100644 index 00000000..ea7b7e8d --- /dev/null +++ b/tools/gen/wine_gen.py @@ -0,0 +1,272 @@ +#!/usr/bin/env python3 +"""Generate the GL thunks of the WineMetalGL Wine module (src/wine). + +Every GL command of the core (tools/gen/gl_gen.py's selection) becomes: + - a PE function in winemetalgl.dll that packs its arguments into a + parameter struct and makes one unix call; + - a unix call in winemetalgl.so that calls the core's entry point; + - a wow64 unix call for 32-bit callers, which reads the 32-bit layout of + the struct and widens pointers (zero-extended) and pointer-sized + integers (sign-extended). +Commands whose arguments need more than that (pointer arrays, returned +pointers, sync objects, callbacks) are listed in MANUAL_UNIX and +MANUAL_WOW64 and implemented by hand in src/wine/unix. + +Writes into OUTDIR: + wine_gl_params.gen.h unix call codes and parameter structs (PE and unix) + wine_gl_pe.gen.c PE functions and the wglGetProcAddress table + wine_gl_unix.gen.c unix and wow64 call tables + winemetalgl.def exports of winemetalgl.dll + opengl32.def exports of opengl32.dll, forwarded to winemetalgl.dll + + wine_gen.py --registry third_party/khronos/gl.xml --extensions tools/gen/extensions.txt --out DIR +""" + +import argparse +import os +import sys + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +import gl_gen # noqa: E402 + +# Unix calls that are not GL commands (src/wine/include/wmgl_wine.h). Their +# codes come first; the GL commands follow in name order. +WGL_CALLS = """ +init debug_message pixel_formats get_pixel_format set_pixel_format create_context delete_context +make_current share_lists copy_context swap_buffers swap_interval get_swap_interval create_pbuffer +destroy_pbuffer get_pbuffer_dc release_pbuffer_dc query_pbuffer bind_tex_image release_tex_image +set_pbuffer_attrib query_renderer_integer query_renderer_string extensions_string +""".split() + +# Pointer-sized types, opaque handles and callbacks (32-bit wide for wow64). +POINTER_SIZED = {"GLintptr", "GLintptrARB", "GLsizeiptr", "GLsizeiptrARB", "GLvdpauSurfaceNV"} +OPAQUE_POINTERS = {"GLeglImageOES", "GLeglClientBufferEXT", "GLsync", "GLDEBUGPROC", "GLDEBUGPROCARB", + "GLDEBUGPROCKHR", "GLDEBUGPROCAMD", "GLVULKANPROCNV"} + +# Implemented by hand for 64-bit and 32-bit callers alike (src/wine/unix): +# the core calls these callbacks on the unix side, where PE code cannot run, +# and glGetPointerv reports them. +MANUAL_UNIX = {"glDebugMessageCallback", "glDebugMessageCallbackARB", "glDebugMessageCallbackAMD", "glGetPointerv", + "glGetPointervEXT"} + +# Implemented by hand for 32-bit callers (src/wine/unix/unix_wow64.c): +# strings the core returns are copied below 4 GB, sync objects become +# 32-bit handles, arrays of pointers are widened and pointers written +# through pointers narrowed. (Buffer storage of 32-bit processes lies below +# 4 GB, wmgl_set_buffer_memory: mapped pointers are narrowed as they are.) +MANUAL_WOW64 = { + "glGetString", "glGetStringi", + "glFenceSync", "glDeleteSync", "glIsSync", "glClientWaitSync", "glWaitSync", "glGetSynciv", + "glObjectPtrLabel", "glGetObjectPtrLabel", + "glShaderSource", "glShaderSourceARB", "glGetVertexAttribPointerv", "glGetVertexAttribPointervARB", + "glGetBufferPointerv", "glGetBufferPointervARB", "glGetNamedBufferPointerv", + "glMultiDrawElements", "glMultiDrawElementsEXT", "glMultiDrawElementsBaseVertex", + "glTransformFeedbackVaryings", "glTransformFeedbackVaryingsEXT", "glGetUniformIndices", + "glCreateShaderProgramv", +} + + +def is_pointer(ptype): + return "*" in ptype or ptype.replace("const", "").strip() in OPAQUE_POINTERS + + +def field64(ptype): + return ptype + + +def field32(ptype): + base = ptype.replace("const", "").strip() + if is_pointer(ptype): + return "UINT" + if base in POINTER_SIZED: + return "INT" + return base + + +def widen(ptype, expr): + """A 32-bit field's value as the 64-bit parameter type.""" + base = ptype.replace("const", "").strip() + if is_pointer(ptype): + return "(%s)ULongToPtr(%s)" % (ptype, expr) + if base in POINTER_SIZED: + return "(%s)%s" % (base, expr) + return expr + + +def narrow(rtype, expr): + base = rtype.replace("const", "").strip() + if is_pointer(rtype): + return "wmgl_wow64_pointer( (const void *)%s )" % expr + if base in POINTER_SIZED: + return "(INT)%s" % expr + return expr + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("--registry", required=True) + parser.add_argument("--extensions", required=True) + parser.add_argument("--out", required=True) + args = parser.parse_args() + + ext_names = [l.split("#")[0].strip() for l in open(args.extensions)] + ext_names = [l for l in ext_names if l] + commands, selected, missing = gl_gen.load(args.registry, ext_names) + if missing: + raise SystemExit("extensions not in registry: " + ", ".join(sorted(missing))) + names = sorted(selected) + os.makedirs(args.out, exist_ok=True) + unknown = (MANUAL_UNIX | MANUAL_WOW64) - set(names) + if unknown: + raise SystemExit("manual commands that are not commands: " + ", ".join(sorted(unknown))) + + # opengl32.dll's GL exports: the GL 1.0 and 1.1 commands. + import xml.etree.ElementTree as ET + root = ET.parse(args.registry).getroot() + exported = set() + for feature in root.findall("feature"): + if feature.get("api") == "gl" and feature.get("number") in ("1.0", "1.1"): + for require in feature.findall("require"): + for cmd in require.findall("command"): + exported.add(cmd.get("name")) + # Where each command comes from: a GL version (always offered by a 4.6 + # context) or an extension (offered when the context advertises it). + version_of = {} + for feature in root.findall("feature"): + if feature.get("api") != "gl": + continue + for require in feature.findall("require"): + for cmd in require.findall("command"): + version_of.setdefault(cmd.get("name"), feature.get("number")) + + def params_of(cmd): + return commands[cmd]["params"] + + def ret_of(cmd): + return commands[cmd]["ret"] + + # ---------------------------------------------------------------- params + with open(os.path.join(args.out, "wine_gl_params.gen.h"), "w") as out: + out.write("/* Generated by tools/gen/wine_gen.py. Do not edit. */\n#pragma once\n\n") + out.write("enum wmgl_unix_call\n{\n") + for call in WGL_CALLS: + out.write(" unix_wmgl_%s,\n" % call) + for cmd in names: + out.write(" unix_%s,\n" % cmd) + out.write(" unix_wmgl_call_count\n};\n\n") + for cmd in names: + fields = [" %s %s;\n" % (field64(t), n) for t, n in params_of(cmd)] + if ret_of(cmd) != "void": + fields.append(" %s ret;\n" % ret_of(cmd)) + if not fields: + fields.append(" int unused;\n") + out.write("struct %s_params\n{\n%s};\n\n" % (cmd, "".join(fields))) + out.write("#ifdef WINE_UNIX_LIB\n\n/* The same structs as built by 32-bit (i386) callers. */\n") + for cmd in names: + fields = [" %s %s;\n" % (field32(t), n) for t, n in params_of(cmd)] + if ret_of(cmd) != "void": + fields.append(" %s ret;\n" % field32(ret_of(cmd))) + if not fields: + fields.append(" int unused;\n") + out.write("struct %s_params32\n{\n%s};\n\n" % (cmd, "".join(fields))) + out.write("#endif /* WINE_UNIX_LIB */\n") + + # -------------------------------------------------------------------- PE + with open(os.path.join(args.out, "wine_gl_pe.gen.c"), "w") as out: + out.write("/* Generated by tools/gen/wine_gen.py. Do not edit. */\n\n#include \"wmgl_pe.h\"\n\n") + for cmd in names: + ret = ret_of(cmd) + decl = ", ".join("%s %s" % (t, n) for t, n in params_of(cmd)) or "void" + init = ", ".join(".%s = %s" % (n, n) for _, n in params_of(cmd)) + out.write("%s WINAPI %s(%s)\n{\n" % (ret, cmd, decl)) + out.write(" struct %s_params args = {%s};\n" % (cmd, (" " + init + " ") if init else "")) + out.write(" WMGL_CALL(%s, &args);\n" % cmd) + if ret != "void": + out.write(" return args.ret;\n") + out.write("}\n\n") + out.write("const struct wmgl_proc wmgl_procs[] =\n{\n") + for cmd in names: + source = version_of.get(cmd) or selected[cmd] + out.write(' {"%s", (PROC)%s, "%s"},\n' % (cmd, cmd, source)) + out.write("};\nconst unsigned int wmgl_proc_count = %d;\n" % len(names)) + + # ------------------------------------------------------------------ unix + with open(os.path.join(args.out, "wine_gl_unix.gen.c"), "w") as out: + out.write("/* Generated by tools/gen/wine_gen.py. Do not edit. */\n\n#include \"wmgl_unix.h\"\n\n") + out.write("struct wmgl_core_procs core;\n\nconst char *const wmgl_core_names[] =\n{\n") + for cmd in names: + out.write(' "%s",\n' % cmd) + out.write("};\n\n") + for cmd in names: + if cmd in MANUAL_UNIX: + out.write("extern NTSTATUS manual_%s( void *args );\n" % cmd) + continue + call = ", ".join("params->%s" % n for _, n in params_of(cmd)) + out.write("static NTSTATUS gl_%s( void *args )\n{\n" % cmd) + if params_of(cmd) or ret_of(cmd) != "void": + out.write(" struct %s_params *params = args;\n" % cmd) + out.write(" %score.p_%s(%s);\n" % ("params->ret = " if ret_of(cmd) != "void" else "", cmd, call)) + out.write(" return wmgl_after_call();\n}\n\n") + out.write("#ifdef _WIN64\n\n") + for cmd in names: + if cmd in MANUAL_UNIX or cmd in MANUAL_WOW64: + out.write("extern NTSTATUS wow64_manual_%s( void *args );\n" % cmd) + continue + call = ", ".join(widen(t, "params->%s" % n) for t, n in params_of(cmd)) + out.write("static NTSTATUS wow64_%s( void *args )\n{\n" % cmd) + if params_of(cmd) or ret_of(cmd) != "void": + out.write(" struct %s_params32 *params = args;\n" % cmd) + if ret_of(cmd) != "void": + out.write(" params->ret = %s;\n" % narrow(ret_of(cmd), "core.p_%s(%s)" % (cmd, call))) + else: + out.write(" core.p_%s(%s);\n" % (cmd, call)) + out.write(" return wmgl_after_call();\n}\n\n") + out.write("#endif /* _WIN64 */\n\n") + # The WGL calls (src/wine/unix) take the same struct layout from + # 32-bit and 64-bit callers. + for call in WGL_CALLS: + out.write("extern NTSTATUS wmgl_unix_%s( void *args );\n" % call) + out.write("\nconst unixlib_entry_t __wine_unix_call_funcs[] =\n{\n") + for call in WGL_CALLS: + out.write(" wmgl_unix_%s,\n" % call) + for cmd in names: + out.write(" %s_%s,\n" % ("manual" if cmd in MANUAL_UNIX else "gl", cmd)) + out.write("};\n\n#ifdef _WIN64\nextern NTSTATUS wmgl_unix_init_wow64( void *args );\n") + out.write("const unixlib_entry_t __wine_unix_call_wow64_funcs[] =\n{\n") + for call in WGL_CALLS: + out.write(" wmgl_unix_%s%s,\n" % (call, "_wow64" if call == "init" else "")) + for cmd in names: + manual = cmd in MANUAL_UNIX or cmd in MANUAL_WOW64 + out.write(" %s_%s,\n" % ("wow64_manual" if manual else "wow64", cmd)) + out.write("};\n#endif\n\nC_ASSERT( ARRAYSIZE(__wine_unix_call_funcs) == unix_wmgl_call_count );\n") + + with open(os.path.join(args.out, "wine_gl_procs.gen.h"), "w") as out: + out.write("/* Generated by tools/gen/wine_gen.py. Do not edit. */\n#pragma once\n\n") + out.write("struct wmgl_core_procs\n{\n") + for cmd in names: + decl = ", ".join("%s %s" % (t, n) for t, n in params_of(cmd)) or "void" + out.write(" %s (*p_%s)(%s);\n" % (ret_of(cmd), cmd, decl)) + out.write("};\n\nextern struct wmgl_core_procs core;\nextern const char *const wmgl_core_names[];\n") + out.write("#define WMGL_CORE_PROC_COUNT %d\n" % len(names)) + + # ------------------------------------------------------------------- def + wgl_exports = """wglChoosePixelFormat wglCopyContext wglCreateContext wglCreateLayerContext wglDeleteContext + wglDescribeLayerPlane wglDescribePixelFormat wglGetCurrentContext wglGetCurrentDC wglGetDefaultProcAddress + wglGetLayerPaletteEntries wglGetPixelFormat wglGetProcAddress wglMakeCurrent wglRealizeLayerPalette + wglSetLayerPaletteEntries wglSetPixelFormat wglShareLists wglSwapBuffers wglSwapLayerBuffers wglUseFontBitmapsA + wglUseFontBitmapsW wglUseFontOutlinesA wglUseFontOutlinesW""".split() + gl_exports = sorted(n for n in names if n in exported) + ["glDebugEntry"] + with open(os.path.join(args.out, "winemetalgl.def"), "w") as out: + out.write("; Generated by tools/gen/wine_gen.py. Do not edit.\nLIBRARY winemetalgl.dll\nEXPORTS\n") + for name in gl_exports + wgl_exports: + out.write(" %s\n" % name) + with open(os.path.join(args.out, "opengl32.def"), "w") as out: + out.write("; Generated by tools/gen/wine_gen.py. Do not edit.\nLIBRARY opengl32.dll\nEXPORTS\n") + for name in gl_exports + wgl_exports: + out.write(" %s = winemetalgl.%s\n" % (name, name)) + print("wine_gen: %d commands (%d exported), %d manual for wow64" % ( + len(names), len(gl_exports), len(MANUAL_WOW64 | MANUAL_UNIX))) + + +if __name__ == "__main__": + main() diff --git a/vendor/glslang/glslang/MachineIndependent/ParseHelper.cpp b/vendor/glslang/glslang/MachineIndependent/ParseHelper.cpp index 278142a9..4c653c85 100644 --- a/vendor/glslang/glslang/MachineIndependent/ParseHelper.cpp +++ b/vendor/glslang/glslang/MachineIndependent/ParseHelper.cpp @@ -7515,10 +7515,16 @@ bool TParseContext::vkRelaxedRemapUniformVariable(const TSourceLoc& loc, TString } if (initializer) { - // WineMetalGL: an unsized array still takes its size from the - // initializer (GLSL 4.30 section 4.1.9) before the value is dropped. - if (type.isUnsizedArray() && initializer->getType().isSizedArray()) - type.changeOuterArraySize(initializer->getType().getOuterArraySize()); + // WineMetalGL: an unsized array (any dimension of an array of + // arrays) still takes its sizes from the initializer (GLSL 4.30 + // section 4.1.9) before the value is dropped. + if (type.isArray() && initializer->getType().isSizedArray()) { + TArraySizes* sizes = type.getArraySizes(); + const TArraySizes* from = initializer->getType().getArraySizes(); + if (sizes && from && sizes->getNumDims() == from->getNumDims()) + for (int d = 0; d < sizes->getNumDims(); ++d) + if (sizes->getDimSize(d) == UnsizedArraySize) sizes->setDimSize(d, from->getDimSize(d)); + } warn(loc, "Ignoring initializer for uniform", identifier.c_str(), ""); initializer = nullptr; } diff --git a/vendor/glslang/glslang/MachineIndependent/linkValidate.cpp b/vendor/glslang/glslang/MachineIndependent/linkValidate.cpp index f2a3c090..08f75bb7 100644 --- a/vendor/glslang/glslang/MachineIndependent/linkValidate.cpp +++ b/vendor/glslang/glslang/MachineIndependent/linkValidate.cpp @@ -174,9 +174,9 @@ void TIntermediate::checkStageIO(TInfoSink& infoSink, TIntermediate& unit) { if (nextStageSymbol->getType().getBasicType() != EbtBlock && nextStageSymbol->getName().compare(0, 3, "gl_") == 0) continue; - // WineMetalGL: an input the stage never reads needs no output - // (GL rules; it is only an error when statically used). - if (nextStageSymbol->getType().getBasicType() != EbtBlock && !unit.inIoAccessed(nextStageSymbol->getName())) + // WineMetalGL: an input (or input block) the stage never reads + // needs no output (GL rules; it is only an error when statically used). + if (!unit.inIoAccessed(nextStageSymbol->getName())) continue; bool found = false; for (auto& curStageInterm : linkerObjects) { @@ -1798,7 +1798,9 @@ int TIntermediate::addUsedLocation(const TQualifier& qualifier, const TType& typ TIoRange range(locationRange, componentRange, basicTy, qualifier.hasIndex() ? qualifier.getIndex() : 0, qualifier.centroid, qualifier.smooth, qualifier.flat, qualifier.sample, qualifier.patch); // check for collisions, except for vertex inputs on desktop targeting OpenGL - if (! (!isEsProfile() && language == EShLangVertex && qualifier.isPipeInput()) || spvVersion.vulkan > 0) + // (WineMetalGL: also under the relaxed Vulkan rules its OpenGL codegen uses) + if (! (!isEsProfile() && language == EShLangVertex && qualifier.isPipeInput()) || + (spvVersion.vulkan > 0 && !spvVersion.vulkanRelaxed)) collision = checkLocationRange(set, range, type, typeCollision); if (collision < 0)