diff --git a/.github/workflows/android-build.yml b/.github/workflows/android-build.yml index 7b12b1583..9584f3c43 100644 --- a/.github/workflows/android-build.yml +++ b/.github/workflows/android-build.yml @@ -13,7 +13,7 @@ permissions: read-all jobs: android-build: name: Check Android build with gradle - runs-on: "ubuntu-20.04" + runs-on: "ubuntu-24.04" strategy: fail-fast: true steps: diff --git a/.github/workflows/check-formatting.yml b/.github/workflows/check-formatting.yml index d581dec72..f4346c0b8 100644 --- a/.github/workflows/check-formatting.yml +++ b/.github/workflows/check-formatting.yml @@ -13,11 +13,11 @@ permissions: read-all jobs: clang-format-check: name: clang-format - runs-on: "ubuntu-20.04" + runs-on: "ubuntu-24.04" steps: - name: Setup clang-format run: | - sudo apt-get install -yqq clang-format-12 + sudo apt-get install -yqq clang-format-14 - name: Checkout repository uses: actions/checkout@v3 with: diff --git a/.github/workflows/linux-build.yml b/.github/workflows/linux-build.yml index 84e0f7794..df3498423 100644 --- a/.github/workflows/linux-build.yml +++ b/.github/workflows/linux-build.yml @@ -13,13 +13,13 @@ permissions: read-all jobs: linux-build: name: Build and run tests on Linux - runs-on: "ubuntu-20.04" + runs-on: "ubuntu-24.04" steps: - name: Setup necessary packages run: | sudo add-apt-repository ppa:kisak/kisak-mesa -y sudo apt update && sudo apt install libxrandr-dev libxinerama-dev libx11-dev libxcursor-dev libxi-dev libx11-xcb-dev clang \ - mesa-vulkan-drivers + mesa-vulkan-drivers imagemagick - name: Setup Vulkan SDK run: | wget -q https://sdk.lunarg.com/sdk/download/1.3.268.0/linux/vulkansdk-linux-x86_64-1.3.268.0.tar.xz @@ -76,7 +76,7 @@ jobs: xvfb-run -a ./vk_graphics_pipeline --frame-count 2 --screenshot-frame-number 1 convert screenshot_frame_1.ppm vk_graphics_pipeline_screenshot.png - name: Upload screenshots - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: screenshots path: build/bin/*.png diff --git a/.github/workflows/windows-build.yml b/.github/workflows/windows-build.yml index 29b2a2a50..3bccfa9c9 100644 --- a/.github/workflows/windows-build.yml +++ b/.github/workflows/windows-build.yml @@ -100,7 +100,7 @@ jobs: .\dx12_graphics_pipeline --headless --frame-count 2 --screenshot-frame-number 1 magick convert screenshot_frame_1.ppm dx12_graphics_pipeline_screenshot.png - name: Upload screenshots - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: screenshots path: build\bin\Debug\*.png diff --git a/CMakeLists.txt b/CMakeLists.txt index 7b16f7df3..7011c4634 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -141,6 +141,7 @@ if(MSVC) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${MSVC_DISABLED_WARNINGS} /MP /Zc:__cplusplus /std:c++17") else() set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} \ + -Werror \ -std=c++17 \ -fdiagnostics-color=always \ -Wno-nullability-completeness \ @@ -153,12 +154,10 @@ else() -Wno-pointer-bool-conversion \ ") - if (NOT PPX_BUILD_XR) - # OpenXR has way too many warnings to enable -Werror with it. - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} \ - -Werror \ - ") - endif () +endif() + +if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 19.0) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-missing-template-arg-list-after-template-kw") endif() set(CMAKE_CXX_STANDARD 17) @@ -255,6 +254,9 @@ endif() if (PPX_BUILD_XR) message("Building OpenXR support") configure_file(${CMAKE_SOURCE_DIR}/CTestCustom.cmake ${CMAKE_BINARY_DIR}) + if (PPX_LINUX) + add_compile_options(-Wno-tautological-pointer-compare -Wno-error=address) + endif() add_subdirectory(third_party/OpenXR-SDK-Source) endif() diff --git a/assets/benchmarks/shaders/Benchmark_Quad.hlsli b/assets/benchmarks/shaders/Benchmark_Quad.hlsli new file mode 100644 index 000000000..24c98af15 --- /dev/null +++ b/assets/benchmarks/shaders/Benchmark_Quad.hlsli @@ -0,0 +1,43 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef BENCHMARKS_QUAD_HLSLI +#define BENCHMARKS_QUAD_HLSLI + +struct ConfigParams { + uint32_t InstCount; + uint32_t RandomSeed; + uint32_t TextureCount; + float3 ColorValue; +}; + +#if defined(__spirv__) +[[vk::push_constant]] +#endif +ConstantBuffer Config : register(b0); + +struct VSOutputPos { + float4 position : SV_POSITION; +}; + +float randomCompute(uint32_t instCount, float4 Position) { + float randNum = frac(float(instCount) * 123.456f); + for (uint32_t i = 0; i < instCount; i++) { + Position.z += Position.x * (1 - randNum) + randNum * Position.y; + } + + return frac(Position.z);; +} + +#endif // BENCHMARKS_QUAD_HLSLI \ No newline at end of file diff --git a/assets/benchmarks/shaders/Benchmark_RandomNoise.hlsl b/assets/benchmarks/shaders/Benchmark_RandomNoise.hlsl index 60496b688..237751458 100644 --- a/assets/benchmarks/shaders/Benchmark_RandomNoise.hlsl +++ b/assets/benchmarks/shaders/Benchmark_RandomNoise.hlsl @@ -12,17 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "VsOutput.hlsli" - -struct RandomParams -{ - uint32_t Seed; -}; - -#if defined(__spirv__) -[[vk::push_constant]] -#endif -ConstantBuffer Random : register(b0); +#include "Benchmark_Quad.hlsli" float random(float2 st, uint32_t seed) { float underOne = sin(float(seed) + 0.5f); @@ -32,6 +22,6 @@ float random(float2 st, uint32_t seed) { float4 psmain(VSOutputPos input) : SV_TARGET { - float rnd = random(input.position.xy, Random.Seed); + float rnd = random(input.position.xy, Config.RandomSeed); return float4(rnd, rnd, rnd, 1.0f); } \ No newline at end of file diff --git a/assets/benchmarks/shaders/Benchmark_SolidColor.hlsl b/assets/benchmarks/shaders/Benchmark_SolidColor.hlsl index cb1df3df5..db8b68cdb 100644 --- a/assets/benchmarks/shaders/Benchmark_SolidColor.hlsl +++ b/assets/benchmarks/shaders/Benchmark_SolidColor.hlsl @@ -12,19 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "VsOutput.hlsli" - -struct ColorParams -{ - float3 Value; -}; - -#if defined(__spirv__) -[[vk::push_constant]] -#endif -ConstantBuffer Color : register(b0); +#include "Benchmark_Quad.hlsli" float4 psmain(VSOutputPos input) : SV_TARGET { - return float4(Color.Value, 1.0f); + float4 color = float4(Config.ColorValue, 1.0f); + color.a = randomCompute(Config.InstCount, input.position) + 0.5f; + return color; } \ No newline at end of file diff --git a/assets/benchmarks/shaders/Benchmark_Texture-100.hlsl b/assets/benchmarks/shaders/Benchmark_Texture-100.hlsl new file mode 100644 index 000000000..88adef299 --- /dev/null +++ b/assets/benchmarks/shaders/Benchmark_Texture-100.hlsl @@ -0,0 +1,43 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "Benchmark_Quad.hlsli" + +Texture2D Tex[10] : register(t1); // Slot 0 is used by push constant. +RWStructuredBuffer dataBuffer : register(u11); + +float4 psmain(VSOutputPos input) : SV_TARGET +{ + uint32_t textureCount = Config.TextureCount; + float4 color1 = {0.0f, 0.0f, 0.0f, 0.0f}; + float4 color2 = {0.0f, 0.0f, 0.0f, 0.0f}; + float4 color3 = {0.0f, 0.0f, 0.0f, 0.0f}; + for(uint32_t i = 0; i < textureCount; i++) + { + color1 += Tex[i].Load(uint3(input.position.x, input.position.y, 0))/float(textureCount); + } + for(uint32_t i = 0; i < textureCount; i++) + { + color2 += Tex[i].Load(uint3(input.position.x, input.position.y, 0)); + } + for(uint32_t i = 0; i < textureCount; i++) + { + color3 += Tex[i].Load(uint3(input.position.x + 1, input.position.y + 1, 0))/float(textureCount); + } + float4 color = color1 + 0.5f * color2 + 0.25f * color3; + if (!any(color)) + dataBuffer[0] = color.r; + color.a = randomCompute(Config.InstCount, input.position); + return color; +} \ No newline at end of file diff --git a/assets/benchmarks/shaders/Benchmark_Texture-62.hlsl b/assets/benchmarks/shaders/Benchmark_Texture-62.hlsl new file mode 100644 index 000000000..969e35f5d --- /dev/null +++ b/assets/benchmarks/shaders/Benchmark_Texture-62.hlsl @@ -0,0 +1,42 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "Benchmark_Quad.hlsli" + +Texture2D Tex[10] : register(t1); // Slot 0 is used by push constant. +RWStructuredBuffer dataBuffer : register(u11); + +float4 psmain(VSOutputPos input) : SV_TARGET +{ + uint32_t textureCount = Config.TextureCount; + float4 color1 = {0.0f, 0.0f, 0.0f, 0.0f}; + float4 color2 = {0.0f, 0.0f, 0.0f, 0.0f}; + + for(uint32_t i = 0; i < textureCount; i++) + { + color1 += Tex[i].Load(uint3(input.position.x, input.position.y, 0))/float(textureCount); + } + + for(uint32_t i = 0; i < textureCount; i++) + { + color2 += Tex[i].Load(uint3(input.position.x, input.position.y, 0)); + } + + float4 color = color1 + 0.5f * color2; + + if (!any(color)) + dataBuffer[0] = color.r; + color.a = randomCompute(Config.InstCount, input.position); + return color; +} \ No newline at end of file diff --git a/assets/benchmarks/shaders/Benchmark_Texture-75.hlsl b/assets/benchmarks/shaders/Benchmark_Texture-75.hlsl new file mode 100644 index 000000000..902de0996 --- /dev/null +++ b/assets/benchmarks/shaders/Benchmark_Texture-75.hlsl @@ -0,0 +1,35 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "Benchmark_Quad.hlsli" + +Texture2D Tex[10] : register(t1); // Slot 0 is used by push constant. +RWStructuredBuffer dataBuffer : register(u11); + +float4 psmain(VSOutputPos input) : SV_TARGET +{ + uint32_t textureCount = Config.TextureCount; + float4 color1 = {0.0f, 0.0f, 0.0f, 0.0f}; + + for(uint32_t i = 0; i < textureCount; i++) + { + color1 += Tex[i].Load(uint3(input.position.x, input.position.y, 0))/float(textureCount); + } + + float4 color = color1; + if (!any(color)) + dataBuffer[0] = color.r; + color.a = randomCompute(Config.InstCount, input.position); + return color; +} diff --git a/assets/benchmarks/shaders/Benchmark_VsSimpleQuads.hlsl b/assets/benchmarks/shaders/Benchmark_VsSimpleQuads.hlsl index 6dbda6261..88edecab3 100644 --- a/assets/benchmarks/shaders/Benchmark_VsSimpleQuads.hlsl +++ b/assets/benchmarks/shaders/Benchmark_VsSimpleQuads.hlsl @@ -12,11 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "VsOutput.hlsli" +#include "Benchmark_Quad.hlsli" -VSOutputPos vsmain(float4 Position : POSITION) -{ - VSOutputPos result; - result.position = Position; - return result; +VSOutputPos vsmain(float4 Position : POSITION) { + VSOutputPos result; + result.position = Position; + result.position.z = randomCompute(Config.InstCount, result.position); + return result; } \ No newline at end of file diff --git a/assets/benchmarks/shaders/CMakeLists.txt b/assets/benchmarks/shaders/CMakeLists.txt index bda4883cb..908bfee25 100644 --- a/assets/benchmarks/shaders/CMakeLists.txt +++ b/assets/benchmarks/shaders/CMakeLists.txt @@ -82,22 +82,32 @@ generate_rules_for_shader("shader_benchmark_skybox" generate_rules_for_shader("shader_benchmark_vs_simple_quads" SOURCE "${PPX_DIR}/assets/benchmarks/shaders/Benchmark_VsSimpleQuads.hlsl" - INCLUDES "${PPX_DIR}/assets/benchmarks/shaders/VsOutput.hlsli" + INCLUDES "${PPX_DIR}/assets/benchmarks/shaders/Benchmark_Quad.hlsli" STAGES "vs") generate_rules_for_shader("shader_benchmark_random_noise" SOURCE "${PPX_DIR}/assets/benchmarks/shaders/Benchmark_RandomNoise.hlsl" - INCLUDES "${PPX_DIR}/assets/benchmarks/shaders/VsOutput.hlsli" + INCLUDES "${PPX_DIR}/assets/benchmarks/shaders/Benchmark_Quad.hlsli" STAGES "ps") generate_rules_for_shader("shader_benchmark_solid_color" SOURCE "${PPX_DIR}/assets/benchmarks/shaders/Benchmark_SolidColor.hlsl" - INCLUDES "${PPX_DIR}/assets/benchmarks/shaders/VsOutput.hlsli" + INCLUDES "${PPX_DIR}/assets/benchmarks/shaders/Benchmark_Quad.hlsli" STAGES "ps") -generate_rules_for_shader("shader_benchmark_texture" - SOURCE "${PPX_DIR}/assets/benchmarks/shaders/Benchmark_Texture.hlsl" - INCLUDES "${PPX_DIR}/assets/benchmarks/shaders/VsOutput.hlsli" +generate_rules_for_shader("shader_benchmark_texture-75" + SOURCE "${PPX_DIR}/assets/benchmarks/shaders/Benchmark_Texture-75.hlsl" + INCLUDES "${PPX_DIR}/assets/benchmarks/shaders/Benchmark_Quad.hlsli" + STAGES "ps") + +generate_rules_for_shader("shader_benchmark_texture-100" + SOURCE "${PPX_DIR}/assets/benchmarks/shaders/Benchmark_Texture-100.hlsl" + INCLUDES "${PPX_DIR}/assets/benchmarks/shaders/Benchmark_Quad.hlsli" + STAGES "ps") + +generate_rules_for_shader("shader_benchmark_texture-62" + SOURCE "${PPX_DIR}/assets/benchmarks/shaders/Benchmark_Texture-62.hlsl" + INCLUDES "${PPX_DIR}/assets/benchmarks/shaders/Benchmark_Quad.hlsli" STAGES "ps") generate_rules_for_shader("shader_foveation_benchmark" diff --git a/assets/benchmarks/textures/fish/1024x1024.png b/assets/benchmarks/textures/fish/1024x1024.png new file mode 100644 index 000000000..aad957549 Binary files /dev/null and b/assets/benchmarks/textures/fish/1024x1024.png differ diff --git a/assets/benchmarks/textures/fish/1182x1440.png b/assets/benchmarks/textures/fish/1182x1440.png new file mode 100644 index 000000000..7c9fa25c7 Binary files /dev/null and b/assets/benchmarks/textures/fish/1182x1440.png differ diff --git a/assets/benchmarks/textures/fish/128x128.png b/assets/benchmarks/textures/fish/128x128.png new file mode 100644 index 000000000..76090e0eb Binary files /dev/null and b/assets/benchmarks/textures/fish/128x128.png differ diff --git a/assets/benchmarks/textures/fish/16x16.png b/assets/benchmarks/textures/fish/16x16.png new file mode 100644 index 000000000..3f8d00fe2 Binary files /dev/null and b/assets/benchmarks/textures/fish/16x16.png differ diff --git a/assets/benchmarks/textures/fish/1x1.png b/assets/benchmarks/textures/fish/1x1.png new file mode 100644 index 000000000..46fe737bf Binary files /dev/null and b/assets/benchmarks/textures/fish/1x1.png differ diff --git a/assets/benchmarks/textures/fish/2048x2048.png b/assets/benchmarks/textures/fish/2048x2048.png new file mode 100644 index 000000000..5b3cdcdbd Binary files /dev/null and b/assets/benchmarks/textures/fish/2048x2048.png differ diff --git a/assets/benchmarks/textures/fish/2364x2880.png b/assets/benchmarks/textures/fish/2364x2880.png new file mode 100644 index 000000000..e7fcc0740 Binary files /dev/null and b/assets/benchmarks/textures/fish/2364x2880.png differ diff --git a/assets/benchmarks/textures/fish/256x256.png b/assets/benchmarks/textures/fish/256x256.png new file mode 100644 index 000000000..5b20d7231 Binary files /dev/null and b/assets/benchmarks/textures/fish/256x256.png differ diff --git a/assets/benchmarks/textures/fish/3072x3072.png b/assets/benchmarks/textures/fish/3072x3072.png new file mode 100644 index 000000000..6c2792313 Binary files /dev/null and b/assets/benchmarks/textures/fish/3072x3072.png differ diff --git a/assets/benchmarks/textures/fish/32x32.png b/assets/benchmarks/textures/fish/32x32.png new file mode 100644 index 000000000..e219d053f Binary files /dev/null and b/assets/benchmarks/textures/fish/32x32.png differ diff --git a/assets/benchmarks/textures/fish/4096x4096.png b/assets/benchmarks/textures/fish/4096x4096.png new file mode 100644 index 000000000..ca5066313 Binary files /dev/null and b/assets/benchmarks/textures/fish/4096x4096.png differ diff --git a/assets/benchmarks/textures/fish/4x4.png b/assets/benchmarks/textures/fish/4x4.png new file mode 100644 index 000000000..5b33394c5 Binary files /dev/null and b/assets/benchmarks/textures/fish/4x4.png differ diff --git a/assets/benchmarks/textures/fish/512x512.png b/assets/benchmarks/textures/fish/512x512.png new file mode 100644 index 000000000..219d12206 Binary files /dev/null and b/assets/benchmarks/textures/fish/512x512.png differ diff --git a/assets/benchmarks/textures/fish/591x720.png b/assets/benchmarks/textures/fish/591x720.png new file mode 100644 index 000000000..c7374baee Binary files /dev/null and b/assets/benchmarks/textures/fish/591x720.png differ diff --git a/assets/benchmarks/textures/fish/64x64.png b/assets/benchmarks/textures/fish/64x64.png new file mode 100644 index 000000000..8cb0cd4ab Binary files /dev/null and b/assets/benchmarks/textures/fish/64x64.png differ diff --git a/assets/benchmarks/textures/fish/8192x8192.png b/assets/benchmarks/textures/fish/8192x8192.png new file mode 100644 index 000000000..380db8ba5 Binary files /dev/null and b/assets/benchmarks/textures/fish/8192x8192.png differ diff --git a/assets/benchmarks/textures/noise_2364x2880.jpg b/assets/benchmarks/textures/noise_2364x2880.jpg new file mode 100644 index 000000000..013174f28 Binary files /dev/null and b/assets/benchmarks/textures/noise_2364x2880.jpg differ diff --git a/assets/benchmarks/textures/white_2364x2880.jpg b/assets/benchmarks/textures/white_2364x2880.jpg new file mode 100644 index 000000000..6d733a99b Binary files /dev/null and b/assets/benchmarks/textures/white_2364x2880.jpg differ diff --git a/benchmarks/graphics_pipeline/CMakeLists.txt b/benchmarks/graphics_pipeline/CMakeLists.txt index cc9015e0a..842cd2df4 100644 --- a/benchmarks/graphics_pipeline/CMakeLists.txt +++ b/benchmarks/graphics_pipeline/CMakeLists.txt @@ -15,7 +15,7 @@ project(graphics_pipeline) add_samples_for_all_apis( NAME ${PROJECT_NAME} - SOURCES + SOURCES "FreeCamera.h" "FreeCamera.cpp" "GraphicsBenchmarkApp.h" @@ -34,6 +34,8 @@ add_samples_for_all_apis( "shader_benchmark_random_noise" "shader_benchmark_skybox" "shader_benchmark_solid_color" - "shader_benchmark_texture" + "shader_benchmark_texture-75" + "shader_benchmark_texture-100" + "shader_benchmark_texture-62" "shader_benchmark_vs_simple_quads" "shader_fullscreen_triangle") diff --git a/benchmarks/graphics_pipeline/GraphicsBenchmarkApp.cpp b/benchmarks/graphics_pipeline/GraphicsBenchmarkApp.cpp index f33c14f8e..660296a84 100644 --- a/benchmarks/graphics_pipeline/GraphicsBenchmarkApp.cpp +++ b/benchmarks/graphics_pipeline/GraphicsBenchmarkApp.cpp @@ -16,8 +16,11 @@ #include "SphereMesh.h" #include "ppx/graphics_util.h" +#include "ppx/grfx/grfx_enums.h" #include "ppx/grfx/grfx_format.h" +#include "ppx/math_config.h" #include "ppx/timer.h" +#include using namespace ppx; @@ -34,7 +37,9 @@ static constexpr size_t SPHERE_NORMAL_SAMPLER_REGISTER = 5; static constexpr size_t SPHERE_METAL_ROUGHNESS_SAMPLED_IMAGE_REGISTER = 6; static constexpr size_t SPHERE_METAL_ROUGHNESS_SAMPLER_REGISTER = 7; -static constexpr size_t QUADS_SAMPLED_IMAGE_REGISTER = 0; +static constexpr size_t QUADS_CONFIG_UNIFORM_BUFFER_REGISTER = 0; +static constexpr size_t QUADS_SAMPLED_IMAGE_REGISTER = 1; +static constexpr size_t QUADS_DUMMY_BUFFER_REGISTER = QUADS_SAMPLED_IMAGE_REGISTER + kMaxTextureCount; #if defined(USE_DX12) const grfx::Api kApi = grfx::API_DX_12_0; @@ -42,6 +47,12 @@ const grfx::Api kApi = grfx::API_DX_12_0; const grfx::Api kApi = grfx::API_VK_1_1; #endif +template +size_t GetPushConstCount(const T&) +{ + return sizeof(T) / sizeof(uint32_t); +} + void GraphicsBenchmarkApp::InitKnobs() { const auto& cl_options = GetExtraOptions(); @@ -120,6 +131,11 @@ void GraphicsBenchmarkApp::InitKnobs() pFullscreenQuadsType->SetFlagDescription("Select the type of the fullscreen quads. See also `--fullscreen-quads-count`."); pFullscreenQuadsType->SetIndent(1); + GetKnobManager().InitKnob(&pTextureShaderALU, "alu-ocupancy-level", /* defaultValue = */ 0, /* minValue = */ 0, 3); + pTextureShaderALU->SetDisplayName("ALU ocuppancy level for texture shader."); + pTextureShaderALU->SetFlagDescription("Select occupancy level: 0 -> 75, 1 -> 100, 2 -> 62."); + pTextureShaderALU->SetVisible(false); + GetKnobManager().InitKnob(&pFullscreenQuadsColor, "fullscreen-quads-color", 0, kFullscreenQuadsColors); pFullscreenQuadsColor->SetDisplayName("Color"); pFullscreenQuadsColor->SetFlagDescription("Select the hue for the solid color fullscreen quads. See also `--fullscreen-quads-count`."); @@ -173,12 +189,41 @@ void GraphicsBenchmarkApp::InitKnobs() pResolution->SetFlagDescription("Select the size of offscreen framebuffer."); pResolution->SetIndent(1); } + + GetKnobManager().InitKnob(&pKnobShaderAluLoopCount, "shader-alu-loop-count", /* defaultValue = */ 0, /* minValue = */ 0, 500); + pKnobShaderAluLoopCount->SetDisplayName("Number of loops in the shader for random alu compute."); + pKnobShaderAluLoopCount->SetFlagDescription("Select the number of loops in the shader for random alu compute."); + pKnobShaderAluLoopCount->SetVisible(false); + + GetKnobManager().InitKnob(&pKnobTextureCount, "texture-count", /* defaultValue = */ 0, /* minValue = */ 0, kMaxTextureCount); + pKnobTextureCount->SetDisplayName("Number of texture to load in the shader"); + pKnobTextureCount->SetFlagDescription("Select the number of texture to load in the shader."); + pKnobTextureCount->SetVisible(false); + + GetKnobManager().InitKnob(&pKnobViewportHeightScale, "viewport_height_scale", 0, kAvailableViewportScales); + pKnobViewportHeightScale->SetDisplayName("Scale viewport height"); + pKnobViewportHeightScale->SetFlagDescription("Scale viewport height to 1, 1/2, 1/4"); + pKnobViewportHeightScale->SetVisible(false); + + GetKnobManager().InitKnob(&pKnobViewportWidthScale, "viewport_width_scale", 0, kAvailableViewportScales); + pKnobViewportWidthScale->SetDisplayName("Scale viewport width"); + pKnobViewportWidthScale->SetFlagDescription("Scale viewport width to 1, 1/2, 1/4"); + pKnobViewportWidthScale->SetVisible(false); + + GetKnobManager().InitKnob(&pKnobQuadBlendMode, "quad_blend_mode", 0, kQuadBlendModes); + pKnobQuadBlendMode->SetDisplayName("Blend mode for quad"); + pKnobQuadBlendMode->SetFlagDescription("Bend mode for quad to none, alpha, disable_output"); + pKnobQuadBlendMode->SetVisible(false); } void GraphicsBenchmarkApp::Config(ppx::ApplicationSettings& settings) { - settings.appName = "graphics_pipeline"; - settings.enableImGui = true; + settings.appName = "graphics_pipeline"; +#if defined(PPX_ANDROID) + settings.enableImGui = false; +#else + settings.enableImGui = true; +#endif settings.window.width = 1920; settings.window.height = 1080; settings.grfx.api = kApi; @@ -187,7 +232,11 @@ void GraphicsBenchmarkApp::Config(ppx::ApplicationSettings& settings) #if defined(PPX_BUILD_XR) // XR specific settings settings.grfx.pacedFrameRate = 0; - settings.xr.enable = false; // Change this to true to enable the XR mode +#if defined(PPX_ANDROID) + settings.xr.enable = true; +#else + settings.xr.enable = false; // Change this to true to enable the XR mode on PC. +#endif #endif settings.standardKnobsDefaultValue.enableMetrics = true; settings.standardKnobsDefaultValue.overwriteMetricsFile = true; @@ -223,9 +272,10 @@ void GraphicsBenchmarkApp::Setup() // Descriptor Pool { grfx::DescriptorPoolCreateInfo createInfo = {}; - createInfo.sampler = 5 * GetNumFramesInFlight(); // 1 for skybox, 3 for spheres, 1 for blit - createInfo.sampledImage = 6 * GetNumFramesInFlight(); // 1 for skybox, 3 for spheres, 1 for quads, 1 for blit - createInfo.uniformBuffer = 2 * GetNumFramesInFlight(); // 1 for skybox, 1 for spheres + createInfo.sampler = 5 * GetNumFramesInFlight(); // 1 for skybox, 3 for spheres, 1 for blit + createInfo.sampledImage = (5 + kMaxTextureCount) * GetNumFramesInFlight(); // 1 for skybox, 3 for spheres, kMaxTextureCount for quads, 1 for blit + createInfo.uniformBuffer = 2 * GetNumFramesInFlight(); // 1 for skybox, 1 for spheres + createInfo.structuredBuffer = 1; // 1 for quads dummy buffer PPX_CHECKED_CALL(GetDevice()->CreateDescriptorPool(&createInfo, &mDescriptorPool)); } @@ -459,13 +509,28 @@ void GraphicsBenchmarkApp::SetupFullscreenQuadsResources() { // Large resolution image grfx_util::TextureOptions options = grfx_util::TextureOptions().MipLevelCount(1); - PPX_CHECKED_CALL(CreateTextureFromFile(GetDevice()->GetGraphicsQueue(), GetAssetPath(pQuadTextureFile->GetValue()), &mQuadsTexture, options)); + for (uint32_t i = 0; i < pKnobTextureCount->GetValue(); i++) { + // Load the same image. + PPX_CHECKED_CALL(CreateTextureFromFile(GetDevice()->GetGraphicsQueue(), GetAssetPath(pQuadTextureFile->GetValue()), &mQuadsTextures[i], options)); + } + } + + // dummy buffer + { + grfx::BufferCreateInfo bufferCreateInfo = {}; + bufferCreateInfo.size = PPX_MINIMUM_STRUCTURED_BUFFER_SIZE; + bufferCreateInfo.structuredElementStride = static_cast(sizeof(float)); + bufferCreateInfo.usageFlags.bits.rwStructuredBuffer = true; + bufferCreateInfo.memoryUsage = grfx::MEMORY_USAGE_GPU_ONLY; + bufferCreateInfo.initialState = grfx::RESOURCE_STATE_GENERAL; + PPX_CHECKED_CALL(GetDevice()->CreateBuffer(&bufferCreateInfo, &mQuadsDummyBuffer)); } // Descriptor set layout for texture shader { grfx::DescriptorSetLayoutCreateInfo layoutCreateInfo = {}; - layoutCreateInfo.bindings.push_back(grfx::DescriptorBinding(QUADS_SAMPLED_IMAGE_REGISTER, grfx::DESCRIPTOR_TYPE_SAMPLED_IMAGE)); + layoutCreateInfo.bindings.push_back(grfx::DescriptorBinding(QUADS_SAMPLED_IMAGE_REGISTER, grfx::DESCRIPTOR_TYPE_SAMPLED_IMAGE, kMaxTextureCount)); + layoutCreateInfo.bindings.push_back(grfx::DescriptorBinding(QUADS_DUMMY_BUFFER_REGISTER, grfx::DESCRIPTOR_TYPE_RW_STRUCTURED_BUFFER)); PPX_CHECKED_CALL(GetDevice()->CreateDescriptorSetLayout(&layoutCreateInfo, &mFullscreenQuads.descriptorSetLayout)); } @@ -482,7 +547,28 @@ void GraphicsBenchmarkApp::SetupFullscreenQuadsResources() SetupShader("Benchmark_VsSimpleQuads.vs", &mVSQuads); SetupShader("Benchmark_RandomNoise.ps", &mQuadsPs[0]); SetupShader("Benchmark_SolidColor.ps", &mQuadsPs[1]); - SetupShader("Benchmark_Texture.ps", &mQuadsPs[2]); + // We choose a shader based on ALU occupancy + switch (pTextureShaderALU->GetValue()) { + case 0: + // This shader has 75% of ALU occupancy + SetupShader("Benchmark_Texture-75.ps", &mQuadsPs[2]); + break; + + case 1: + // This shader has 100% of ALU occupancy + SetupShader("Benchmark_Texture-100.ps", &mQuadsPs[2]); + break; + + case 2: + // This shader has 62% of ALU occupancy + SetupShader("Benchmark_Texture-62.ps", &mQuadsPs[2]); + break; + + default: + // This shader has 75% of ALU occupancy + // We use it as default since it has less operations + SetupShader("Benchmark_Texture-75.ps", &mQuadsPs[2]); + } } void GraphicsBenchmarkApp::UpdateSkyBoxDescriptors() @@ -529,7 +615,18 @@ void GraphicsBenchmarkApp::UpdateFullscreenQuadsDescriptors() uint32_t n = GetNumFramesInFlight(); for (size_t i = 0; i < n; i++) { grfx::DescriptorSetPtr pDescriptorSet = mFullscreenQuads.descriptorSets[i]; - PPX_CHECKED_CALL(pDescriptorSet->UpdateSampledImage(QUADS_SAMPLED_IMAGE_REGISTER, 0, mQuadsTexture)); + for (uint32_t j = 0; j < pKnobTextureCount->GetValue(); j++) { + PPX_CHECKED_CALL(pDescriptorSet->UpdateSampledImage(QUADS_SAMPLED_IMAGE_REGISTER, j, mQuadsTextures[j])); + } + grfx::WriteDescriptor write = {}; + write.binding = QUADS_DUMMY_BUFFER_REGISTER; + write.arrayIndex = 0; + write.type = grfx::DESCRIPTOR_TYPE_RW_STRUCTURED_BUFFER; + write.bufferOffset = 0; + write.bufferRange = PPX_WHOLE_SIZE; + write.structuredElementCount = 1; + write.pBuffer = mQuadsDummyBuffer; + PPX_CHECKED_CALL(pDescriptorSet->UpdateDescriptors(1, &write)); } } @@ -552,15 +649,14 @@ void GraphicsBenchmarkApp::SetupSphereMeshes() } } - const uint32_t requiredSphereCount = std::max(pSphereInstanceCount->GetValue(), kDefaultSphereInstanceCount); - const uint32_t initSphereCount = std::min(kMaxSphereInstanceCount, 2 * std::max(requiredSphereCount, mInitializedSpheres)); + const uint32_t initSphereCount = std::min(kMaxSphereInstanceCount, pSphereInstanceCount->GetValue()); // Create the meshes OrderedGrid grid(initSphereCount, kSeed); uint32_t meshIndex = 0; for (const auto& lod : kAvailableLODs) { PPX_LOG_INFO("LOD: " << lod.name); - SphereMesh sphereMesh(/* radius = */ 1, lod.value.longitudeSegments, lod.value.latitudeSegments); + SphereMesh sphereMesh(/* radius = */ 1, lod.value.longitudeSegments, lod.value.latitudeSegments, GetSettings()->xr.enable); sphereMesh.ApplyGrid(grid); // Create a giant vertex buffer for each vb type to accommodate all copies of the sphere mesh PPX_CHECKED_CALL(grfx_util::CreateMeshFromGeometry(GetGraphicsQueue(), sphereMesh.GetLowPrecisionInterleaved(), &mSphereMeshes[meshIndex++])); @@ -729,7 +825,7 @@ Result GraphicsBenchmarkApp::CompilePipeline(const QuadPipelineKey& key) gpCreateInfo.frontFace = grfx::FRONT_FACE_CW; gpCreateInfo.depthReadEnable = false; gpCreateInfo.depthWriteEnable = false; - gpCreateInfo.blendModes[0] = grfx::BLEND_MODE_NONE; + gpCreateInfo.blendModes[0] = pKnobQuadBlendMode->GetValue(); gpCreateInfo.outputState.renderTargetCount = 1; gpCreateInfo.outputState.renderTargetFormats[0] = key.renderFormat; gpCreateInfo.outputState.depthStencilFormat = GetSwapchain()->GetDepthFormat(); @@ -785,8 +881,8 @@ void GraphicsBenchmarkApp::SetupFullscreenQuadsPipelines() { grfx::PipelineInterfaceCreateInfo piCreateInfo = {}; piCreateInfo.setCount = 0; - piCreateInfo.pushConstants.count = sizeof(uint32_t) / 4; - piCreateInfo.pushConstants.binding = 0; + piCreateInfo.pushConstants.count = GetPushConstCount(mQuadPushConstant); + piCreateInfo.pushConstants.binding = QUADS_CONFIG_UNIFORM_BUFFER_REGISTER; piCreateInfo.pushConstants.set = 0; PPX_CHECKED_CALL(GetDevice()->CreatePipelineInterface(&piCreateInfo, &mQuadsPipelineInterfaces[0])); @@ -795,8 +891,8 @@ void GraphicsBenchmarkApp::SetupFullscreenQuadsPipelines() { grfx::PipelineInterfaceCreateInfo piCreateInfo = {}; piCreateInfo.setCount = 0; - piCreateInfo.pushConstants.count = sizeof(float3) / 4; - piCreateInfo.pushConstants.binding = 0; + piCreateInfo.pushConstants.count = GetPushConstCount(mQuadPushConstant); + piCreateInfo.pushConstants.binding = QUADS_CONFIG_UNIFORM_BUFFER_REGISTER; piCreateInfo.pushConstants.set = 0; PPX_CHECKED_CALL(GetDevice()->CreatePipelineInterface(&piCreateInfo, &mQuadsPipelineInterfaces[1])); @@ -807,6 +903,9 @@ void GraphicsBenchmarkApp::SetupFullscreenQuadsPipelines() piCreateInfo.setCount = 1; piCreateInfo.sets[0].set = 0; piCreateInfo.sets[0].pLayout = mFullscreenQuads.descriptorSetLayout; + piCreateInfo.pushConstants.count = GetPushConstCount(mQuadPushConstant); + piCreateInfo.pushConstants.binding = QUADS_CONFIG_UNIFORM_BUFFER_REGISTER; + piCreateInfo.pushConstants.set = 0; PPX_CHECKED_CALL(GetDevice()->CreatePipelineInterface(&piCreateInfo, &mQuadsPipelineInterfaces[2])); } @@ -1393,7 +1492,7 @@ ppx::Result GraphicsBenchmarkApp::CreateOffscreenFrame(OffscreenFrame& frame, gr frame = OffscreenFrame{width, height, colorFormat, depthFormat}; { grfx::ImageCreateInfo colorCreateInfo = grfx::ImageCreateInfo::RenderTarget2D(width, height, colorFormat); - colorCreateInfo.initialState = grfx::RESOURCE_STATE_PRESENT; + colorCreateInfo.initialState = GetSettings()->xr.enable ? grfx::RESOURCE_STATE_RENDER_TARGET : grfx::RESOURCE_STATE_PRESENT; colorCreateInfo.usageFlags.bits.sampled = true; ppx::Result ppxres = GetDevice()->CreateImage(&colorCreateInfo, &frame.colorImage); if (ppxres != ppx::SUCCESS) { @@ -1508,8 +1607,9 @@ ppx::grfx::Format GraphicsBenchmarkApp::RenderFormat() void GraphicsBenchmarkApp::CreateColorsForDrawCalls() { // Create colors randomly. - mColorsForDrawCalls.resize(kMaxSphereInstanceCount); - for (size_t i = 0; i < kMaxSphereInstanceCount; i++) { + uint32_t sphereCount = pSphereInstanceCount->GetValue(); + mColorsForDrawCalls.resize(sphereCount); + for (size_t i = 0; i < sphereCount; i++) { mColorsForDrawCalls[i] = float4(mRandom.Float(), mRandom.Float(), mRandom.Float(), 0.5f); } } @@ -1522,7 +1622,13 @@ void GraphicsBenchmarkApp::RecordCommandBuffer(PerFrame& frame, const RenderPass frame.cmd->WriteTimestamp(frame.timestampQuery, grfx::PIPELINE_STAGE_TOP_OF_PIPE_BIT, /* queryIndex = */ 0); if (!pRenderOffscreen->GetValue()) { frame.cmd->SetScissors(GetScissor()); - frame.cmd->SetViewports(GetViewport()); + grfx::Viewport viewport = GetViewport(); + QuadViewportScale height_scale = pKnobViewportHeightScale->GetValue(); + QuadViewportScale width_scale = pKnobViewportWidthScale->GetValue(); + viewport.height *= height_scale.scale; + viewport.width *= width_scale.scale; + + frame.cmd->SetViewports(viewport); } else { uint32_t width = mOffscreenFrame.back().width; @@ -1718,10 +1824,17 @@ void GraphicsBenchmarkApp::RecordCommandBufferSpheres(PerFrame& frame) void GraphicsBenchmarkApp::RecordCommandBufferFullscreenQuad(PerFrame& frame, size_t seed) { + // Vertex shader push constant + { + mQuadPushConstant.InstCount = pKnobShaderAluLoopCount->GetValue(); + frame.cmd->PushGraphicsConstants(mQuadsPipelineInterfaces[static_cast(FullscreenQuadsType::FULLSCREEN_QUADS_TYPE_NOISE)], GetPushConstCount(mQuadPushConstant.InstCount), &mQuadPushConstant.InstCount, offsetof(QuadPushConstant, InstCount) / sizeof(uint32_t)); + frame.cmd->PushGraphicsConstants(mQuadsPipelineInterfaces[static_cast(FullscreenQuadsType::FULLSCREEN_QUADS_TYPE_SOLID_COLOR)], GetPushConstCount(mQuadPushConstant.InstCount), &mQuadPushConstant.InstCount, offsetof(QuadPushConstant, InstCount) / sizeof(uint32_t)); + frame.cmd->PushGraphicsConstants(mQuadsPipelineInterfaces[static_cast(FullscreenQuadsType::FULLSCREEN_QUADS_TYPE_TEXTURE)], GetPushConstCount(mQuadPushConstant.InstCount), &mQuadPushConstant.InstCount, offsetof(QuadPushConstant, InstCount) / sizeof(uint32_t)); + } switch (pFullscreenQuadsType->GetValue()) { case FullscreenQuadsType::FULLSCREEN_QUADS_TYPE_NOISE: { - uint32_t noiseQuadRandomSeed = (uint32_t)seed; - frame.cmd->PushGraphicsConstants(mQuadsPipelineInterfaces[0], 1, &noiseQuadRandomSeed); + mQuadPushConstant.RandomSeed = seed; + frame.cmd->PushGraphicsConstants(mQuadsPipelineInterfaces[static_cast(pFullscreenQuadsType->GetValue())], GetPushConstCount(mQuadPushConstant.RandomSeed), &mQuadPushConstant.RandomSeed, offsetof(QuadPushConstant, RandomSeed) / sizeof(uint32_t)); break; } case FullscreenQuadsType::FULLSCREEN_QUADS_TYPE_SOLID_COLOR: { @@ -1738,15 +1851,19 @@ void GraphicsBenchmarkApp::RecordCommandBufferFullscreenQuad(PerFrame& frame, si } float3 colorValues = pFullscreenQuadsColor->GetValue(); colorValues *= intensity; - frame.cmd->PushGraphicsConstants(mQuadsPipelineInterfaces[1], 3, &colorValues); + mQuadPushConstant.ColorValue = colorValues; + frame.cmd->PushGraphicsConstants(mQuadsPipelineInterfaces[static_cast(pFullscreenQuadsType->GetValue())], GetPushConstCount(mQuadPushConstant.ColorValue), &mQuadPushConstant.ColorValue, offsetof(QuadPushConstant, ColorValue) / sizeof(uint32_t)); break; } case FullscreenQuadsType::FULLSCREEN_QUADS_TYPE_TEXTURE: + mQuadPushConstant.TextureCount = pKnobTextureCount->GetValue(); + frame.cmd->PushGraphicsConstants(mQuadsPipelineInterfaces[static_cast(pFullscreenQuadsType->GetValue())], GetPushConstCount(mQuadPushConstant.TextureCount), &mQuadPushConstant.TextureCount, offsetof(QuadPushConstant, TextureCount) / sizeof(uint32_t)); break; default: PPX_ASSERT_MSG(true, "unsupported FullscreenQuadsType: " << static_cast(pFullscreenQuadsType->GetValue())); break; } + frame.cmd->Draw(3, 1, 0, 0); } diff --git a/benchmarks/graphics_pipeline/GraphicsBenchmarkApp.h b/benchmarks/graphics_pipeline/GraphicsBenchmarkApp.h index f59998ca1..fdf0720bf 100644 --- a/benchmarks/graphics_pipeline/GraphicsBenchmarkApp.h +++ b/benchmarks/graphics_pipeline/GraphicsBenchmarkApp.h @@ -19,6 +19,7 @@ #include "MultiDimensionalIndexer.h" #include "ppx/grfx/grfx_config.h" +#include "ppx/grfx/grfx_enums.h" #include "ppx/grfx/grfx_format.h" #include "ppx/knob.h" #include "ppx/math_config.h" @@ -30,16 +31,17 @@ #include #include -static constexpr uint32_t kMaxSphereInstanceCount = 3000; -static constexpr uint32_t kDefaultSphereInstanceCount = 50; +static constexpr uint32_t kMaxSphereInstanceCount = 5000; +static constexpr uint32_t kDefaultSphereInstanceCount = 1; static constexpr uint32_t kSeed = 89977; static constexpr uint32_t kMaxFullscreenQuadsCount = 1000; +static constexpr uint32_t kMaxTextureCount = 10; static constexpr float4 kDefaultDrawCallColor = float4(1.0f, 0.175f, 0.365f, 0.5f); static constexpr uint32_t kDebugColorPushConstantCount = sizeof(float4) / sizeof(uint32_t); static constexpr const char* kShaderBaseDir = "benchmarks/shaders"; -static constexpr const char* kQuadTextureFile = "benchmarks/textures/resolution.jpg"; +static constexpr const char* kQuadTextureFile = "benchmarks/textures/fish/2364x2880.png"; enum class DebugView { @@ -219,6 +221,27 @@ static constexpr std::array, 8 + 9> kVRPerEyeResolutions = { {3680, 3140}, // Vision Pro, estimation from Wikipedia }}; +struct QuadViewportScale +{ + float scale; +}; + +static constexpr std::array, 7> kAvailableViewportScales = {{ + {"1", 1.0}, // No scale + {"1/2", 0.5}, // scale to 1/2 + {"1/4", 0.25}, // scale to 1/4 + {"1/16", 0.0625}, // scale to 1/16" + {"1/64", 0.015625}, // scale to 1/64" + {"1/256", 0.00390625}, // scale to 1/256" + {"1/1024", 0.0009765625}, // scale t0 1/1024 +}}; + +static constexpr std::array, 3> kQuadBlendModes = {{ + {"none", grfx::BLEND_MODE_NONE}, + {"alpha", grfx::BLEND_MODE_ALPHA}, + {"disable_output", grfx::BLEND_MODE_DISABLE_OUTPUT}, +}}; + class GraphicsBenchmarkApp : public ppx::Application { @@ -427,6 +450,15 @@ class GraphicsBenchmarkApp grfx::FullscreenQuadPtr quad; }; + // Needs to match with the definition at assets/benchmarks/shaders/Benchmark_Quad.hlsli + struct QuadPushConstant + { + uint32_t InstCount; + uint32_t RandomSeed; + uint32_t TextureCount; + float3 ColorValue; + }; + private: using SpherePipelineMap = std::unordered_map; using SkyboxPipelineMap = std::unordered_map; @@ -470,7 +502,8 @@ class GraphicsBenchmarkApp // Fullscreen quads resources Entity2D mFullscreenQuads; grfx::ShaderModulePtr mVSQuads; - grfx::TexturePtr mQuadsTexture; + std::array mQuadsTextures; + grfx::BufferPtr mQuadsDummyBuffer; QuadPipelineMap mQuadsPipelines; std::array mQuadsPipelineInterfaces; std::array mQuadsPs; @@ -492,6 +525,8 @@ class GraphicsBenchmarkApp // This is used to skip first several frames after the knob of quad count being changed uint32_t mSkipRecordBandwidthMetricFrameCounter = 0; + QuadPushConstant mQuadPushConstant; + private: std::shared_ptr pEnableSkyBox; std::shared_ptr pEnableSpheres; @@ -509,6 +544,7 @@ class GraphicsBenchmarkApp std::shared_ptr> pFullscreenQuadsCount; std::shared_ptr> pFullscreenQuadsType; + std::shared_ptr> pTextureShaderALU; std::shared_ptr> pFullscreenQuadsColor; std::shared_ptr pFullscreenQuadsSingleRenderpass; std::shared_ptr> pQuadTextureFile; @@ -518,6 +554,12 @@ class GraphicsBenchmarkApp std::shared_ptr> pFramebufferFormat; std::shared_ptr>> pResolution; + std::shared_ptr> pKnobShaderAluLoopCount; + std::shared_ptr> pKnobTextureCount; + std::shared_ptr> pKnobViewportHeightScale; + std::shared_ptr> pKnobViewportWidthScale; + std::shared_ptr> pKnobQuadBlendMode; + private: // ===================================================================== // SETUP (One-time setup for objects) diff --git a/benchmarks/graphics_pipeline/SphereMesh.cpp b/benchmarks/graphics_pipeline/SphereMesh.cpp index 8895d985c..0928d81b6 100644 --- a/benchmarks/graphics_pipeline/SphereMesh.cpp +++ b/benchmarks/graphics_pipeline/SphereMesh.cpp @@ -38,14 +38,23 @@ OrderedGrid::OrderedGrid(uint32_t count, uint32_t randomSeed) Shuffle(mOrderedPointIndices.begin(), mOrderedPointIndices.end(), std::mt19937(randomSeed)); } -float4x4 OrderedGrid::GetModelMatrix(uint32_t sphereIndex) const +float4x4 OrderedGrid::GetModelMatrix(uint32_t sphereIndex, bool isXR) const { uint32_t id = mOrderedPointIndices[sphereIndex]; - uint32_t x = (id % (mSizeX * mSizeY)) / mSizeY; - uint32_t y = id % mSizeY; - uint32_t z = id / (mSizeX * mSizeY); + float x = static_cast((id % (mSizeX * mSizeY)) / mSizeY); + float y = static_cast(id % mSizeY); + float z = static_cast(id / (mSizeX * mSizeY)); + + // Put it in the center of the screen. + x -= static_cast(mSizeX - 1) / 2.0; + y -= static_cast(mSizeY - 1) / 2.0; + z += static_cast(mSizeZ); + if (isXR) { + z *= -1.0; + } - return glm::translate(float3(x * mStep, y * mStep, z * mStep)); + float scale_factor = static_cast(mSizeX) * 5.0f; + return glm::translate(float3(x * mStep, y * mStep, z * mStep)) * glm::scale(float3(scale_factor, scale_factor, scale_factor)); } // ===================================================================== @@ -202,7 +211,7 @@ void SphereMesh::RepeatGeometryNonPositionVertexData(const Geometry& srcGeom, Ve void SphereMesh::WriteSpherePosition(const OrderedGrid& grid, uint32_t sphereIndex) { - float4x4 modelMatrix = grid.GetModelMatrix(sphereIndex); + float4x4 modelMatrix = grid.GetModelMatrix(sphereIndex, IsXR()); for (uint32_t j = 0; j < mSingleSphereVertexCount; ++j) { TriMeshVertexData vertexData = {}; diff --git a/benchmarks/graphics_pipeline/SphereMesh.h b/benchmarks/graphics_pipeline/SphereMesh.h index e63d63c2b..deb45a79d 100644 --- a/benchmarks/graphics_pipeline/SphereMesh.h +++ b/benchmarks/graphics_pipeline/SphereMesh.h @@ -34,7 +34,7 @@ class OrderedGrid uint32_t GetCount() const { return static_cast(mOrderedPointIndices.size()); } // Get model matrix that can be applied to move a model space object to this point - float4x4 GetModelMatrix(uint32_t pointIndex) const; + float4x4 GetModelMatrix(uint32_t pointIndex, bool isXR) const; private: uint32_t mSizeX; @@ -83,11 +83,12 @@ class SphereMesh }; // Creates a SphereMesh and populates info for one sphere - SphereMesh(float radius, uint32_t longitudeSegments, uint32_t latitudeSegments) + SphereMesh(float radius, uint32_t longitudeSegments, uint32_t latitudeSegments, bool isXR) { mSingleSphereMesh = TriMesh::CreateSphere(radius, longitudeSegments, latitudeSegments, TriMeshOptions().Indices().TexCoords().Normals().Tangents()); mSingleSphereVertexCount = mSingleSphereMesh.GetCountPositions(); mSingleSphereTriCount = mSingleSphereMesh.GetCountTriangles(); + mIsXR = isXR; PPX_LOG_INFO("Creating SphereMesh:"); PPX_LOG_INFO(" Sphere vertex count: " << mSingleSphereVertexCount << " | triangle count: " << mSingleSphereTriCount); @@ -102,6 +103,8 @@ class SphereMesh const Geometry* GetHighPrecisionInterleaved() const { return &mHighInterleaved; } const Geometry* GetHighPrecisionPositionPlanar() const { return &mHighPlanar; } + bool IsXR() const { return mIsXR; } + private: // Create all single sphere and full geometries void CreateAllGeometries(); @@ -143,6 +146,8 @@ class SphereMesh Geometry mLowPlanar; Geometry mHighInterleaved; Geometry mHighPlanar; + + bool mIsXR; }; // Overwrite the position data within a position buffer with vtx.position, at vertex elementIndex only diff --git a/include/ppx/config.h b/include/ppx/config.h index 25922637a..dddafc8eb 100644 --- a/include/ppx/config.h +++ b/include/ppx/config.h @@ -251,6 +251,7 @@ inline const char* ToString(ppx::Result value) case Result::ERROR_GRFX_INVALID_BINDING_NUMBER : return "ERROR_GRFX_INVALID_BINDING_NUMBER"; case Result::ERROR_GRFX_INVALID_SET_NUMBER : return "ERROR_GRFX_INVALID_SET_NUMBER"; case Result::ERROR_GRFX_OPERATION_NOT_PERMITTED : return "ERROR_GRFX_OPERATION_NOT_PERMITTED"; + case Result::ERROR_GRFX_INVALID_SEMAPHORE_TYPE : return "ERROR_GRFX_INVALID_SEMAPHORE_TYPE"; case Result::ERROR_IMAGE_FILE_LOAD_FAILED : return "ERROR_IMAGE_FILE_LOAD_FAILED"; case Result::ERROR_IMAGE_FILE_SAVE_FAILED : return "ERROR_IMAGE_FILE_SAVE_FAILED"; @@ -264,12 +265,37 @@ inline const char* ToString(ppx::Result value) case Result::ERROR_GEOMETRY_NO_INDEX_DATA : return "ERROR_GEOMETRY_NO_INDEX_DATA"; case Result::ERROR_GEOMETRY_FILE_LOAD_FAILED : return "ERROR_GEOMETRY_FILE_LOAD_FAILED"; case Result::ERROR_GEOMETRY_FILE_NO_DATA : return "ERROR_GEOMETRY_FILE_NO_DATA"; + case Result::ERROR_GEOMETRY_INVALID_VERTEX_SEMANTIC : return "ERROR_GEOMETRY_INVALID_VERTEX_SEMANTIC"; case Result::ERROR_WINDOW_EVENTS_ALREADY_REGISTERED : return "ERROR_WINDOW_EVENTS_ALREADY_REGISTERED"; case Result::ERROR_IMGUI_INITIALIZATION_FAILED : return "ERROR_IMGUI_INITIALIZATION_FAILED"; case Result::ERROR_FONT_PARSE_FAILED : return "ERROR_FONT_PARSE_FAILED"; case Result::ERROR_INVALID_UTF8_STRING : return "ERROR_INVALID_UTF8_STRING"; + + case Result::ERROR_PPM_EXPORT_FORMAT_NOT_SUPPORTED : return "ERROR_PPM_EXPORT_FORMAT_NOT_SUPPORTED"; + case Result::ERROR_PPM_EXPORT_INVALID_SIZE : return "ERROR_PPM_EXPORT_INVALID_SIZE"; + + case Result::ERROR_SCENE_UNSUPPORTED_FILE_TYPE : return "ERROR_SCENE_UNSUPPORTED_FILE_TYPE"; + case Result::ERROR_SCENE_UNSUPPORTED_NODE_TYPE : return "ERROR_SCENE_UNSUPPORTED_NODE_TYPE"; + case Result::ERROR_SCENE_UNSUPPORTED_CAMERA_TYPE : return "ERROR_SCENE_UNSUPPORTED_CAMERA_TYPE"; + case Result::ERROR_SCENE_UNSUPPORTED_TOPOLOGY_TYPE : return "ERROR_SCENE_UNSUPPORTED_TOPOLOGY_TYPE"; + case Result::ERROR_SCENE_SOURCE_FILE_LOAD_FAILED : return "ERROR_SCENE_SOURCE_FILE_LOAD_FAILED"; + case Result::ERROR_SCENE_NO_SOURCE_DATA : return "ERROR_SCENE_NO_SOURCE_DATA"; + case Result::ERROR_SCENE_INVALID_SOURCE_SCENE : return "ERROR_SCENE_INVALID_SOURCE_SCENE"; + case Result::ERROR_SCENE_INVALID_SOURCE_NODE : return "ERROR_SCENE_INVALID_SOURCE_NODE"; + case Result::ERROR_SCENE_INVALID_SOURCE_CAMERA : return "ERROR_SCENE_INVALID_SOURCE_CAMERA"; + case Result::ERROR_SCENE_INVALID_SOURCE_LIGHT : return "ERROR_SCENE_INVALID_SOURCE_LIGHT"; + case Result::ERROR_SCENE_INVALID_SOURCE_MESH : return "ERROR_SCENE_INVALID_SOURCE_MESH"; + case Result::ERROR_SCENE_INVALID_SOURCE_GEOMETRY_INDEX_TYPE : return "ERROR_SCENE_INVALID_SOURCE_GEOMETRY_INDEX_TYPE"; + case Result::ERROR_SCENE_INVALID_SOURCE_GEOMETRY_INDEX_DATA : return "ERROR_SCENE_INVALID_SOURCE_GEOMETRY_INDEX_DATA"; + case Result::ERROR_SCENE_INVALID_SOURCE_GEOMETRY_VERTEX_DATA : return "ERROR_SCENE_INVALID_SOURCE_GEOMETRY_VERTEX_DATA"; + case Result::ERROR_SCENE_INVALID_SOURCE_MATERIAL : return "ERROR_SCENE_INVALID_SOURCE_MATERIAL"; + case Result::ERROR_SCENE_INVALID_SOURCE_TEXTURE : return "ERROR_SCENE_INVALID_SOURCE_TEXTURE"; + case Result::ERROR_SCENE_INVALID_SOURCE_IMAGE : return "ERROR_SCENE_INVALID_SOURCE_IMAGE"; + case Result::ERROR_SCENE_INVALID_NODE_HIERARCHY : return "ERROR_SCENE_INVALID_NODE_HIERARCHY"; + case Result::ERROR_SCENE_INVALID_STANDALONE_OPERATION : return "ERROR_SCENE_INVALID_STANDALONE_OPERATION"; + case Result::ERROR_SCENE_NODE_ALREADY_HAS_PARENT : return "ERROR_SCENE_NODE_ALREADY_HAS_PARENT"; } // clang-format on return ""; diff --git a/include/ppx/geometry.h b/include/ppx/geometry.h index 032f7986b..aadb0b024 100644 --- a/include/ppx/geometry.h +++ b/include/ppx/geometry.h @@ -60,13 +60,16 @@ struct GeometryCreateInfo grfx::VertexBinding vertexBindings[PPX_MAX_VERTEX_BINDINGS] = {}; grfx::PrimitiveTopology primitiveTopology = grfx::PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; - // Creates a create info objects with a UINT16 or UINT32 index + // Creates a create info objects with UINT8, UINT16 or UINT32 index // type and position vertex attribute. // + static GeometryCreateInfo InterleavedU8(grfx::Format format = grfx::FORMAT_R32G32B32_FLOAT); static GeometryCreateInfo InterleavedU16(grfx::Format format = grfx::FORMAT_R32G32B32_FLOAT); static GeometryCreateInfo InterleavedU32(grfx::Format format = grfx::FORMAT_R32G32B32_FLOAT); + static GeometryCreateInfo PlanarU8(grfx::Format format = grfx::FORMAT_R32G32B32_FLOAT); static GeometryCreateInfo PlanarU16(grfx::Format format = grfx::FORMAT_R32G32B32_FLOAT); static GeometryCreateInfo PlanarU32(grfx::Format format = grfx::FORMAT_R32G32B32_FLOAT); + static GeometryCreateInfo PositionPlanarU8(grfx::Format format = grfx::FORMAT_R32G32B32_FLOAT); static GeometryCreateInfo PositionPlanarU16(grfx::Format format = grfx::FORMAT_R32G32B32_FLOAT); static GeometryCreateInfo PositionPlanarU32(grfx::Format format = grfx::FORMAT_R32G32B32_FLOAT); @@ -77,6 +80,7 @@ struct GeometryCreateInfo static GeometryCreateInfo PositionPlanar(); GeometryCreateInfo& IndexType(grfx::IndexType indexType_); + GeometryCreateInfo& IndexTypeU8(); GeometryCreateInfo& IndexTypeU16(); GeometryCreateInfo& IndexTypeU32(); @@ -242,6 +246,7 @@ class Geometry // Appends single index, triangle, or edge vertex indices to index buffer // // Will cast to uint16_t if geometry index type is UINT16. + // Will cast to uint8_t if geometry index type is UINT8. // NOOP if index type is UNDEFINED (geometry does not have index data). // void AppendIndex(uint32_t idx); diff --git a/include/ppx/grfx/grfx_enums.h b/include/ppx/grfx/grfx_enums.h index 16f3e81ff..4b3a6099a 100644 --- a/include/ppx/grfx/grfx_enums.h +++ b/include/ppx/grfx/grfx_enums.h @@ -69,12 +69,13 @@ enum BlendFactor //! enum BlendMode { - BLEND_MODE_NONE = 0, - BLEND_MODE_ADDITIVE = 1, - BLEND_MODE_ALPHA = 2, - BLEND_MODE_OVER = 3, - BLEND_MODE_UNDER = 4, - BLEND_MODE_PREMULT_ALPHA = 5, + BLEND_MODE_NONE = 0, + BLEND_MODE_ADDITIVE = 1, + BLEND_MODE_ALPHA = 2, + BLEND_MODE_OVER = 3, + BLEND_MODE_UNDER = 4, + BLEND_MODE_PREMULT_ALPHA = 5, + BLEND_MODE_DISABLE_OUTPUT = 6, // Mode used to disable vs output. }; enum BlendOp @@ -322,6 +323,9 @@ enum IndexType INDEX_TYPE_UNDEFINED = 0, INDEX_TYPE_UINT16 = 1, INDEX_TYPE_UINT32 = 2, + // Vulkan: UINT8 requires VK_EXT_index_type_uint8 + // DX12: UINT8 is not supported + INDEX_TYPE_UINT8 = 3, }; enum LogicOp @@ -504,8 +508,9 @@ enum VendorId enum VertexInputRate { - VERTEX_INPUT_RATE_VERTEX = 0, - VERETX_INPUT_RATE_INSTANCE = 1, + INVALID_VERTEX_INPUT_RATE = 0, + VERTEX_INPUT_RATE_VERTEX, + VERETX_INPUT_RATE_INSTANCE, }; enum VertexSemantic diff --git a/include/ppx/grfx/grfx_helper.h b/include/ppx/grfx/grfx_helper.h index 1dcc0abfa..36d3dee7f 100644 --- a/include/ppx/grfx/grfx_helper.h +++ b/include/ppx/grfx/grfx_helper.h @@ -486,11 +486,9 @@ class VertexBinding VertexBinding& operator+=(const grfx::VertexAttribute& rhs); private: - static const grfx::VertexInputRate kInvalidVertexInputRate = static_cast(~0); - uint32_t mBinding = 0; uint32_t mStride = 0; - grfx::VertexInputRate mInputRate = kInvalidVertexInputRate; + grfx::VertexInputRate mInputRate = grfx::INVALID_VERTEX_INPUT_RATE; std::vector mAttributes; }; diff --git a/include/ppx/grfx/grfx_pipeline.h b/include/ppx/grfx/grfx_pipeline.h index c82ea5d4c..71963b44a 100644 --- a/include/ppx/grfx/grfx_pipeline.h +++ b/include/ppx/grfx/grfx_pipeline.h @@ -128,13 +128,15 @@ struct BlendAttachmentState grfx::ColorComponentFlags colorWriteMask = grfx::ColorComponentFlags::RGBA(); // These are best guesses based on random formulas off of the internet. - // Correct later when authorative literature is found. + // Correct later when authoritative literature is found. // + static grfx::BlendAttachmentState BlendModeNone(); static grfx::BlendAttachmentState BlendModeAdditive(); static grfx::BlendAttachmentState BlendModeAlpha(); static grfx::BlendAttachmentState BlendModeOver(); static grfx::BlendAttachmentState BlendModeUnder(); static grfx::BlendAttachmentState BlendModePremultAlpha(); + static grfx::BlendAttachmentState BlendModeDisableOutput(); }; struct ColorBlendState diff --git a/include/ppx/grfx/vk/vk_shading_rate.h b/include/ppx/grfx/vk/vk_shading_rate.h index 854a9293c..c4e7a2268 100644 --- a/include/ppx/grfx/vk/vk_shading_rate.h +++ b/include/ppx/grfx/vk/vk_shading_rate.h @@ -18,6 +18,7 @@ #include #include "ppx/grfx/vk/vk_config.h" +#include "ppx/grfx/vk/vk_device.h" #include "ppx/grfx/grfx_shading_rate.h" namespace ppx { diff --git a/projects/CMakeLists.txt b/projects/CMakeLists.txt index d99fbfb3a..de90bd555 100644 --- a/projects/CMakeLists.txt +++ b/projects/CMakeLists.txt @@ -16,6 +16,7 @@ project(projects) if (!PPX_ANDROID) add_subdirectory(sample_00_ppx_info) endif () + add_subdirectory(sample_01_triangle) add_subdirectory(sample_02_triangle_spinning) add_subdirectory(sample_03_square_textured) @@ -45,6 +46,7 @@ add_subdirectory(push_descriptors) add_subdirectory(mipmap_demo) add_subdirectory(gltf) add_subdirectory(dynamic_rendering) +add_subdirectory(gltf_basic_materials) add_subdirectory(alloc) add_subdirectory(fishtornado) add_subdirectory(fluid_simulation) diff --git a/projects/gltf_basic_materials/CMakeLists.txt b/projects/gltf_basic_materials/CMakeLists.txt new file mode 100644 index 000000000..705e2f6a0 --- /dev/null +++ b/projects/gltf_basic_materials/CMakeLists.txt @@ -0,0 +1,29 @@ +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +cmake_minimum_required(VERSION 3.0 FATAL_ERROR) + +project(gltf_basic_materials) + +add_samples_for_all_apis( + NAME ${PROJECT_NAME} + SOURCES + "GltfBasicMaterials.h" + "GltfBasicMaterials.cpp" + "main.cpp" + SHADER_DEPENDENCIES + "shader_scene_renderer_vertex_material_vertex" + "shader_scene_renderer_material_error" + "shader_scene_renderer_material_unlit" + "shader_scene_renderer_material_standard" +) diff --git a/projects/gltf_basic_materials/GltfBasicMaterials.cpp b/projects/gltf_basic_materials/GltfBasicMaterials.cpp new file mode 100644 index 000000000..a9cce8a4e --- /dev/null +++ b/projects/gltf_basic_materials/GltfBasicMaterials.cpp @@ -0,0 +1,411 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "GltfBasicMaterials.h" +#include "ppx/scene/scene_gltf_loader.h" +#include "ppx/graphics_util.h" + +namespace { + +using namespace ppx; + +#if defined(USE_DX12) +const grfx::Api kApi = grfx::API_DX_12_0; +#elif defined(USE_VK) +const grfx::Api kApi = grfx::API_VK_1_1; +#endif + +// Calculates a world space bounding box for the mesh. May be bigger than the actual bounding box (especially if rotation is applied) since the node's bounding box is the starting point for transformation (not the individual vertices). +ppx::AABB GetMeshNodeBoundingBox(const scene::MeshNode& meshNode) +{ + float3 obbVertices[8] = {}; + meshNode.GetMesh()->GetBoundingBox().Transform(meshNode.GetEvaluatedMatrix(), obbVertices); + ppx::AABB transformedBoundingBox; + transformedBoundingBox.Expand(obbVertices[0]); + transformedBoundingBox.Expand(obbVertices[1]); + transformedBoundingBox.Expand(obbVertices[2]); + transformedBoundingBox.Expand(obbVertices[3]); + transformedBoundingBox.Expand(obbVertices[4]); + transformedBoundingBox.Expand(obbVertices[5]); + transformedBoundingBox.Expand(obbVertices[6]); + transformedBoundingBox.Expand(obbVertices[7]); + return transformedBoundingBox; +} + +ppx::AABB GetSceneBoundingBox(const scene::Scene& scene) +{ + ppx::AABB sceneBoundingBox; + for (uint32_t i = 0; i < scene.GetMeshNodeCount(); ++i) { + const ppx::AABB transformedMeshNodeBoundingBox = GetMeshNodeBoundingBox(*scene.GetMeshNode(i)); + sceneBoundingBox.Expand(transformedMeshNodeBoundingBox.GetMax()); + sceneBoundingBox.Expand(transformedMeshNodeBoundingBox.GetMin()); + } + return sceneBoundingBox; +} + +} // namespace + +void GltfBasicMaterialsApp::Config(ppx::ApplicationSettings& settings) +{ + settings.appName = "gltf_basic_materials"; + settings.enableImGui = true; + settings.grfx.api = kApi; + settings.window.resizable = false; + settings.grfx.swapchain.depthFormat = grfx::FORMAT_D32_FLOAT; + settings.allowThirdPartyAssets = true; +} + +void GltfBasicMaterialsApp::Setup() +{ + // Per frame data + { + PerFrame frame = {}; + + PPX_CHECKED_CALL(GetGraphicsQueue()->CreateCommandBuffer(&frame.cmd)); + + grfx::SemaphoreCreateInfo semaCreateInfo = {}; + PPX_CHECKED_CALL(GetDevice()->CreateSemaphore(&semaCreateInfo, &frame.imageAcquiredSemaphore)); + + grfx::FenceCreateInfo fenceCreateInfo = {}; + PPX_CHECKED_CALL(GetDevice()->CreateFence(&fenceCreateInfo, &frame.imageAcquiredFence)); + + PPX_CHECKED_CALL(GetDevice()->CreateSemaphore(&semaCreateInfo, &frame.renderCompleteSemaphore)); + + fenceCreateInfo = {true}; // Create signaled + PPX_CHECKED_CALL(GetDevice()->CreateFence(&fenceCreateInfo, &frame.renderCompleteFence)); + + mPerFrame.push_back(frame); + } + + // Load GLTF scene + { + scene::GltfLoader* pLoader = nullptr; + // + PPX_CHECKED_CALL(scene::GltfLoader::Create(GetAssetPath(mSceneAssetKnob->GetValue()), /*pMaterialSelector=*/nullptr, &pLoader)); + + PPX_CHECKED_CALL(pLoader->LoadScene(GetDevice(), 0, &mScene)); + if (mScene->GetCameraNodeCount() == 0) { + PPX_LOG_WARN("Scene doesn't have a camera node. Using a default camera"); + mDefaultCamera = ArcballCamera(); + mDefaultCamera->SetPerspective(60.0f, GetWindowAspect()); + ppx::AABB boundingBox = GetSceneBoundingBox(*mScene); + // Bias FitToBoundingBox to keep the camera view straight-on the Z axis by placing the camera right in front of the scene on the Z axis. This tends to work well for most Khronos glTF-Sample-Assets. + float3 center = (boundingBox.GetMin() + boundingBox.GetMax()) / 2.0f; + mDefaultCamera->LookAt(center + float3(0, 0, 1), center); + mDefaultCamera->FitToBoundingBox(boundingBox.GetMin(), boundingBox.GetMax()); + } + PPX_ASSERT_MSG((mScene->GetMeshNodeCount() > 0), "scene doesn't have mesh nodes"); + + delete pLoader; + } + + // IBL Textures + { + PPX_CHECKED_CALL(grfx_util::CreateIBLTexturesFromFile(GetDevice()->GetGraphicsQueue(), GetAssetPath("poly_haven/ibl/old_depot_4k.ibl"), &mIBLIrrMap, &mIBLEnvMap)); + } + + // Pipeline args + { + PPX_CHECKED_CALL(scene::MaterialPipelineArgs::Create(GetDevice(), &mPipelineArgs)); + + // Populate material samplers + auto samplersIndexMap = mScene->GetSamplersArrayIndexMap(); + for (auto it : samplersIndexMap) { + mPipelineArgs->SetMaterialSampler(it.second, it.first); + } + + // Populate material images + auto imagesIndexMap = mScene->GetImagesArrayIndexMap(); + for (auto it : imagesIndexMap) { + mPipelineArgs->SetMaterialTexture(it.second, it.first); + } + + // Populate material params + mMaterialIndexMap = mScene->GetMaterialsArrayIndexMap(); + for (auto it : mMaterialIndexMap) { + auto pMaterial = it.first; + const uint32_t index = it.second; + + auto pMaterialParams = mPipelineArgs->GetMaterialParams(index); + + if (pMaterial->GetIdentString() == PPX_MATERIAL_IDENT_STANDARD) { + auto pStandardMaterial = static_cast(pMaterial); + + pMaterialParams->baseColorFactor = pStandardMaterial->GetBaseColorFactor(); + pMaterialParams->metallicFactor = pStandardMaterial->GetMetallicFactor(); + pMaterialParams->roughnessFactor = pStandardMaterial->GetRoughnessFactor(); + pMaterialParams->occlusionStrength = pStandardMaterial->GetOcclusionStrength(); + pMaterialParams->emissiveFactor = pStandardMaterial->GetEmissiveFactor(); + pMaterialParams->emissiveStrength = pStandardMaterial->GetEmissiveStrength(); + + scene::CopyMaterialTextureParams(samplersIndexMap, imagesIndexMap, pStandardMaterial->GetBaseColorTextureView(), pMaterialParams->baseColorTex); + scene::CopyMaterialTextureParams(samplersIndexMap, imagesIndexMap, pStandardMaterial->GetMetallicRoughnessTextureView(), pMaterialParams->metallicRoughnessTex); + scene::CopyMaterialTextureParams(samplersIndexMap, imagesIndexMap, pStandardMaterial->GetNormalTextureView(), pMaterialParams->normalTex); + scene::CopyMaterialTextureParams(samplersIndexMap, imagesIndexMap, pStandardMaterial->GetOcclusionTextureView(), pMaterialParams->occlusionTex); + scene::CopyMaterialTextureParams(samplersIndexMap, imagesIndexMap, pStandardMaterial->GetEmissiveTextureView(), pMaterialParams->emssiveTex); + } + else if (pMaterial->GetIdentString() == PPX_MATERIAL_IDENT_UNLIT) { + auto pUnlitMaterial = static_cast(pMaterial); + pMaterialParams->baseColorFactor = pUnlitMaterial->GetBaseColorFactor(); + scene::CopyMaterialTextureParams(samplersIndexMap, imagesIndexMap, pUnlitMaterial->GetBaseColorTextureView(), pMaterialParams->baseColorTex); + } + } + + // Populate IBL textures + mPipelineArgs->SetIBLTextures(0, mIBLIrrMap->GetSampledImageView(), mIBLEnvMap->GetSampledImageView()); + } + + // Pipelines + { + grfx::PipelineInterfaceCreateInfo piCreateInfo = {}; + piCreateInfo.pushConstants.count = 32; + piCreateInfo.pushConstants.binding = 0; + piCreateInfo.pushConstants.set = 0; + piCreateInfo.setCount = 1; + piCreateInfo.sets[0].set = 0; + piCreateInfo.sets[0].pLayout = mPipelineArgs->GetDescriptorSetLayout(); + PPX_CHECKED_CALL(GetDevice()->CreatePipelineInterface(&piCreateInfo, &mPipelineInterface)); + + // Get vertex bindings - every mesh in the test scene should have the same attributes + auto vertexBindings = mScene->GetMeshNode(0)->GetMesh()->GetMeshData()->GetAvailableVertexBindings(); + + auto CreatePipeline = [this, &vertexBindings](const std::string& vsName, const std::string& psName, grfx::GraphicsPipeline** ppPipeline) { + std::vector bytecode = LoadShader("scene_renderer/shaders", vsName); + PPX_ASSERT_MSG(!bytecode.empty(), "VS shader bytecode load failed"); + grfx::ShaderModuleCreateInfo shaderCreateInfo = {static_cast(bytecode.size()), bytecode.data()}; + PPX_CHECKED_CALL(GetDevice()->CreateShaderModule(&shaderCreateInfo, &mVS)); + + bytecode = LoadShader("scene_renderer/shaders", psName); + PPX_ASSERT_MSG(!bytecode.empty(), "PS shader bytecode load failed"); + shaderCreateInfo = {static_cast(bytecode.size()), bytecode.data()}; + PPX_CHECKED_CALL(GetDevice()->CreateShaderModule(&shaderCreateInfo, &mPS)); + + grfx::GraphicsPipelineCreateInfo2 gpCreateInfo = {}; + gpCreateInfo.VS = {mVS.Get(), "vsmain"}; + gpCreateInfo.PS = {mPS.Get(), "psmain"}; + gpCreateInfo.topology = grfx::PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + gpCreateInfo.polygonMode = grfx::POLYGON_MODE_FILL; + gpCreateInfo.cullMode = grfx::CULL_MODE_BACK; + gpCreateInfo.frontFace = grfx::FRONT_FACE_CCW; + gpCreateInfo.depthReadEnable = true; + gpCreateInfo.depthWriteEnable = true; + gpCreateInfo.blendModes[0] = grfx::BLEND_MODE_NONE; + gpCreateInfo.outputState.renderTargetCount = 1; + gpCreateInfo.outputState.renderTargetFormats[0] = GetSwapchain()->GetColorFormat(); + gpCreateInfo.outputState.depthStencilFormat = GetSwapchain()->GetDepthFormat(); + gpCreateInfo.pPipelineInterface = mPipelineInterface; + + gpCreateInfo.vertexInputState.bindingCount = CountU32(vertexBindings); + for (uint32_t i = 0; i < gpCreateInfo.vertexInputState.bindingCount; ++i) { + gpCreateInfo.vertexInputState.bindings[i] = vertexBindings[i]; + } + + PPX_CHECKED_CALL(GetDevice()->CreateGraphicsPipeline(&gpCreateInfo, ppPipeline)); + }; + + // Pipelines + CreatePipeline("MaterialVertex.vs", "StandardMaterial.ps", &mStandardMaterialPipeline); + CreatePipeline("MaterialVertex.vs", "UnlitMaterial.ps", &mUnlitMaterialPipeline); + CreatePipeline("MaterialVertex.vs", "ErrorMaterial.ps", &mErrorMaterialPipeline); + + // Compile pipelines for mmaterials + for (auto it : mMaterialIndexMap) { + auto pMaterial = it.first; + auto ident = pMaterial->GetIdentString(); + + if (ident == PPX_MATERIAL_IDENT_STANDARD) { + mMaterialPipelineMap[pMaterial] = mStandardMaterialPipeline; + } + else if (ident == PPX_MATERIAL_IDENT_UNLIT) { + mMaterialPipelineMap[pMaterial] = mUnlitMaterialPipeline; + } + else { + mMaterialPipelineMap[pMaterial] = mErrorMaterialPipeline; + } + } + } +} + +void GltfBasicMaterialsApp::Shutdown() +{ + delete mScene; + delete mPipelineArgs; +} + +void GltfBasicMaterialsApp::Render() +{ + PerFrame& frame = mPerFrame[0]; + + grfx::SwapchainPtr swapchain = GetSwapchain(); + + // Wait for and reset render complete fence + PPX_CHECKED_CALL(frame.renderCompleteFence->WaitAndReset()); + + uint32_t imageIndex = UINT32_MAX; + PPX_CHECKED_CALL(swapchain->AcquireNextImage(UINT64_MAX, frame.imageAcquiredSemaphore, frame.imageAcquiredFence, &imageIndex)); + + // Wait for and reset image acquired fence + PPX_CHECKED_CALL(frame.imageAcquiredFence->WaitAndReset()); + + // Update camera params + const ppx::Camera& camera = mDefaultCamera.has_value() ? *mDefaultCamera : *mScene->GetCameraNode(0)->GetCamera(); + mPipelineArgs->SetCameraParams(&camera); + + // Update instance params + { + const uint32_t numMeshNodes = mScene->GetMeshNodeCount(); + for (uint32_t instanceIdx = 0; instanceIdx < numMeshNodes; ++instanceIdx) { + auto pNode = mScene->GetMeshNode(instanceIdx); + auto pInstanceParmas = mPipelineArgs->GetInstanceParams(instanceIdx); + pInstanceParmas->modelMatrix = pNode->GetEvaluatedMatrix(); + } + } + + // Build command buffer + PPX_CHECKED_CALL(frame.cmd->Begin()); + { + // Copy pipeline args buffers + mPipelineArgs->CopyBuffers(frame.cmd); + + // Set descriptor set from pipeline args + auto pDescriptorSets = mPipelineArgs->GetDescriptorSet(); + frame.cmd->BindGraphicsDescriptorSets(mPipelineInterface, 1, &pDescriptorSets); + + grfx::RenderPassPtr renderPass = swapchain->GetRenderPass(imageIndex); + PPX_ASSERT_MSG(!renderPass.IsNull(), "render pass object is null"); + + grfx::RenderPassBeginInfo beginInfo = {}; + beginInfo.pRenderPass = renderPass; + beginInfo.renderArea = renderPass->GetRenderArea(); + beginInfo.RTVClearCount = 1; + beginInfo.RTVClearValues[0] = {{0.2f, 0.2f, 0.3f, 1}}; + + frame.cmd->TransitionImageLayout(renderPass->GetRenderTargetImage(0), PPX_ALL_SUBRESOURCES, grfx::RESOURCE_STATE_PRESENT, grfx::RESOURCE_STATE_RENDER_TARGET); + frame.cmd->BeginRenderPass(&beginInfo); + { + frame.cmd->SetScissors(GetScissor()); + frame.cmd->SetViewports(GetViewport()); + frame.cmd->BindGraphicsDescriptorSets(mPipelineInterface, 0, nullptr); + + // Set DrawParams::iblIndex and DrawParams::iblLevelCount + const uint32_t iblIndex = 0; + const uint32_t iblLevelCount = mIBLEnvMap->GetMipLevelCount(); + frame.cmd->PushGraphicsConstants(mPipelineInterface, 1, &iblIndex, 2); + frame.cmd->PushGraphicsConstants(mPipelineInterface, 1, &iblLevelCount, 3); + + // Draw scene + const uint32_t numMeshNodes = mScene->GetMeshNodeCount(); + for (uint32_t instanceIdx = 0; instanceIdx < numMeshNodes; ++instanceIdx) { + auto pNode = mScene->GetMeshNode(instanceIdx); + auto pMesh = pNode->GetMesh(); + + // Set DrawParams::instanceIndex + frame.cmd->PushGraphicsConstants( + mPipelineInterface, + 1, + &instanceIdx, + scene::MaterialPipelineArgs::INSTANCE_INDEX_CONSTANT_OFFSET); + + // Draw batches + auto& batches = pMesh->GetBatches(); + for (auto& batch : batches) { + // Set pipeline + auto pipeline = mMaterialPipelineMap[batch.GetMaterial()]; + frame.cmd->BindGraphicsPipeline(pipeline); + + // Set DrawParams::materialIndex + uint32_t materialIndex = mMaterialIndexMap[batch.GetMaterial()]; + frame.cmd->PushGraphicsConstants( + mPipelineInterface, + 1, + &materialIndex, + scene::MaterialPipelineArgs::MATERIAL_INDEX_CONSTANT_OFFSET); + + // Index buffer + frame.cmd->BindIndexBuffer(&batch.GetIndexBufferView()); + + // Vertex buffers + std::vector vertexBufferViews = { + batch.GetPositionBufferView(), + batch.GetAttributeBufferView()}; + frame.cmd->BindVertexBuffers(CountU32(vertexBufferViews), DataPtr(vertexBufferViews)); + + frame.cmd->DrawIndexed(batch.GetIndexCount(), 1, 0, 0, 0); + } + } + + // Draw ImGui + DrawDebugInfo(); + DrawImGui(frame.cmd); + } + frame.cmd->EndRenderPass(); + frame.cmd->TransitionImageLayout(renderPass->GetRenderTargetImage(0), PPX_ALL_SUBRESOURCES, grfx::RESOURCE_STATE_RENDER_TARGET, grfx::RESOURCE_STATE_PRESENT); + } + PPX_CHECKED_CALL(frame.cmd->End()); + + grfx::SubmitInfo submitInfo = {}; + submitInfo.commandBufferCount = 1; + submitInfo.ppCommandBuffers = &frame.cmd; + submitInfo.waitSemaphoreCount = 1; + submitInfo.ppWaitSemaphores = &frame.imageAcquiredSemaphore; + submitInfo.signalSemaphoreCount = 1; + submitInfo.ppSignalSemaphores = &frame.renderCompleteSemaphore; + submitInfo.pFence = frame.renderCompleteFence; + + PPX_CHECKED_CALL(GetGraphicsQueue()->Submit(&submitInfo)); + + PPX_CHECKED_CALL(swapchain->Present(imageIndex, 1, &frame.renderCompleteSemaphore)); +} + +void GltfBasicMaterialsApp::InitKnobs() +{ + GetKnobManager().InitKnob(&mSceneAssetKnob, "gltf-scene-asset", "scene_renderer/scenes/tests/gltf_test_basic_materials.glb"); + mSceneAssetKnob->SetFlagDescription("GLTF asset to load and render"); +} + +void GltfBasicMaterialsApp::MouseMove(int32_t x, int32_t y, int32_t dx, int32_t dy, uint32_t buttons) +{ + if (!mDefaultCamera) { + return; + } + + if (buttons & ppx::MOUSE_BUTTON_LEFT) { + int32_t prevX = x - dx; + int32_t prevY = y - dy; + + float2 prevPos = GetNormalizedDeviceCoordinates(prevX, prevY); + float2 curPos = GetNormalizedDeviceCoordinates(x, y); + + mDefaultCamera->Rotate(prevPos, curPos); + } + else if (buttons & ppx::MOUSE_BUTTON_RIGHT) { + int32_t prevX = x - dx; + int32_t prevY = y - dy; + + float2 prevPos = GetNormalizedDeviceCoordinates(prevX, prevY); + float2 curPos = GetNormalizedDeviceCoordinates(x, y); + float2 delta = curPos - prevPos; + + mDefaultCamera->Pan(delta); + } +} + +void GltfBasicMaterialsApp::Scroll(float dx, float dy) +{ + if (!mDefaultCamera) { + return; + } + constexpr float kZoomSpeed = 0.5f; + mDefaultCamera->Zoom(dy * kZoomSpeed); +} diff --git a/projects/gltf_basic_materials/GltfBasicMaterials.h b/projects/gltf_basic_materials/GltfBasicMaterials.h new file mode 100644 index 000000000..fc5a4d471 --- /dev/null +++ b/projects/gltf_basic_materials/GltfBasicMaterials.h @@ -0,0 +1,70 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef GLTF_BASIC_MATERIALS_H +#define GLTF_BASIC_MATERIALS_H + +#include "ppx/ppx.h" +#include "ppx/scene/scene_material.h" +#include "ppx/scene/scene_mesh.h" +#include "ppx/scene/scene_pipeline_args.h" + +#include + +class GltfBasicMaterialsApp + : public ppx::Application +{ +public: + void Config(ppx::ApplicationSettings& settings) override; + void Setup() override; + void Shutdown() override; + void Render() override; + void InitKnobs() override; + void MouseMove(int32_t x, int32_t y, int32_t dx, int32_t dy, uint32_t buttons) override; + void Scroll(float dx, float dy) override; + +private: + struct PerFrame + { + ppx::grfx::CommandBufferPtr cmd; + ppx::grfx::SemaphorePtr imageAcquiredSemaphore; + ppx::grfx::FencePtr imageAcquiredFence; + ppx::grfx::SemaphorePtr renderCompleteSemaphore; + ppx::grfx::FencePtr renderCompleteFence; + }; + + std::vector mPerFrame; + ppx::grfx::ShaderModulePtr mVS; + ppx::grfx::ShaderModulePtr mPS; + ppx::grfx::PipelineInterfacePtr mPipelineInterface; + ppx::grfx::GraphicsPipelinePtr mStandardMaterialPipeline = nullptr; + ppx::grfx::GraphicsPipelinePtr mUnlitMaterialPipeline = nullptr; + ppx::grfx::GraphicsPipelinePtr mErrorMaterialPipeline = nullptr; + + ppx::scene::Scene* mScene = nullptr; + ppx::scene::MaterialPipelineArgs* mPipelineArgs = nullptr; + + std::unordered_map mMaterialIndexMap; + std::unordered_map mMaterialPipelineMap; + + ppx::grfx::TexturePtr mIBLIrrMap; + ppx::grfx::TexturePtr mIBLEnvMap; + + std::shared_ptr> mSceneAssetKnob; + + // Contains a value only if the GLTF scene doesn't have a camera. + std::optional mDefaultCamera; +}; + +#endif // GLTF_BASIC_MATERIALS_H diff --git a/assets/benchmarks/shaders/Benchmark_Texture.hlsl b/projects/gltf_basic_materials/main.cpp similarity index 72% rename from assets/benchmarks/shaders/Benchmark_Texture.hlsl rename to projects/gltf_basic_materials/main.cpp index cfbc3441e..2f24d1f1a 100644 --- a/assets/benchmarks/shaders/Benchmark_Texture.hlsl +++ b/projects/gltf_basic_materials/main.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Google LLC +// Copyright 2024 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,11 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "VsOutput.hlsli" +#include "GltfBasicMaterials.h" -Texture2D Tex0 : register(t0); - -float4 psmain(VSOutputPos input) : SV_TARGET -{ - return Tex0.Load(uint3(input.position.x, input.position.y, /* mipmap */ 0)); -} \ No newline at end of file +SETUP_APPLICATION(GltfBasicMaterialsApp) diff --git a/src/android/AndroidManifest.XR.xml b/src/android/AndroidManifest.XR.xml index 013a7509a..4b0fb9bf8 100644 --- a/src/android/AndroidManifest.XR.xml +++ b/src/android/AndroidManifest.XR.xml @@ -14,19 +14,29 @@ - + + + + + + + + + diff --git a/src/ppx/geometry.cpp b/src/ppx/geometry.cpp index 31cf8b1ae..99ef219e0 100644 --- a/src/ppx/geometry.cpp +++ b/src/ppx/geometry.cpp @@ -206,6 +206,7 @@ class VertexDataProcessorInterleaved : public VertexDataProcessorBase const uint32_t vertexBindingCount = this->GetVertexBindingCount(pGeom); if (vertexBindingCount != 1) { PPX_ASSERT_MSG(false, "interleaved layout must have 1 binding"); + return false; } return true; } @@ -304,6 +305,7 @@ class VertexDataProcessorPositionPlanar : public VertexDataProcessorBase const uint32_t vertexBindingCount = this->GetVertexBindingCount(pGeom); if (vertexBindingCount != 2) { PPX_ASSERT_MSG(false, "position planar layout must have 2 bindings"); + return false; } return true; } @@ -413,6 +415,16 @@ static VertexDataProcessorPositionPlanar sVDProcess // ------------------------------------------------------------------------------------------------- // GeometryCreateInfo // ------------------------------------------------------------------------------------------------- +GeometryCreateInfo GeometryCreateInfo::InterleavedU8(grfx::Format format) +{ + GeometryCreateInfo ci = {}; + ci.vertexAttributeLayout = GEOMETRY_VERTEX_ATTRIBUTE_LAYOUT_INTERLEAVED; + ci.indexType = grfx::INDEX_TYPE_UINT8; + ci.vertexBindingCount = 1; // Interleave attribute layout always has 1 vertex binding + ci.AddPosition(format); + return ci; +} + GeometryCreateInfo GeometryCreateInfo::InterleavedU16(grfx::Format format) { GeometryCreateInfo ci = {}; @@ -433,6 +445,15 @@ GeometryCreateInfo GeometryCreateInfo::InterleavedU32(grfx::Format format) return ci; } +GeometryCreateInfo GeometryCreateInfo::PlanarU8(grfx::Format format) +{ + GeometryCreateInfo ci = {}; + ci.vertexAttributeLayout = GEOMETRY_VERTEX_ATTRIBUTE_LAYOUT_PLANAR; + ci.indexType = grfx::INDEX_TYPE_UINT8; + ci.AddPosition(format); + return ci; +} + GeometryCreateInfo GeometryCreateInfo::PlanarU16(grfx::Format format) { GeometryCreateInfo ci = {}; @@ -451,6 +472,15 @@ GeometryCreateInfo GeometryCreateInfo::PlanarU32(grfx::Format format) return ci; } +GeometryCreateInfo GeometryCreateInfo::PositionPlanarU8(grfx::Format format) +{ + GeometryCreateInfo ci = {}; + ci.vertexAttributeLayout = GEOMETRY_VERTEX_ATTRIBUTE_LAYOUT_POSITION_PLANAR; + ci.indexType = grfx::INDEX_TYPE_UINT8; + ci.AddPosition(format); + return ci; +} + GeometryCreateInfo GeometryCreateInfo::PositionPlanarU16(grfx::Format format) { GeometryCreateInfo ci = {}; @@ -503,6 +533,11 @@ GeometryCreateInfo& GeometryCreateInfo::IndexType(grfx::IndexType indexType_) return *this; } +GeometryCreateInfo& GeometryCreateInfo::IndexTypeU8() +{ + return IndexType(grfx::INDEX_TYPE_UINT8); +} + GeometryCreateInfo& GeometryCreateInfo::IndexTypeU16() { return IndexType(grfx::INDEX_TYPE_UINT16); @@ -619,6 +654,9 @@ GeometryCreateInfo& GeometryCreateInfo::AddBitangent(grfx::Format format) // ------------------------------------------------------------------------------------------------- uint32_t Geometry::Buffer::GetElementCount() const { + if (mElementSize == 0) { + return 0; + } size_t sizeOfData = mUsedSize; // round up for the case of interleaved buffers uint32_t count = static_cast(std::ceil(static_cast(sizeOfData) / static_cast(mElementSize))); @@ -678,20 +716,6 @@ Result Geometry::Create(const GeometryCreateInfo& createInfo, Geometry* pGeometr return ppx::ERROR_INVALID_CREATE_ARGUMENT; } - if (createInfo.indexType != grfx::INDEX_TYPE_UNDEFINED) { - uint32_t elementSize = 0; - if (createInfo.indexType == grfx::INDEX_TYPE_UINT16) { - elementSize = sizeof(uint16_t); - } - else if (createInfo.indexType == grfx::INDEX_TYPE_UINT32) { - elementSize = sizeof(uint32_t); - } - else { - PPX_ASSERT_MSG(false, "invalid index type"); - return ppx::ERROR_INVALID_CREATE_ARGUMENT; - } - } - if (createInfo.vertexBindingCount == 0) { PPX_ASSERT_MSG(false, "must have at least one vertex binding"); return ppx::ERROR_INVALID_CREATE_ARGUMENT; @@ -1091,6 +1115,9 @@ void Geometry::AppendIndex(uint32_t idx) else if (mCreateInfo.indexType == grfx::INDEX_TYPE_UINT32) { mIndexBuffer.Append(idx); } + else if (mCreateInfo.indexType == grfx::INDEX_TYPE_UINT8) { + mIndexBuffer.Append(static_cast(idx)); + } } void Geometry::AppendIndicesTriangle(uint32_t idx0, uint32_t idx1, uint32_t idx2) @@ -1105,6 +1132,11 @@ void Geometry::AppendIndicesTriangle(uint32_t idx0, uint32_t idx1, uint32_t idx2 mIndexBuffer.Append(idx1); mIndexBuffer.Append(idx2); } + else if (mCreateInfo.indexType == grfx::INDEX_TYPE_UINT8) { + mIndexBuffer.Append(static_cast(idx0)); + mIndexBuffer.Append(static_cast(idx1)); + mIndexBuffer.Append(static_cast(idx2)); + } } void Geometry::AppendIndicesEdge(uint32_t idx0, uint32_t idx1) @@ -1117,6 +1149,10 @@ void Geometry::AppendIndicesEdge(uint32_t idx0, uint32_t idx1) mIndexBuffer.Append(idx0); mIndexBuffer.Append(idx1); } + else if (mCreateInfo.indexType == grfx::INDEX_TYPE_UINT8) { + mIndexBuffer.Append(static_cast(idx0)); + mIndexBuffer.Append(static_cast(idx1)); + } } void Geometry::AppendIndicesU32(uint32_t count, const uint32_t* pIndices) diff --git a/src/ppx/grfx/grfx_helper.cpp b/src/ppx/grfx/grfx_helper.cpp index 4a3a6843a..f67fdbff3 100644 --- a/src/ppx/grfx/grfx_helper.cpp +++ b/src/ppx/grfx/grfx_helper.cpp @@ -79,7 +79,7 @@ VertexBinding& VertexBinding::AppendAttribute(const grfx::VertexAttribute& attri { mAttributes.push_back(attribute); - if (mInputRate == grfx::VertexBinding::kInvalidVertexInputRate) { + if (mInputRate == grfx::INVALID_VERTEX_INPUT_RATE) { mInputRate = attribute.inputRate; } diff --git a/src/ppx/grfx/grfx_pipeline.cpp b/src/ppx/grfx/grfx_pipeline.cpp index 92f4fde13..3a144d335 100644 --- a/src/ppx/grfx/grfx_pipeline.cpp +++ b/src/ppx/grfx/grfx_pipeline.cpp @@ -12,9 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include "ppx/config.h" #include "ppx/grfx/grfx_device.h" #include "ppx/grfx/grfx_pipeline.h" #include "ppx/grfx/grfx_descriptor.h" +#include "ppx/grfx/grfx_enums.h" namespace ppx { namespace grfx { @@ -22,6 +24,15 @@ namespace grfx { // ------------------------------------------------------------------------------------------------- // BlendAttachmentState // ------------------------------------------------------------------------------------------------- +grfx::BlendAttachmentState BlendAttachmentState::BlendModeNone() +{ + grfx::BlendAttachmentState state = {}; + state.blendEnable = false; + state.colorWriteMask = grfx::ColorComponentFlags::RGBA(); + + return state; +} + grfx::BlendAttachmentState BlendAttachmentState::BlendModeAdditive() { grfx::BlendAttachmentState state = {}; @@ -97,6 +108,15 @@ grfx::BlendAttachmentState BlendAttachmentState::BlendModePremultAlpha() return state; } +grfx::BlendAttachmentState BlendAttachmentState::BlendModeDisableOutput() +{ + grfx::BlendAttachmentState state = {}; + state.blendEnable = false; + state.colorWriteMask = grfx::ColorComponentFlags(0); + + return state; +} + namespace internal { // ------------------------------------------------------------------------------------------------- @@ -123,7 +143,7 @@ void FillOutGraphicsPipelineCreateInfo( } } - // Input aasembly + // Input assembly { pDstCreateInfo->inputAssemblyState.topology = pSrcCreateInfo->topology; } @@ -153,7 +173,10 @@ void FillOutGraphicsPipelineCreateInfo( pDstCreateInfo->colorBlendState.blendAttachmentCount = pSrcCreateInfo->outputState.renderTargetCount; for (uint32_t i = 0; i < pDstCreateInfo->colorBlendState.blendAttachmentCount; ++i) { switch (pSrcCreateInfo->blendModes[i]) { - default: break; + case grfx::BLEND_MODE_NONE: { + pDstCreateInfo->colorBlendState.blendAttachments[i] = grfx::BlendAttachmentState::BlendModeNone(); + break; + } case grfx::BLEND_MODE_ADDITIVE: { pDstCreateInfo->colorBlendState.blendAttachments[i] = grfx::BlendAttachmentState::BlendModeAdditive(); @@ -174,8 +197,17 @@ void FillOutGraphicsPipelineCreateInfo( case grfx::BLEND_MODE_PREMULT_ALPHA: { pDstCreateInfo->colorBlendState.blendAttachments[i] = grfx::BlendAttachmentState::BlendModePremultAlpha(); } break; + + case grfx::BLEND_MODE_DISABLE_OUTPUT: { + pDstCreateInfo->colorBlendState.blendAttachments[i] = grfx::BlendAttachmentState::BlendModeDisableOutput(); + break; + } + + default: { + PPX_ASSERT_MSG(false, "Unknown BlendMode"); + break; + } } - pDstCreateInfo->colorBlendState.blendAttachments[i].colorWriteMask = grfx::ColorComponentFlags::RGBA(); } } diff --git a/src/ppx/grfx/grfx_util.cpp b/src/ppx/grfx/grfx_util.cpp index be46a7918..b61575901 100644 --- a/src/ppx/grfx/grfx_util.cpp +++ b/src/ppx/grfx/grfx_util.cpp @@ -97,6 +97,7 @@ const char* ToString(grfx::IndexType value) case grfx::INDEX_TYPE_UNDEFINED: return "INDEX_TYPE_UNDEFINED"; case grfx::INDEX_TYPE_UINT16: return "INDEX_TYPE_UINT16"; case grfx::INDEX_TYPE_UINT32: return "INDEX_TYPE_UINT32"; + case grfx::INDEX_TYPE_UINT8: return "INDEX_TYPE_UINT8"; } return ""; } @@ -108,6 +109,7 @@ uint32_t IndexTypeSize(grfx::IndexType value) default: break; case grfx::INDEX_TYPE_UINT16: return sizeof(uint16_t); break; case grfx::INDEX_TYPE_UINT32: return sizeof(uint32_t); break; + case grfx::INDEX_TYPE_UINT8: return sizeof(uint8_t); break; } // clang-format on return 0; diff --git a/src/ppx/grfx/vk/vk_device.cpp b/src/ppx/grfx/vk/vk_device.cpp index aed5443ef..a9d5718ae 100644 --- a/src/ppx/grfx/vk/vk_device.cpp +++ b/src/ppx/grfx/vk/vk_device.cpp @@ -70,19 +70,19 @@ Result Device::ConfigureQueueInfo(const grfx::DeviceCreateInfo* pCreateInfo, std { std::unordered_set createdQueues; // Graphics - if (mGraphicsQueueFamilyIndex != PPX_VALUE_IGNORED) { + if (mGraphicsQueueFamilyIndex != PPX_VALUE_IGNORED && pCreateInfo->graphicsQueueCount > 0) { VkDeviceQueueCreateInfo vkci = {VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO}; vkci.queueFamilyIndex = mGraphicsQueueFamilyIndex; - vkci.queueCount = pCreateInfo->pGpu->GetGraphicsQueueCount(); + vkci.queueCount = pCreateInfo->graphicsQueueCount; vkci.pQueuePriorities = DataPtr(queuePriorities); queueCreateInfos.push_back(vkci); createdQueues.insert(mGraphicsQueueFamilyIndex); } // Compute - if (mComputeQueueFamilyIndex != PPX_VALUE_IGNORED && createdQueues.find(mComputeQueueFamilyIndex) == createdQueues.end()) { + if (mComputeQueueFamilyIndex != PPX_VALUE_IGNORED && createdQueues.find(mComputeQueueFamilyIndex) == createdQueues.end() && pCreateInfo->computeQueueCount > 0) { VkDeviceQueueCreateInfo vkci = {VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO}; vkci.queueFamilyIndex = mComputeQueueFamilyIndex; - vkci.queueCount = pCreateInfo->pGpu->GetComputeQueueCount(); + vkci.queueCount = pCreateInfo->computeQueueCount; vkci.pQueuePriorities = DataPtr(queuePriorities); queueCreateInfos.push_back(vkci); createdQueues.insert(mComputeQueueFamilyIndex); @@ -91,10 +91,10 @@ Result Device::ConfigureQueueInfo(const grfx::DeviceCreateInfo* pCreateInfo, std PPX_LOG_WARN("Graphics queue will be shared with compute queue."); } // Transfer - if (mTransferQueueFamilyIndex != PPX_VALUE_IGNORED && createdQueues.find(mTransferQueueFamilyIndex) == createdQueues.end()) { + if (mTransferQueueFamilyIndex != PPX_VALUE_IGNORED && createdQueues.find(mTransferQueueFamilyIndex) == createdQueues.end() && pCreateInfo->transferQueueCount > 0) { VkDeviceQueueCreateInfo vkci = {VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO}; vkci.queueFamilyIndex = mTransferQueueFamilyIndex; - vkci.queueCount = pCreateInfo->pGpu->GetTransferQueueCount(); + vkci.queueCount = pCreateInfo->transferQueueCount; vkci.pQueuePriorities = DataPtr(queuePriorities); queueCreateInfos.push_back(vkci); createdQueues.insert(mTransferQueueFamilyIndex); @@ -102,6 +102,10 @@ Result Device::ConfigureQueueInfo(const grfx::DeviceCreateInfo* pCreateInfo, std else if (createdQueues.find(mTransferQueueFamilyIndex) != createdQueues.end()) { PPX_LOG_WARN("Transfer queue will be shared with graphics or compute queue."); } + + if (createdQueues.size() == 0) { + PPX_LOG_WARN("No queues were requested. This is probably an error."); + } } return ppx::SUCCESS; @@ -499,6 +503,7 @@ Result Device::CreateQueues(const grfx::DeviceCreateInfo* pCreateInfo) uint32_t queueFamilyIndex = ToApi(pCreateInfo->pGpu)->GetGraphicsQueueFamilyIndex(); for (uint32_t queueIndex = 0; queueIndex < pCreateInfo->graphicsQueueCount; ++queueIndex) { grfx::internal::QueueCreateInfo queueCreateInfo = {}; + queueCreateInfo.commandType = grfx::COMMAND_TYPE_GRAPHICS; queueCreateInfo.queueFamilyIndex = queueFamilyIndex; queueCreateInfo.queueIndex = queueIndex; @@ -514,6 +519,7 @@ Result Device::CreateQueues(const grfx::DeviceCreateInfo* pCreateInfo) uint32_t queueFamilyIndex = ToApi(pCreateInfo->pGpu)->GetComputeQueueFamilyIndex(); for (uint32_t queueIndex = 0; queueIndex < pCreateInfo->computeQueueCount; ++queueIndex) { grfx::internal::QueueCreateInfo queueCreateInfo = {}; + queueCreateInfo.commandType = grfx::COMMAND_TYPE_COMPUTE; queueCreateInfo.queueFamilyIndex = queueFamilyIndex; queueCreateInfo.queueIndex = queueIndex; @@ -529,6 +535,7 @@ Result Device::CreateQueues(const grfx::DeviceCreateInfo* pCreateInfo) uint32_t queueFamilyIndex = ToApi(pCreateInfo->pGpu)->GetTransferQueueFamilyIndex(); for (uint32_t queueIndex = 0; queueIndex < pCreateInfo->transferQueueCount; ++queueIndex) { grfx::internal::QueueCreateInfo queueCreateInfo = {}; + queueCreateInfo.commandType = grfx::COMMAND_TYPE_TRANSFER; queueCreateInfo.queueFamilyIndex = queueFamilyIndex; queueCreateInfo.queueIndex = queueIndex; diff --git a/src/ppx/grfx/vk/vk_image.cpp b/src/ppx/grfx/vk/vk_image.cpp index 8a7a1e7c3..d5b0cc1ce 100644 --- a/src/ppx/grfx/vk/vk_image.cpp +++ b/src/ppx/grfx/vk/vk_image.cpp @@ -35,7 +35,7 @@ Result Image::CreateApiObjects(const grfx::ImageCreateInfo* pCreateInfo) extent.height = pCreateInfo->height; extent.depth = pCreateInfo->depth; - VkImageCreateFlags createFlags = VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT; + VkImageCreateFlags createFlags = 0; if (pCreateInfo->type == grfx::IMAGE_TYPE_CUBE) { createFlags |= VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT; } diff --git a/src/ppx/grfx/vk/vk_util.cpp b/src/ppx/grfx/vk/vk_util.cpp index 4dcae5aa1..657ace1ec 100644 --- a/src/ppx/grfx/vk/vk_util.cpp +++ b/src/ppx/grfx/vk/vk_util.cpp @@ -614,6 +614,7 @@ VkIndexType ToVkIndexType(grfx::IndexType value) default: break; case grfx::INDEX_TYPE_UINT16 : return VK_INDEX_TYPE_UINT16; break; case grfx::INDEX_TYPE_UINT32 : return VK_INDEX_TYPE_UINT32; break; + case grfx::INDEX_TYPE_UINT8 : return VK_INDEX_TYPE_UINT8_EXT; break; } // clang-format on return ppx::InvalidValue(); @@ -820,16 +821,28 @@ static Result ToVkBarrier( VkAccessFlags& accessMask, VkImageLayout& layout) { - VkPipelineStageFlags PIPELINE_STAGE_ALL_SHADER_STAGES = VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT; - if (commandType == grfx::CommandType::COMMAND_TYPE_GRAPHICS) { + VkPipelineStageFlags PIPELINE_STAGE_ALL_SHADER_STAGES = {}; + if (commandType == grfx::CommandType::COMMAND_TYPE_COMPUTE) { + PIPELINE_STAGE_ALL_SHADER_STAGES |= VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT; + } + else if (commandType == grfx::CommandType::COMMAND_TYPE_GRAPHICS) { PIPELINE_STAGE_ALL_SHADER_STAGES |= VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; } + else { + PIPELINE_STAGE_ALL_SHADER_STAGES |= VK_PIPELINE_STAGE_TRANSFER_BIT; + } - VkPipelineStageFlags PIPELINE_STAGE_NON_PIXEL_SHADER_STAGES = VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT; - if (commandType == grfx::CommandType::COMMAND_TYPE_GRAPHICS) { + VkPipelineStageFlags PIPELINE_STAGE_NON_PIXEL_SHADER_STAGES = {}; + if (commandType == grfx::CommandType::COMMAND_TYPE_COMPUTE) { + PIPELINE_STAGE_NON_PIXEL_SHADER_STAGES |= VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT; + } + else if (commandType == grfx::CommandType::COMMAND_TYPE_GRAPHICS) { PIPELINE_STAGE_NON_PIXEL_SHADER_STAGES |= VK_PIPELINE_STAGE_VERTEX_SHADER_BIT; } + else { + PIPELINE_STAGE_NON_PIXEL_SHADER_STAGES |= VK_PIPELINE_STAGE_TRANSFER_BIT; + } if (commandType == grfx::CommandType::COMMAND_TYPE_GRAPHICS && features.geometryShader) { PIPELINE_STAGE_ALL_SHADER_STAGES |= VK_PIPELINE_STAGE_GEOMETRY_SHADER_BIT; @@ -859,12 +872,16 @@ static Result ToVkBarrier( layout = VK_IMAGE_LAYOUT_GENERAL; } break; - case grfx::RESOURCE_STATE_CONSTANT_BUFFER: - case grfx::RESOURCE_STATE_VERTEX_BUFFER: { + case grfx::RESOURCE_STATE_CONSTANT_BUFFER: { stageMask = VK_PIPELINE_STAGE_VERTEX_INPUT_BIT | PIPELINE_STAGE_ALL_SHADER_STAGES; accessMask = VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT | VK_ACCESS_UNIFORM_READ_BIT; layout = InvalidValue(); } break; + case grfx::RESOURCE_STATE_VERTEX_BUFFER: { + stageMask = VK_PIPELINE_STAGE_VERTEX_INPUT_BIT | PIPELINE_STAGE_ALL_SHADER_STAGES; + accessMask = VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT; + layout = InvalidValue(); + } break; case grfx::RESOURCE_STATE_INDEX_BUFFER: { stageMask = VK_PIPELINE_STAGE_VERTEX_INPUT_BIT; @@ -915,7 +932,7 @@ static Result ToVkBarrier( } break; case grfx::RESOURCE_STATE_SHADER_RESOURCE: { - stageMask = PIPELINE_STAGE_NON_PIXEL_SHADER_STAGES | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; + stageMask = PIPELINE_STAGE_ALL_SHADER_STAGES; accessMask = VK_ACCESS_SHADER_READ_BIT; layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; } break; @@ -993,6 +1010,7 @@ static Result ToVkBarrier( } break; } + PPX_ASSERT_MSG(stageMask != 0, "stageMask must never be 0 (we don't use synchronization2)."); return ppx::SUCCESS; } diff --git a/src/ppx/scene/scene_gltf_loader.cpp b/src/ppx/scene/scene_gltf_loader.cpp index 8b5de101f..ccb7ae2ca 100644 --- a/src/ppx/scene/scene_gltf_loader.cpp +++ b/src/ppx/scene/scene_gltf_loader.cpp @@ -23,11 +23,11 @@ #undef LoadImage #endif -#define KHR_MATERIALS_UNLIT_EXTENSION_NAME "KHR_materials_unlit" - namespace ppx { namespace scene { +namespace { + #define GLTF_LOD_CLAMP_NONE 1000.0f enum GltfTextureFilter @@ -159,60 +159,6 @@ static grfx::SamplerAddressMode ToSamplerAddressMode(cgltf_int mode) return grfx::SAMPLER_ADDRESS_MODE_REPEAT; } -template -static bool HasExtension( - const std::string& extensionName, - const GltfObjectT* pGltfObject) -{ - if (extensionName.empty() || IsNull(pGltfObject) || IsNull(pGltfObject->extensions)) { - return false; - } - - for (cgltf_size i = 0; i < pGltfObject->extensions_count; ++i) { - const std::string name = ToStringSafe(pGltfObject->extensions[i].name); - if (extensionName == name) { - return true; - } - } - - return false; -} - -// Returns the widest index type used by a mesh -grfx::IndexType GetIndexType( - const cgltf_mesh* pGltfMesh) -{ - if (IsNull(pGltfMesh)) { - return grfx::INDEX_TYPE_UNDEFINED; - } - - uint32_t finalBitCount = 0; - for (cgltf_size primIdx = 0; primIdx < pGltfMesh->primitives_count; ++primIdx) { - const cgltf_primitive* pGltfPrimitive = &pGltfMesh->primitives[primIdx]; - // Convert to grfx::Format - auto format = GetFormat(pGltfPrimitive->indices); - - uint32_t bitCount = 0; - switch (format) { - // Bail if we don't recognize the format - default: return grfx::INDEX_TYPE_UNDEFINED; - case grfx::FORMAT_R16_UINT: bitCount = 16; break; - case grfx::FORMAT_R32_UINT: bitCount = 32; break; - } - - finalBitCount = std::max(bitCount, finalBitCount); - } - - if (finalBitCount == 32) { - return grfx::INDEX_TYPE_UINT32; - } - else if (finalBitCount == 16) { - return grfx::INDEX_TYPE_UINT16; - } - - return grfx::INDEX_TYPE_UNDEFINED; -} - // Calcualte a unique hash based a meshes primitive accessors static uint64_t GetMeshAccessorsHash( const cgltf_data* pGltfData, @@ -312,14 +258,13 @@ static const void* GetStartAddress( // Get an accessor's starting address static const void* GetStartAddress( - const cgltf_data* pGltfData, const cgltf_accessor* pGltfAccessor) { // // NOTE: Don't assert in this function since any of the fields can be NULL for different reasons. // - if (IsNull(pGltfData) || IsNull(pGltfAccessor)) { + if (IsNull(pGltfAccessor)) { return nullptr; } @@ -336,6 +281,86 @@ static const void* GetStartAddress( return static_cast(pAccessorDataStart); } +const char* ToString(cgltf_component_type componentType) +{ + switch (componentType) { + case cgltf_component_type_r_8: + return "BYTE"; + case cgltf_component_type_r_8u: + return "UNSIGNED_BYTE"; + case cgltf_component_type_r_16: + return "SHORT"; + case cgltf_component_type_r_16u: + return "UNSIGNED_SHORT"; + case cgltf_component_type_r_32u: + return "UNSIGNED_INT"; + case cgltf_component_type_r_32f: + return "FLOAT"; + default: + break; + } + + return ""; +} + +const char* ToString(cgltf_type type) +{ + switch (type) { + case cgltf_type_scalar: + return "SCALAR"; + case cgltf_type_vec2: + return "VEC2"; + case cgltf_type_vec3: + return "VEC3"; + case cgltf_type_vec4: + return "VEC4"; + case cgltf_type_mat2: + return "MAT2"; + case cgltf_type_mat3: + return "MAT3"; + case cgltf_type_mat4: + return "MAT4"; + default: + break; + } + + return ""; +} + +// Tries to derive an IndexType from the accessor. Fails for formats that don't comply to the GLTF spec. +// The GLTF 2.0 spec 5.24.2 says "When [format] is undefined, the primitive defines non-indexed geometry. When defined, the accessor MUST have SCALAR type and an unsigned integer component type". +ppx::Result ValidateAccessorIndexType(const cgltf_accessor* pGltfAccessor, grfx::IndexType& outIndexType) +{ + if (IsNull(pGltfAccessor)) { + outIndexType = grfx::INDEX_TYPE_UNDEFINED; + return ppx::SUCCESS; + } + + if (pGltfAccessor->type != cgltf_type_scalar) { + PPX_ASSERT_MSG(false, "Index accessor type must be SCALAR, got: " << ToString(pGltfAccessor->type)); + return ppx::ERROR_SCENE_INVALID_SOURCE_GEOMETRY_INDEX_TYPE; + } + + switch (pGltfAccessor->component_type) { + case cgltf_component_type_r_8u: + outIndexType = grfx::INDEX_TYPE_UINT8; + return ppx::SUCCESS; + case cgltf_component_type_r_16u: + outIndexType = grfx::INDEX_TYPE_UINT16; + return ppx::SUCCESS; + case cgltf_component_type_r_32u: + outIndexType = grfx::INDEX_TYPE_UINT32; + return ppx::SUCCESS; + default: + break; + } + + PPX_ASSERT_MSG(false, "Index accessor component type must be an unsigned integer, got: " << ToString(pGltfAccessor->component_type)); + return ppx::ERROR_SCENE_INVALID_SOURCE_GEOMETRY_INDEX_TYPE; +} + +} // namespace + // ------------------------------------------------------------------------------------------------- // GltfMaterialSelector // ------------------------------------------------------------------------------------------------- @@ -1243,22 +1268,28 @@ ppx::Result GltfLoader::LoadMeshData( struct BatchInfo { - scene::MaterialRef material = nullptr; - uint32_t indexDataOffset = 0; // Must have 4 byte alignment - uint32_t indexDataSize = 0; - uint32_t positionDataOffset = 0; // Must have 4 byte alignment - uint32_t positionDataSize = 0; - uint32_t attributeDataOffset = 0; // Must have 4 byte alignment - uint32_t attributeDataSize = 0; - grfx::Format indexFormat = grfx::FORMAT_UNDEFINED; - uint32_t indexCount = 0; - uint32_t vertexCount = 0; - ppx::AABB boundingBox = {}; + scene::MaterialRef material = nullptr; + // Start of the index plane in the final repacked GPU buffer. + uint32_t indexDataOffset = 0; // Must have 4 byte alignment + // Total size of the index plane in the final repacked GPU buffer. + uint32_t indexDataSize = 0; + uint32_t positionDataOffset = 0; // Must have 4 byte alignment + uint32_t positionDataSize = 0; + uint32_t attributeDataOffset = 0; // Must have 4 byte alignment + uint32_t attributeDataSize = 0; + // Format of the input index buffer. + grfx::IndexType indexType = grfx::INDEX_TYPE_UNDEFINED; + // Format of the index plane in the final repacked GPU buffer. + grfx::IndexType repackedIndexType = grfx::INDEX_TYPE_UNDEFINED; + // How many indices are in the input index buffer. + uint32_t indexCount = 0; + uint32_t vertexCount = 0; + ppx::AABB boundingBox = {}; }; // Build out batch infos std::vector batchInfos; - // + // Size of the final GPU buffer to allocate. Must account for growth during repacking. uint32_t totalDataSize = 0; // for (cgltf_size primIdx = 0; primIdx < pGltfMesh->primitives_count; ++primIdx) { @@ -1270,27 +1301,32 @@ ppx::Result GltfLoader::LoadMeshData( return ppx::ERROR_SCENE_UNSUPPORTED_TOPOLOGY_TYPE; } - // We require index data so bail if there isn't index data. - if (IsNull(pGltfPrimitive->indices)) { + // Get index format + grfx::IndexType indexType = grfx::INDEX_TYPE_UNDEFINED; + if (ppx::Result ppxres = ValidateAccessorIndexType(pGltfPrimitive->indices, indexType); Failed(ppxres)) { + return ppxres; + } + + // We require index data so bail if there isn't index data. See #474 + if (indexType == grfx::INDEX_TYPE_UNDEFINED) { PPX_ASSERT_MSG(false, "GLTF mesh primitive does not have index data"); return ppx::ERROR_SCENE_INVALID_SOURCE_GEOMETRY_INDEX_DATA; } - // Get index format - // - // It's valid for this to be UNDEFINED, means the primitive doesn't have any index data. - // However, if it's not UNDEFINED, UINT16, or UINT32 then it's a format we can't handle. - // - auto indexFormat = GetFormat(pGltfPrimitive->indices); - if ((indexFormat != grfx::FORMAT_UNDEFINED) && (indexFormat != grfx::FORMAT_R16_UINT) && (indexFormat != grfx::FORMAT_R32_UINT)) { - PPX_ASSERT_MSG(false, "GLTF mesh primitive has unrecognized index format"); - return ppx::ERROR_SCENE_INVALID_SOURCE_GEOMETRY_INDEX_TYPE; + // UINT8 index buffer availability varies: Vulkan requires an extension, whereas DX12 lacks support entirely. + // If it's not supported then repack as UINT16 (the smallest mandated size for both). + grfx::IndexType repackedIndexType = indexType; + if (repackedIndexType == grfx::INDEX_TYPE_UINT8 && !loadParams.pDevice->IndexTypeUint8Supported()) { + PPX_LOG_INFO("Device doesn't support UINT8 index buffers! Repacking data as UINT16."); + repackedIndexType = grfx::INDEX_TYPE_UINT16; } - // Index data size + // Index data size of input const uint32_t indexCount = !IsNull(pGltfPrimitive->indices) ? static_cast(pGltfPrimitive->indices->count) : 0; - const uint32_t indexElementSize = grfx::GetFormatDescription(indexFormat)->bytesPerTexel; - const uint32_t indexDataSize = indexCount * indexElementSize; + const uint32_t indexElementSize = grfx::IndexTypeSize(indexType); + // If we repack indices into a buffer of a different format then we need to account for disparity between input and output sizes. + const uint32_t repackedSizeRatio = grfx::IndexTypeSize(repackedIndexType) / indexElementSize; + const uint32_t indexDataSize = indexCount * indexElementSize * repackedSizeRatio; // Get position accessor const VertexAccessors gltflAccessors = GetVertexAccessors(pGltfPrimitive); @@ -1322,7 +1358,8 @@ ppx::Result GltfLoader::LoadMeshData( batchInfo.positionDataSize = positionDataSize; batchInfo.attributeDataOffset = attributeDataOffset; batchInfo.attributeDataSize = attributeDataSize; - batchInfo.indexFormat = indexFormat; + batchInfo.indexType = indexType; + batchInfo.repackedIndexType = repackedIndexType; batchInfo.indexCount = indexCount; // Material @@ -1406,26 +1443,13 @@ ppx::Result GltfLoader::LoadMeshData( const cgltf_primitive* pGltfPrimitive = &pGltfMesh->primitives[primIdx]; BatchInfo& batch = batchInfos[primIdx]; - // Our resulting geometry must have index data for draw efficiency. - // This means that if the index format is undefined we need to generate - // topology indices for it. - // - // - bool genTopologyIndices = false; - if (batch.indexFormat == grfx::FORMAT_UNDEFINED) { - genTopologyIndices = true; - batch.indexFormat = (batch.vertexCount < 65536) ? grfx::FORMAT_R16_UINT : grfx::FORMAT_R32_UINT; - } - - // Create genTopologyIndices so we can repack gemetry data into position planar + packed vertex attributes. + // Create targetGeometry so we can repack gemetry data into position planar + packed vertex attributes. Geometry targetGeometry = {}; const bool hasAttributes = (loadParams.requiredVertexAttributes.mask != 0); // { - auto createInfo = hasAttributes ? GeometryCreateInfo::PositionPlanarU16() : GeometryCreateInfo::PlanarU16(); - if (batch.indexFormat == grfx::FORMAT_R32_UINT) { - createInfo = hasAttributes ? GeometryCreateInfo::PositionPlanarU32() : GeometryCreateInfo::PlanarU32(); - } + GeometryCreateInfo createInfo = (hasAttributes ? GeometryCreateInfo::PositionPlanar() : GeometryCreateInfo::Planar()).IndexType(batch.repackedIndexType); + // clang-format off if (loadParams.requiredVertexAttributes.bits.texCoords) createInfo.AddTexCoord(targetTexCoordFormat); if (loadParams.requiredVertexAttributes.bits.normals) createInfo.AddNormal(targetNormalFormat); @@ -1445,26 +1469,22 @@ ppx::Result GltfLoader::LoadMeshData( // // REMINDER: It's possible for a primitive to not have index data // - if (!IsNull(pGltfPrimitive->indices)) { - // Get start of index data - auto pGltfAccessor = pGltfPrimitive->indices; - auto pGltfIndices = GetStartAddress(mGltfData, pGltfAccessor); - PPX_ASSERT_MSG(!IsNull(pGltfIndices), "GLTF: indices data start is NULL"); - - // UINT32 - if (batch.indexFormat == grfx::FORMAT_R32_UINT) { - const uint32_t* pGltfIndex = static_cast(pGltfIndices); - for (cgltf_size i = 0; i < pGltfAccessor->count; ++i, ++pGltfIndex) { - targetGeometry.AppendIndex(*pGltfIndex); - } - } - // UINT16 - else if (batch.indexFormat == grfx::FORMAT_R16_UINT) { - const uint16_t* pGltfIndex = static_cast(pGltfIndices); - for (cgltf_size i = 0; i < pGltfAccessor->count; ++i, ++pGltfIndex) { - targetGeometry.AppendIndex(*pGltfIndex); + switch (batch.indexType) { + case grfx::INDEX_TYPE_UNDEFINED: + PPX_ASSERT_MSG(false, "Non-indexed geoemetry is not supported. See #474"); + break; + case grfx::INDEX_TYPE_UINT16: + case grfx::INDEX_TYPE_UINT32: + case grfx::INDEX_TYPE_UINT8: + for (cgltf_size i = 0; i < pGltfPrimitive->indices->count; ++i) { + cgltf_uint value = 0; + if (!cgltf_accessor_read_uint(pGltfPrimitive->indices, i, &value, /*element_size=*/1)) { + PPX_ASSERT_MSG(false, "cgltf_accessor_read_uint failed. Index " << i); + return ppx::ERROR_SCENE_INVALID_SOURCE_GEOMETRY_INDEX_DATA; + } + targetGeometry.AppendIndex(value); } - } + break; } } @@ -1521,11 +1541,11 @@ ppx::Result GltfLoader::LoadMeshData( } // Data starts - const float3* pGltflPositions = static_cast(GetStartAddress(mGltfData, gltflAccessors.pPositions)); - const float3* pGltflNormals = static_cast(GetStartAddress(mGltfData, gltflAccessors.pNormals)); - const float4* pGltflTangents = static_cast(GetStartAddress(mGltfData, gltflAccessors.pTangents)); - const float3* pGltflColors = static_cast(GetStartAddress(mGltfData, gltflAccessors.pColors)); - const float2* pGltflTexCoords = static_cast(GetStartAddress(mGltfData, gltflAccessors.pTexCoords)); + const float3* pGltflPositions = static_cast(GetStartAddress(gltflAccessors.pPositions)); + const float3* pGltflNormals = static_cast(GetStartAddress(gltflAccessors.pNormals)); + const float4* pGltflTangents = static_cast(GetStartAddress(gltflAccessors.pTangents)); + const float3* pGltflColors = static_cast(GetStartAddress(gltflAccessors.pColors)); + const float2* pGltflTexCoords = static_cast(GetStartAddress(gltflAccessors.pTexCoords)); // Process vertex data for (cgltf_size i = 0; i < gltflAccessors.pPositions->count; ++i) { @@ -1558,12 +1578,6 @@ ppx::Result GltfLoader::LoadMeshData( // Append vertex data targetGeometry.AppendVertexData(vertexData); - // Generate topolgoy indices if necessary - if (genTopologyIndices) { - uint32_t index = (targetGeometry.GetVertexCount() - 1); - targetGeometry.AppendIndex(index); - } - if (!hasBoundingBox) { if (i > 0) { batch.boundingBox.Expand(vertexData.position); @@ -1580,7 +1594,7 @@ ppx::Result GltfLoader::LoadMeshData( const uint32_t repackedPositionBufferSize = targetGeometry.GetVertexBuffer(0)->GetSize(); const uint32_t repackedAttributeBufferSize = hasAttributes ? targetGeometry.GetVertexBuffer(1)->GetSize() : 0; if (repackedIndexBufferSize != batch.indexDataSize) { - PPX_ASSERT_MSG(false, "repacked index buffer size does not match batch's index data size"); + PPX_ASSERT_MSG(false, "repacked index buffer size (" << repackedIndexBufferSize << ") does not match batch's index data size (" << batch.indexDataSize << ")"); return ppx::ERROR_SCENE_INVALID_SOURCE_GEOMETRY_INDEX_DATA; } if (repackedPositionBufferSize != batch.positionDataSize) { @@ -1644,8 +1658,7 @@ ppx::Result GltfLoader::LoadMeshData( for (uint32_t batchIdx = 0; batchIdx < CountU32(batchInfos); ++batchIdx) { const auto& batch = batchInfos[batchIdx]; - const grfx::IndexType indexType = (batch.indexFormat == grfx::FORMAT_R32_UINT) ? grfx::INDEX_TYPE_UINT32 : grfx::INDEX_TYPE_UINT16; - grfx::IndexBufferView indexBufferView = grfx::IndexBufferView(targetGpuBuffer, indexType, batch.indexDataOffset, batch.indexDataSize); + grfx::IndexBufferView indexBufferView = grfx::IndexBufferView(targetGpuBuffer, batch.repackedIndexType, batch.indexDataOffset, batch.indexDataSize); grfx::VertexBufferView positionBufferView = grfx::VertexBufferView(targetGpuBuffer, targetPositionElementSize, batch.positionDataOffset, batch.positionDataSize); grfx::VertexBufferView attributeBufferView = grfx::VertexBufferView((batch.attributeDataSize != 0) ? targetGpuBuffer : nullptr, targetAttributesElementSize, batch.attributeDataOffset, batch.attributeDataSize); diff --git a/src/ppx/tri_mesh.cpp b/src/ppx/tri_mesh.cpp index d7c8eb3e7..d9875c71d 100644 --- a/src/ppx/tri_mesh.cpp +++ b/src/ppx/tri_mesh.cpp @@ -28,6 +28,8 @@ TriMesh::TriMesh() TriMesh::TriMesh(grfx::IndexType indexType) : mIndexType(indexType) { + // TODO: #514 - Remove assert when UINT8 is supported + PPX_ASSERT_MSG(mIndexType != grfx::INDEX_TYPE_UINT8, "INDEX_TYPE_UINT8 unsupported in TriMesh"); } TriMesh::TriMesh(TriMeshAttributeDim texCoordDim) @@ -38,6 +40,8 @@ TriMesh::TriMesh(TriMeshAttributeDim texCoordDim) TriMesh::TriMesh(grfx::IndexType indexType, TriMeshAttributeDim texCoordDim) : mIndexType(indexType), mTexCoordDim(texCoordDim) { + // TODO: #514 - Remove assert when UINT8 is supported + PPX_ASSERT_MSG(mIndexType != grfx::INDEX_TYPE_UINT8, "INDEX_TYPE_UINT8 unsupported in TriMesh"); } TriMesh::~TriMesh() diff --git a/src/ppx/wire_mesh.cpp b/src/ppx/wire_mesh.cpp index 92f657c95..cb6f52bdb 100644 --- a/src/ppx/wire_mesh.cpp +++ b/src/ppx/wire_mesh.cpp @@ -25,6 +25,8 @@ WireMesh::WireMesh() WireMesh::WireMesh(grfx::IndexType indexType) : mIndexType(indexType) { + // TODO: #514 - Remove assert when UINT8 is supported + PPX_ASSERT_MSG(mIndexType != grfx::INDEX_TYPE_UINT8, "INDEX_TYPE_UINT8 unsupported in WireMesh"); } WireMesh::~WireMesh() diff --git a/src/ppx/xr_component.cpp b/src/ppx/xr_component.cpp index c5b5202be..5d704d983 100644 --- a/src/ppx/xr_component.cpp +++ b/src/ppx/xr_component.cpp @@ -927,7 +927,7 @@ void XrCamera::UpdateCamera() const quat orientation = FromXr(mView.pose.orientation); mEyePosition = FromXr(mView.pose.position); - mViewDirection = glm::rotate(orientation, forward); + mViewDirection = PPX_CAMERA_DEFAULT_VIEW_DIRECTION; mTarget = mEyePosition + mViewDirection; mWorldUp = glm::rotate(orientation, up); } diff --git a/src/test/geometry_test.cpp b/src/test/geometry_test.cpp index 722b93ba9..1f58a9823 100644 --- a/src/test/geometry_test.cpp +++ b/src/test/geometry_test.cpp @@ -27,8 +27,34 @@ namespace { #define PERFORM_DEATH_TESTS #endif +using ::testing::AllOf; +using ::testing::ElementsAreArray; using ::testing::IsNull; using ::testing::NotNull; +using ::testing::Property; + +MATCHER(BufferIsEmpty, "") +{ + EXPECT_EQ(arg.GetElementSize(), 0); + EXPECT_EQ(arg.GetElementCount(), 0); + EXPECT_EQ(arg.GetSize(), 0); + EXPECT_THAT(arg.GetData(), IsNull()); + return true; +} + +MATCHER_P(BufferEq, expected, "") +{ + constexpr auto valueTypeSize = sizeof(typename decltype(expected)::value_type); + EXPECT_EQ(arg.GetElementSize(), valueTypeSize); + EXPECT_EQ(arg.GetElementCount(), expected.size()); + EXPECT_EQ(arg.GetSize(), valueTypeSize * expected.size()); + if (arg.GetData() == nullptr) { + return false; + } + const auto* data = reinterpret_cast(arg.GetData()); + EXPECT_THAT(std::vector(data, data + arg.GetElementCount()), ElementsAreArray(expected)); + return true; +} TEST(GeometryTest, AppendIndicesU32PacksDataAsUint32) { @@ -71,10 +97,162 @@ TEST_P(GeometryDeathTest, AppendIndicesU32DiesIfIndexTypeIsNotU32) ASSERT_DEATH(geometry.AppendIndicesU32(indices.size(), indices.data()), ""); } -INSTANTIATE_TEST_SUITE_P(GeometryDeathTest, GeometryDeathTest, testing::Values(grfx::INDEX_TYPE_UINT16, grfx::INDEX_TYPE_UNDEFINED), [](const testing::TestParamInfo& info) { +INSTANTIATE_TEST_SUITE_P(GeometryDeathTest, GeometryDeathTest, testing::Values(grfx::INDEX_TYPE_UINT16, grfx::INDEX_TYPE_UNDEFINED, grfx::INDEX_TYPE_UINT8), [](const testing::TestParamInfo& info) { return ToString(info.param); }); #endif +TEST(GeometryUint16IndexTest, AppendIndexPacksDataAsUint16) +{ + Geometry geometry; + EXPECT_EQ(Geometry::Create(GeometryCreateInfo{}.IndexType(grfx::INDEX_TYPE_UINT16).AddPosition(), &geometry), ppx::SUCCESS); + EXPECT_EQ(geometry.GetIndexType(), grfx::INDEX_TYPE_UINT16); + + geometry.AppendIndex(0); + geometry.AppendIndex(1); + geometry.AppendIndex(2); + EXPECT_EQ(geometry.GetIndexCount(), 3); + ASSERT_THAT(geometry.GetIndexBuffer(), NotNull()); + EXPECT_THAT(*geometry.GetIndexBuffer(), BufferEq(std::vector({0, 1, 2}))); +} + +TEST(GeometryUint16IndexTest, AppendIndicesTrianglePacksDataAsUint16) +{ + Geometry geometry; + EXPECT_EQ(Geometry::Create(GeometryCreateInfo{}.IndexType(grfx::INDEX_TYPE_UINT16).AddPosition(), &geometry), ppx::SUCCESS); + EXPECT_EQ(geometry.GetIndexType(), grfx::INDEX_TYPE_UINT16); + + geometry.AppendIndicesTriangle(0, 1, 2); + EXPECT_EQ(geometry.GetIndexCount(), 3); + ASSERT_THAT(geometry.GetIndexBuffer(), NotNull()); + EXPECT_THAT(*geometry.GetIndexBuffer(), BufferEq(std::vector({0, 1, 2}))); +} + +TEST(GeometryUint16IndexTest, AppendIndicesEdgePacksDataAsUint16) +{ + Geometry geometry; + EXPECT_EQ(Geometry::Create(GeometryCreateInfo{}.IndexType(grfx::INDEX_TYPE_UINT16).AddPosition(), &geometry), ppx::SUCCESS); + EXPECT_EQ(geometry.GetIndexType(), grfx::INDEX_TYPE_UINT16); + + geometry.AppendIndicesEdge(0, 1); + EXPECT_EQ(geometry.GetIndexCount(), 2); + ASSERT_THAT(geometry.GetIndexBuffer(), NotNull()); + EXPECT_THAT(*geometry.GetIndexBuffer(), BufferEq(std::vector({0, 1}))); +} + +TEST(GeometryUint32IndexTest, AppendIndexPacksDataAsUint32) +{ + Geometry geometry; + EXPECT_EQ(Geometry::Create(GeometryCreateInfo{}.IndexType(grfx::INDEX_TYPE_UINT32).AddPosition(), &geometry), ppx::SUCCESS); + EXPECT_EQ(geometry.GetIndexType(), grfx::INDEX_TYPE_UINT32); + + geometry.AppendIndex(0); + geometry.AppendIndex(1); + geometry.AppendIndex(2); + EXPECT_EQ(geometry.GetIndexCount(), 3); + ASSERT_THAT(geometry.GetIndexBuffer(), NotNull()); + EXPECT_THAT(*geometry.GetIndexBuffer(), BufferEq(std::vector({0, 1, 2}))); +} + +TEST(GeometryUint32IndexTest, AppendIndicesTrianglePacksDataAsUint32) +{ + Geometry geometry; + EXPECT_EQ(Geometry::Create(GeometryCreateInfo{}.IndexType(grfx::INDEX_TYPE_UINT32).AddPosition(), &geometry), ppx::SUCCESS); + EXPECT_EQ(geometry.GetIndexType(), grfx::INDEX_TYPE_UINT32); + + geometry.AppendIndicesTriangle(0, 1, 2); + EXPECT_EQ(geometry.GetIndexCount(), 3); + ASSERT_THAT(geometry.GetIndexBuffer(), NotNull()); + EXPECT_THAT(*geometry.GetIndexBuffer(), BufferEq(std::vector({0, 1, 2}))); +} + +TEST(GeometryUint32IndexTest, AppendIndicesEdgePacksDataAsUint32) +{ + Geometry geometry; + EXPECT_EQ(Geometry::Create(GeometryCreateInfo{}.IndexType(grfx::INDEX_TYPE_UINT32).AddPosition(), &geometry), ppx::SUCCESS); + EXPECT_EQ(geometry.GetIndexType(), grfx::INDEX_TYPE_UINT32); + + geometry.AppendIndicesEdge(0, 1); + EXPECT_EQ(geometry.GetIndexCount(), 2); + ASSERT_THAT(geometry.GetIndexBuffer(), NotNull()); + EXPECT_THAT(*geometry.GetIndexBuffer(), BufferEq(std::vector({0, 1}))); +} + +TEST(GeometryUndefinedIndexTest, AppendIndexDoesNothing) +{ + Geometry geometry; + EXPECT_EQ(Geometry::Create(GeometryCreateInfo{}.IndexType(grfx::INDEX_TYPE_UNDEFINED).AddPosition(), &geometry), ppx::SUCCESS); + EXPECT_EQ(geometry.GetIndexType(), grfx::INDEX_TYPE_UNDEFINED); + + geometry.AppendIndex(0); + geometry.AppendIndex(1); + geometry.AppendIndex(2); + EXPECT_EQ(geometry.GetIndexCount(), 0); + ASSERT_THAT(geometry.GetIndexBuffer(), NotNull()); + EXPECT_THAT(*geometry.GetIndexBuffer(), BufferIsEmpty()); +} + +TEST(GeometryUndefinedIndexTest, AppendIndicesTriangleDoesNothing) +{ + Geometry geometry; + EXPECT_EQ(Geometry::Create(GeometryCreateInfo{}.IndexType(grfx::INDEX_TYPE_UNDEFINED).AddPosition(), &geometry), ppx::SUCCESS); + EXPECT_EQ(geometry.GetIndexType(), grfx::INDEX_TYPE_UNDEFINED); + + geometry.AppendIndicesTriangle(0, 1, 2); + EXPECT_EQ(geometry.GetIndexCount(), 0); + ASSERT_THAT(geometry.GetIndexBuffer(), NotNull()); + EXPECT_THAT(*geometry.GetIndexBuffer(), BufferIsEmpty()); +} + +TEST(GeometryUndefinedIndexTest, AppendIndicesEdgeDoesNothing) +{ + Geometry geometry; + EXPECT_EQ(Geometry::Create(GeometryCreateInfo{}.IndexType(grfx::INDEX_TYPE_UNDEFINED).AddPosition(), &geometry), ppx::SUCCESS); + EXPECT_EQ(geometry.GetIndexType(), grfx::INDEX_TYPE_UNDEFINED); + + geometry.AppendIndicesEdge(0, 1); + EXPECT_EQ(geometry.GetIndexCount(), 0); + ASSERT_THAT(geometry.GetIndexBuffer(), NotNull()); + EXPECT_THAT(*geometry.GetIndexBuffer(), BufferIsEmpty()); +} + +TEST(GeometryUint8IndexTest, AppendIndexPacksDataAsUint8) +{ + Geometry geometry; + EXPECT_EQ(Geometry::Create(GeometryCreateInfo{}.IndexType(grfx::INDEX_TYPE_UINT8).AddPosition(), &geometry), ppx::SUCCESS); + EXPECT_EQ(geometry.GetIndexType(), grfx::INDEX_TYPE_UINT8); + + geometry.AppendIndex(0); + geometry.AppendIndex(1); + geometry.AppendIndex(2); + EXPECT_EQ(geometry.GetIndexCount(), 3); + ASSERT_THAT(geometry.GetIndexBuffer(), NotNull()); + EXPECT_THAT(*geometry.GetIndexBuffer(), BufferEq(std::vector({0, 1, 2}))); +} + +TEST(GeometryUint8IndexTest, AppendIndicesTrianglePacksDataAsUint8) +{ + Geometry geometry; + EXPECT_EQ(Geometry::Create(GeometryCreateInfo{}.IndexType(grfx::INDEX_TYPE_UINT8).AddPosition(), &geometry), ppx::SUCCESS); + EXPECT_EQ(geometry.GetIndexType(), grfx::INDEX_TYPE_UINT8); + + geometry.AppendIndicesTriangle(0, 1, 2); + EXPECT_EQ(geometry.GetIndexCount(), 3); + ASSERT_THAT(geometry.GetIndexBuffer(), NotNull()); + EXPECT_THAT(*geometry.GetIndexBuffer(), BufferEq(std::vector({0, 1, 2}))); +} + +TEST(GeometryUint8IndexTest, AppendIndicesEdgePacksDataAsUint8) +{ + Geometry geometry; + EXPECT_EQ(Geometry::Create(GeometryCreateInfo{}.IndexType(grfx::INDEX_TYPE_UINT8).AddPosition(), &geometry), ppx::SUCCESS); + EXPECT_EQ(geometry.GetIndexType(), grfx::INDEX_TYPE_UINT8); + + geometry.AppendIndicesEdge(0, 1); + EXPECT_EQ(geometry.GetIndexCount(), 2); + ASSERT_THAT(geometry.GetIndexBuffer(), NotNull()); + EXPECT_THAT(*geometry.GetIndexBuffer(), BufferEq(std::vector({0, 1}))); +} + } // namespace } // namespace ppx diff --git a/third_party/OpenXR-SDK-Source b/third_party/OpenXR-SDK-Source index 455583cc6..61709a5a4 160000 --- a/third_party/OpenXR-SDK-Source +++ b/third_party/OpenXR-SDK-Source @@ -1 +1 @@ -Subproject commit 455583cc6ddb463f7433fc0e9bc2a4b9b0c943c6 +Subproject commit 61709a5a4a91338f33835075dd859a4a8a77c35e diff --git a/third_party/assets/scene_renderer/scenes/tests/gltf_test_basic_materials.glb b/third_party/assets/scene_renderer/scenes/tests/gltf_test_basic_materials.glb new file mode 100644 index 000000000..82d14cc77 Binary files /dev/null and b/third_party/assets/scene_renderer/scenes/tests/gltf_test_basic_materials.glb differ diff --git a/tools/benchmark_testcases.csv b/tools/benchmark_testcases.csv deleted file mode 100644 index a1eb3a49b..000000000 --- a/tools/benchmark_testcases.csv +++ /dev/null @@ -1,23 +0,0 @@ -texture_load_1, Texture load (1 image), vk_texture_load, --num-images 1 -texture_load_4, Texture load (4 images), vk_texture_load, --num-images 4 -texture_sample_1_linear, Texture sample (1 image) (linear min/mag filter) (linear mip filter), vk_texture_sample, --num-images 1 --force-mip-level 0 --filter-type linear --mipmap-filter-type linear -texture_sample_1_nearest, Texture sample (1 image) (nearest min/mag filter) (nearest mip filter), vk_texture_sample, --num-images 1 --force-mip-level 0 --filter-type nearest --mipmap-filter-type nearest -texture_sample_4_linear, Texture sample (4 images) (linear min/mag filter) (linear mip filter), vk_texture_sample, --num-images 4 --force-mip-level 0 --filter-type linear --mipmap-filter-type linear -texture_sample_4_nearest, Texture sample (4 images) (nearest min/mag filter) (nearest mip filter), vk_texture_sample, --num-images 4 --force-mip-level 0 --filter-type nearest --mipmap-filter-type nearest -draw_call_10_noninstanced, Draw call (10 triangles) (non-instanced), vk_draw_call, --num-triangles 10 --instanced-draw false -draw_call_100_noninstanced, Draw call (100 triangles) (non-instanced), vk_draw_call, --num-triangles 100 --instanced-draw false -draw_call_1000_noninstanced, Draw call (1000 triangles) (non-instanced), vk_draw_call, --num-triangles 1000 --instanced-draw false -draw_call_10_instanced, Draw call (10 triangles) (instanced), vk_draw_call, --num-triangles 10 --instanced-draw true -draw_call_100_instanced, Draw call (100 triangles) (instanced), vk_draw_call, --num-triangles 100 --instanced-draw true -draw_call_1000_instanced, Draw call (1000 triangles) (instanced), vk_draw_call, --num-triangles 1000 --instanced-draw true -primitive_assembly_10, Primitive assembly (10 triangles), vk_primitive_assembly, --triangles 10 -primitive_assembly_100, Primitive assembly (100 triangles), vk_primitive_assembly, --triangles 100 -primitive_assembly_1000, Primitive assembly (1000 triangles), vk_primitive_assembly, --triangles 1000 -overdraw_4_frontback, Overdraw (4 layers) (front-to-back), vk_overdraw, --num-layers 4 --draw-front-to-back true -overdraw_4_frontback_nodepth, Overdraw (4 layers) (front-to-back) (depth disabled), vk_overdraw, --num-layers 4 --draw-front-to-back true --enable-depth false -overdraw_4_frontback_forcedearlyz, Overdraw (4 layers) (front-to-back) (forced early-z), vk_overdraw, --num-layers 4 --draw-front-to-back true --use-explicit-early-z -overdraw_4_backfront, Overdraw (4 layers) (back-to-front), vk_overdraw, --num-layers 4 --draw-front-to-back false -overdraw_8_frontback, Overdraw (8 layers) (front-to-back), vk_overdraw, --num-layers 8 --draw-front-to-back true -overdraw_8_frontback_nodepth, Overdraw (8 layers) (front-to-back) (depth disabled), vk_overdraw, --num-layers 8 --draw-front-to-back true --enable-depth false -overdraw_8_frontback_forcedearlyz, Overdraw (8 layers) (front-to-back) (forced early-z), vk_overdraw, --num-layers 8 --draw-front-to-back true --use-explicit-early-z -overdraw_8_backfront, Overdraw (8 layers) (back-to-front), vk_overdraw, --num-layers 8 --draw-front-to-back false diff --git a/tools/generate_gltf_report.sh b/tools/generate_gltf_report.sh new file mode 100755 index 000000000..2972902f1 --- /dev/null +++ b/tools/generate_gltf_report.sh @@ -0,0 +1,32 @@ +#!/bin/bash + +set -eu + +if [ $# -ne 1 ]; then + echo "Usage: $0 BUILD_PATH" + exit 1 +fi + +BUILD_PATH=$1 + +SAMPLE_ASSETS_PATH=third_party/assets/glTF-Sample-Assets +if [ ! -e "$SAMPLE_ASSETS_PATH" ]; then + git clone https://github.com/KhronosGroup/glTF-Sample-Assets.git "$SAMPLE_ASSETS_PATH" +else + echo "$SAMPLE_ASSETS_PATH already exists; skipping clone" +fi +cmake --build "$BUILD_PATH" + +TEST_RESULTS_PATH=/tmp/test_gltf_$(date +%s) +python3 ./tools/test_gltf_sample_assets.py \ + --program "$BUILD_PATH/bin/vk_gltf_basic_materials" \ + --model-index "$SAMPLE_ASSETS_PATH/Models/model-index.json" \ + --output "$TEST_RESULTS_PATH" + +REPORT_PATH=/tmp/gltf_report_$(date +%s) +python3 ./tools/make_gltf_sample_assets_report.py \ + --input "$TEST_RESULTS_PATH" \ + --model-index "$SAMPLE_ASSETS_PATH/Models/model-index.json" \ + --output "$REPORT_PATH" + +echo "Report written to: $REPORT_PATH" diff --git a/tools/make_gltf_sample_assets_report.py b/tools/make_gltf_sample_assets_report.py new file mode 100644 index 000000000..4c8f2d24a --- /dev/null +++ b/tools/make_gltf_sample_assets_report.py @@ -0,0 +1,145 @@ +"""Renders all glTF-Sample-Assets using gltf_scene_viewer and produces a report.""" + +import argparse +import json +import os +import pathlib +import shutil +import subprocess +import xml.etree.ElementTree as ET + + +def _make_report(input_path: pathlib.Path, + model_index_path: pathlib.Path, + output_path: pathlib.Path): + """Generates an HTML website with a table of test results. + + This does several things: + + 1. Generates index.html containing the report text. + 2. Copies and converts artifacts into `output_path` so that it's shareable. + + Arguments: + input_path: Location of test results. + model_index_path: Path to glTF-Sample-Assets model-index.json. + output_path: Destination of the HTML report and associated artifacts. + """ + + model_index_dir = model_index_path.absolute().parent + + with model_index_path.open('r') as fd: + model_index = json.load(fd) + + with (input_path / 'meta.json').open('r') as meta_file: + meta = json.load(meta_file) + + html = ET.Element('html') + + # Alternate row color to make table easier to parse + head = ET.SubElement(html, 'head') + ET.SubElement(head, 'style').text = ( + 'tbody tr:nth-child(odd) { background-color: #eee; }') + + body = ET.SubElement(html, 'body') + ET.SubElement(body, 'p').text = f'Time: {meta["datetime"]}' + ET.SubElement(body, 'p').text = ( + f'BigWheels Commit SHA: {meta["bigwheels_commit_sha"]}') + ET.SubElement(body, 'p').text = ( + f'gltf-Sample-Assets Commit SHA: {meta["glTF-Sample-Assets_commit_sha"]}') + ET.SubElement(body, 'p').text = f'Host: {meta["host"]}' + + table = ET.SubElement(body, 'table') + + thead_tr = ET.SubElement(ET.SubElement(table, 'thead'), 'tr') + ET.SubElement(thead_tr, 'th').text = 'Label' + ET.SubElement(thead_tr, 'th').text = 'glTF-Sample-Assets Screenshot' + ET.SubElement(thead_tr, 'th').text = 'BigWheels Screenshot' + ET.SubElement(thead_tr, 'th').text = 'Logs' + + tbody = ET.SubElement(table, 'tbody') + for model in model_index: + label = model['label'] # human readable + name = model['name'] # path in glTF-Sample-Assets repo + screenshot = model['screenshot'] + variants = model['variants'] + + for variant in variants: + test_name = f'{name}-{variant}' + test_input_path = input_path / test_name + + # Either: + # 1. The scene wasn't tested (partial results), or + # 2. There's a mismatch between the model-index.json used to run + # the test and make the report. + if not test_input_path.exists(): + continue + + test_output_path = output_path / test_name + os.mkdir(test_output_path) + + tr = ET.SubElement(tbody, 'tr') + + # Label + ET.SubElement(tr, 'td').text = f'{label} ({variant})' + + # glTF-Sample-Assets Screenshot + # (Use relative paths in and so we can easily share) + sample_ext = os.path.splitext(screenshot)[1] + shutil.copy2(model_index_dir / name / screenshot, + test_output_path / f'expected{sample_ext}') + ET.SubElement( + ET.SubElement(tr, 'td'), + 'img', src=f'{test_name}/expected{sample_ext}', width='640px') + + # BigWheels Screenshot + # (There won't be a screenshot if the scene fails to load) + if (test_input_path / 'actual.ppm').exists(): + # Convert PPM -> PNG to support more browsers. + # Use an external program since Python lacks an image standard lib. + subprocess.run(['convert', + str(test_input_path / 'actual.ppm'), + str(test_output_path / 'actual.png')], check=True) + ET.SubElement( + ET.SubElement(tr, 'td'), + 'img', src=f'{test_name}/actual.png', width='640px') + else: + ET.SubElement(tr, 'td').text = 'None!' + + # Logs + logs_td = ET.SubElement(tr, 'td') + shutil.copy2(test_input_path / 'stdout.log', + test_output_path / 'stdout.log') + ET.SubElement( + ET.SubElement(logs_td, 'p'), + 'a', href=f'{test_name}/stdout.log').text = 'stdout.log' + shutil.copy2(test_input_path / 'stderr.log', + test_output_path / 'stderr.log') + ET.SubElement( + ET.SubElement(logs_td, 'p'), + 'a', href=f'{test_name}/stderr.log').text = 'stderr.log' + shutil.copy2(test_input_path / 'ppx.log', test_output_path / 'ppx.log') + ET.SubElement( + ET.SubElement(logs_td, 'p'), + 'a', href=f'{test_name}/ppx.log').text = 'ppx.log' + + ET.ElementTree(element=html).write(output_path / 'index.html', method='html') + + +def main(): + parser = argparse.ArgumentParser( + description=('Creates an HTML report given the output of ' + + 'test_gltf_sample_assets.py')) + parser.add_argument('--input', type=pathlib.Path, required=True, + help='Directory created by test_gltf_sample_assets.py.') + parser.add_argument('--model-index', type=pathlib.Path, required=True, + help='Path to glTF-Sample-Asssets model-index.json.') + parser.add_argument('--output', type=pathlib.Path, required=True, + help='Directory to store the generated report.') + args = parser.parse_args() + + os.mkdir(args.output) + _make_report(args.input, args.model_index, args.output) + + +if __name__ == '__main__': + main() diff --git a/tools/test_gltf_sample_assets.py b/tools/test_gltf_sample_assets.py new file mode 100644 index 000000000..1d9453ce8 --- /dev/null +++ b/tools/test_gltf_sample_assets.py @@ -0,0 +1,125 @@ +"""Loads and renders all scenes in glTF-Sample-Assets.""" + +import argparse +import datetime +import json +import os +import pathlib +import socket +import subprocess + + +def _get_git_head_commit(path: pathlib.Path) -> str: + """Returns the repository HEAD commit SHA. + + Args: + path: The path on disk to the git repository (or a subdirectory). + + Returns: + The full SHA of the HEAD commit of the provided git repo. + + Raises: + subprocess.CalledProcessError: git failed (e.g. `path` is not a repo). + """ + process = subprocess.run( + ['git', 'rev-parse', 'HEAD'], cwd=path, capture_output=True, check=True) + return process.stdout.decode().strip() + + +def _build_test_cases(model_index) -> dict[str, str]: + """Transforms model-index.json into a flat list of test cases to be run. + + Args: + model_index: glTF-Sample-Assets model-index.json, loaded into memory. + + Returns: + Mapping of test name to test asset. The test name is `name-variant`. + """ + test_cases: dict[str, str] = {} + for model in model_index: + name = model['name'] + variants = model['variants'] + for variant in variants: + variant_file = variants[variant] + test_cases[f'{name}-{variant}'] = ( + f'glTF-Sample-Assets/Models/{name}/{variant}/{variant_file}') + return test_cases + + +def _run_test(program: pathlib.Path, + asset: str, + output_path: pathlib.Path): + """Loads and renders a glTF-Sample-Asset scene. + + Several outputs are written to disk: + + - actual.ppm: Screenshot (if the scene rendered) + - ppx.log: Log generated by BigWheels + - stdout.log: stdout of `program` + - stderr.log stderr of `program` + + Args: + program: The program under test used to render the asset under test. + asset: The glTF-Sample-Asset under test. + output_path: Empty directory to store test results. + """ + command = [program, + '--frame-count', '2', + '--screenshot-frame-number', '1', + '--gltf-scene-asset', asset, + '--screenshot-path', 'actual.ppm', + '--headless'] + process = subprocess.run( + command, cwd=output_path, capture_output=True, check=False) + + # Dump debugging information to disk for triaging after a test run + (output_path / 'stdout.log').write_bytes(process.stdout) + (output_path / 'stderr.log').write_bytes(process.stderr) + + +def main(): + """Loads and renders all scenes in glTF-Sample-Assets.""" + parser = argparse.ArgumentParser( + description='Loads and renders all glTF-Sample-Assets.') + parser.add_argument('--program', type=pathlib.Path, required=True, + help=('The program used to load and render a glTF ' + + 'scene. Must support --gltf-scene-asset and ' + + 'other BigWheels options.')) + parser.add_argument('--model-index', type=pathlib.Path, required=True, + help='Path to glTF-Sample-Asssets model-index.json.') + parser.add_argument('--output', type=pathlib.Path, required=True, + help='Directory to store test results.') + args = parser.parse_args() + + program = args.program.resolve() + + with args.model_index.open('r') as model_index_file: + model_index = json.load(model_index_file) + + os.mkdir(args.output) + + # Dump some state of the test environment to be included in the report. + bigwheels_commit_sha = _get_git_head_commit( + pathlib.Path(__file__).parent.resolve()) + assets_commit_sha = _get_git_head_commit(args.model_index.parent.resolve()) + with (args.output / 'meta.json').open('w') as meta_file: + json.dump({'host': str(socket.getfqdn()), + 'datetime': str(datetime.datetime.now()), + 'bigwheels_commit_sha': bigwheels_commit_sha, + 'glTF-Sample-Assets_commit_sha': assets_commit_sha}, meta_file) + + test_cases = _build_test_cases(model_index) + test_count = len(test_cases) # Used for printing progress + test_index = 1 # Used for printing progress + for test_name in test_cases: + print(f'{test_index}/{test_count}: {test_name}') + + test_output_path = args.output / test_name + os.mkdir(test_output_path) + _run_test(program, test_cases[test_name], test_output_path) + + test_index += 1 + + +if __name__ == '__main__': + main()