From 995752da75a0a292d3c469467370ce2687d1f0e8 Mon Sep 17 00:00:00 2001 From: omigamedev Date: Tue, 2 Jun 2026 17:18:48 +0200 Subject: [PATCH] Add renderer backend feature reporting --- docs/modernization/build-inventory.md | 7 +++--- docs/modernization/roadmap.md | 6 ++--- src/renderer_api/recording_renderer.cpp | 10 ++++++++ src/renderer_api/recording_renderer.h | 1 + src/renderer_api/renderer_api.h | 11 +++++++++ src/renderer_gl/opengl_capabilities.cpp | 12 +++++++++ src/renderer_gl/opengl_capabilities.h | 4 +++ tests/CMakeLists.txt | 2 +- tests/renderer_api/renderer_api_tests.cpp | 30 +++++++++++++++++++++++ tests/renderer_gl/capabilities_tests.cpp | 13 ++++++++++ tools/pano_cli/main.cpp | 14 +++++++++++ 11 files changed, 103 insertions(+), 7 deletions(-) diff --git a/docs/modernization/build-inventory.md b/docs/modernization/build-inventory.md index ecca991..51f1aa0 100644 --- a/docs/modernization/build-inventory.md +++ b/docs/modernization/build-inventory.md @@ -284,8 +284,9 @@ Known local toolchain state: renderer-boundary guard that reports JSON and fails if active non-backend source code reintroduces raw `GL_*`/`WGL_*` constants outside the allowed legacy OpenGL implementation files. -- `pp_renderer_api` exposes a headless `RecordingRenderDevice` that validates - backend-owned resource creation, explicit texture usage flags, command order, +- `pp_renderer_api` exposes a headless `RecordingRenderDevice` that reports + renderer feature flags and validates backend-owned resource creation, + explicit texture usage flags, command order, render-pass color/depth/stencil clear intent, scissor state, depth state, blend state, texture-slot binding, sampler-state binding, texture-upload byte counts, texture mip-level counts, resource debug labels, mipmap-generation commands, @@ -297,7 +298,7 @@ Known local toolchain state: frame-capture/blit commands, draw mesh inputs, explicit draw ranges, and records trace markers and scopes without a window or GL context. - `pano_cli record-render` exposes the recording renderer through JSON - automation, including render-pass/depth-clear counts, scissor/depth/blend/ + automation, including backend feature flags, render-pass/depth-clear counts, scissor/depth/blend/ shader-uniform/texture-bind/sampler-bind/upload/mipmap-generation/texture-transition/texture-copy/readback/ frame-capture/blit command and byte totals, trace marker/scope counts, labeled descriptor counts, backend resource creation counts, plus draw diff --git a/docs/modernization/roadmap.md b/docs/modernization/roadmap.md index 7a0a848..593ad27 100644 --- a/docs/modernization/roadmap.md +++ b/docs/modernization/roadmap.md @@ -414,7 +414,7 @@ Goal: make OpenGL an implementation detail and establish parity tests before adding new backends. Status: started. `pp_renderer_api` exists as a headless renderer-neutral target -with explicit texture usage flags, texture descriptor, byte-size, viewport, +with renderer feature flags, explicit texture usage flags, texture descriptor, byte-size, viewport, mesh, readback bounds, command context, render device, shader program descriptor, mesh, render target, readback byte-size helpers, texture mip-level validation, resource debug-label validation, @@ -830,7 +830,7 @@ Results: reintroduces raw `GL_*`/`WGL_*` constants outside the allowed legacy OpenGL implementation files. - `pp_renderer_api` now includes a headless `RecordingRenderDevice` with strict - renderer-owned resource factory and + renderer feature flags, renderer-owned resource factory and command-order/render-pass-clear/scissor-state/depth-state/blend-state/ texture-usage/texture-bind/sampler-bind/shader-uniform/texture-upload/ mipmap-generation/texture-transition/readback/frame-capture/blit validation plus explicit draw @@ -843,7 +843,7 @@ Results: and render-target blits, giving automation a backend-neutral render path that does not require a window or GL context. - `pano_cli record-render` exercises that headless recording renderer and emits - JSON command counts, resource creation counts, target dimensions, backend + JSON command counts, backend feature flags, resource creation counts, target dimensions, backend name, trace marker/scope and draw summary, labeled descriptor counts, render-pass/depth-clear counts, and draw descriptor vertex/index totals, scissor/depth/blend-state plus diff --git a/src/renderer_api/recording_renderer.cpp b/src/renderer_api/recording_renderer.cpp index 651257e..28161d4 100644 --- a/src/renderer_api/recording_renderer.cpp +++ b/src/renderer_api/recording_renderer.cpp @@ -654,6 +654,16 @@ const char* RecordingRenderDevice::backend_name() const noexcept return "recording"; } +RenderDeviceFeatures RecordingRenderDevice::features() const noexcept +{ + return RenderDeviceFeatures { + .explicit_texture_transitions = true, + .texture_copy = true, + .render_target_blit = true, + .frame_capture = true, + }; +} + pp::foundation::Result> RecordingRenderDevice::create_texture( TextureDesc desc) noexcept { diff --git a/src/renderer_api/recording_renderer.h b/src/renderer_api/recording_renderer.h index faad25c..bb43332 100644 --- a/src/renderer_api/recording_renderer.h +++ b/src/renderer_api/recording_renderer.h @@ -199,6 +199,7 @@ public: RecordingRenderDevice() noexcept; [[nodiscard]] const char* backend_name() const noexcept override; + [[nodiscard]] RenderDeviceFeatures features() const noexcept override; [[nodiscard]] pp::foundation::Result> create_texture( TextureDesc desc) noexcept override; [[nodiscard]] pp::foundation::Result> create_render_target( diff --git a/src/renderer_api/renderer_api.h b/src/renderer_api/renderer_api.h index c76c713..2e24dba 100644 --- a/src/renderer_api/renderer_api.h +++ b/src/renderer_api/renderer_api.h @@ -226,6 +226,16 @@ struct ShaderProgramDesc { ShaderStageSource fragment; }; +struct RenderDeviceFeatures { + bool framebuffer_fetch = false; + bool explicit_texture_transitions = false; + bool texture_copy = false; + bool render_target_blit = false; + bool frame_capture = false; + bool float16_render_targets = false; + bool float32_render_targets = false; +}; + class ITexture2D { public: virtual ~ITexture2D() = default; @@ -321,6 +331,7 @@ class IRenderDevice { public: virtual ~IRenderDevice() = default; [[nodiscard]] virtual const char* backend_name() const noexcept = 0; + [[nodiscard]] virtual RenderDeviceFeatures features() const noexcept = 0; [[nodiscard]] virtual pp::foundation::Result> create_texture( TextureDesc desc) noexcept = 0; [[nodiscard]] virtual pp::foundation::Result> create_render_target( diff --git a/src/renderer_gl/opengl_capabilities.cpp b/src/renderer_gl/opengl_capabilities.cpp index 6e17227..e004104 100644 --- a/src/renderer_gl/opengl_capabilities.cpp +++ b/src/renderer_gl/opengl_capabilities.cpp @@ -176,6 +176,18 @@ OpenGlCapabilities detect_opengl_capabilities( return capabilities; } +pp::renderer::RenderDeviceFeatures render_device_features(OpenGlCapabilities capabilities) noexcept +{ + return pp::renderer::RenderDeviceFeatures { + .framebuffer_fetch = capabilities.framebuffer_fetch, + .texture_copy = true, + .render_target_blit = true, + .frame_capture = true, + .float16_render_targets = capabilities.float16_textures, + .float32_render_targets = capabilities.float32_textures, + }; +} + std::uint32_t extension_count_query() noexcept { return gl_num_extensions; diff --git a/src/renderer_gl/opengl_capabilities.h b/src/renderer_gl/opengl_capabilities.h index ae09c93..5e79f57 100644 --- a/src/renderer_gl/opengl_capabilities.h +++ b/src/renderer_gl/opengl_capabilities.h @@ -1,5 +1,7 @@ #pragma once +#include "renderer_api/renderer_api.h" + #include #include #include @@ -46,6 +48,8 @@ struct OpenGlWindowsWglContextConfig { [[nodiscard]] OpenGlCapabilities detect_opengl_capabilities( std::span extensions, OpenGlRuntime runtime) noexcept; +[[nodiscard]] pp::renderer::RenderDeviceFeatures render_device_features( + OpenGlCapabilities capabilities) noexcept; [[nodiscard]] std::uint32_t extension_count_query() noexcept; [[nodiscard]] std::uint32_t extension_string_name() noexcept; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 6e93251..82325d1 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -365,7 +365,7 @@ if(TARGET pano_cli) COMMAND pano_cli record-render --width 32 --height 16) set_tests_properties(pano_cli_record_render_smoke PROPERTIES LABELS "renderer;integration;desktop-fast" - PASS_REGULAR_EXPRESSION "\"backend\":\"recording\".*\"width\":32.*\"height\":16.*\"createdResources\":7.*\"labeledCommandDescriptors\":16.*\"commands\":25.*\"renderPasses\":1.*\"depthClears\":1.*\"stencilClears\":0.*\"drawCommands\":1.*\"drawVertices\":3.*\"drawIndices\":3.*\"scissorCommands\":1.*\"blendCommands\":1.*\"depthCommands\":1.*\"uniformCommands\":1.*\"uniformBytes\":64.*\"bindTextureCommands\":1.*\"bindSamplerCommands\":1.*\"boundTextureBytes\":2048.*\"uploadCommands\":1.*\"uploadBytes\":4.*\"mipmapCommands\":1.*\"mipmapLevels\":3.*\"mipmapBytes\":84.*\"transitionCommands\":4.*\"transitionToUpload\":1.*\"transitionToShaderRead\":2.*\"copyCommands\":1.*\"copySourceBytes\":2048.*\"copyDestinationBytes\":2048.*\"readbackCommands\":1.*\"readbackBytes\":2048.*\"captureCommands\":1.*\"captureBytes\":2048.*\"blitCommands\":1.*\"blitSourceBytes\":2048.*\"blitDestinationBytes\":2048.*\"traceMarkers\":1.*\"traceBeginScopes\":1.*\"traceEndScopes\":1") + PASS_REGULAR_EXPRESSION "\"backend\":\"recording\".*\"framebufferFetch\":false.*\"explicitTextureTransitions\":true.*\"textureCopy\":true.*\"renderTargetBlit\":true.*\"frameCapture\":true.*\"float16RenderTargets\":false.*\"float32RenderTargets\":false.*\"width\":32.*\"height\":16.*\"createdResources\":7.*\"labeledCommandDescriptors\":16.*\"commands\":25.*\"renderPasses\":1.*\"depthClears\":1.*\"stencilClears\":0.*\"drawCommands\":1.*\"drawVertices\":3.*\"drawIndices\":3.*\"scissorCommands\":1.*\"blendCommands\":1.*\"depthCommands\":1.*\"uniformCommands\":1.*\"uniformBytes\":64.*\"bindTextureCommands\":1.*\"bindSamplerCommands\":1.*\"boundTextureBytes\":2048.*\"uploadCommands\":1.*\"uploadBytes\":4.*\"mipmapCommands\":1.*\"mipmapLevels\":3.*\"mipmapBytes\":84.*\"transitionCommands\":4.*\"transitionToUpload\":1.*\"transitionToShaderRead\":2.*\"copyCommands\":1.*\"copySourceBytes\":2048.*\"copyDestinationBytes\":2048.*\"readbackCommands\":1.*\"readbackBytes\":2048.*\"captureCommands\":1.*\"captureBytes\":2048.*\"blitCommands\":1.*\"blitSourceBytes\":2048.*\"blitDestinationBytes\":2048.*\"traceMarkers\":1.*\"traceBeginScopes\":1.*\"traceEndScopes\":1") add_test(NAME pano_cli_record_render_rejects_oversized_target COMMAND "${CMAKE_COMMAND}" diff --git a/tests/renderer_api/renderer_api_tests.cpp b/tests/renderer_api/renderer_api_tests.cpp index 0861796..7914326 100644 --- a/tests/renderer_api/renderer_api_tests.cpp +++ b/tests/renderer_api/renderer_api_tests.cpp @@ -43,6 +43,7 @@ using pp::renderer::RecordingRenderDevice; using pp::renderer::RecordingRenderTarget; using pp::renderer::RecordingShaderProgram; using pp::renderer::RecordingTexture2D; +using pp::renderer::RenderDeviceFeatures; using pp::renderer::RenderPassDesc; using pp::renderer::SamplerAddressMode; using pp::renderer::sampler_address_mode_name; @@ -596,6 +597,19 @@ public: return "fake"; } + [[nodiscard]] RenderDeviceFeatures features() const noexcept override + { + return RenderDeviceFeatures { + .framebuffer_fetch = true, + .explicit_texture_transitions = true, + .texture_copy = true, + .render_target_blit = true, + .frame_capture = true, + .float16_render_targets = true, + .float32_render_targets = true, + }; + } + [[nodiscard]] pp::foundation::Result> create_texture( TextureDesc desc) noexcept override { @@ -1552,6 +1566,14 @@ void renderer_interfaces_support_backend_neutral_dispatch(pp::tests::Harness& h) FakeMesh mesh; PP_EXPECT(h, device.backend_name() == std::string_view("fake")); + const auto features = device.features(); + PP_EXPECT(h, features.framebuffer_fetch); + PP_EXPECT(h, features.explicit_texture_transitions); + PP_EXPECT(h, features.texture_copy); + PP_EXPECT(h, features.render_target_blit); + PP_EXPECT(h, features.frame_capture); + PP_EXPECT(h, features.float16_render_targets); + PP_EXPECT(h, features.float32_render_targets); PP_EXPECT(h, device.trace()->begin_scope("renderer", "dispatch").ok()); PP_EXPECT(h, device.trace()->marker("renderer", "begin").ok()); PP_EXPECT(h, device.trace()->end_scope().ok()); @@ -1690,7 +1712,15 @@ void render_devices_create_validated_resources(pp::tests::Harness& h) .debug_name = "factory-mesh", }); const auto readback = device.create_readback_buffer(8U * 4U * 4U); + const auto features = device.features(); + PP_EXPECT(h, !features.framebuffer_fetch); + PP_EXPECT(h, features.explicit_texture_transitions); + PP_EXPECT(h, features.texture_copy); + PP_EXPECT(h, features.render_target_blit); + PP_EXPECT(h, features.frame_capture); + PP_EXPECT(h, !features.float16_render_targets); + PP_EXPECT(h, !features.float32_render_targets); PP_EXPECT(h, texture.ok()); PP_EXPECT(h, texture.value()->desc().extent.width == 8U); PP_EXPECT(h, !has_texture_usage(texture.value()->desc().usage, TextureUsage::render_target)); diff --git a/tests/renderer_gl/capabilities_tests.cpp b/tests/renderer_gl/capabilities_tests.cpp index a59495b..b11b26e 100644 --- a/tests/renderer_gl/capabilities_tests.cpp +++ b/tests/renderer_gl/capabilities_tests.cpp @@ -36,6 +36,15 @@ void detects_common_extension_capabilities(pp::tests::Harness& h) PP_EXPECT(h, capabilities.map_buffer_alignment); PP_EXPECT(h, !capabilities.float32_textures); PP_EXPECT(h, !capabilities.float16_textures); + + const auto features = pp::renderer::gl::render_device_features(capabilities); + PP_EXPECT(h, features.framebuffer_fetch); + PP_EXPECT(h, !features.explicit_texture_transitions); + PP_EXPECT(h, features.texture_copy); + PP_EXPECT(h, features.render_target_blit); + PP_EXPECT(h, features.frame_capture); + PP_EXPECT(h, !features.float16_render_targets); + PP_EXPECT(h, !features.float32_render_targets); } void treats_desktop_gl_float_rendering_as_core(pp::tests::Harness& h) @@ -47,6 +56,10 @@ void treats_desktop_gl_float_rendering_as_core(pp::tests::Harness& h) PP_EXPECT(h, capabilities.float32_textures); PP_EXPECT(h, capabilities.float32_linear); PP_EXPECT(h, capabilities.float16_textures); + + const auto features = pp::renderer::gl::render_device_features(capabilities); + PP_EXPECT(h, features.float16_render_targets); + PP_EXPECT(h, features.float32_render_targets); } void detects_gles_texture_float_extensions(pp::tests::Harness& h) diff --git a/tools/pano_cli/main.cpp b/tools/pano_cli/main.cpp index 7de5f85..aa17e16 100644 --- a/tools/pano_cli/main.cpp +++ b/tools/pano_cli/main.cpp @@ -203,6 +203,11 @@ std::string json_escape(std::string_view value) return escaped; } +const char* json_bool(bool value) noexcept +{ + return value ? "true" : "false"; +} + pp::foundation::Result parse_float_arg(std::string_view text) { float value = 0.0F; @@ -2566,6 +2571,7 @@ int record_render(int argc, char** argv) } }; const auto commands = device.commands(); + const auto features = device.features(); for (const auto& command : commands) { if (command.kind == pp::renderer::RecordedRenderCommandKind::begin_render_pass) { ++render_passes; @@ -2650,6 +2656,14 @@ int record_render(int argc, char** argv) std::cout << "{\"ok\":true,\"command\":\"record-render\"" << ",\"backend\":\"" << device.backend_name() << "\"" + << ",\"features\":{\"framebufferFetch\":" << json_bool(features.framebuffer_fetch) + << ",\"explicitTextureTransitions\":" << json_bool(features.explicit_texture_transitions) + << ",\"textureCopy\":" << json_bool(features.texture_copy) + << ",\"renderTargetBlit\":" << json_bool(features.render_target_blit) + << ",\"frameCapture\":" << json_bool(features.frame_capture) + << ",\"float16RenderTargets\":" << json_bool(features.float16_render_targets) + << ",\"float32RenderTargets\":" << json_bool(features.float32_render_targets) + << "}" << ",\"target\":{\"width\":" << args.width << ",\"height\":" << args.height << ",\"format\":\"rgba8\"}"