Add opt-in desktop GPU readback gate
This commit is contained in:
@@ -18,6 +18,12 @@ agent or engineer to remove them without reconstructing context from chat.
|
|||||||
|
|
||||||
## Recent Reductions
|
## Recent Reductions
|
||||||
|
|
||||||
|
- 2026-06-12: DEBT-0036 was narrowed again. The opt-in `desktop-gpu`
|
||||||
|
preset now owns a real OpenGL readback golden gate through
|
||||||
|
`pp_renderer_gl_gpu_readback_tests`, validating a deterministic 1x1 clear
|
||||||
|
and `glReadPixels` result against exact RGBA bytes. The first context helper
|
||||||
|
is Windows/WGL-only and skips clearly on platforms without a helper; macOS
|
||||||
|
and Linux GPU context helpers plus broader golden coverage remain open.
|
||||||
- 2026-06-12: DEBT-0060 was closed. Retained Android standard/Quest/Focus
|
- 2026-06-12: DEBT-0060 was closed. Retained Android standard/Quest/Focus
|
||||||
package CMake files no longer generate or prepend a patched `nanort.h`
|
package CMake files no longer generate or prepend a patched `nanort.h`
|
||||||
overlay. Android package configure now applies the tracked nanort source
|
overlay. Android package configure now applies the tracked nanort source
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ or temporary adapters live only in chat history.
|
|||||||
| 4 | Component Split Without Behavior Change | Started | Each extracted target builds and tests |
|
| 4 | Component Split Without Behavior Change | Started | Each extracted target builds and tests |
|
||||||
| 5 | Renderer Boundary And OpenGL Parity | Started | OpenGL output matches golden readbacks |
|
| 5 | Renderer Boundary And OpenGL Parity | Started | OpenGL output matches golden readbacks |
|
||||||
| 6 | Platform Alignment | Started | Every supported platform has named validation |
|
| 6 | Platform Alignment | Started | Every supported platform has named validation |
|
||||||
| 7 | Hardening, Coverage, And Breaking-Point Tests | Not started | Each component has edge/failure tests |
|
| 7 | Hardening, Coverage, And Breaking-Point Tests | Started | Each component has edge/failure tests |
|
||||||
| 8 | Future Backend Readiness | Not started | Vulkan/Metal lab targets remain non-default |
|
| 8 | Future Backend Readiness | Not started | Vulkan/Metal lab targets remain non-default |
|
||||||
|
|
||||||
## Measurable Task Tracking
|
## Measurable Task Tracking
|
||||||
@@ -1729,6 +1729,14 @@ Gate:
|
|||||||
|
|
||||||
## Phase 7: Hardening, Coverage, And Breaking-Point Tests
|
## Phase 7: Hardening, Coverage, And Breaking-Point Tests
|
||||||
|
|
||||||
|
Status: started. The first opt-in desktop GPU golden/readback gate now lives in
|
||||||
|
`pp_renderer_gl_gpu_readback_tests` and is selected by the existing
|
||||||
|
`desktop-gpu` preset. It creates a tiny desktop OpenGL context, clears to a
|
||||||
|
deterministic 1x1 red fixture, reads back exact RGBA bytes, and skips with a
|
||||||
|
clear message when no GPU/context helper is available. The first helper is
|
||||||
|
Windows/WGL-only; macOS/Linux helpers and broader render golden coverage remain
|
||||||
|
tracked under `DEBT-0036`.
|
||||||
|
|
||||||
Goal: tests should try to break components, not only confirm current happy
|
Goal: tests should try to break components, not only confirm current happy
|
||||||
paths.
|
paths.
|
||||||
|
|
||||||
|
|||||||
@@ -37,8 +37,8 @@ auditable steps rather than by subjective estimates.
|
|||||||
| Legacy adapter retirement | 20 | 7 | `legacy_*_services` and singleton bridges are deleted or reduced to trivial composition. |
|
| Legacy adapter retirement | 20 | 7 | `legacy_*_services` and singleton bridges are deleted or reduced to trivial composition. |
|
||||||
| Renderer boundary and OpenGL parity | 15 | 10 | Live render/export/readback paths execute through renderer interfaces with parity checks. |
|
| Renderer boundary and OpenGL parity | 15 | 10 | Live render/export/readback paths execute through renderer interfaces with parity checks. |
|
||||||
| Platform and package parity | 10 | 6 | Required platforms have root CMake/package validation and injected platform services. |
|
| Platform and package parity | 10 | 6 | Required platforms have root CMake/package validation and injected platform services. |
|
||||||
| Hardening and future backend readiness | 10 | 0 | Edge, fuzz, golden, stress, and backend-lab gates exist for high-risk paths. |
|
| Hardening and future backend readiness | 10 | 2 | Edge, fuzz, golden, stress, and backend-lab gates exist for high-risk paths. |
|
||||||
| **Total** | **100** | **53** | Only completed tasks below may change this number. |
|
| **Total** | **100** | **55** | Only completed tasks below may change this number. |
|
||||||
|
|
||||||
When updating `Current`, add a dated note under "Completed Task Log" with the
|
When updating `Current`, add a dated note under "Completed Task Log" with the
|
||||||
task id, points moved, validation command, and commit hash.
|
task id, points moved, validation command, and commit hash.
|
||||||
@@ -331,7 +331,7 @@ cmake --build --preset windows-msvc-default --config Debug --target PanoPainter
|
|||||||
|
|
||||||
### RND-004 - Add First Desktop GPU Golden Gate
|
### RND-004 - Add First Desktop GPU Golden Gate
|
||||||
|
|
||||||
Status: Blocked
|
Status: Done
|
||||||
Score: +2 hardening and future backend readiness
|
Score: +2 hardening and future backend readiness
|
||||||
Debt: `DEBT-0036`
|
Debt: `DEBT-0036`
|
||||||
Scope: `tests/`, `CMakeLists.txt`, renderer test helpers only
|
Scope: `tests/`, `CMakeLists.txt`, renderer test helpers only
|
||||||
@@ -526,6 +526,7 @@ Done Checks:
|
|||||||
|
|
||||||
| Date | Task | Score Change | Validation | Commit |
|
| Date | Task | Score Change | Validation | Commit |
|
||||||
| --- | --- | ---: | --- | --- |
|
| --- | --- | ---: | --- | --- |
|
||||||
|
| 2026-06-12 | RND-004 | +2 hardening and future backend readiness | `ctest --preset desktop-gpu --build-config Debug --output-on-failure`; `ctest --preset desktop-fast --build-config Debug -R "pp_renderer_gl\|pp_paint_renderer" --output-on-failure` | pending |
|
||||||
| 2026-06-12 | DEP-002 | +1 build and CMake ownership | `powershell -ExecutionPolicy Bypass -File scripts\automation\android-legacy-package-build.ps1 -Packages standard` | 648404ee |
|
| 2026-06-12 | DEP-002 | +1 build and CMake ownership | `powershell -ExecutionPolicy Bypass -File scripts\automation\android-legacy-package-build.ps1 -Packages standard` | 648404ee |
|
||||||
| 2026-06-12 | RND-002 | +2 renderer boundary and OpenGL parity | `ctest --preset desktop-fast --build-config Debug -R "pp_app_core_document_export\|pp_paint_renderer_compositor\|pano_cli_plan_export_snapshot_route\|pano_cli_simulate_document_export" --output-on-failure` | 46fb8ef |
|
| 2026-06-12 | RND-002 | +2 renderer boundary and OpenGL parity | `ctest --preset desktop-fast --build-config Debug -R "pp_app_core_document_export\|pp_paint_renderer_compositor\|pano_cli_plan_export_snapshot_route\|pano_cli_simulate_document_export" --output-on-failure` | 46fb8ef |
|
||||||
| 2026-06-12 | RND-001 | +2 renderer boundary and OpenGL parity | `ctest --preset desktop-fast --build-config Debug -R "pp_app_core_document_export\|pp_paint_renderer_compositor\|pano_cli_plan_export_snapshot_route\|pano_cli_simulate_document_export" --output-on-failure` | 46fb8ef |
|
| 2026-06-12 | RND-001 | +2 renderer boundary and OpenGL parity | `ctest --preset desktop-fast --build-config Debug -R "pp_app_core_document_export\|pp_paint_renderer_compositor\|pano_cli_plan_export_snapshot_route\|pano_cli_simulate_document_export" --output-on-failure` | 46fb8ef |
|
||||||
|
|||||||
@@ -248,6 +248,23 @@ if(TARGET pp_renderer_gl)
|
|||||||
add_test(NAME pp_renderer_gl_command_plan_tests COMMAND pp_renderer_gl_command_plan_tests)
|
add_test(NAME pp_renderer_gl_command_plan_tests COMMAND pp_renderer_gl_command_plan_tests)
|
||||||
set_tests_properties(pp_renderer_gl_command_plan_tests PROPERTIES
|
set_tests_properties(pp_renderer_gl_command_plan_tests PROPERTIES
|
||||||
LABELS "renderer;desktop-fast")
|
LABELS "renderer;desktop-fast")
|
||||||
|
|
||||||
|
add_executable(pp_renderer_gl_gpu_readback_tests
|
||||||
|
renderer_gl/gpu_readback_tests.cpp)
|
||||||
|
target_link_libraries(pp_renderer_gl_gpu_readback_tests PRIVATE
|
||||||
|
pp_renderer_gl
|
||||||
|
pp_test_harness)
|
||||||
|
if(WIN32)
|
||||||
|
target_link_libraries(pp_renderer_gl_gpu_readback_tests PRIVATE
|
||||||
|
gdi32
|
||||||
|
opengl32
|
||||||
|
user32)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
add_test(NAME pp_renderer_gl_gpu_readback_tests COMMAND pp_renderer_gl_gpu_readback_tests)
|
||||||
|
set_tests_properties(pp_renderer_gl_gpu_readback_tests PROPERTIES
|
||||||
|
LABELS "renderer;gpu"
|
||||||
|
SKIP_REGULAR_EXPRESSION "\\[skip\\]")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
add_executable(pp_paint_renderer_compositor_tests
|
add_executable(pp_paint_renderer_compositor_tests
|
||||||
|
|||||||
185
tests/renderer_gl/gpu_readback_tests.cpp
Normal file
185
tests/renderer_gl/gpu_readback_tests.cpp
Normal file
@@ -0,0 +1,185 @@
|
|||||||
|
#include "../test_harness.h"
|
||||||
|
|
||||||
|
#if defined(_WIN32)
|
||||||
|
#ifndef WIN32_LEAN_AND_MEAN
|
||||||
|
#define WIN32_LEAN_AND_MEAN
|
||||||
|
#endif
|
||||||
|
#ifndef NOMINMAX
|
||||||
|
#define NOMINMAX
|
||||||
|
#endif
|
||||||
|
#include <windows.h>
|
||||||
|
#include <GL/gl.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
#if defined(_WIN32)
|
||||||
|
|
||||||
|
class HiddenWglContext {
|
||||||
|
public:
|
||||||
|
HiddenWglContext()
|
||||||
|
{
|
||||||
|
instance_ = GetModuleHandleW(nullptr);
|
||||||
|
|
||||||
|
WNDCLASSW window_class {};
|
||||||
|
window_class.style = CS_OWNDC;
|
||||||
|
window_class.lpfnWndProc = DefWindowProcW;
|
||||||
|
window_class.hInstance = instance_;
|
||||||
|
window_class.lpszClassName = class_name_;
|
||||||
|
|
||||||
|
registered_ = RegisterClassW(&window_class) != 0 || GetLastError() == ERROR_CLASS_ALREADY_EXISTS;
|
||||||
|
if (!registered_) {
|
||||||
|
skip_reason_ = "RegisterClassW failed";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
window_ = CreateWindowExW(
|
||||||
|
WS_EX_TOOLWINDOW,
|
||||||
|
class_name_,
|
||||||
|
L"PanoPainter GPU readback test",
|
||||||
|
WS_POPUP,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
16,
|
||||||
|
16,
|
||||||
|
nullptr,
|
||||||
|
nullptr,
|
||||||
|
instance_,
|
||||||
|
nullptr);
|
||||||
|
if (window_ == nullptr) {
|
||||||
|
skip_reason_ = "CreateWindowExW failed";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ShowWindow(window_, SW_SHOWNA);
|
||||||
|
UpdateWindow(window_);
|
||||||
|
|
||||||
|
device_context_ = GetDC(window_);
|
||||||
|
if (device_context_ == nullptr) {
|
||||||
|
skip_reason_ = "GetDC failed";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
PIXELFORMATDESCRIPTOR pixel_format {};
|
||||||
|
pixel_format.nSize = sizeof(pixel_format);
|
||||||
|
pixel_format.nVersion = 1;
|
||||||
|
pixel_format.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;
|
||||||
|
pixel_format.iPixelType = PFD_TYPE_RGBA;
|
||||||
|
pixel_format.cColorBits = 24;
|
||||||
|
pixel_format.cAlphaBits = 8;
|
||||||
|
pixel_format.cDepthBits = 24;
|
||||||
|
pixel_format.iLayerType = PFD_MAIN_PLANE;
|
||||||
|
|
||||||
|
const int format = ChoosePixelFormat(device_context_, &pixel_format);
|
||||||
|
if (format == 0 || SetPixelFormat(device_context_, format, &pixel_format) == FALSE) {
|
||||||
|
skip_reason_ = "OpenGL pixel format setup failed";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
render_context_ = wglCreateContext(device_context_);
|
||||||
|
if (render_context_ == nullptr) {
|
||||||
|
skip_reason_ = "wglCreateContext failed";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wglMakeCurrent(device_context_, render_context_) == FALSE) {
|
||||||
|
skip_reason_ = "wglMakeCurrent failed";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ready_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
HiddenWglContext(const HiddenWglContext&) = delete;
|
||||||
|
HiddenWglContext& operator=(const HiddenWglContext&) = delete;
|
||||||
|
|
||||||
|
~HiddenWglContext()
|
||||||
|
{
|
||||||
|
if (render_context_ != nullptr) {
|
||||||
|
wglMakeCurrent(nullptr, nullptr);
|
||||||
|
wglDeleteContext(render_context_);
|
||||||
|
}
|
||||||
|
if (window_ != nullptr && device_context_ != nullptr) {
|
||||||
|
ReleaseDC(window_, device_context_);
|
||||||
|
}
|
||||||
|
if (window_ != nullptr) {
|
||||||
|
DestroyWindow(window_);
|
||||||
|
}
|
||||||
|
if (registered_) {
|
||||||
|
UnregisterClassW(class_name_, instance_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] bool ready() const noexcept
|
||||||
|
{
|
||||||
|
return ready_;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] const char* skip_reason() const noexcept
|
||||||
|
{
|
||||||
|
return skip_reason_;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
static constexpr const wchar_t* class_name_ = L"PanoPainterGpuReadbackTestWindow";
|
||||||
|
|
||||||
|
HINSTANCE instance_ = nullptr;
|
||||||
|
HWND window_ = nullptr;
|
||||||
|
HDC device_context_ = nullptr;
|
||||||
|
HGLRC render_context_ = nullptr;
|
||||||
|
const char* skip_reason_ = "OpenGL context unavailable";
|
||||||
|
bool registered_ = false;
|
||||||
|
bool ready_ = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void opengl_clear_readback_matches_fixture(pp::tests::Harness& h)
|
||||||
|
{
|
||||||
|
#if defined(_WIN32)
|
||||||
|
HiddenWglContext context;
|
||||||
|
if (!context.ready()) {
|
||||||
|
std::cout << "[skip] desktop GPU OpenGL readback unavailable: " << context.skip_reason() << "\n";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
glViewport(0, 0, 1, 1);
|
||||||
|
glDrawBuffer(GL_BACK);
|
||||||
|
glReadBuffer(GL_BACK);
|
||||||
|
glClearColor(1.0F, 0.0F, 0.0F, 1.0F);
|
||||||
|
glClear(GL_COLOR_BUFFER_BIT);
|
||||||
|
glFinish();
|
||||||
|
|
||||||
|
std::array<std::uint8_t, 4> pixel {};
|
||||||
|
glReadPixels(0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel.data());
|
||||||
|
|
||||||
|
constexpr std::array<std::uint8_t, 4> expected {
|
||||||
|
255,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
255,
|
||||||
|
};
|
||||||
|
if (pixel != expected) {
|
||||||
|
std::cout << "readback rgba: "
|
||||||
|
<< static_cast<int>(pixel[0]) << ", "
|
||||||
|
<< static_cast<int>(pixel[1]) << ", "
|
||||||
|
<< static_cast<int>(pixel[2]) << ", "
|
||||||
|
<< static_cast<int>(pixel[3]) << "\n";
|
||||||
|
}
|
||||||
|
PP_EXPECT(h, pixel == expected);
|
||||||
|
#else
|
||||||
|
std::cout << "[skip] desktop GPU OpenGL readback unavailable: no platform context helper\n";
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
pp::tests::Harness harness;
|
||||||
|
harness.run("opengl_clear_readback_matches_fixture", opengl_clear_readback_matches_fixture);
|
||||||
|
return harness.finish();
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user