#include "pch.h" #include "log.h" #include "rtt.h" #include "util.h" #include "app.h" #include "legacy_gl_framebuffer_dispatch.h" #include "legacy_gl_pixel_buffer_dispatch.h" #include "legacy_gl_renderbuffer_dispatch.h" #include "legacy_gl_texture_dispatch.h" #include "legacy_ui_gl_dispatch.h" #include "renderer_gl/opengl_capabilities.h" #include RTT& RTT::operator=(RTT&& other) { int_fmt = other.int_fmt; texID = other.texID; fboID = other.fboID; rboID = other.rboID; w = other.w; h = other.h; bound = other.bound; other.int_fmt = 0; other.texID = 0; other.fboID = 0; other.rboID = 0; other.w = 0; other.h = 0; other.bound = false; return *this; } RTT::RTT() { int_fmt = 0; fboID = 0; rboID = 0; w = 0; h = 0; bound = false; } RTT::RTT(RTT&& other) { int_fmt = other.int_fmt; texID = other.texID; fboID = other.fboID; rboID = other.rboID; w = other.w; h = other.h; bound = other.bound; other.int_fmt = 0; other.texID = 0; other.fboID = 0; other.rboID = 0; other.w = 0; other.h = 0; other.bound = false; } RTT::~RTT() { #ifdef _DEBUG if (valid()) LOG("RTT not destroyed"); #endif // _DEBUG destroy(); } bool RTT::resize(int width, int height) { bool ret = false; RTT new_rtt; App::I->render_task([&] { ret = new_rtt.create(width, height, -1, int_fmt, rboID != 0); if (!ret) { LOG("failed to resize rtt from %dx%d to %dx%d", w, h, width, height); return; } const auto status = pp::renderer::gl::blit_opengl_framebuffer( pp::renderer::gl::OpenGlFramebufferBlit { .source_framebuffer = static_cast(fboID), .destination_framebuffer = static_cast(new_rtt.fboID), .source_rect = pp::renderer::gl::OpenGlFramebufferRect { .x1 = w, .y1 = h }, .destination_rect = pp::renderer::gl::OpenGlFramebufferRect { .x1 = new_rtt.w, .y1 = new_rtt.h, }, .mask = pp::renderer::gl::framebuffer_color_buffer_mask(), .filter = pp::renderer::gl::framebuffer_blit_filter(true), }, pp::legacy::gl_framebuffer::framebuffer_blit_dispatch()); if (!status.ok()) { LOG("RTT::resize blit failed because: %s", status.message); ret = false; return; } destroy(); }); if (!ret) return false; oldRFboID = 0; oldDFboID = 0; *this = std::move(new_rtt); return true; } void RTT::destroy() { if (!valid()) return; App::I->render_task_async([rboID=rboID, texID=texID, fboID=fboID] { if (rboID) { pp::legacy::gl_renderbuffer::delete_renderbuffer( static_cast(rboID), "RTT::destroy renderbuffer delete"); } if (texID) { //unbindTexture(); pp::legacy::gl_texture::delete_texture_2d( static_cast(texID), "RTT::destroy texture delete"); //LOG("TEX rtt destroy %d", texID) } if (fboID) { //unbindFramebuffer(); const auto status = pp::renderer::gl::delete_opengl_framebuffer( static_cast(fboID), pp::legacy::gl_framebuffer::framebuffer_delete_dispatch()); if (!status.ok()) LOG("RTT::destroy framebuffer delete failed because: %s", status.message); //LOG("RTT DESTROY %d", fboID); } }); texID = 0; fboID = 0; rboID = 0; // w = 0; // h = 0; } void RTT::copy(const RTT & source) { if (!valid() || !source.valid()) return; App::I->render_task([&] { const auto status = pp::renderer::gl::blit_opengl_framebuffer( pp::renderer::gl::OpenGlFramebufferBlit { .source_framebuffer = static_cast(source.fboID), .destination_framebuffer = static_cast(fboID), .source_rect = pp::renderer::gl::OpenGlFramebufferRect { .x1 = source.w, .y1 = source.h }, .destination_rect = pp::renderer::gl::OpenGlFramebufferRect { .x1 = w, .y1 = h }, .mask = pp::renderer::gl::framebuffer_color_buffer_mask(), .filter = pp::renderer::gl::framebuffer_blit_filter(true), }, pp::legacy::gl_framebuffer::framebuffer_blit_dispatch()); if (!status.ok()) LOG("RTT::copy blit failed because: %s", status.message); }); } void RTT::copy(const RTT& source, const glm::vec4& rect) { if (!valid() || !source.valid()) return; App::I->render_task([&] { auto r = rect_intersection(rect, { 0, 0, w, h }); const auto status = pp::renderer::gl::blit_opengl_framebuffer( pp::renderer::gl::OpenGlFramebufferBlit { .source_framebuffer = static_cast(source.fboID), .destination_framebuffer = static_cast(fboID), .source_rect = pp::renderer::gl::OpenGlFramebufferRect { .x0 = static_cast(r.x), .y0 = static_cast(r.y), .x1 = static_cast(r.z), .y1 = static_cast(r.w), }, .destination_rect = pp::renderer::gl::OpenGlFramebufferRect { .x0 = static_cast(r.x), .y0 = static_cast(r.y), .x1 = static_cast(r.z), .y1 = static_cast(r.w), }, .mask = pp::renderer::gl::framebuffer_color_buffer_mask(), .filter = pp::renderer::gl::framebuffer_blit_filter(false), }, pp::legacy::gl_framebuffer::framebuffer_blit_dispatch()); if (!status.ok()) LOG("RTT::copy region blit failed because: %s", status.message); }); } RTT RTT::clone() const noexcept { RTT dup; dup.create(w, h); dup.copy(*this); return dup; } bool RTT::create(int width, int height) { return create( width, height, -1, static_cast(pp::renderer::gl::rgba8_internal_format())); } bool RTT::create(int width, int height, int tex) { return create( width, height, tex, static_cast(pp::renderer::gl::rgba8_internal_format())); } bool RTT::create(int width, int height, int tex/* = -1*/, GLint internal_format, bool depth_buffer /*= false*/) { std::uint32_t status = 0; App::I->render_task([&] { // Destroy any previously created object destroy(); w = width; h = height; int_fmt = internal_format; if (tex == -1) { const auto texture = pp::legacy::gl_texture::allocate_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, }, "RTT::create texture allocation"); if (texture == 0U) { status = 0; return; } texID = static_cast(texture); //LOG("TEX rtt create %d", texID); } else { texID = tex; } if (!pp::legacy::gl_texture::set_texture_2d_parameters( static_cast(texID), pp::renderer::gl::default_render_target_texture_parameters(), "RTT::create texture parameter setup")) { status = 0; return; } // Create a renderbuffer object to store depth info if (depth_buffer) { const auto renderbuffer = pp::legacy::gl_renderbuffer::allocate_depth_renderbuffer( width, height, "RTT::create depth renderbuffer allocation"); if (renderbuffer == 0U) { status = 0; return; } rboID = static_cast(renderbuffer); } // Create a framebuffer object const auto framebuffer = pp::renderer::gl::allocate_opengl_render_target_framebuffer( static_cast(texID), static_cast(rboID), pp::legacy::gl_framebuffer::render_target_allocation_dispatch( pp::legacy::gl_renderbuffer::attach_opengl_framebuffer_renderbuffer)); 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); // Check FBO status if (status != pp::renderer::gl::framebuffer_complete_status()) LOG("RTT::create failed because: %s", pp::renderer::gl::framebuffer_status_name(status)); oldRFboID = 0; oldDFboID = 0; }); return status == pp::renderer::gl::framebuffer_complete_status(); } void RTT::bindFramebuffer() { assert(App::I->is_render_thread()); #ifdef _DEBUG if (bound) { LOG("framebuffer bound twice!"); #ifdef _WIN32 __debugbreak(); #endif } #endif // _DEBUG const auto binding = pp::renderer::gl::bind_opengl_framebuffer_for_draw_read( static_cast(fboID), pp::legacy::gl_framebuffer::framebuffer_bind_dispatch()); if (!binding.ok()) { LOG("RTT::bindFramebuffer() failed because: %s", binding.status().message); return; } oldDFboID = static_cast(binding.value().draw_framebuffer); oldRFboID = static_cast(binding.value().read_framebuffer); bound = true; } void RTT::unbindFramebuffer() { assert(App::I->is_render_thread()); if (!bound) return; const auto status = pp::renderer::gl::restore_opengl_framebuffer_binding( pp::renderer::gl::OpenGlFramebufferBindingState { .draw_framebuffer = oldDFboID, .read_framebuffer = oldRFboID, }, pp::legacy::gl_framebuffer::framebuffer_restore_dispatch()); if (!status.ok()) { LOG("RTT::unbindFramebuffer() failed because: %s", status.message); return; } oldRFboID = 0; oldDFboID = 0; bound = false; } void RTT::clear(glm::vec4 color) { assert(App::I->is_render_thread()); const auto status = pp::renderer::gl::clear_opengl_render_target( pp::renderer::gl::OpenGlDefaultClear { .color = { color.r, color.g, color.b, color.a }, .mask = pp::renderer::gl::framebuffer_color_buffer_mask() | pp::renderer::gl::framebuffer_depth_buffer_mask(), }, pp::renderer::gl::OpenGlClearDispatch { .clear_color = pp::legacy::ui_gl::set_opengl_clear_color, .clear = pp::legacy::ui_gl::clear_opengl_buffer, }); if (!status.ok()) LOG("RTT::clear() failed because: %s", status.message); } void RTT::clear_mask(glm::bool4 mask, glm::vec4 color) { assert(App::I->is_render_thread()); const auto status = pp::renderer::gl::clear_opengl_color_buffer_with_write_mask( pp::renderer::gl::OpenGlColorMaskedClear { .mask = { .r = static_cast(mask.r), .g = static_cast(mask.g), .b = static_cast(mask.b), .a = static_cast(mask.a), }, .color = { color.r, color.g, color.b, color.a }, }, pp::renderer::gl::OpenGlColorMaskedClearDispatch { .get_boolean = pp::legacy::ui_gl::get_opengl_boolean, .color_mask = pp::legacy::ui_gl::set_opengl_color_mask, .clear_color = pp::legacy::ui_gl::set_opengl_clear_color, .clear = pp::legacy::ui_gl::clear_opengl_buffer, }); if (!status.ok()) LOG("RTT::clear_mask() failed because: %s", status.message); } glm::ivec4 RTT::calc_bounds() const noexcept { if (!valid()) return glm::vec4(0); auto data = std::unique_ptr(reinterpret_cast(readTextureData())); glm::ivec2 bbmin(w, h); glm::ivec2 bbmax(0); for (int y = 0; y < h; y++) { for (int x = 0; x < w; x++) { if (data[x + y * w].a > 0) { bbmin = glm::min(bbmin, { x, y }); bbmax = glm::max(bbmax, { x + 1, y + 1 }); } } } return { bbmin, bbmax }; } bool RTT::readPixelsRgba8(int x, int y, int width, int height, void* buffer) const noexcept { if (!valid() || buffer == nullptr) return false; bool ret = true; App::I->render_task([&] { const auto readback = pp::renderer::gl::rgba8_readback_format(); const auto status = pp::renderer::gl::readback_opengl_framebuffer( pp::renderer::gl::OpenGlFramebufferReadback { .framebuffer = static_cast(fboID), .x = x, .y = y, .width = width, .height = height, .format = readback, .pixels = buffer, }, pp::legacy::gl_framebuffer::framebuffer_readback_dispatch()); if (!status.ok()) { LOG("RTT::readPixelsRgba8() failed because: %s", status.message); ret = false; } }); return ret; } bool RTT::updateRgba8(int x, int y, int width, int height, const void* data) noexcept { if (!valid() || data == nullptr) return false; if (!pp::legacy::gl_texture::update_texture_2d( pp::renderer::gl::OpenGlTexture2DUpdate { .texture_id = texID, .x = x, .y = y, .width = width, .height = height, .pixel_format = pp::renderer::gl::rgba_pixel_format(), .component_type = pp::renderer::gl::unsigned_byte_component_type(), .data = data, }, "RTT::updateRgba8()")) { return false; } unbindTexture(); return true; } uint8_t* RTT::readTextureData(uint8_t* buffer) const noexcept { if (!valid()) return nullptr; if (!buffer) buffer = createBuffer(); App::I->render_task([&] { const auto readback = pp::renderer::gl::rgba8_readback_format(); const auto status = pp::renderer::gl::readback_opengl_framebuffer( pp::renderer::gl::OpenGlFramebufferReadback { .framebuffer = static_cast(fboID), .width = w, .height = h, .format = readback, .pixels = buffer, }, pp::legacy::gl_framebuffer::framebuffer_readback_dispatch()); if (!status.ok()) LOG("RTT::readTextureData() failed because: %s", status.message); }); return buffer; } float* RTT::readTextureDataFloat(float* buffer) const noexcept { if (!valid()) return nullptr; if (!buffer) buffer = createBufferFloat(); App::I->render_task([&] { const auto readback = pp::renderer::gl::rgba32f_readback_format(); const auto status = pp::renderer::gl::readback_opengl_framebuffer( pp::renderer::gl::OpenGlFramebufferReadback { .framebuffer = static_cast(fboID), .width = w, .height = h, .format = readback, .pixels = buffer, }, pp::legacy::gl_framebuffer::framebuffer_readback_dispatch()); if (!status.ok()) LOG("RTT::readTextureDataFloat() failed because: %s", status.message); }); return buffer; } uint8_t* RTT::createBuffer() const noexcept { const auto readback = pp::renderer::gl::rgba8_readback_format(); return new uint8_t[pp::renderer::gl::readback_byte_count( readback, static_cast(w), static_cast(h))]; } float * RTT::createBufferFloat() const noexcept { const auto readback = pp::renderer::gl::rgba32f_readback_format(); return new float[pp::renderer::gl::readback_byte_count( readback, static_cast(w), static_cast(h)) / sizeof(float)]; } void RTT::bindTexture() { assert(App::I->is_render_thread()); pp::legacy::gl_texture::bind_texture_2d( static_cast(texID), "RTT::bindTexture()"); } void RTT::unbindTexture() { assert(App::I->is_render_thread()); pp::legacy::gl_texture::bind_texture_2d( 0U, "RTT::unbindTexture()"); } Image RTT::get_image() const noexcept { Image ret; ret.create(w, h, readTextureData()); return ret; } bool RTT::valid() const noexcept { return texID || rboID || fboID; } ////////////////////////////////////////////////////////////////////////// PBO::PBO(PBO&& other) noexcept { bound_slot = other.bound_slot; buffer_id = other.buffer_id; mapped_ptr = other.mapped_ptr; width = other.width; height = other.height; other.bound_slot = 0; other.buffer_id = 0; other.mapped_ptr = nullptr; other.width = 0; other.height = 0; } PBO& PBO::operator=(PBO&& other) noexcept { bound_slot = other.bound_slot; buffer_id = other.buffer_id; mapped_ptr = other.mapped_ptr; width = other.width; height = other.height; other.bound_slot = 0; other.buffer_id = 0; other.mapped_ptr = nullptr; other.width = 0; other.height = 0; return *this; } PBO::~PBO() noexcept { destroy(); } bool PBO::create() noexcept { App::I->render_task([this] { const auto result = pp::renderer::gl::allocate_opengl_pixel_buffer( pp::legacy::gl_pixel_buffer::pixel_buffer_allocation_dispatch()); if (!result.ok()) { LOG("%s", result.status().message); buffer_id = 0U; return; } buffer_id = result.value(); }); return true; } bool PBO::create(RTT& rtt) noexcept { App::I->render_task([this, &rtt] { width = rtt.getWidth(); height = rtt.getHeight(); rtt.bindFramebuffer(); const auto result = pp::renderer::gl::readback_opengl_framebuffer_to_pixel_buffer( width, height, pp::renderer::gl::rgba8_readback_format(), pp::legacy::gl_pixel_buffer::pixel_buffer_readback_dispatch()); rtt.unbindFramebuffer(); if (!result.ok()) { LOG("%s", result.status().message); buffer_id = 0U; return; } buffer_id = result.value(); }); return true; } void PBO::destroy() noexcept { if (buffer_id) { App::I->render_task_async([id=buffer_id] { const auto status = pp::renderer::gl::delete_opengl_pixel_buffer( id, pp::legacy::gl_pixel_buffer::pixel_buffer_delete_dispatch()); if (!status.ok()) { LOG("%s", status.message); } }); buffer_id = 0; bound_slot = 0; width = 0; height = 0; mapped_ptr = nullptr; } } glm::uint8_t* PBO::map() noexcept { App::I->render_task([this] { const auto result = pp::renderer::gl::map_opengl_pixel_buffer( buffer_id, width, height, pp::renderer::gl::rgba8_readback_format(), pp::legacy::gl_pixel_buffer::pixel_buffer_map_dispatch()); if (!result.ok()) { LOG("%s", result.status().message); mapped_ptr = nullptr; return; } mapped_ptr = static_cast(result.value()); }); return mapped_ptr; } void PBO::unmap() noexcept { App::I->render_task([this] { const auto status = pp::renderer::gl::unmap_opengl_pixel_buffer( buffer_id, pp::legacy::gl_pixel_buffer::pixel_buffer_unmap_dispatch()); if (!status.ok()) { LOG("%s", status.message); } }); }