Add renderer backend feature reporting

This commit is contained in:
2026-06-02 17:18:48 +02:00
parent 18617cdbd2
commit 995752da75
11 changed files with 103 additions and 7 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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<std::unique_ptr<ITexture2D>> RecordingRenderDevice::create_texture(
TextureDesc desc) noexcept
{

View File

@@ -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<std::unique_ptr<ITexture2D>> create_texture(
TextureDesc desc) noexcept override;
[[nodiscard]] pp::foundation::Result<std::unique_ptr<IRenderTarget>> create_render_target(

View File

@@ -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<std::unique_ptr<ITexture2D>> create_texture(
TextureDesc desc) noexcept = 0;
[[nodiscard]] virtual pp::foundation::Result<std::unique_ptr<IRenderTarget>> create_render_target(

View File

@@ -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;

View File

@@ -1,5 +1,7 @@
#pragma once
#include "renderer_api/renderer_api.h"
#include <array>
#include <cstdint>
#include <span>
@@ -46,6 +48,8 @@ struct OpenGlWindowsWglContextConfig {
[[nodiscard]] OpenGlCapabilities detect_opengl_capabilities(
std::span<const std::string_view> 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;

View File

@@ -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}"

View File

@@ -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<std::unique_ptr<pp::renderer::ITexture2D>> 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));

View File

@@ -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)

View File

@@ -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<float> 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\"}"