Route framebuffer texture copies through GL backend
This commit is contained in:
@@ -292,7 +292,11 @@ Known local toolchain state:
|
||||
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
|
||||
canvas pick/history/snapshot and transform history paths. It also
|
||||
canvas pick/history/snapshot and transform history paths. 2D
|
||||
framebuffer-to-texture copies used by retained canvas, transform,
|
||||
layer-conversion, panorama UI, and brush-preview paths now execute through a
|
||||
tested `pp_renderer_gl` dispatch via `copy_framebuffer_to_texture_2d`; the
|
||||
retained cube-map framebuffer copy remains tracked by `DEBT-0036`. It also
|
||||
validates renderer API primitive-topology to OpenGL draw-mode mapping, Shape
|
||||
index-type, fill/stroke primitive-mode, buffer target, static upload usage,
|
||||
and vertex attribute component/normalization mapping used by
|
||||
@@ -414,12 +418,12 @@ Known local toolchain state:
|
||||
Early canvas draw helpers also consume backend-owned pick readback
|
||||
format/type and RTT-backed region-readback execution, stroke mixer
|
||||
depth/scissor/blend state, saved viewport and clear-state queries, active
|
||||
texture units, fallback 2D texture unbind targets, and stroke background copy
|
||||
targets.
|
||||
texture units, fallback 2D texture unbind targets, and stroke background
|
||||
framebuffer-copy dispatch.
|
||||
Canvas stroke commit also consumes backend-owned saved viewport/clear/blend
|
||||
state, history readback format/type and RTT-backed region-readback execution,
|
||||
active texture units, fallback 2D texture unbind targets, and layer
|
||||
compositing copy targets.
|
||||
compositing framebuffer-copy dispatch.
|
||||
Canvas layer merge rendering and explicit layer-merge compositing also consume
|
||||
backend-owned depth/blend state, active texture units, fallback 2D texture
|
||||
unbind targets, and merge framebuffer copy targets.
|
||||
@@ -615,7 +619,8 @@ 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 framebuffer
|
||||
paths, 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,
|
||||
|
||||
@@ -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. Actual live stroke rasterization, dual-brush compositing, pattern feedback math, thumbnail layer compositing, brush-preview compositing, 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, 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.*`, but the bridge still owns legacy recording thread startup/shutdown, platform recorded-file cleanup, progress UI, PBO readback through `App::rec_loop`, 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`; `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, 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 |
|
||||
|
||||
@@ -1005,7 +1005,11 @@ 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. RTT clear color/depth masks, renderer API render-pass
|
||||
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
|
||||
copy is tracked under `DEBT-0036`. RTT clear color/depth masks, renderer API render-pass
|
||||
color/depth/stencil clear-mask and clear-value mapping, and color-write-mask query tokens also
|
||||
live in `pp_renderer_gl`. `RTT` no longer spells GL enum names directly.
|
||||
Renderer API primitive-topology to OpenGL draw-mode mapping, mesh index-type
|
||||
@@ -1979,13 +1983,13 @@ Results:
|
||||
and fallback 2D texture unbinds through the renderer GL backend mapping;
|
||||
platform VR SDK bridges remain isolated for later platform-shell extraction.
|
||||
- Canvas mode overlay, mask, and transform paths now route generic OpenGL
|
||||
blend/depth state, active texture units, 2D copy targets, RGBA8 readback
|
||||
formats, and RTT-backed transform history region readbacks through the
|
||||
renderer GL backend mapping.
|
||||
blend/depth state, active texture units, 2D framebuffer-to-texture copy
|
||||
dispatch, RGBA8 readback formats, and RTT-backed transform history region
|
||||
readbacks through the renderer GL backend mapping.
|
||||
- `NodeCanvas` panorama UI rendering now routes sampler defaults, saved
|
||||
viewport/clear/blend/depth/scissor state, color clears, active texture units,
|
||||
fallback 2D texture unbinds, copy targets, and RGBA8 render-target formats
|
||||
through the renderer GL backend mapping.
|
||||
fallback 2D texture unbinds, 2D framebuffer-to-texture copy dispatch, and
|
||||
RGBA8 render-target formats through the renderer GL backend mapping.
|
||||
- Canvas resource setup now routes stroke-buffer RGBA8/RGBA16F/RGBA32F
|
||||
formats, flood-fill texture upload format/type, brush/stencil/mix sampler
|
||||
filters and wraps, and cube-strip import channel formats through the renderer
|
||||
@@ -1999,7 +2003,9 @@ Results:
|
||||
readbacks, active texture units, fallback 2D texture unbinds, and layer
|
||||
compositing copy targets through the renderer GL backend mapping; the
|
||||
RTT-backed dirty-region readbacks now execute through the retained `RTT`
|
||||
region-readback helper rather than direct `glReadPixels`.
|
||||
region-readback helper rather than direct `glReadPixels`, and 2D framebuffer
|
||||
copies now execute through the retained utility bridge instead of direct
|
||||
`glCopyTexSubImage2D`.
|
||||
- Canvas layer merge rendering and explicit layer-merge compositing now route
|
||||
depth/blend state, active texture units, fallback 2D texture unbinds, and
|
||||
merge framebuffer copy targets through the renderer GL backend mapping.
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "node_progress_bar.h"
|
||||
#include "paint_renderer/compositor.h"
|
||||
#include "renderer_gl/opengl_capabilities.h"
|
||||
#include "util.h"
|
||||
#include <thread>
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
@@ -564,8 +565,7 @@ glm::vec4 Canvas::stroke_draw_samples(
|
||||
glm::ivec2 tex_sz = glm::clamp(glm::ceil(bb_sz) + pad * 2.f, { 0, 0 }, (glm::vec2)(glm::ivec2(m_width, m_height) - tex_pos));
|
||||
if (copy_stroke_destination)
|
||||
{
|
||||
glCopyTexSubImage2D(texture_2d_target(), 0, tex_pos.x, tex_pos.y,
|
||||
tex_pos.x, tex_pos.y, tex_sz.x, tex_sz.y);
|
||||
copy_framebuffer_to_texture_2d(tex_pos.x, tex_pos.y, tex_pos.x, tex_pos.y, tex_sz.x, tex_sz.y);
|
||||
}
|
||||
|
||||
if (P.size() == 4)
|
||||
@@ -835,7 +835,7 @@ void Canvas::stroke_draw()
|
||||
glm::vec2 sz = glm::min(m_size, zw(b) + pad) - o;
|
||||
m_tex[i].bind();
|
||||
if (sz.x > 0 && sz.y > 0)
|
||||
glCopyTexSubImage2D(texture_2d_target(), 0, o.x, o.y, o.x, o.y, sz.x, sz.y);
|
||||
copy_framebuffer_to_texture_2d(o.x, o.y, o.x, o.y, sz.x, sz.y);
|
||||
}
|
||||
m_brush_shape.draw_fill();
|
||||
m_tmp[i].unbindFramebuffer();
|
||||
@@ -1050,7 +1050,7 @@ void Canvas::stroke_commit()
|
||||
// copy to tmp2 for layer blending
|
||||
set_active_texture_unit(0);
|
||||
m_tex2[i].bind();
|
||||
glCopyTexSubImage2D(texture_2d_target(), 0, 0, 0, 0, 0, m_width, m_height);
|
||||
copy_framebuffer_to_texture_2d(0, 0, 0, 0, m_width, m_height);
|
||||
m_tex2[i].unbind();
|
||||
|
||||
m_tmp[i].bindTexture();
|
||||
@@ -1161,7 +1161,7 @@ void Canvas::stroke_commit()
|
||||
ShaderManager::u_int(kShaderUniform::TexBG, 0);
|
||||
set_active_texture_unit(0);
|
||||
m_tex2[i].bind();
|
||||
glCopyTexSubImage2D(texture_2d_target(), 0, 0, 0, 0, 0, m_width, m_height);
|
||||
copy_framebuffer_to_texture_2d(0, 0, 0, 0, m_width, m_height);
|
||||
m_plane.draw_fill();
|
||||
|
||||
m_layers[m_current_layer_idx]->rtt(i).unbindFramebuffer();
|
||||
@@ -1390,7 +1390,7 @@ void Canvas::draw_merge(bool draw_checkerboard, std::array<bool, 6> faces /*= SI
|
||||
{
|
||||
set_active_texture_unit(2);
|
||||
m_merge_tex.bind();
|
||||
glCopyTexSubImage2D(texture_2d_target(), 0, 0, 0, 0, 0, m_width, m_height);
|
||||
copy_framebuffer_to_texture_2d(0, 0, 0, 0, m_width, m_height);
|
||||
}
|
||||
|
||||
m_plane.draw_fill();
|
||||
@@ -1407,7 +1407,7 @@ void Canvas::draw_merge(bool draw_checkerboard, std::array<bool, 6> faces /*= SI
|
||||
|
||||
set_active_texture_unit(2);
|
||||
m_merge_tex.bind();
|
||||
glCopyTexSubImage2D(texture_2d_target(), 0, 0, 0, 0, 0, m_width, m_height);
|
||||
copy_framebuffer_to_texture_2d(0, 0, 0, 0, m_width, m_height);
|
||||
|
||||
// draw the grid behind the layers using a temporary copy
|
||||
if (use_blend)
|
||||
@@ -1593,7 +1593,7 @@ void Canvas::layer_merge(int source_idx, int dest_idx) // m_layer index
|
||||
// copy to tmp2 for layer blending
|
||||
set_active_texture_unit(0);
|
||||
m_tex2[i].bind();
|
||||
glCopyTexSubImage2D(texture_2d_target(), 0, 0, 0, 0, 0, m_width, m_height);
|
||||
copy_framebuffer_to_texture_2d(0, 0, 0, 0, m_width, m_height);
|
||||
m_tex2[i].unbind();
|
||||
|
||||
m_sampler.bind(0);
|
||||
@@ -2920,7 +2920,7 @@ Image Canvas::thumbnail_generate(int w, int h)
|
||||
if (copy_layer_destination)
|
||||
{
|
||||
set_active_texture_unit(2);
|
||||
glCopyTexSubImage2D(texture_2d_target(), 0, 0, 0, 0, 0, w, h);
|
||||
copy_framebuffer_to_texture_2d(0, 0, 0, 0, w, h);
|
||||
}
|
||||
ShaderManager::u_int(kShaderUniform::BlendMode, m_layers[layer_index]->m_blend_mode);
|
||||
ShaderManager::u_float(kShaderUniform::Alpha, m_layers[layer_index]->m_opacity);
|
||||
@@ -2939,7 +2939,7 @@ Image Canvas::thumbnail_generate(int w, int h)
|
||||
set_active_texture_unit(0);
|
||||
blendtex.bind();
|
||||
// copy the content of the fb before drawing the grid
|
||||
glCopyTexSubImage2D(texture_2d_target(), 0, 0, 0, 0, 0, w, h);
|
||||
copy_framebuffer_to_texture_2d(0, 0, 0, 0, w, h);
|
||||
|
||||
// draw the grid
|
||||
ShaderManager::use(kShader::Checkerboard);
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "app.h"
|
||||
#include "renderer_gl/opengl_capabilities.h"
|
||||
#include "rtt.h"
|
||||
#include "util.h"
|
||||
|
||||
uint32_t Layer::s_count = 0;
|
||||
|
||||
@@ -89,7 +90,7 @@ Texture2D Layer::gen_equirect(glm::ivec2 size /*= { 0, 0 }*/)
|
||||
Canvas::I->m_plane.draw_fill();
|
||||
|
||||
ret.bind();
|
||||
glCopyTexSubImage2D(pp::renderer::gl::texture_2d_target(), 0, 0, 0, 0, 0, latlong.getWidth(), latlong.getHeight());
|
||||
copy_framebuffer_to_texture_2d(0, 0, 0, 0, latlong.getWidth(), latlong.getHeight());
|
||||
|
||||
latlong.unbindFramebuffer();
|
||||
|
||||
|
||||
@@ -1252,9 +1252,13 @@ void CanvasModeTransform::enter(kCanvasMode prev)
|
||||
Canvas::I->m_layers[Canvas::I->m_current_layer_idx]->rtt(plane).bindFramebuffer();
|
||||
m_tex[plane].create(bb_sz.x, bb_sz.y);
|
||||
m_tex[plane].bind();
|
||||
glCopyTexSubImage2D(
|
||||
pp::renderer::gl::texture_2d_target(),
|
||||
0, 0, 0, bb_min.x, bb_min.y, bb_sz.x, bb_sz.y);
|
||||
copy_framebuffer_to_texture_2d(
|
||||
0,
|
||||
0,
|
||||
static_cast<int>(bb_min.x),
|
||||
static_cast<int>(bb_min.y),
|
||||
static_cast<int>(bb_sz.x),
|
||||
static_cast<int>(bb_sz.y));
|
||||
m_tex[plane].unbind();
|
||||
Canvas::I->m_layers[Canvas::I->m_current_layer_idx]->rtt(plane).unbindFramebuffer();
|
||||
});
|
||||
@@ -1433,9 +1437,13 @@ void CanvasModeTransform::leave(kCanvasMode next)
|
||||
// copy fb content to texture for blending
|
||||
set_active_texture_unit(0);
|
||||
Canvas::I->m_tex2[i].bind();
|
||||
glCopyTexSubImage2D(
|
||||
pp::renderer::gl::texture_2d_target(),
|
||||
0, bb_min.x, bb_min.y, bb_min.x, bb_min.y, bb_sz.x, bb_sz.y);
|
||||
copy_framebuffer_to_texture_2d(
|
||||
static_cast<int>(bb_min.x),
|
||||
static_cast<int>(bb_min.y),
|
||||
static_cast<int>(bb_min.x),
|
||||
static_cast<int>(bb_min.y),
|
||||
static_cast<int>(bb_sz.x),
|
||||
static_cast<int>(bb_sz.y));
|
||||
// slot for m_tex
|
||||
set_active_texture_unit(1);
|
||||
for (int j = 0; j < 6; j++)
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#include "paint_renderer/compositor.h"
|
||||
#include "settings.h"
|
||||
#include "renderer_gl/opengl_capabilities.h"
|
||||
#include "util.h"
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -523,8 +524,13 @@ void NodeCanvas::draw()
|
||||
{
|
||||
set_active_texture_unit(2);
|
||||
m_blender_bg.bind();
|
||||
glCopyTexSubImage2D(pp::renderer::gl::texture_2d_target(), 0, 0, 0, 0, 0,
|
||||
m_blender_bg.size().x, m_blender_bg.size().y);
|
||||
copy_framebuffer_to_texture_2d(
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
m_blender_bg.size().x,
|
||||
m_blender_bg.size().y);
|
||||
}
|
||||
|
||||
m_face_plane.draw_fill();
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "app.h"
|
||||
#include "paint_renderer/compositor.h"
|
||||
#include "renderer_gl/opengl_capabilities.h"
|
||||
#include "util.h"
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
|
||||
@@ -208,8 +209,7 @@ glm::vec4 NodeStrokePreview::stroke_draw_samples(
|
||||
if (copy_stroke_destination)
|
||||
{
|
||||
// this is also used by the mixer
|
||||
glCopyTexSubImage2D(pp::renderer::gl::texture_2d_target(), 0, tex_pos.x, tex_pos.y,
|
||||
tex_pos.x, tex_pos.y, tex_sz.x, tex_sz.y);
|
||||
copy_framebuffer_to_texture_2d(tex_pos.x, tex_pos.y, tex_pos.x, tex_pos.y, tex_sz.x, tex_sz.y);
|
||||
}
|
||||
|
||||
if (P.size() == 4)
|
||||
@@ -432,9 +432,7 @@ void NodeStrokePreview::draw_stroke_immediate()
|
||||
// copy raw stroke to tex
|
||||
glActiveTexture(pp::renderer::gl::active_texture_unit(1U));
|
||||
m_tex_dual.bind();
|
||||
glCopyTexSubImage2D(
|
||||
pp::renderer::gl::texture_2d_target(),
|
||||
0,
|
||||
copy_framebuffer_to_texture_2d(
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
@@ -453,9 +451,7 @@ void NodeStrokePreview::draw_stroke_immediate()
|
||||
m_plane.draw_fill();
|
||||
//m_rtt.clear({ .3f, .3f, .3f, 1.f });
|
||||
m_tex_background.bind();
|
||||
glCopyTexSubImage2D(
|
||||
pp::renderer::gl::texture_2d_target(),
|
||||
0,
|
||||
copy_framebuffer_to_texture_2d(
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
@@ -508,9 +504,7 @@ void NodeStrokePreview::draw_stroke_immediate()
|
||||
// copy raw stroke to tex
|
||||
glActiveTexture(pp::renderer::gl::active_texture_unit(1U));
|
||||
m_tex.bind();
|
||||
glCopyTexSubImage2D(
|
||||
pp::renderer::gl::texture_2d_target(),
|
||||
0,
|
||||
copy_framebuffer_to_texture_2d(
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
@@ -564,9 +558,7 @@ void NodeStrokePreview::draw_stroke_immediate()
|
||||
|
||||
// copy the result to the actual preview
|
||||
m_tex_preview.bind();
|
||||
glCopyTexSubImage2D(
|
||||
pp::renderer::gl::texture_2d_target(),
|
||||
0,
|
||||
copy_framebuffer_to_texture_2d(
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
|
||||
@@ -748,6 +748,36 @@ pp::foundation::Status update_opengl_texture_2d(
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
pp::foundation::Status copy_opengl_framebuffer_to_texture_2d(
|
||||
OpenGlTexture2DFramebufferCopy copy,
|
||||
OpenGlTexture2DFramebufferCopyDispatch dispatch) noexcept
|
||||
{
|
||||
if (dispatch.copy_tex_sub_image_2d == nullptr) {
|
||||
return pp::foundation::Status::invalid_argument(
|
||||
"OpenGL framebuffer-to-texture copy dispatch callback must not be null");
|
||||
}
|
||||
|
||||
if (copy.width < 0 || copy.height < 0) {
|
||||
return pp::foundation::Status::invalid_argument(
|
||||
"OpenGL framebuffer-to-texture copy dimensions are invalid");
|
||||
}
|
||||
|
||||
if (copy.width == 0 || copy.height == 0) {
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
dispatch.copy_tex_sub_image_2d(
|
||||
texture_2d_target(),
|
||||
copy.level,
|
||||
copy.destination_x,
|
||||
copy.destination_y,
|
||||
copy.source_x,
|
||||
copy.source_y,
|
||||
copy.width,
|
||||
copy.height);
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
pp::foundation::Status generate_opengl_texture_2d_mipmaps(
|
||||
std::uint32_t texture_id,
|
||||
OpenGlTexture2DMipmapDispatch dispatch) noexcept
|
||||
|
||||
@@ -149,6 +149,16 @@ struct OpenGlTexture2DUpdate {
|
||||
const void* data = nullptr;
|
||||
};
|
||||
|
||||
struct OpenGlTexture2DFramebufferCopy {
|
||||
std::int32_t level = 0;
|
||||
std::int32_t destination_x = 0;
|
||||
std::int32_t destination_y = 0;
|
||||
std::int32_t source_x = 0;
|
||||
std::int32_t source_y = 0;
|
||||
std::int32_t width = 0;
|
||||
std::int32_t height = 0;
|
||||
};
|
||||
|
||||
struct OpenGlTexture2DReadback {
|
||||
std::uint32_t texture_id = 0;
|
||||
std::int32_t width = 0;
|
||||
@@ -426,6 +436,15 @@ using OpenGlTexSubImage2DFn = void (*)(
|
||||
std::uint32_t pixel_format,
|
||||
std::uint32_t component_type,
|
||||
const void* data) noexcept;
|
||||
using OpenGlCopyTexSubImage2DFn = void (*)(
|
||||
std::uint32_t target,
|
||||
std::int32_t level,
|
||||
std::int32_t destination_x,
|
||||
std::int32_t destination_y,
|
||||
std::int32_t source_x,
|
||||
std::int32_t source_y,
|
||||
std::int32_t width,
|
||||
std::int32_t height) noexcept;
|
||||
using OpenGlGenerateMipmapFn = void (*)(std::uint32_t target) noexcept;
|
||||
using OpenGlFramebufferTexture2DFn = void (*)(
|
||||
std::uint32_t target,
|
||||
@@ -582,6 +601,10 @@ struct OpenGlTexture2DUpdateDispatch {
|
||||
OpenGlTexSubImage2DFn tex_sub_image_2d = nullptr;
|
||||
};
|
||||
|
||||
struct OpenGlTexture2DFramebufferCopyDispatch {
|
||||
OpenGlCopyTexSubImage2DFn copy_tex_sub_image_2d = nullptr;
|
||||
};
|
||||
|
||||
struct OpenGlTexture2DMipmapDispatch {
|
||||
OpenGlBindTextureFn bind_texture = nullptr;
|
||||
OpenGlGenerateMipmapFn generate_mipmap = nullptr;
|
||||
@@ -820,6 +843,9 @@ struct OpenGlMeshDeleteDispatch {
|
||||
[[nodiscard]] pp::foundation::Status update_opengl_texture_2d(
|
||||
OpenGlTexture2DUpdate update,
|
||||
OpenGlTexture2DUpdateDispatch dispatch) noexcept;
|
||||
[[nodiscard]] pp::foundation::Status copy_opengl_framebuffer_to_texture_2d(
|
||||
OpenGlTexture2DFramebufferCopy copy,
|
||||
OpenGlTexture2DFramebufferCopyDispatch dispatch) noexcept;
|
||||
[[nodiscard]] pp::foundation::Status generate_opengl_texture_2d_mipmaps(
|
||||
std::uint32_t texture_id,
|
||||
OpenGlTexture2DMipmapDispatch dispatch) noexcept;
|
||||
|
||||
50
src/util.cpp
50
src/util.cpp
@@ -67,6 +67,27 @@ void bind_opengl_sampler(std::uint32_t unit, std::uint32_t sampler) noexcept
|
||||
glBindSampler(static_cast<GLuint>(unit), static_cast<GLuint>(sampler));
|
||||
}
|
||||
|
||||
void copy_opengl_tex_sub_image_2d(
|
||||
std::uint32_t target,
|
||||
std::int32_t level,
|
||||
std::int32_t destination_x,
|
||||
std::int32_t destination_y,
|
||||
std::int32_t source_x,
|
||||
std::int32_t source_y,
|
||||
std::int32_t width,
|
||||
std::int32_t height) noexcept
|
||||
{
|
||||
glCopyTexSubImage2D(
|
||||
static_cast<GLenum>(target),
|
||||
static_cast<GLint>(level),
|
||||
static_cast<GLint>(destination_x),
|
||||
static_cast<GLint>(destination_y),
|
||||
static_cast<GLint>(source_x),
|
||||
static_cast<GLint>(source_y),
|
||||
static_cast<GLsizei>(width),
|
||||
static_cast<GLsizei>(height));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
template<>
|
||||
@@ -719,6 +740,35 @@ void check_OpenGLError(const char* stmt, const char* fname, int line)
|
||||
}
|
||||
}
|
||||
|
||||
bool copy_framebuffer_to_texture_2d(
|
||||
int destination_x,
|
||||
int destination_y,
|
||||
int source_x,
|
||||
int source_y,
|
||||
int width,
|
||||
int height,
|
||||
int level) noexcept
|
||||
{
|
||||
const auto status = pp::renderer::gl::copy_opengl_framebuffer_to_texture_2d(
|
||||
pp::renderer::gl::OpenGlTexture2DFramebufferCopy {
|
||||
.level = level,
|
||||
.destination_x = destination_x,
|
||||
.destination_y = destination_y,
|
||||
.source_x = source_x,
|
||||
.source_y = source_y,
|
||||
.width = width,
|
||||
.height = height,
|
||||
},
|
||||
pp::renderer::gl::OpenGlTexture2DFramebufferCopyDispatch {
|
||||
.copy_tex_sub_image_2d = copy_opengl_tex_sub_image_2d,
|
||||
});
|
||||
if (!status.ok()) {
|
||||
LOG("OpenGL framebuffer-to-texture copy failed: %s", status.message);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t curl_data_handler(void *contents, size_t size, size_t nmemb, void *userp)
|
||||
{
|
||||
auto buffer = reinterpret_cast<std::string*>(userp);
|
||||
|
||||
@@ -201,6 +201,14 @@ std::string str_replace(const std::string& string, const std::string& search, co
|
||||
size_t curl_data_handler(void *contents, size_t size, size_t nmemb, void *userp);
|
||||
size_t curl_data_write(void *ptr, size_t size, size_t nmemb, FILE *stream);
|
||||
void check_OpenGLError(const char* stmt, const char* fname, int line);
|
||||
bool copy_framebuffer_to_texture_2d(
|
||||
int destination_x,
|
||||
int destination_y,
|
||||
int source_x,
|
||||
int source_y,
|
||||
int width,
|
||||
int height,
|
||||
int level = 0) noexcept;
|
||||
inline glm::vec2 xy(const glm::vec4& v) { return glm::vec2(v.x, v.y); }
|
||||
inline glm::vec3 xyz(const glm::vec4& v) { return glm::vec3(v.x, v.y, v.z); }
|
||||
inline glm::vec2 zw(const glm::vec4& v) { return glm::vec2(v.z, v.w); }
|
||||
|
||||
@@ -55,6 +55,17 @@ struct RecordedOpenGlTextureImageCall {
|
||||
bool sub_image = false;
|
||||
};
|
||||
|
||||
struct RecordedOpenGlFramebufferTextureCopyCall {
|
||||
std::uint32_t target = 0;
|
||||
std::int32_t level = 0;
|
||||
std::int32_t destination_x = 0;
|
||||
std::int32_t destination_y = 0;
|
||||
std::int32_t source_x = 0;
|
||||
std::int32_t source_y = 0;
|
||||
std::int32_t width = 0;
|
||||
std::int32_t height = 0;
|
||||
};
|
||||
|
||||
struct RecordedOpenGlFramebufferAttachmentCall {
|
||||
std::uint32_t target = 0;
|
||||
std::uint32_t attachment = 0;
|
||||
@@ -186,6 +197,7 @@ std::vector<RecordedOpenGlBindingCall> recorded_binding_calls;
|
||||
std::vector<std::uint32_t> recorded_generated_texture_counts;
|
||||
std::vector<std::uint32_t> recorded_deleted_textures;
|
||||
std::vector<RecordedOpenGlTextureImageCall> recorded_texture_image_calls;
|
||||
std::vector<RecordedOpenGlFramebufferTextureCopyCall> recorded_framebuffer_texture_copy_calls;
|
||||
std::vector<std::uint32_t> recorded_mipmap_targets;
|
||||
std::vector<std::uint32_t> recorded_generated_framebuffer_counts;
|
||||
std::vector<std::uint32_t> recorded_deleted_framebuffers;
|
||||
@@ -524,6 +536,28 @@ void record_tex_sub_image_2d(
|
||||
});
|
||||
}
|
||||
|
||||
void record_copy_tex_sub_image_2d(
|
||||
std::uint32_t target,
|
||||
std::int32_t level,
|
||||
std::int32_t destination_x,
|
||||
std::int32_t destination_y,
|
||||
std::int32_t source_x,
|
||||
std::int32_t source_y,
|
||||
std::int32_t width,
|
||||
std::int32_t height) noexcept
|
||||
{
|
||||
recorded_framebuffer_texture_copy_calls.push_back(RecordedOpenGlFramebufferTextureCopyCall {
|
||||
.target = target,
|
||||
.level = level,
|
||||
.destination_x = destination_x,
|
||||
.destination_y = destination_y,
|
||||
.source_x = source_x,
|
||||
.source_y = source_y,
|
||||
.width = width,
|
||||
.height = height,
|
||||
});
|
||||
}
|
||||
|
||||
void record_generate_mipmap(std::uint32_t target) noexcept
|
||||
{
|
||||
recorded_mipmap_targets.push_back(target);
|
||||
@@ -3775,6 +3809,76 @@ void updates_texture_2d_through_dispatch(pp::tests::Harness& h)
|
||||
PP_EXPECT(h, recorded_texture_image_calls[0].data == pixels.data());
|
||||
}
|
||||
|
||||
void copies_framebuffer_to_texture_2d_through_dispatch(pp::tests::Harness& h)
|
||||
{
|
||||
recorded_framebuffer_texture_copy_calls.clear();
|
||||
|
||||
const auto status = pp::renderer::gl::copy_opengl_framebuffer_to_texture_2d(
|
||||
pp::renderer::gl::OpenGlTexture2DFramebufferCopy {
|
||||
.level = 1,
|
||||
.destination_x = 2,
|
||||
.destination_y = 3,
|
||||
.source_x = 4,
|
||||
.source_y = 5,
|
||||
.width = 6,
|
||||
.height = 7,
|
||||
},
|
||||
pp::renderer::gl::OpenGlTexture2DFramebufferCopyDispatch {
|
||||
.copy_tex_sub_image_2d = record_copy_tex_sub_image_2d,
|
||||
});
|
||||
|
||||
PP_EXPECT(h, status.ok());
|
||||
PP_EXPECT(h, recorded_framebuffer_texture_copy_calls.size() == 1U);
|
||||
PP_EXPECT(h, recorded_framebuffer_texture_copy_calls[0].target == 0x0DE1U);
|
||||
PP_EXPECT(h, recorded_framebuffer_texture_copy_calls[0].level == 1);
|
||||
PP_EXPECT(h, recorded_framebuffer_texture_copy_calls[0].destination_x == 2);
|
||||
PP_EXPECT(h, recorded_framebuffer_texture_copy_calls[0].destination_y == 3);
|
||||
PP_EXPECT(h, recorded_framebuffer_texture_copy_calls[0].source_x == 4);
|
||||
PP_EXPECT(h, recorded_framebuffer_texture_copy_calls[0].source_y == 5);
|
||||
PP_EXPECT(h, recorded_framebuffer_texture_copy_calls[0].width == 6);
|
||||
PP_EXPECT(h, recorded_framebuffer_texture_copy_calls[0].height == 7);
|
||||
}
|
||||
|
||||
void skips_zero_sized_framebuffer_to_texture_copies(pp::tests::Harness& h)
|
||||
{
|
||||
recorded_framebuffer_texture_copy_calls.clear();
|
||||
|
||||
const auto status = pp::renderer::gl::copy_opengl_framebuffer_to_texture_2d(
|
||||
pp::renderer::gl::OpenGlTexture2DFramebufferCopy {
|
||||
.width = 0,
|
||||
.height = 5,
|
||||
},
|
||||
pp::renderer::gl::OpenGlTexture2DFramebufferCopyDispatch {
|
||||
.copy_tex_sub_image_2d = record_copy_tex_sub_image_2d,
|
||||
});
|
||||
|
||||
PP_EXPECT(h, status.ok());
|
||||
PP_EXPECT(h, recorded_framebuffer_texture_copy_calls.empty());
|
||||
}
|
||||
|
||||
void rejects_invalid_framebuffer_to_texture_copies(pp::tests::Harness& h)
|
||||
{
|
||||
const auto missing_dispatch = pp::renderer::gl::copy_opengl_framebuffer_to_texture_2d(
|
||||
pp::renderer::gl::OpenGlTexture2DFramebufferCopy {
|
||||
.width = 1,
|
||||
.height = 1,
|
||||
},
|
||||
pp::renderer::gl::OpenGlTexture2DFramebufferCopyDispatch {});
|
||||
const auto invalid_dimensions = pp::renderer::gl::copy_opengl_framebuffer_to_texture_2d(
|
||||
pp::renderer::gl::OpenGlTexture2DFramebufferCopy {
|
||||
.width = -1,
|
||||
.height = 1,
|
||||
},
|
||||
pp::renderer::gl::OpenGlTexture2DFramebufferCopyDispatch {
|
||||
.copy_tex_sub_image_2d = record_copy_tex_sub_image_2d,
|
||||
});
|
||||
|
||||
PP_EXPECT(h, !missing_dispatch.ok());
|
||||
PP_EXPECT(h, missing_dispatch.code == pp::foundation::StatusCode::invalid_argument);
|
||||
PP_EXPECT(h, !invalid_dimensions.ok());
|
||||
PP_EXPECT(h, invalid_dimensions.code == pp::foundation::StatusCode::invalid_argument);
|
||||
}
|
||||
|
||||
void generates_texture_2d_mipmaps_through_dispatch(pp::tests::Harness& h)
|
||||
{
|
||||
recorded_binding_calls.clear();
|
||||
@@ -4656,6 +4760,9 @@ int main()
|
||||
harness.run("updates_draws_and_deletes_mesh_through_dispatch", updates_draws_and_deletes_mesh_through_dispatch);
|
||||
harness.run("rejects_invalid_mesh_dispatch", rejects_invalid_mesh_dispatch);
|
||||
harness.run("updates_texture_2d_through_dispatch", updates_texture_2d_through_dispatch);
|
||||
harness.run("copies_framebuffer_to_texture_2d_through_dispatch", copies_framebuffer_to_texture_2d_through_dispatch);
|
||||
harness.run("skips_zero_sized_framebuffer_to_texture_copies", skips_zero_sized_framebuffer_to_texture_copies);
|
||||
harness.run("rejects_invalid_framebuffer_to_texture_copies", rejects_invalid_framebuffer_to_texture_copies);
|
||||
harness.run("generates_texture_2d_mipmaps_through_dispatch", generates_texture_2d_mipmaps_through_dispatch);
|
||||
harness.run("reads_back_texture_2d_through_framebuffer_dispatch", reads_back_texture_2d_through_framebuffer_dispatch);
|
||||
harness.run("reports_incomplete_texture_2d_readback_framebuffer", reports_incomplete_texture_2d_readback_framebuffer);
|
||||
|
||||
Reference in New Issue
Block a user