#include "pch.h" #include "log.h" #include "rtt.h" #include "util.h" #include "app.h" RTT::RTT() { fboID = 0; rboID = 0; w = 0; h = 0; bound = false; } RTT::RTT(RTT&& other) { fboID = other.fboID; rboID = other.rboID; w = other.w; h = other.h; bound = other.bound; other.fboID = 0; other.rboID = 0; other.w = 0; other.h = 0; other.bound = false; } RTT::~RTT() { //destroy(); if (texID || rboID || fboID) LOG("RTT not destroyed"); } void RTT::resize(int width, int height) { RTT new_rtt; App::I->render_task([&] { glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &oldDFboID); glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &oldRFboID); new_rtt.create(width, height, -1, int_fmt, rboID != 0); 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(); }); oldRFboID = 0; oldDFboID = 0; fboID = new_rtt.fboID; rboID = new_rtt.rboID; texID = new_rtt.texID; int_fmt = new_rtt.int_fmt; w = new_rtt.w; h = new_rtt.h; } void RTT::destroy() { 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) { 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) { 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); }); } 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; } auto ifmt = GL_UNSIGNED_BYTE; if (internal_format == GL_RGBA32F) ifmt = GL_FLOAT; if (internal_format == GL_RGBA16F) ifmt = GL_HALF_FLOAT; glBindTexture(GL_TEXTURE_2D, texID); if (tex == -1) glTexImage2D(GL_TEXTURE_2D, 0, internal_format, width, height, 0, GL_RGBA, ifmt, 0); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); 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); } auto err2str = [](GLenum err) { switch (err) { case GL_FRAMEBUFFER_UNDEFINED: return "GL_FRAMEBUFFER_UNDEFINED"; case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: return "GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT"; case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: return "GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT"; case GL_FRAMEBUFFER_UNSUPPORTED: return "GL_FRAMEBUFFER_UNSUPPORTED"; case GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE: return "GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE"; default: return "UNKNOWN"; } }; // Check FBO status status = glCheckFramebufferStatus(GL_FRAMEBUFFER); if (status != GL_FRAMEBUFFER_COMPLETE) LOG("RTT::create failed because: %s", err2str(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 { 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 (!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 (!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; }