#include "pch.h" #include "log.h" #include "rtt.h" #include "util.h" #include "app.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([&] { glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &oldDFboID); glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &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(GL_DRAW_FRAMEBUFFER, new_rtt.fboID); glBindFramebuffer(GL_READ_FRAMEBUFFER, fboID); glBlitFramebuffer(0, 0, w, h, 0, 0, new_rtt.w, new_rtt.h, GL_COLOR_BUFFER_BIT, GL_LINEAR); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, oldDFboID); glBindFramebuffer(GL_READ_FRAMEBUFFER, 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(GL_DRAW_FRAMEBUFFER_BINDING, &old_draw); glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &old_read); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fboID); glBindFramebuffer(GL_READ_FRAMEBUFFER, source.fboID); glBlitFramebuffer(0, 0, source.w, source.h, 0, 0, w, h, GL_COLOR_BUFFER_BIT, GL_LINEAR); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, old_draw); glBindFramebuffer(GL_READ_FRAMEBUFFER, 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(GL_DRAW_FRAMEBUFFER_BINDING, &old_draw); glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &old_read); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fboID); glBindFramebuffer(GL_READ_FRAMEBUFFER, source.fboID); glBlitFramebuffer(r.x, r.y, r.z, r.w, r.x, r.y, r.z, r.w, GL_COLOR_BUFFER_BIT, GL_NEAREST); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, old_draw); glBindFramebuffer(GL_READ_FRAMEBUFFER, 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, 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(GL_TEXTURE_2D, texID); if (tex == -1) glTexImage2D(GL_TEXTURE_2D, 0, internal_format, width, height, 0, GL_RGBA, ifmt, 0); for (const auto parameter : pp::renderer::gl::default_render_target_texture_parameters()) { glTexParameterf( GL_TEXTURE_2D, static_cast(parameter.name), static_cast(parameter.value)); } glBindTexture(GL_TEXTURE_2D, 0); // Create a renderbuffer object to store depth info if (depth_buffer) { glGenRenderbuffers(1, &rboID); glBindRenderbuffer(GL_RENDERBUFFER, rboID); glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, width, height); glBindRenderbuffer(GL_RENDERBUFFER, 0); } GLint oldFboID; glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &oldFboID); // Create a framebuffer object glGenFramebuffers(1, &fboID); glBindFramebuffer(GL_FRAMEBUFFER, fboID); //LOG("RTT CREATE %d - tex %d", fboID, texID); // Attach the texture to FBO color attachment point glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texID, 0); if (depth_buffer) { // Attach the renderbuffer to depth attachment point glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, rboID); } // Check FBO status status = glCheckFramebufferStatus(GL_FRAMEBUFFER); if (status != GL_FRAMEBUFFER_COMPLETE) LOG("RTT::create failed because: %s", pp::renderer::gl::framebuffer_status_name(static_cast(status))); // Switch back to window-system-provided framebuffer glBindFramebuffer(GL_FRAMEBUFFER, oldFboID); oldRFboID = 0; oldDFboID = 0; }); return status == GL_FRAMEBUFFER_COMPLETE; } void RTT::bindFramebuffer() { assert(App::I->is_render_thread()); #ifdef _DEBUG if (bound) { LOG("framebuffer bound twice!"); #ifdef _WIN32 __debugbreak(); #endif } #endif // _DEBUG glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &oldDFboID); glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &oldRFboID); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fboID); glBindFramebuffer(GL_READ_FRAMEBUFFER, fboID); bound = true; } void RTT::unbindFramebuffer() { assert(App::I->is_render_thread()); if (!bound) return; glBindFramebuffer(GL_DRAW_FRAMEBUFFER, oldDFboID); glBindFramebuffer(GL_READ_FRAMEBUFFER, 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(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); } void RTT::clear_mask(glm::bool4 mask, glm::vec4 color) { assert(App::I->is_render_thread()); // save old state std::array old_mask; glGetBooleanv(GL_COLOR_WRITEMASK, 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(GL_COLOR_BUFFER_BIT); // 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([&] { GLint old; glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &old); glBindFramebuffer(GL_READ_FRAMEBUFFER, fboID); glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, buffer); glBindFramebuffer(GL_READ_FRAMEBUFFER, old); }); return buffer; } float* RTT::readTextureDataFloat(float* buffer) const noexcept { if (!valid()) return nullptr; if (!buffer) buffer = createBufferFloat(); App::I->render_task([&] { GLint old; glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &old); glBindFramebuffer(GL_READ_FRAMEBUFFER, fboID); glReadPixels(0, 0, w, h, GL_RGBA, GL_FLOAT, buffer); glBindFramebuffer(GL_READ_FRAMEBUFFER, old); }); return buffer; } uint8_t* RTT::createBuffer() const noexcept { return new uint8_t[w * h * 4]; } float * RTT::createBufferFloat() const noexcept { return new float[w * h * 4]; } void RTT::bindTexture() { assert(App::I->is_render_thread()); glBindTexture(GL_TEXTURE_2D, texID); } void RTT::unbindTexture() { assert(App::I->is_render_thread()); glBindTexture(GL_TEXTURE_2D, 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(); rtt.bindFramebuffer(); glGenBuffers(1, &buffer_id); glBindBuffer(GL_PIXEL_PACK_BUFFER, buffer_id); glBufferData(GL_PIXEL_PACK_BUFFER, width * height * 4, 0, GL_STREAM_READ); glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, 0); glBindBuffer(GL_PIXEL_PACK_BUFFER, 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] { glBindBuffer(GL_PIXEL_UNPACK_BUFFER, buffer_id); bound_slot = GL_PIXEL_UNPACK_BUFFER; }); } 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] { glBindBuffer(GL_PIXEL_PACK_BUFFER, buffer_id); mapped_ptr = (GLubyte*)glMapBufferRange(GL_PIXEL_PACK_BUFFER, 0, width * height * 4, GL_MAP_READ_BIT); glBindBuffer(GL_PIXEL_PACK_BUFFER, 0); }); return mapped_ptr; } void PBO::unmap() noexcept { App::I->render_task([this] { glBindBuffer(GL_PIXEL_PACK_BUFFER, buffer_id); glUnmapBuffer(GL_PIXEL_PACK_BUFFER); glBindBuffer(GL_PIXEL_PACK_BUFFER, 0); }); }