#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()); } [[nodiscard]] GLenum draw_framebuffer_target() noexcept { return static_cast(pp::renderer::gl::draw_framebuffer_target()); } [[nodiscard]] GLenum read_framebuffer_target() noexcept { return static_cast(pp::renderer::gl::read_framebuffer_target()); } [[nodiscard]] GLenum draw_framebuffer_binding_query() noexcept { return static_cast(pp::renderer::gl::draw_framebuffer_binding_query()); } [[nodiscard]] GLenum read_framebuffer_binding_query() noexcept { return static_cast(pp::renderer::gl::read_framebuffer_binding_query()); } } 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([&] { glGetIntegerv(draw_framebuffer_binding_query(), &oldDFboID); glGetIntegerv(read_framebuffer_binding_query(), &oldRFboID); 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; } glBindFramebuffer(draw_framebuffer_target(), new_rtt.fboID); glBindFramebuffer(read_framebuffer_target(), fboID); glBlitFramebuffer( 0, 0, w, h, 0, 0, new_rtt.w, new_rtt.h, static_cast(pp::renderer::gl::framebuffer_color_buffer_mask()), static_cast(pp::renderer::gl::framebuffer_blit_filter(true))); glBindFramebuffer(draw_framebuffer_target(), oldDFboID); glBindFramebuffer(read_framebuffer_target(), oldRFboID); 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([&] { GLint old_draw = 0; GLint old_read = 0; glGetIntegerv(draw_framebuffer_binding_query(), &old_draw); glGetIntegerv(read_framebuffer_binding_query(), &old_read); glBindFramebuffer(draw_framebuffer_target(), fboID); glBindFramebuffer(read_framebuffer_target(), source.fboID); glBlitFramebuffer(0, 0, source.w, source.h, 0, 0, w, h, static_cast(pp::renderer::gl::framebuffer_color_buffer_mask()), static_cast(pp::renderer::gl::framebuffer_blit_filter(true))); glBindFramebuffer(draw_framebuffer_target(), old_draw); glBindFramebuffer(read_framebuffer_target(), old_read); }); } 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 }); GLint old_draw = 0; GLint old_read = 0; glGetIntegerv(draw_framebuffer_binding_query(), &old_draw); glGetIntegerv(read_framebuffer_binding_query(), &old_read); glBindFramebuffer(draw_framebuffer_target(), fboID); glBindFramebuffer(read_framebuffer_target(), source.fboID); glBlitFramebuffer(r.x, r.y, r.z, r.w, r.x, r.y, r.z, r.w, static_cast(pp::renderer::gl::framebuffer_color_buffer_mask()), static_cast(pp::renderer::gl::framebuffer_blit_filter(false))); glBindFramebuffer(draw_framebuffer_target(), old_draw); glBindFramebuffer(read_framebuffer_target(), old_read); }); } 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(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 glGetIntegerv(draw_framebuffer_binding_query(), &oldDFboID); glGetIntegerv(read_framebuffer_binding_query(), &oldRFboID); glBindFramebuffer(draw_framebuffer_target(), fboID); glBindFramebuffer(read_framebuffer_target(), fboID); bound = true; } void RTT::unbindFramebuffer() { assert(App::I->is_render_thread()); if (!bound) return; glBindFramebuffer(draw_framebuffer_target(), oldDFboID); glBindFramebuffer(read_framebuffer_target(), oldRFboID); 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(); GLint old; glGetIntegerv(read_framebuffer_binding_query(), &old); glBindFramebuffer(read_framebuffer_target(), fboID); glReadPixels( 0, 0, w, h, static_cast(readback.pixel_format), static_cast(readback.component_type), buffer); glBindFramebuffer(read_framebuffer_target(), old); }); 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(); GLint old; glGetIntegerv(read_framebuffer_binding_query(), &old); glBindFramebuffer(read_framebuffer_target(), fboID); glReadPixels( 0, 0, w, h, static_cast(readback.pixel_format), static_cast(readback.component_type), buffer); glBindFramebuffer(read_framebuffer_target(), old); }); 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); }); }