#include "pch.h" #include "log.h" #include "rtt.h" #include "util.h" #include "app.h" #include "renderer_gl/opengl_capabilities.h" #include namespace { [[nodiscard]] GLenum texture_2d_target() noexcept { return static_cast(pp::renderer::gl::texture_2d_target()); } [[nodiscard]] GLenum renderbuffer_target() noexcept { return static_cast(pp::renderer::gl::renderbuffer_target()); } [[nodiscard]] GLenum framebuffer_target() noexcept { return static_cast(pp::renderer::gl::framebuffer_target()); } void query_opengl_integer(std::uint32_t name, std::int32_t* value) noexcept { glGetIntegerv(static_cast(name), reinterpret_cast(value)); } void bind_opengl_framebuffer(std::uint32_t target, std::uint32_t framebuffer) noexcept { glBindFramebuffer(static_cast(target), static_cast(framebuffer)); } void blit_opengl_framebuffer( std::int32_t source_x0, std::int32_t source_y0, std::int32_t source_x1, std::int32_t source_y1, std::int32_t destination_x0, std::int32_t destination_y0, std::int32_t destination_x1, std::int32_t destination_y1, std::uint32_t mask, std::uint32_t filter) noexcept { glBlitFramebuffer( static_cast(source_x0), static_cast(source_y0), static_cast(source_x1), static_cast(source_y1), static_cast(destination_x0), static_cast(destination_y0), static_cast(destination_x1), static_cast(destination_y1), static_cast(mask), static_cast(filter)); } void read_opengl_pixels( std::int32_t x, std::int32_t y, std::int32_t width, std::int32_t height, std::uint32_t pixel_format, std::uint32_t component_type, void* pixels) noexcept { glReadPixels( static_cast(x), static_cast(y), static_cast(width), static_cast(height), static_cast(pixel_format), static_cast(component_type), pixels); } } 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::renderer::gl::OpenGlFramebufferBlitDispatch { .get_integer = query_opengl_integer, .bind_framebuffer = bind_opengl_framebuffer, .blit_framebuffer = blit_opengl_framebuffer, }); 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) { glDeleteRenderbuffers(1, &rboID); } if (texID) { //unbindTexture(); glDeleteTextures(1, &texID); //LOG("TEX rtt destroy %d", texID) } if (fboID) { //unbindFramebuffer(); glDeleteFramebuffers(1, &fboID); //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::renderer::gl::OpenGlFramebufferBlitDispatch { .get_integer = query_opengl_integer, .bind_framebuffer = bind_opengl_framebuffer, .blit_framebuffer = blit_opengl_framebuffer, }); 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::renderer::gl::OpenGlFramebufferBlitDispatch { .get_integer = query_opengl_integer, .bind_framebuffer = bind_opengl_framebuffer, .blit_framebuffer = blit_opengl_framebuffer, }); 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*/) { GLenum status = 0; App::I->render_task([&] { // Destroy any previously created object destroy(); w = width; h = height; int_fmt = internal_format; if (tex == -1) { glGenTextures(1, &texID); //LOG("TEX rtt create %d", texID); } else { 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()) { glTexParameterf( texture_2d_target(), static_cast(parameter.name), static_cast(parameter.value)); } 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()), width, height); glBindRenderbuffer(renderbuffer_target(), 0); } GLint oldFboID; glGetIntegerv(static_cast(pp::renderer::gl::draw_framebuffer_binding_query()), &oldFboID); // Create a framebuffer object glGenFramebuffers(1, &fboID); glBindFramebuffer(framebuffer_target(), fboID); //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())) LOG("RTT::create failed because: %s", pp::renderer::gl::framebuffer_status_name(static_cast(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()); } 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::renderer::gl::OpenGlFramebufferBindDispatch { .get_integer = query_opengl_integer, .bind_framebuffer = bind_opengl_framebuffer, }); 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::renderer::gl::OpenGlFramebufferRestoreDispatch { .bind_framebuffer = bind_opengl_framebuffer, }); 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()); glClearColor(color.r, color.g, color.b, color.a); glClear(static_cast( pp::renderer::gl::framebuffer_color_buffer_mask() | pp::renderer::gl::framebuffer_depth_buffer_mask())); } void RTT::clear_mask(glm::bool4 mask, glm::vec4 color) { assert(App::I->is_render_thread()); // save old state std::array old_mask; glGetBooleanv(static_cast(pp::renderer::gl::color_write_mask_query()), old_mask.data()); // clear with mask glColorMask(mask.r, mask.g, mask.b, mask.a); glClearColor(color.r, color.g, color.b, color.a); glClear(static_cast(pp::renderer::gl::framebuffer_color_buffer_mask())); // restore old state glColorMask(old_mask[0], old_mask[1], old_mask[2], old_mask[3]); } 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 }; } 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::renderer::gl::OpenGlFramebufferReadbackDispatch { .get_integer = query_opengl_integer, .bind_framebuffer = bind_opengl_framebuffer, .read_pixels = read_opengl_pixels, }); 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::renderer::gl::OpenGlFramebufferReadbackDispatch { .get_integer = query_opengl_integer, .bind_framebuffer = bind_opengl_framebuffer, .read_pixels = read_opengl_pixels, }); 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()); glBindTexture(texture_2d_target(), texID); } void RTT::unbindTexture() { assert(App::I->is_render_thread()); glBindTexture(texture_2d_target(), 0); } 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] { glGenBuffers(1, &buffer_id); }); return true; } bool PBO::create(RTT& rtt) noexcept { App::I->render_task([this, &rtt] { width = rtt.getWidth(); height = rtt.getHeight(); const auto readback = pp::renderer::gl::rgba8_readback_format(); const auto buffer_target = static_cast(pp::renderer::gl::pixel_pack_buffer_target()); rtt.bindFramebuffer(); glGenBuffers(1, &buffer_id); glBindBuffer(buffer_target, buffer_id); glBufferData( buffer_target, static_cast(pp::renderer::gl::readback_byte_count( readback, static_cast(width), static_cast(height))), 0, static_cast(pp::renderer::gl::pixel_buffer_stream_read_usage())); glReadPixels( 0, 0, width, height, static_cast(readback.pixel_format), static_cast(readback.component_type), 0); glBindBuffer(buffer_target, 0); rtt.unbindFramebuffer(); }); return true; } void PBO::destroy() noexcept { if (buffer_id) { App::I->render_task_async([id=buffer_id] { glDeleteBuffers(1, &id); }); buffer_id = 0; bound_slot = 0; width = 0; height = 0; mapped_ptr = nullptr; } } /* void PBO::bind_read() noexcept { App::I->render_task([this] { const auto buffer_target = static_cast(pp::renderer::gl::pixel_unpack_buffer_target()); glBindBuffer(buffer_target, buffer_id); bound_slot = buffer_target; }); } void PBO::unbind() noexcept { App::I->render_task([this] { glBindBuffer(bound_slot, buffer_id); }); } */ glm::uint8_t* PBO::map() noexcept { App::I->render_task([this] { const auto readback = pp::renderer::gl::rgba8_readback_format(); const auto buffer_target = static_cast(pp::renderer::gl::pixel_pack_buffer_target()); glBindBuffer(buffer_target, buffer_id); mapped_ptr = (GLubyte*)glMapBufferRange(buffer_target, 0, static_cast(pp::renderer::gl::readback_byte_count( readback, static_cast(width), static_cast(height))), static_cast(pp::renderer::gl::pixel_buffer_map_read_access())); glBindBuffer(buffer_target, 0); }); return mapped_ptr; } void PBO::unmap() noexcept { App::I->render_task([this] { const auto buffer_target = static_cast(pp::renderer::gl::pixel_pack_buffer_target()); glBindBuffer(buffer_target, buffer_id); glUnmapBuffer(buffer_target); glBindBuffer(buffer_target, 0); }); }