From ce787ce18665f1b5166837ce765ace615120492d Mon Sep 17 00:00:00 2001 From: omigamedev Date: Thu, 4 Jun 2026 21:47:19 +0200 Subject: [PATCH] Route RTT lifecycle through GL backend --- docs/modernization/build-inventory.md | 21 +- docs/modernization/debt.md | 2 +- docs/modernization/roadmap.md | 18 +- src/renderer_gl/opengl_capabilities.cpp | 99 +++++++++ src/renderer_gl/opengl_capabilities.h | 35 +++ src/rtt.cpp | 266 +++++++++++++++++------ tests/renderer_gl/capabilities_tests.cpp | 205 +++++++++++++++++ 7 files changed, 569 insertions(+), 77 deletions(-) diff --git a/docs/modernization/build-inventory.md b/docs/modernization/build-inventory.md index 98fb5be..6d98656 100644 --- a/docs/modernization/build-inventory.md +++ b/docs/modernization/build-inventory.md @@ -287,13 +287,14 @@ Known local toolchain state: used by `TextureCube`. It also owns and validates framebuffer blit color mask and linear/nearest filters used by `RTT::resize` and `RTT::copy`, renderer API blit-filter to OpenGL token - mapping, plus the default linear clamp-to-edge - render-target texture parameters, texture/renderbuffer targets, depth format, - framebuffer targets, binding queries, attachment points, and completion - status used by `RTT::create` and framebuffer bind/restore paths, plus RTT - clear color/depth masks. Canvas object-drawing depth renderbuffer - allocation/storage/delete and framebuffer depth attach/detach also execute - through tested dispatch contracts here. Renderer API render-pass color/depth/stencil + mapping, plus the default linear clamp-to-edge render-target texture + parameters and parameter dispatch, texture/renderbuffer targets, depth format, + framebuffer targets, binding queries, attachment points, render-target + framebuffer allocation/delete, binding restore, and completion status used by + `RTT::create`/`RTT::destroy` and framebuffer bind/restore paths, plus RTT + clear color/depth masks. Optional RTT depth renderbuffer allocation/storage/delete + and framebuffer depth attach/detach, plus canvas object-drawing depth + renderbuffer setup, also execute through tested dispatch contracts here. Renderer API render-pass color/depth/stencil clear-mask and clear-value mapping, and color-write-mask query tokens. `RTT` no longer spells GL enum names directly. `RTT` also exposes a retained RGBA8 region-readback helper that uses the tested framebuffer readback dispatch for @@ -626,12 +627,14 @@ Known local toolchain state: consumed by the retained `gl_state` utility, tested texture lifecycle/readback dispatch consumed by the retained `Texture2D` utility, tested framebuffer blit/readback dispatch consumed by retained `RTT` resize/copy/readback and RGBA8 region-readback - paths, tested pixel-buffer allocation/readback/map/unmap/delete dispatch + paths, tested render-target texture parameter, framebuffer allocation/delete, + color/depth attachment, status-check, and binding-restore dispatch consumed + by retained `RTT::create`/`RTT::destroy`, tested pixel-buffer allocation/readback/map/unmap/delete dispatch consumed by retained `PBO` recording readbacks, tested framebuffer-to-texture 2D copy dispatch consumed by retained canvas/UI paint paths, tested framebuffer bind/restore dispatch consumed by retained `RTT` render-target pass entry and exit paths, tested depth renderbuffer allocation/delete and framebuffer - depth attach/detach dispatch consumed by canvas object-drawing helpers, + depth attach/detach dispatch consumed by retained RTT and canvas object-drawing helpers, tested convert-command state dispatch consumed by `App::cmd_convert`, tested render platform hint dispatch consumed by `WindowsPlatformServices` and the retained macOS legacy fallback, tested diff --git a/docs/modernization/debt.md b/docs/modernization/debt.md index 7561902..b0113ca 100644 --- a/docs/modernization/debt.md +++ b/docs/modernization/debt.md @@ -53,7 +53,7 @@ agent or engineer to remove them without reconstructing context from chat. | DEBT-0033 | Open | Modernization | Tools menu planning and direct command execution dispatch now consume pure `pp_app_core` through `App::init_menu_tools`, `pano_cli plan-tools-menu`, `pano_cli plan-tools-panel`, and the `ToolsMenuServices` boundary, and direct command execution is centralized in `src/legacy_app_shell_services.*`; SonarPen availability/startup now routes through `PlatformServices`, but live adapters still construct legacy `NodePanelFloating` panels, mutate legacy panel nodes, clear `CanvasModeGrid`, reset `NodeCanvas` camera state, open legacy shortcuts UI, and rely on the legacy platform adapter for the retained iOS SonarPen bridge | Preserve current Tools menu behavior while UI shell actions move toward app/UI/platform services | `pp_app_core_tools_menu_tests`; `pp_platform_api_tests`; `pano_cli plan-tools-menu --command shortcuts`; `pano_cli plan-tools-panel --panel layers`; `pano_cli plan-tools-panel --panel animation --already-visible`; `ctest --preset desktop-fast --build-config Debug` | Tools panel creation, submenu routing, grid clear, camera reset, shortcuts dialog, and SonarPen dispatch are owned by injected app/UI/platform services with `App::init_menu_tools` acting only as a UI adapter and no legacy Tools adapter | | DEBT-0034 | Open | Modernization | About menu command planning and execution dispatch now consume pure `pp_app_core` through `App::init_menu_about`, `pano_cli plan-about-menu`, and the `AboutMenuServices` boundary, and live execution is centralized in `src/legacy_app_shell_services.*`, but the bridge still opens legacy About/manual/what's-new dialogs, invokes the injected crash hook, and runs the legacy Canvas stroke performance test directly | Preserve About menu behavior while dialogs and diagnostics move toward app/UI/platform services | `pp_app_core_about_menu_tests`; `pano_cli plan-about-menu --command news --version-major 2 --version-minor 5 --version-fix 7`; `pano_cli plan-about-menu --command performance --no-canvas`; `ctest --preset desktop-fast --build-config Debug` | About/manual/what's-new dialog dispatch, crash-test dispatch, and performance-test execution are owned by injected app/UI/platform services with `App::init_menu_about` acting only as a UI adapter and no legacy About adapter | | DEBT-0035 | Open | Modernization | Main toolbar/status command planning and execution dispatch now consume pure `pp_app_core` through `App::init_toolbar_main`, `pano_cli plan-main-toolbar`, and the `MainToolbarServices` boundary, history/canvas commands now hand off through `HistoryUiServices` and `DocumentCanvasClearServices`, and live execution is centralized in `src/legacy_app_shell_services.*`, but the bridge still opens legacy open/save/settings/message-box dialogs and delegates to legacy history/canvas adapters | Preserve reachable toolbar/status behavior while app shell commands move toward app/document/UI services | `pp_app_core_main_toolbar_tests`; `pano_cli plan-main-toolbar --command undo --undo-count 2`; `pano_cli plan-main-toolbar --command clear-canvas --no-canvas`; `ctest --preset desktop-fast --build-config Debug` | Open/save/settings/message-box routing, undo/redo/clear-history execution, and canvas-clear execution are owned by injected app/document/UI services with `App::init_toolbar_main` acting only as a UI adapter and no legacy toolbar adapter | -| DEBT-0036 | Open | Modernization | `pp_renderer_api`, `pp_paint_renderer`, `pano_cli plan-paint-feedback`, and `pano_cli plan-stroke-composite` can choose backend-neutral complex paint feedback strategies for fixed-function blending, framebuffer-fetch-capable renderers, or ping-pong render targets. OpenGL extension detection now stores `pp::renderer::RenderDeviceFeatures` through `ShaderManager`, using `pp_renderer_gl::query_opengl_capability_detection`, `detect_opengl_feature_state`, and `render_device_features` as the backend conversion point; that feature snapshot now includes float32-linear filtering, so canvas stroke texture format selection, renderer diagnostics, grid lightmap render planning, and grid bake target selection no longer read `ShaderManager::ext_*` flags directly. `pp_paint_renderer::plan_canvas_blend_gate` owns the compatibility mapping from persisted layer/brush blend indices to the extracted stroke-composite planner, and live `Canvas::draw_merge` plus `NodeCanvas` panorama rendering both call it with the stored renderer-neutral feature set for their existing shader-blend gates and destination-copy versus framebuffer-fetch decisions. `pp_paint_renderer::plan_canvas_stroke_feedback` also owns the current destination-feedback decision, and live `Canvas::stroke_draw`, thumbnail layer blending, and `NodeStrokePreview` brush-preview rendering use it for framebuffer-fetch versus destination-copy decisions. The retained `copy_framebuffer_to_texture_2d` utility bridge now routes 2D framebuffer-to-texture copies through tested `pp_renderer_gl` dispatch, but actual live stroke rasterization, dual-brush compositing, pattern feedback math, thumbnail layer compositing, brush-preview compositing, the retained cube-map framebuffer copy, and the retained `ShaderManager::ext_*` compatibility fields still use legacy OpenGL canvas/UI execution | Preserve current painting behavior while the renderer boundary matures for OpenGL parity and later Vulkan/Metal experiments | `pp_renderer_api_tests`; `pp_renderer_gl_capabilities_tests`; `pp_paint_renderer_compositor_tests`; `pano_cli plan-paint-feedback --framebuffer-fetch --explicit-transitions --render-only`; `pano_cli plan-paint-feedback --texture-copy`; `pano_cli plan-stroke-composite --stroke-blend 10 --framebuffer-fetch --explicit-transitions --render-only`; `pano_cli plan-stroke-composite --layer-blend 4 --dual-blend --texture-copy`; `ctest --preset desktop-fast --build-config Debug`; `cmake --build --preset windows-msvc-default --config Debug --target PanoPainter` | Live stroke/layer compositing chooses its feedback path through `pp_paint_renderer` and renderer services, with OpenGL golden parity and Vulkan/Metal lab tests covering framebuffer-fetch and ping-pong behavior | +| DEBT-0036 | Open | Modernization | `pp_renderer_api`, `pp_paint_renderer`, `pano_cli plan-paint-feedback`, and `pano_cli plan-stroke-composite` can choose backend-neutral complex paint feedback strategies for fixed-function blending, framebuffer-fetch-capable renderers, or ping-pong render targets. OpenGL extension detection now stores `pp::renderer::RenderDeviceFeatures` through `ShaderManager`, using `pp_renderer_gl::query_opengl_capability_detection`, `detect_opengl_feature_state`, and `render_device_features` as the backend conversion point; that feature snapshot now includes float32-linear filtering, so canvas stroke texture format selection, renderer diagnostics, grid lightmap render planning, and grid bake target selection no longer read `ShaderManager::ext_*` flags directly. `pp_paint_renderer::plan_canvas_blend_gate` owns the compatibility mapping from persisted layer/brush blend indices to the extracted stroke-composite planner, and live `Canvas::draw_merge` plus `NodeCanvas` panorama rendering both call it with the stored renderer-neutral feature set for their existing shader-blend gates and destination-copy versus framebuffer-fetch decisions. `pp_paint_renderer::plan_canvas_stroke_feedback` also owns the current destination-feedback decision, and live `Canvas::stroke_draw`, thumbnail layer blending, and `NodeStrokePreview` brush-preview rendering use it for framebuffer-fetch versus destination-copy decisions. The retained `copy_framebuffer_to_texture_2d` utility bridge now routes 2D framebuffer-to-texture copies through tested `pp_renderer_gl` dispatch, and retained `RTT::create`/`RTT::destroy` render-target texture parameter setup, optional depth renderbuffer allocation, framebuffer allocation/attachment/status checks, binding restore, and resource deletion now route through tested `pp_renderer_gl` dispatch, but actual live stroke rasterization, dual-brush compositing, pattern feedback math, thumbnail layer compositing, brush-preview compositing, the retained cube-map framebuffer copy, and the retained `ShaderManager::ext_*` compatibility fields still use legacy OpenGL canvas/UI execution | Preserve current painting behavior while the renderer boundary matures for OpenGL parity and later Vulkan/Metal experiments | `pp_renderer_api_tests`; `pp_renderer_gl_capabilities_tests`; `pp_paint_renderer_compositor_tests`; `pano_cli plan-paint-feedback --framebuffer-fetch --explicit-transitions --render-only`; `pano_cli plan-paint-feedback --texture-copy`; `pano_cli plan-stroke-composite --stroke-blend 10 --framebuffer-fetch --explicit-transitions --render-only`; `pano_cli plan-stroke-composite --layer-blend 4 --dual-blend --texture-copy`; `ctest --preset desktop-fast --build-config Debug`; `cmake --build --preset windows-msvc-default --config Debug --target PanoPainter` | Live stroke/layer compositing chooses its feedback path through `pp_paint_renderer` and renderer services, with OpenGL golden parity and Vulkan/Metal lab tests covering framebuffer-fetch and ping-pong behavior | | DEBT-0037 | Open | Modernization | Recording lifecycle/export planning and execution dispatch now consume pure `pp_app_core` through `App::rec_start`, `App::rec_stop`, `App::rec_clear`, `App::rec_export`, `pano_cli plan-recording-session`, and the `RecordingServices` boundary; live execution is centralized in `src/legacy_recording_services.*`, and retained `PBO` allocation/readback/map/unmap/delete operations now route through tested `pp_renderer_gl` dispatch, but the bridge still owns legacy recording thread startup/shutdown, platform recorded-file cleanup, progress UI, retained `App::rec_loop` readback call sites, and `MP4Encoder::write_mp4` execution | Preserve current timelapse/MP4 behavior while recording moves toward app/document/renderer/video services | `pp_app_core_document_recording_tests`; `pp_renderer_gl_capabilities_tests`; `pano_cli plan-recording-session --running --frame-count 12`; `pano_cli plan-recording-session --platform-clears-files`; `ctest --preset desktop-fast --build-config Debug` | Recording thread lifecycle, frame readback scheduling, platform cleanup, progress reporting, and MP4 writing are owned by injected app/renderer/video services with `App` methods acting only as adapters | | DEBT-0038 | Open | Modernization | Cloud upload/browse/bulk planning and execution dispatch now consume pure `pp_app_core` through `App::cloud_upload`, `App::cloud_upload_all`, `App::cloud_browse`, `pano_cli plan-cloud-upload`, `pano_cli plan-cloud-upload-all`, `pano_cli plan-cloud-browse`, and the `CloudServices` boundary; live execution is centralized in `src/legacy_cloud_services.*`, the app-owned `upload`/`download`/license curl helpers now ask `PlatformServices` for the Android TLS-verification bypass policy, and retained `Asset::open_url`, `LogRemote::net_init`, and `NodeDialogCloud::load_thumbs_thread` curl sites consume the `pp_platform_api` default TLS policy helper instead of spelling Android branches locally, but the bridge still uses legacy save-before-upload, app-owned curl helpers instead of an injected network service, progress/message UI, OpenGL context guarding, `NodeDialogCloud`, `Canvas` project open, layer refresh, and `ActionManager` reset | Preserve current cloud behavior while cloud/network/document import flows move toward app/document/platform services | `pp_app_core_document_cloud_tests`; `pp_platform_api_tests`; `pano_cli plan-cloud-upload --new-document --unsaved`; `pano_cli plan-cloud-browse --selected-file demo.ppi`; `pano_cli plan-cloud-upload-all --file-count 3`; `ctest --preset desktop-fast --build-config Debug` | Cloud upload/download, TLS policy, save-before-upload, progress reporting, cloud browse dialog, downloaded project opening, layer refresh, OpenGL context ownership, and action-history reset are owned by injected app/document/network/platform/renderer services with `App` methods acting only as adapters | | DEBT-0039 | Open | Modernization | Document-open planning and execution dispatch now consume pure `pp_app_core` through `App::open_document`, `pano_cli plan-open-route`, `DocumentOpenServices`, and `src/legacy_document_open_services.*`, but the bridge still opens ABR/PPBR import prompts before delegating import execution to `src/legacy_brush_package_import_services.*`, applies unsaved-project discard prompts, calls legacy project-open execution, refreshes layer UI, updates the app title, and clears legacy history directly | Preserve current file-open/import behavior while document loading and brush import move toward app/document/asset/UI services | `pp_app_core_document_route_tests`; `pp_app_core_document_session_tests`; `pano_cli plan-open-route --path D:/Paint/Scenes/demo.ppi --unsaved`; `pano_cli plan-open-route --path D:/Paint/Brushes/clouds.ABR --unsaved`; `ctest --preset desktop-fast --build-config Debug` | Brush import prompting, project-open execution, unsaved-project discard prompting, layer refresh, title updates, and history clearing are owned by injected app/document/asset/UI services with `App::open_document` acting only as an adapter | diff --git a/docs/modernization/roadmap.md b/docs/modernization/roadmap.md index 8823db3..1abf30f 100644 --- a/docs/modernization/roadmap.md +++ b/docs/modernization/roadmap.md @@ -810,6 +810,10 @@ resource lifecycle path behind the renderer backend boundary. Legacy `RTT` resize/copy blits and byte/float framebuffer readbacks now execute through tested `pp_renderer_gl` framebuffer dispatch contracts with draw/read framebuffer binding restore handled by the backend helper. +Legacy `RTT::create`/`RTT::destroy` now route render-target texture allocation, +default texture parameter setup, optional depth renderbuffer allocation, +framebuffer color/depth attachment, framebuffer status checks, binding restore, +and resource deletion through tested `pp_renderer_gl` dispatch helpers. Legacy `RTT` also exposes an RGBA8 region-readback helper that uses the same backend framebuffer readback dispatch; canvas pick/history/snapshot and transform history paths now call that helper instead of binding an RTT and @@ -1005,12 +1009,14 @@ dispatch sequences used by retained recording readbacks now live in `pp_renderer_gl`. The framebuffer blit color mask and linear/nearest filter tokens used by `RTT::resize` and `RTT::copy`, renderer API blit-filter to OpenGL token mapping, plus the default -render-target texture parameters, texture/renderbuffer targets, depth format, -framebuffer targets, binding queries, attachment points, and completion status -used by `RTT::create` and framebuffer bind/restore paths, also live in -`pp_renderer_gl`. Depth renderbuffer allocation/storage/delete and framebuffer -depth attach/detach sequences used by canvas object-drawing helpers now execute -through tested `pp_renderer_gl` dispatch contracts. 2D framebuffer-to-texture +render-target texture parameters and parameter dispatch, texture/renderbuffer +targets, depth format, framebuffer targets, binding queries, attachment points, +render-target framebuffer allocation/delete, binding restore, and completion +status used by `RTT::create`/`RTT::destroy` and framebuffer bind/restore paths, +also live in `pp_renderer_gl`. Depth renderbuffer allocation/storage/delete and +framebuffer depth attach/detach sequences used by retained `RTT` and canvas +object-drawing helpers now execute through tested `pp_renderer_gl` dispatch +contracts. 2D framebuffer-to-texture copies used by canvas, transform, layer-conversion, panorama UI, and brush preview paths now route through a tested `pp_renderer_gl` copy dispatch via the retained `copy_framebuffer_to_texture_2d` utility bridge; the remaining cube-map diff --git a/src/renderer_gl/opengl_capabilities.cpp b/src/renderer_gl/opengl_capabilities.cpp index 46c98ca..ca4c4f5 100644 --- a/src/renderer_gl/opengl_capabilities.cpp +++ b/src/renderer_gl/opengl_capabilities.cpp @@ -748,6 +748,33 @@ pp::foundation::Status update_opengl_texture_2d( return pp::foundation::Status::success(); } +pp::foundation::Status set_opengl_texture_2d_parameters( + std::uint32_t texture_id, + std::span parameters, + OpenGlTexture2DParameterDispatch dispatch) noexcept +{ + if (dispatch.bind_texture == nullptr || dispatch.tex_parameter_f == nullptr) { + return pp::foundation::Status::invalid_argument("OpenGL texture parameter dispatch callbacks must not be null"); + } + + if (texture_id == 0U || parameters.empty()) { + return pp::foundation::Status::invalid_argument("OpenGL texture parameter request is invalid"); + } + + for (const auto parameter : parameters) { + if (parameter.name == 0U) { + return pp::foundation::Status::invalid_argument("OpenGL texture parameter name is invalid"); + } + } + + dispatch.bind_texture(texture_2d_target(), texture_id); + for (const auto parameter : parameters) { + dispatch.tex_parameter_f(texture_2d_target(), parameter.name, static_cast(parameter.value)); + } + dispatch.bind_texture(texture_2d_target(), default_framebuffer_id()); + return pp::foundation::Status::success(); +} + pp::foundation::Status copy_opengl_framebuffer_to_texture_2d( OpenGlTexture2DFramebufferCopy copy, OpenGlTexture2DFramebufferCopyDispatch dispatch) noexcept @@ -969,6 +996,78 @@ pp::foundation::Status restore_opengl_framebuffer_binding( return pp::foundation::Status::success(); } +pp::foundation::Result allocate_opengl_render_target_framebuffer( + std::uint32_t texture_id, + std::uint32_t depth_renderbuffer_id, + OpenGlRenderTargetFramebufferAllocationDispatch dispatch) noexcept +{ + if (dispatch.gen_framebuffers == nullptr + || dispatch.get_integer == nullptr + || dispatch.bind_framebuffer == nullptr + || dispatch.framebuffer_texture_2d == nullptr + || dispatch.framebuffer_renderbuffer == nullptr + || dispatch.check_framebuffer_status == nullptr) { + return pp::foundation::Result::failure( + pp::foundation::Status::invalid_argument( + "OpenGL render-target framebuffer allocation dispatch callbacks must not be null")); + } + + if (texture_id == 0U) { + return pp::foundation::Result::failure( + pp::foundation::Status::invalid_argument("OpenGL render-target framebuffer texture id is invalid")); + } + + std::int32_t old_framebuffer = 0; + dispatch.get_integer(draw_framebuffer_binding_query(), &old_framebuffer); + + std::uint32_t framebuffer_id = 0U; + dispatch.gen_framebuffers(1U, &framebuffer_id); + if (framebuffer_id == 0U) { + dispatch.bind_framebuffer(framebuffer_target(), static_cast(old_framebuffer)); + return pp::foundation::Result::failure( + pp::foundation::Status::out_of_range("OpenGL render-target framebuffer allocation returned id 0")); + } + + dispatch.bind_framebuffer(framebuffer_target(), framebuffer_id); + dispatch.framebuffer_texture_2d( + framebuffer_target(), + framebuffer_color_attachment(), + texture_2d_target(), + texture_id, + 0); + if (depth_renderbuffer_id != 0U) { + dispatch.framebuffer_renderbuffer( + framebuffer_target(), + framebuffer_depth_attachment(), + renderbuffer_target(), + depth_renderbuffer_id); + } + + const auto framebuffer_status = dispatch.check_framebuffer_status(framebuffer_target()); + dispatch.bind_framebuffer(framebuffer_target(), static_cast(old_framebuffer)); + return pp::foundation::Result::success( + OpenGlRenderTargetFramebufferAllocationResult { + .framebuffer_id = framebuffer_id, + .framebuffer_status = framebuffer_status, + }); +} + +pp::foundation::Status delete_opengl_framebuffer( + std::uint32_t framebuffer_id, + OpenGlFramebufferDeleteDispatch dispatch) noexcept +{ + if (dispatch.delete_framebuffers == nullptr) { + return pp::foundation::Status::invalid_argument("OpenGL framebuffer delete dispatch callback must not be null"); + } + + if (framebuffer_id == 0U) { + return pp::foundation::Status::success(); + } + + dispatch.delete_framebuffers(1U, &framebuffer_id); + return pp::foundation::Status::success(); +} + pp::foundation::Result allocate_opengl_pixel_buffer( OpenGlPixelBufferAllocationDispatch dispatch) noexcept { diff --git a/src/renderer_gl/opengl_capabilities.h b/src/renderer_gl/opengl_capabilities.h index 93763a3..d44c9c9 100644 --- a/src/renderer_gl/opengl_capabilities.h +++ b/src/renderer_gl/opengl_capabilities.h @@ -172,6 +172,11 @@ struct OpenGlTexture2DReadbackResult { bool pixels_read = false; }; +struct OpenGlRenderTargetFramebufferAllocationResult { + std::uint32_t framebuffer_id = 0; + std::uint32_t framebuffer_status = 0; +}; + struct OpenGlShaderCompileInfo { std::uint32_t shader_id = 0; std::int32_t compile_status = 0; @@ -442,6 +447,7 @@ using OpenGlTexSubImage2DFn = void (*)( std::uint32_t pixel_format, std::uint32_t component_type, const void* data) noexcept; +using OpenGlTexParameterfFn = void (*)(std::uint32_t target, std::uint32_t parameter, float value) noexcept; using OpenGlCopyTexSubImage2DFn = void (*)( std::uint32_t target, std::int32_t level, @@ -607,6 +613,11 @@ struct OpenGlTexture2DUpdateDispatch { OpenGlTexSubImage2DFn tex_sub_image_2d = nullptr; }; +struct OpenGlTexture2DParameterDispatch { + OpenGlBindTextureFn bind_texture = nullptr; + OpenGlTexParameterfFn tex_parameter_f = nullptr; +}; + struct OpenGlTexture2DFramebufferCopyDispatch { OpenGlCopyTexSubImage2DFn copy_tex_sub_image_2d = nullptr; }; @@ -648,6 +659,19 @@ struct OpenGlFramebufferRestoreDispatch { OpenGlBindFramebufferFn bind_framebuffer = nullptr; }; +struct OpenGlRenderTargetFramebufferAllocationDispatch { + OpenGlGenObjectsFn gen_framebuffers = nullptr; + OpenGlGetIntegerFn get_integer = nullptr; + OpenGlBindFramebufferFn bind_framebuffer = nullptr; + OpenGlFramebufferTexture2DFn framebuffer_texture_2d = nullptr; + OpenGlFramebufferRenderbufferFn framebuffer_renderbuffer = nullptr; + OpenGlCheckFramebufferStatusFn check_framebuffer_status = nullptr; +}; + +struct OpenGlFramebufferDeleteDispatch { + OpenGlDeleteObjectsFn delete_framebuffers = nullptr; +}; + struct OpenGlPixelBufferAllocationDispatch { OpenGlGenObjectsFn gen_buffers = nullptr; }; @@ -874,6 +898,10 @@ struct OpenGlMeshDeleteDispatch { [[nodiscard]] pp::foundation::Status update_opengl_texture_2d( OpenGlTexture2DUpdate update, OpenGlTexture2DUpdateDispatch dispatch) noexcept; +[[nodiscard]] pp::foundation::Status set_opengl_texture_2d_parameters( + std::uint32_t texture_id, + std::span parameters, + OpenGlTexture2DParameterDispatch dispatch) noexcept; [[nodiscard]] pp::foundation::Status copy_opengl_framebuffer_to_texture_2d( OpenGlTexture2DFramebufferCopy copy, OpenGlTexture2DFramebufferCopyDispatch dispatch) noexcept; @@ -895,6 +923,13 @@ struct OpenGlMeshDeleteDispatch { [[nodiscard]] pp::foundation::Status restore_opengl_framebuffer_binding( OpenGlFramebufferBindingState binding, OpenGlFramebufferRestoreDispatch dispatch) noexcept; +[[nodiscard]] pp::foundation::Result allocate_opengl_render_target_framebuffer( + std::uint32_t texture_id, + std::uint32_t depth_renderbuffer_id, + OpenGlRenderTargetFramebufferAllocationDispatch dispatch) noexcept; +[[nodiscard]] pp::foundation::Status delete_opengl_framebuffer( + std::uint32_t framebuffer_id, + OpenGlFramebufferDeleteDispatch dispatch) noexcept; [[nodiscard]] pp::foundation::Result allocate_opengl_pixel_buffer( OpenGlPixelBufferAllocationDispatch dispatch) noexcept; [[nodiscard]] pp::foundation::Result readback_opengl_framebuffer_to_pixel_buffer( diff --git a/src/rtt.cpp b/src/rtt.cpp index c6484e1..ac755a7 100644 --- a/src/rtt.cpp +++ b/src/rtt.cpp @@ -34,6 +34,120 @@ void bind_opengl_framebuffer(std::uint32_t target, std::uint32_t framebuffer) no glBindFramebuffer(static_cast(target), static_cast(framebuffer)); } +void gen_opengl_textures(std::uint32_t count, std::uint32_t* ids) noexcept +{ + glGenTextures(static_cast(count), reinterpret_cast(ids)); +} + +void delete_opengl_textures(std::uint32_t count, const std::uint32_t* ids) noexcept +{ + glDeleteTextures(static_cast(count), reinterpret_cast(ids)); +} + +void bind_opengl_texture(std::uint32_t target, std::uint32_t texture) noexcept +{ + glBindTexture(static_cast(target), static_cast(texture)); +} + +void set_opengl_texture_2d_image( + std::uint32_t target, + std::int32_t level, + std::int32_t internal_format, + std::int32_t width, + std::int32_t height, + std::int32_t border, + std::uint32_t pixel_format, + std::uint32_t component_type, + const void* data) noexcept +{ + glTexImage2D( + static_cast(target), + static_cast(level), + static_cast(internal_format), + static_cast(width), + static_cast(height), + static_cast(border), + static_cast(pixel_format), + static_cast(component_type), + data); +} + +void set_opengl_texture_parameter_f(std::uint32_t target, std::uint32_t parameter, float value) noexcept +{ + glTexParameterf(static_cast(target), static_cast(parameter), static_cast(value)); +} + +void gen_opengl_renderbuffers(std::uint32_t count, std::uint32_t* ids) noexcept +{ + glGenRenderbuffers(static_cast(count), reinterpret_cast(ids)); +} + +void delete_opengl_renderbuffers(std::uint32_t count, const std::uint32_t* ids) noexcept +{ + glDeleteRenderbuffers(static_cast(count), reinterpret_cast(ids)); +} + +void bind_opengl_renderbuffer(std::uint32_t target, std::uint32_t renderbuffer) noexcept +{ + glBindRenderbuffer(static_cast(target), static_cast(renderbuffer)); +} + +void set_opengl_renderbuffer_storage( + std::uint32_t target, + std::uint32_t internal_format, + std::int32_t width, + std::int32_t height) noexcept +{ + glRenderbufferStorage( + static_cast(target), + static_cast(internal_format), + static_cast(width), + static_cast(height)); +} + +void gen_opengl_framebuffers(std::uint32_t count, std::uint32_t* ids) noexcept +{ + glGenFramebuffers(static_cast(count), reinterpret_cast(ids)); +} + +void delete_opengl_framebuffers(std::uint32_t count, const std::uint32_t* ids) noexcept +{ + glDeleteFramebuffers(static_cast(count), reinterpret_cast(ids)); +} + +void attach_opengl_framebuffer_texture_2d( + std::uint32_t target, + std::uint32_t attachment, + std::uint32_t texture_target, + std::uint32_t texture, + std::int32_t level) noexcept +{ + glFramebufferTexture2D( + static_cast(target), + static_cast(attachment), + static_cast(texture_target), + static_cast(texture), + static_cast(level)); +} + +void attach_opengl_framebuffer_renderbuffer( + std::uint32_t target, + std::uint32_t attachment, + std::uint32_t renderbuffer_target, + std::uint32_t renderbuffer) noexcept +{ + glFramebufferRenderbuffer( + static_cast(target), + static_cast(attachment), + static_cast(renderbuffer_target), + static_cast(renderbuffer)); +} + +std::uint32_t check_opengl_framebuffer_status(std::uint32_t target) noexcept +{ + return static_cast(glCheckFramebufferStatus(static_cast(target))); +} + void blit_opengl_framebuffer( std::int32_t source_x0, std::int32_t source_y0, @@ -243,18 +357,36 @@ void RTT::destroy() { if (rboID) { - glDeleteRenderbuffers(1, &rboID); + const auto status = pp::renderer::gl::delete_opengl_renderbuffer( + static_cast(rboID), + pp::renderer::gl::OpenGlRenderbufferDeleteDispatch { + .delete_renderbuffers = delete_opengl_renderbuffers, + }); + if (!status.ok()) + LOG("RTT::destroy renderbuffer delete failed because: %s", status.message); } if (texID) { //unbindTexture(); - glDeleteTextures(1, &texID); + const auto status = pp::renderer::gl::delete_opengl_texture_2d( + static_cast(texID), + pp::renderer::gl::OpenGlTexture2DDeleteDispatch { + .delete_textures = delete_opengl_textures, + }); + if (!status.ok()) + LOG("RTT::destroy texture delete failed because: %s", status.message); //LOG("TEX rtt destroy %d", texID) } if (fboID) { //unbindFramebuffer(); - glDeleteFramebuffers(1, &fboID); + const auto status = pp::renderer::gl::delete_opengl_framebuffer( + static_cast(fboID), + pp::renderer::gl::OpenGlFramebufferDeleteDispatch { + .delete_framebuffers = delete_opengl_framebuffers, + }); + if (!status.ok()) + LOG("RTT::destroy framebuffer delete failed because: %s", status.message); //LOG("RTT DESTROY %d", fboID); } }); @@ -354,7 +486,7 @@ bool RTT::create(int width, int height, int tex) bool RTT::create(int width, int height, int tex/* = -1*/, GLint internal_format, bool depth_buffer /*= false*/) { - GLenum status = 0; + std::uint32_t status = 0; App::I->render_task([&] { // Destroy any previously created object @@ -366,7 +498,28 @@ bool RTT::create(int width, int height, int tex/* = -1*/, GLint internal_format, if (tex == -1) { - glGenTextures(1, &texID); + const auto texture = pp::renderer::gl::allocate_opengl_texture_2d( + pp::renderer::gl::OpenGlTexture2DAllocation { + .width = width, + .height = height, + .internal_format = internal_format, + .pixel_format = pp::renderer::gl::rgba_pixel_format(), + .component_type = pp::renderer::gl::texture_upload_type_for_internal_format( + static_cast(internal_format)), + .data = nullptr, + }, + pp::renderer::gl::OpenGlTexture2DAllocationDispatch { + .gen_textures = gen_opengl_textures, + .bind_texture = bind_opengl_texture, + .tex_image_2d = set_opengl_texture_2d_image, + }); + if (!texture.ok()) + { + LOG("RTT::create texture allocation failed because: %s", texture.status().message); + status = 0; + return; + } + texID = static_cast(texture.value()); //LOG("TEX rtt create %d", texID); } else @@ -374,81 +527,72 @@ bool RTT::create(int width, int height, int tex/* = -1*/, GLint internal_format, texID = tex; } - const auto ifmt = static_cast( - pp::renderer::gl::texture_upload_type_for_internal_format(static_cast(internal_format))); - - glBindTexture(texture_2d_target(), texID); - if (tex == -1) - glTexImage2D( - texture_2d_target(), - 0, - internal_format, - width, - height, - 0, - static_cast(pp::renderer::gl::rgba_pixel_format()), - ifmt, - 0); - for (const auto parameter : pp::renderer::gl::default_render_target_texture_parameters()) + const auto parameter_status = pp::renderer::gl::set_opengl_texture_2d_parameters( + static_cast(texID), + pp::renderer::gl::default_render_target_texture_parameters(), + pp::renderer::gl::OpenGlTexture2DParameterDispatch { + .bind_texture = bind_opengl_texture, + .tex_parameter_f = set_opengl_texture_parameter_f, + }); + if (!parameter_status.ok()) { - glTexParameterf( - texture_2d_target(), - static_cast(parameter.name), - static_cast(parameter.value)); + LOG("RTT::create texture parameter setup failed because: %s", parameter_status.message); + status = 0; + return; } - glBindTexture(texture_2d_target(), 0); // Create a renderbuffer object to store depth info if (depth_buffer) { - glGenRenderbuffers(1, &rboID); - glBindRenderbuffer(renderbuffer_target(), rboID); - glRenderbufferStorage( - renderbuffer_target(), - static_cast(pp::renderer::gl::depth_component24_format()), + const auto renderbuffer = pp::renderer::gl::allocate_opengl_depth_renderbuffer( width, - height); - glBindRenderbuffer(renderbuffer_target(), 0); + height, + pp::renderer::gl::OpenGlDepthRenderbufferAllocationDispatch { + .gen_renderbuffers = gen_opengl_renderbuffers, + .bind_renderbuffer = bind_opengl_renderbuffer, + .renderbuffer_storage = set_opengl_renderbuffer_storage, + }); + if (!renderbuffer.ok()) + { + LOG("RTT::create depth renderbuffer allocation failed because: %s", renderbuffer.status().message); + status = 0; + return; + } + rboID = static_cast(renderbuffer.value()); } - GLint oldFboID; - glGetIntegerv(static_cast(pp::renderer::gl::draw_framebuffer_binding_query()), &oldFboID); - // Create a framebuffer object - glGenFramebuffers(1, &fboID); - glBindFramebuffer(framebuffer_target(), fboID); + const auto framebuffer = pp::renderer::gl::allocate_opengl_render_target_framebuffer( + static_cast(texID), + static_cast(rboID), + pp::renderer::gl::OpenGlRenderTargetFramebufferAllocationDispatch { + .gen_framebuffers = gen_opengl_framebuffers, + .get_integer = query_opengl_integer, + .bind_framebuffer = bind_opengl_framebuffer, + .framebuffer_texture_2d = attach_opengl_framebuffer_texture_2d, + .framebuffer_renderbuffer = attach_opengl_framebuffer_renderbuffer, + .check_framebuffer_status = check_opengl_framebuffer_status, + }); + if (!framebuffer.ok()) + { + LOG("RTT::create framebuffer allocation failed because: %s", framebuffer.status().message); + status = 0; + return; + } + fboID = static_cast(framebuffer.value().framebuffer_id); + status = framebuffer.value().framebuffer_status; //LOG("RTT CREATE %d - tex %d", fboID, texID); - // Attach the texture to FBO color attachment point - glFramebufferTexture2D( - framebuffer_target(), - static_cast(pp::renderer::gl::framebuffer_color_attachment()), - texture_2d_target(), - texID, - 0); - if (depth_buffer) - { - // Attach the renderbuffer to depth attachment point - glFramebufferRenderbuffer( - framebuffer_target(), - static_cast(pp::renderer::gl::framebuffer_depth_attachment()), - renderbuffer_target(), - rboID); - } - // Check FBO status - status = glCheckFramebufferStatus(framebuffer_target()); - if (status != static_cast(pp::renderer::gl::framebuffer_complete_status())) + if (status != pp::renderer::gl::framebuffer_complete_status()) LOG("RTT::create failed because: %s", - pp::renderer::gl::framebuffer_status_name(static_cast(status))); + pp::renderer::gl::framebuffer_status_name(status)); - // Switch back to window-system-provided framebuffer - glBindFramebuffer(framebuffer_target(), oldFboID); oldRFboID = 0; oldDFboID = 0; }); - return status == static_cast(pp::renderer::gl::framebuffer_complete_status()); + return status == pp::renderer::gl::framebuffer_complete_status(); } void RTT::bindFramebuffer() diff --git a/tests/renderer_gl/capabilities_tests.cpp b/tests/renderer_gl/capabilities_tests.cpp index b365cdc..f939672 100644 --- a/tests/renderer_gl/capabilities_tests.cpp +++ b/tests/renderer_gl/capabilities_tests.cpp @@ -55,6 +55,12 @@ struct RecordedOpenGlTextureImageCall { bool sub_image = false; }; +struct RecordedOpenGlTextureParameterCall { + std::uint32_t target = 0; + std::uint32_t parameter = 0; + float value = 0.0F; +}; + struct RecordedOpenGlFramebufferTextureCopyCall { std::uint32_t target = 0; std::int32_t level = 0; @@ -204,6 +210,7 @@ std::vector recorded_binding_calls; std::vector recorded_generated_texture_counts; std::vector recorded_deleted_textures; std::vector recorded_texture_image_calls; +std::vector recorded_texture_parameter_calls; std::vector recorded_framebuffer_texture_copy_calls; std::vector recorded_mipmap_targets; std::vector recorded_generated_framebuffer_counts; @@ -545,6 +552,15 @@ void record_tex_sub_image_2d( }); } +void record_tex_parameter_f(std::uint32_t target, std::uint32_t parameter, float value) noexcept +{ + recorded_texture_parameter_calls.push_back(RecordedOpenGlTextureParameterCall { + .target = target, + .parameter = parameter, + .value = value, + }); +} + void record_copy_tex_sub_image_2d( std::uint32_t target, std::int32_t level, @@ -2607,6 +2623,75 @@ void deletes_texture_objects_through_dispatch(pp::tests::Harness& h) PP_EXPECT(h, recorded_deleted_textures[2] == 11U); } +void sets_texture_2d_parameters_through_dispatch(pp::tests::Harness& h) +{ + recorded_binding_calls.clear(); + recorded_texture_parameter_calls.clear(); + + constexpr std::array parameters { + pp::renderer::gl::OpenGlTextureParameter { .name = 0x2800U, .value = 0x2601U }, + pp::renderer::gl::OpenGlTextureParameter { .name = 0x2801U, .value = 0x2600U }, + }; + const auto status = pp::renderer::gl::set_opengl_texture_2d_parameters( + 37U, + parameters, + pp::renderer::gl::OpenGlTexture2DParameterDispatch { + .bind_texture = record_bind_texture, + .tex_parameter_f = record_tex_parameter_f, + }); + + PP_EXPECT(h, status.ok()); + PP_EXPECT(h, recorded_binding_calls.size() == 2U); + PP_EXPECT(h, recorded_binding_calls[0].kind == RecordedOpenGlBindingCall::Kind::bind_texture); + PP_EXPECT(h, recorded_binding_calls[0].first == 0x0DE1U); + PP_EXPECT(h, recorded_binding_calls[0].second == 37U); + PP_EXPECT(h, recorded_binding_calls[1].second == 0U); + PP_EXPECT(h, recorded_texture_parameter_calls.size() == 2U); + PP_EXPECT(h, recorded_texture_parameter_calls[0].target == 0x0DE1U); + PP_EXPECT(h, recorded_texture_parameter_calls[0].parameter == 0x2800U); + PP_EXPECT(h, recorded_texture_parameter_calls[0].value == 0x2601U); + PP_EXPECT(h, recorded_texture_parameter_calls[1].parameter == 0x2801U); + PP_EXPECT(h, recorded_texture_parameter_calls[1].value == 0x2600U); +} + +void rejects_invalid_texture_2d_parameter_dispatch(pp::tests::Harness& h) +{ + constexpr std::array parameters { + pp::renderer::gl::OpenGlTextureParameter { .name = 0x2800U, .value = 0x2601U }, + }; + constexpr std::array invalid_parameters { + pp::renderer::gl::OpenGlTextureParameter { .name = 0U, .value = 0x2601U }, + }; + + const auto missing_dispatch = pp::renderer::gl::set_opengl_texture_2d_parameters( + 37U, + parameters, + pp::renderer::gl::OpenGlTexture2DParameterDispatch { + .bind_texture = record_bind_texture, + }); + const auto invalid_texture = pp::renderer::gl::set_opengl_texture_2d_parameters( + 0U, + parameters, + pp::renderer::gl::OpenGlTexture2DParameterDispatch { + .bind_texture = record_bind_texture, + .tex_parameter_f = record_tex_parameter_f, + }); + const auto invalid_parameter = pp::renderer::gl::set_opengl_texture_2d_parameters( + 37U, + invalid_parameters, + pp::renderer::gl::OpenGlTexture2DParameterDispatch { + .bind_texture = record_bind_texture, + .tex_parameter_f = record_tex_parameter_f, + }); + + PP_EXPECT(h, !missing_dispatch.ok()); + PP_EXPECT(h, missing_dispatch.code == pp::foundation::StatusCode::invalid_argument); + PP_EXPECT(h, !invalid_texture.ok()); + PP_EXPECT(h, invalid_texture.code == pp::foundation::StatusCode::invalid_argument); + PP_EXPECT(h, !invalid_parameter.ok()); + PP_EXPECT(h, invalid_parameter.code == pp::foundation::StatusCode::invalid_argument); +} + void allocates_texture_cube_through_dispatch(pp::tests::Harness& h) { recorded_generated_texture_counts.clear(); @@ -4433,6 +4518,121 @@ void rejects_incomplete_framebuffer_binding_dispatch(pp::tests::Harness& h) PP_EXPECT(h, restore.code == pp::foundation::StatusCode::invalid_argument); } +void allocates_and_deletes_render_target_framebuffer(pp::tests::Harness& h) +{ + recorded_integer_queries.clear(); + recorded_generated_framebuffer_counts.clear(); + recorded_binding_calls.clear(); + recorded_framebuffer_attachment_calls.clear(); + recorded_framebuffer_status_queries.clear(); + recorded_deleted_framebuffers.clear(); + configured_framebuffer_status = 0x8CD5U; + next_framebuffer_id = 44U; + + const auto framebuffer = pp::renderer::gl::allocate_opengl_render_target_framebuffer( + 27U, + 81U, + pp::renderer::gl::OpenGlRenderTargetFramebufferAllocationDispatch { + .gen_framebuffers = record_gen_framebuffers, + .get_integer = record_get_integer, + .bind_framebuffer = record_bind_framebuffer, + .framebuffer_texture_2d = record_framebuffer_texture_2d, + .framebuffer_renderbuffer = record_framebuffer_renderbuffer, + .check_framebuffer_status = record_check_framebuffer_status, + }); + const auto deleted = pp::renderer::gl::delete_opengl_framebuffer( + framebuffer.ok() ? framebuffer.value().framebuffer_id : 0U, + pp::renderer::gl::OpenGlFramebufferDeleteDispatch { + .delete_framebuffers = record_delete_framebuffers, + }); + + PP_EXPECT(h, framebuffer.ok()); + PP_EXPECT(h, framebuffer.value().framebuffer_id == 44U); + PP_EXPECT(h, framebuffer.value().framebuffer_status == 0x8CD5U); + PP_EXPECT(h, deleted.ok()); + PP_EXPECT(h, recorded_integer_queries.size() == 1U); + PP_EXPECT(h, recorded_integer_queries[0] == 0x8CA6U); + PP_EXPECT(h, recorded_generated_framebuffer_counts.size() == 1U); + PP_EXPECT(h, recorded_generated_framebuffer_counts[0] == 1U); + PP_EXPECT(h, recorded_binding_calls.size() == 2U); + PP_EXPECT(h, recorded_binding_calls[0].kind == RecordedOpenGlBindingCall::Kind::bind_framebuffer); + PP_EXPECT(h, recorded_binding_calls[0].first == 0x8D40U); + PP_EXPECT(h, recorded_binding_calls[0].second == 44U); + PP_EXPECT(h, recorded_binding_calls[1].first == 0x8D40U); + PP_EXPECT(h, recorded_binding_calls[1].second == 7U); + PP_EXPECT(h, recorded_framebuffer_attachment_calls.size() == 2U); + PP_EXPECT(h, recorded_framebuffer_attachment_calls[0].target == 0x8D40U); + PP_EXPECT(h, recorded_framebuffer_attachment_calls[0].attachment == 0x8CE0U); + PP_EXPECT(h, recorded_framebuffer_attachment_calls[0].texture_target == 0x0DE1U); + PP_EXPECT(h, recorded_framebuffer_attachment_calls[0].texture == 27U); + PP_EXPECT(h, recorded_framebuffer_attachment_calls[1].attachment == 0x8D00U); + PP_EXPECT(h, recorded_framebuffer_attachment_calls[1].texture_target == 0x8D41U); + PP_EXPECT(h, recorded_framebuffer_attachment_calls[1].texture == 81U); + PP_EXPECT(h, recorded_framebuffer_status_queries.size() == 1U); + PP_EXPECT(h, recorded_framebuffer_status_queries[0] == 0x8D40U); + PP_EXPECT(h, recorded_deleted_framebuffers.size() == 1U); + PP_EXPECT(h, recorded_deleted_framebuffers[0] == 44U); +} + +void reports_incomplete_render_target_framebuffer(pp::tests::Harness& h) +{ + recorded_framebuffer_attachment_calls.clear(); + configured_framebuffer_status = 0x8CD6U; + next_framebuffer_id = 45U; + + const auto framebuffer = pp::renderer::gl::allocate_opengl_render_target_framebuffer( + 27U, + 0U, + pp::renderer::gl::OpenGlRenderTargetFramebufferAllocationDispatch { + .gen_framebuffers = record_gen_framebuffers, + .get_integer = record_get_integer, + .bind_framebuffer = record_bind_framebuffer, + .framebuffer_texture_2d = record_framebuffer_texture_2d, + .framebuffer_renderbuffer = record_framebuffer_renderbuffer, + .check_framebuffer_status = record_check_framebuffer_status, + }); + + PP_EXPECT(h, framebuffer.ok()); + PP_EXPECT(h, framebuffer.value().framebuffer_id == 45U); + PP_EXPECT(h, framebuffer.value().framebuffer_status == 0x8CD6U); + PP_EXPECT(h, recorded_framebuffer_attachment_calls.size() == 1U); + PP_EXPECT(h, recorded_framebuffer_attachment_calls[0].texture == 27U); + configured_framebuffer_status = 0x8CD5U; +} + +void rejects_invalid_render_target_framebuffer_dispatch(pp::tests::Harness& h) +{ + const auto missing_dispatch = pp::renderer::gl::allocate_opengl_render_target_framebuffer( + 27U, + 0U, + pp::renderer::gl::OpenGlRenderTargetFramebufferAllocationDispatch { + .gen_framebuffers = record_gen_framebuffers, + .get_integer = record_get_integer, + .bind_framebuffer = record_bind_framebuffer, + }); + const auto invalid_texture = pp::renderer::gl::allocate_opengl_render_target_framebuffer( + 0U, + 0U, + pp::renderer::gl::OpenGlRenderTargetFramebufferAllocationDispatch { + .gen_framebuffers = record_gen_framebuffers, + .get_integer = record_get_integer, + .bind_framebuffer = record_bind_framebuffer, + .framebuffer_texture_2d = record_framebuffer_texture_2d, + .framebuffer_renderbuffer = record_framebuffer_renderbuffer, + .check_framebuffer_status = record_check_framebuffer_status, + }); + const auto missing_delete_dispatch = pp::renderer::gl::delete_opengl_framebuffer( + 44U, + pp::renderer::gl::OpenGlFramebufferDeleteDispatch {}); + + PP_EXPECT(h, !missing_dispatch.ok()); + PP_EXPECT(h, missing_dispatch.status().code == pp::foundation::StatusCode::invalid_argument); + PP_EXPECT(h, !invalid_texture.ok()); + PP_EXPECT(h, invalid_texture.status().code == pp::foundation::StatusCode::invalid_argument); + PP_EXPECT(h, !missing_delete_dispatch.ok()); + PP_EXPECT(h, missing_delete_dispatch.code == pp::foundation::StatusCode::invalid_argument); +} + void allocates_attaches_and_deletes_depth_renderbuffer(pp::tests::Harness& h) { recorded_generated_renderbuffer_counts.clear(); @@ -4942,6 +5142,8 @@ int main() harness.run("rejects_invalid_texture_2d_allocation", rejects_invalid_texture_2d_allocation); harness.run("deletes_and_binds_texture_2d_through_dispatch", deletes_and_binds_texture_2d_through_dispatch); harness.run("deletes_texture_objects_through_dispatch", deletes_texture_objects_through_dispatch); + harness.run("sets_texture_2d_parameters_through_dispatch", sets_texture_2d_parameters_through_dispatch); + harness.run("rejects_invalid_texture_2d_parameter_dispatch", rejects_invalid_texture_2d_parameter_dispatch); harness.run("allocates_texture_cube_through_dispatch", allocates_texture_cube_through_dispatch); harness.run("rejects_invalid_texture_cube_allocation", rejects_invalid_texture_cube_allocation); harness.run("binds_texture_cube_through_dispatch", binds_texture_cube_through_dispatch); @@ -4984,6 +5186,9 @@ int main() harness.run("binds_framebuffer_draw_read_through_dispatch", binds_framebuffer_draw_read_through_dispatch); harness.run("restores_framebuffer_draw_read_through_dispatch", restores_framebuffer_draw_read_through_dispatch); harness.run("rejects_incomplete_framebuffer_binding_dispatch", rejects_incomplete_framebuffer_binding_dispatch); + harness.run("allocates_and_deletes_render_target_framebuffer", allocates_and_deletes_render_target_framebuffer); + harness.run("reports_incomplete_render_target_framebuffer", reports_incomplete_render_target_framebuffer); + harness.run("rejects_invalid_render_target_framebuffer_dispatch", rejects_invalid_render_target_framebuffer_dispatch); harness.run("allocates_attaches_and_deletes_depth_renderbuffer", allocates_attaches_and_deletes_depth_renderbuffer); harness.run("rejects_invalid_depth_renderbuffer_dispatch", rejects_invalid_depth_renderbuffer_dispatch); harness.run("maps_renderer_viewports_and_scissors", maps_renderer_viewports_and_scissors);