Route framebuffer texture copies through GL backend

This commit is contained in:
2026-06-04 21:12:46 +02:00
parent 15c58bfb21
commit 6440bde002
13 changed files with 285 additions and 46 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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