All checks were successful
Run ReCI Build Test / Explore-Gitea-Actions (push) Successful in 2m16s
368 lines
15 KiB
C++
368 lines
15 KiB
C++
#include <JGL/types/RenderTarget.h>
|
|
#include <JGL/types/Texture.h>
|
|
#include <JGL/logger/logger.h>
|
|
#include <stdexcept>
|
|
|
|
const JGL::Texture* JGL::RenderTarget::GetTexture() const {
|
|
return texture;
|
|
}
|
|
|
|
GLuint JGL::RenderTarget::GetTextureHandle() const {
|
|
return texture->GetHandle();
|
|
}
|
|
|
|
GLuint JGL::RenderTarget::GetGLFramebufferObjectHandle() const {
|
|
return framebuffer_object;
|
|
}
|
|
|
|
GLuint JGL::RenderTarget::GetGLDepthBufferHandle() const {
|
|
return depth_buffer;
|
|
}
|
|
|
|
GLuint JGL::RenderTarget::GetActiveGLFramebufferHandle() {
|
|
GLuint fbo;
|
|
glGetIntegerv(GL_FRAMEBUFFER_BINDING, (GLint*) &fbo);
|
|
return fbo;
|
|
}
|
|
|
|
void JGL::RenderTarget::SetActiveGLRenderTarget(const RenderTarget& render_target) {
|
|
glBindFramebuffer(GL_FRAMEBUFFER, render_target.MSAAEnabled() ? render_target.msaa_framebuffer_object : render_target.GetGLFramebufferObjectHandle());
|
|
glViewport(0,0, render_target.GetDimensions().x, render_target.GetDimensions().y);
|
|
}
|
|
|
|
Vector2i JGL::RenderTarget::GetDimensions() const {
|
|
return size;
|
|
}
|
|
|
|
void JGL::RenderTarget::Erase() {
|
|
if (GetActiveGLFramebufferHandle() == framebuffer_object)
|
|
Logger::Warning("Deleting the framebuffer that's currently in use?");
|
|
|
|
if (using_depth)
|
|
glDeleteRenderbuffers(1, &depth_buffer);
|
|
|
|
glDeleteFramebuffers(1, &framebuffer_object);
|
|
|
|
if (MSAAEnabled())
|
|
SetMSAAEnabled(MSAA_SAMPLE_RATE::MSAA_NONE);
|
|
}
|
|
|
|
Color4 JGL::RenderTarget::GetClearColor() const {
|
|
return clear_color;
|
|
}
|
|
|
|
/// Idk why you'd ever want to clear it out if you're rendering onto a texture you passed in :shrug:.
|
|
JGL::RenderTarget::RenderTarget(const JGL::Texture* texture, const Color4& clear_color) {
|
|
if (texture->GetDimensions().x < 1 || texture->GetDimensions().y < 1)
|
|
Logger::Fatal("Creating a render target where the color attachment is empty?");
|
|
|
|
this->size = { static_cast<int>(texture->GetDimensions().x), static_cast<int>(texture->GetDimensions().y) };
|
|
GLuint current_fbo = GetActiveGLFramebufferHandle();
|
|
GLint viewport[4] = {0, 0, 0, 0};
|
|
glGetIntegerv(GL_VIEWPORT, viewport);
|
|
|
|
glGenFramebuffers(1, &framebuffer_object);
|
|
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer_object);
|
|
glViewport(0,0, size.x, size.y);
|
|
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture->GetHandle(), 0);
|
|
|
|
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
|
|
throw std::runtime_error("A new framebuffer could not be allocated.");
|
|
|
|
glBindFramebuffer(GL_FRAMEBUFFER, current_fbo);
|
|
glViewport(viewport[0], viewport[1], viewport[2], viewport[3]);
|
|
this->clear_color = clear_color;
|
|
this->texture = texture;
|
|
texture_created_by_us = false;
|
|
}
|
|
|
|
JGL::RenderTarget::RenderTarget(const Vector2i& size, const Color4& clear_color, bool use_depth, MSAA_SAMPLE_RATE sample_rate) {
|
|
if (size.x < 1 || size.y < 1)
|
|
Logger::Fatal("Creating a render target where the color attachment is empty?");
|
|
|
|
GLuint current_fbo = GetActiveGLFramebufferHandle();
|
|
GLint viewport[4] = {0, 0, 0, 0};
|
|
glGetIntegerv(GL_VIEWPORT, viewport);
|
|
//Textures behave strangely if they're not square aaaaaaaaaaaaa.
|
|
|
|
texture = new Texture(size);
|
|
glGenFramebuffers(1, &framebuffer_object);
|
|
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer_object);
|
|
glViewport(0,0, size.x, size.y);
|
|
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture->GetHandle(), 0);
|
|
|
|
if (use_depth) {
|
|
GLuint depthBuffer;
|
|
glGenRenderbuffers(1, &depthBuffer);
|
|
glBindRenderbuffer(GL_RENDERBUFFER, depthBuffer);
|
|
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, size.x, size.y);
|
|
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depthBuffer);
|
|
glClear(GL_DEPTH_BUFFER_BIT);
|
|
using_depth = true;
|
|
}
|
|
|
|
GLfloat old_clear_color[4];
|
|
glGetFloatv(GL_COLOR_CLEAR_VALUE, old_clear_color);
|
|
glClearColor(clear_color.RedChannelNormalized(), clear_color.GreenChannelNormalized(), clear_color.BlueChannelNormalized(), clear_color.AlphaChannelNormalized());
|
|
glClear(GL_COLOR_BUFFER_BIT);
|
|
glClearColor(old_clear_color[0], old_clear_color[1], old_clear_color[2], old_clear_color[3]);
|
|
|
|
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
|
|
throw std::runtime_error("A new framebuffer could not be allocated.");
|
|
|
|
glBindFramebuffer(GL_FRAMEBUFFER, current_fbo);
|
|
glViewport(viewport[0], viewport[1], viewport[2], viewport[3]);
|
|
this->clear_color = clear_color;
|
|
this->size = size;
|
|
texture_created_by_us = true;
|
|
|
|
if (sample_rate != MSAA_SAMPLE_RATE::MSAA_NONE)
|
|
SetMSAAEnabled(sample_rate);
|
|
}
|
|
|
|
std::vector<GLfloat> JGL::RenderTarget::GetPixels() const {
|
|
std::vector<GLfloat> data(GetDimensions().x * GetDimensions().y * 4);
|
|
GLuint current_fbo = GetActiveGLFramebufferHandle();
|
|
|
|
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer_object);
|
|
glReadPixels(0, 0, GetDimensions().x, GetDimensions().y, GL_RGBA, GL_FLOAT, data.data());
|
|
glBindFramebuffer(GL_FRAMEBUFFER, current_fbo);
|
|
return data;
|
|
}
|
|
|
|
void JGL::RenderTarget::Resize(const Vector2i& new_size) {
|
|
if (!texture_created_by_us)
|
|
Logger::Error("Resizing a Render Target that does not own it's texture?");
|
|
|
|
GLuint current_fbo = GetActiveGLFramebufferHandle();
|
|
GLfloat old_clear_color[4];
|
|
GLint old_viewport[4] = {0, 0, 0, 0};
|
|
glGetIntegerv(GL_VIEWPORT, old_viewport);
|
|
glGetFloatv(GL_COLOR_CLEAR_VALUE, old_clear_color);
|
|
|
|
//If we have to remake the texture.
|
|
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer_object);
|
|
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
|
|
|
|
// Erase it.
|
|
delete texture;
|
|
|
|
auto cc = GetClearColor();
|
|
glClearColor(cc.RedChannelNormalized(), cc.GreenChannelNormalized(), cc.BlueChannelNormalized(), cc.AlphaChannelNormalized());
|
|
glViewport(0,0, size.x, size.y);
|
|
|
|
texture = new Texture(new_size);
|
|
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture->GetHandle(), 0);
|
|
|
|
glBindFramebuffer(GL_FRAMEBUFFER, current_fbo);
|
|
glClearColor(old_clear_color[0], old_clear_color[1], old_clear_color[2], old_clear_color[3]);
|
|
glViewport(old_viewport[0], old_viewport[1], old_viewport[2], old_viewport[3]);
|
|
size = new_size;
|
|
|
|
//Disable & Re-enable MSAA so the msaa buffer is remade with the correct dimensions.
|
|
if (MSAAEnabled()) {
|
|
MSAA_SAMPLE_RATE current_sample_rate = msaa_sample_rate;
|
|
SetMSAAEnabled(MSAA_SAMPLE_RATE::MSAA_NONE);
|
|
SetMSAAEnabled(current_sample_rate);
|
|
}
|
|
}
|
|
|
|
JGL::RenderTarget::~RenderTarget() {
|
|
Erase();
|
|
if (texture_created_by_us)
|
|
delete texture;
|
|
}
|
|
|
|
bool JGL::RenderTarget::OwnsTexture() const {
|
|
return texture_created_by_us;
|
|
}
|
|
|
|
JGL::MSAA_SAMPLE_RATE JGL::RenderTarget::GetMSAASampleRate() const {
|
|
return msaa_sample_rate;
|
|
}
|
|
|
|
bool JGL::RenderTarget::MSAAEnabled() const {
|
|
return msaa_sample_rate != MSAA_SAMPLE_RATE::MSAA_NONE;
|
|
}
|
|
|
|
bool JGL::RenderTarget::SetMSAAEnabled(JGL::MSAA_SAMPLE_RATE sample_rate) {
|
|
// If we'd be setting the same sample_rate we already have.
|
|
if (sample_rate == msaa_sample_rate)
|
|
return false;
|
|
|
|
// If we'd be rendering onto a texture and not a plain render target we don't want this.
|
|
if (!OwnsTexture())
|
|
return false;
|
|
|
|
// Remove it if they request no msaa or if what they requested is different than what they already have.
|
|
if (sample_rate == MSAA_SAMPLE_RATE::MSAA_NONE || msaa_sample_rate != MSAA_SAMPLE_RATE::MSAA_NONE) {
|
|
if(using_depth)
|
|
glDeleteRenderbuffers(1, &msaa_depth_buffer);
|
|
|
|
glDeleteRenderbuffers(1, &msaa_render_buffer);
|
|
glDeleteFramebuffers(1, &msaa_framebuffer_object);
|
|
|
|
msaa_framebuffer_object = 0;
|
|
msaa_depth_buffer = 0;
|
|
msaa_render_buffer = 0;
|
|
msaa_sample_rate = MSAA_SAMPLE_RATE::MSAA_NONE;
|
|
|
|
// Only return here if they specifically requested no MSAA. else continue to change mode.
|
|
if (sample_rate == MSAA_SAMPLE_RATE::MSAA_NONE)
|
|
return true;
|
|
}
|
|
|
|
GLuint current_fbo = GetActiveGLFramebufferHandle();
|
|
glGenFramebuffers(1, &msaa_framebuffer_object);
|
|
glBindFramebuffer(GL_FRAMEBUFFER, msaa_framebuffer_object);
|
|
|
|
GLint current_renderbuffer = 0;
|
|
glGetIntegerv(GL_RENDERBUFFER_BINDING, ¤t_renderbuffer);
|
|
glGenRenderbuffers(1, &msaa_render_buffer);
|
|
glBindRenderbuffer(GL_RENDERBUFFER, msaa_render_buffer);
|
|
glRenderbufferStorageMultisample(GL_RENDERBUFFER, JGL::to_int(sample_rate), GL_RGBA, size.x, size.y);
|
|
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, msaa_render_buffer);
|
|
|
|
if (using_depth) {
|
|
glGenRenderbuffers(1, &msaa_depth_buffer);
|
|
glBindRenderbuffer(GL_RENDERBUFFER, msaa_depth_buffer);
|
|
glRenderbufferStorageMultisample(GL_RENDERBUFFER, JGL::to_int(sample_rate), GL_DEPTH_COMPONENT, size.x, size.y);
|
|
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, msaa_depth_buffer);
|
|
}
|
|
|
|
bool failure = false;
|
|
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
|
|
failure = true,
|
|
Logger::Fatal("A new MSAA " + std::to_string(to_int(sample_rate)) + "x framebuffer couldn't be allocated.");
|
|
|
|
glBindRenderbuffer(GL_RENDERBUFFER, current_renderbuffer);
|
|
glBindFramebuffer(GL_FRAMEBUFFER, current_fbo);
|
|
msaa_sample_rate = sample_rate;
|
|
|
|
if (failure)
|
|
SetMSAAEnabled(MSAA_SAMPLE_RATE::MSAA_NONE);
|
|
return failure;
|
|
}
|
|
|
|
void JGL::RenderTarget::MSAABlit() const {
|
|
if (MSAAEnabled() && OwnsTexture()) {
|
|
// Save the GL state.
|
|
GLuint current_fbo = GetActiveGLFramebufferHandle();
|
|
GLint current_draw_fbo = 0;
|
|
GLint current_read_fbo = 0;
|
|
glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, ¤t_read_fbo);
|
|
glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, ¤t_draw_fbo);
|
|
|
|
// Draw the contents of one into the other.
|
|
glBindFramebuffer(GL_READ_FRAMEBUFFER, msaa_framebuffer_object);
|
|
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, framebuffer_object);
|
|
glBlitFramebuffer(0, 0, size.x, size.y, 0, 0, size.x, size.y, GL_COLOR_BUFFER_BIT, GL_NEAREST);
|
|
|
|
// Put the GL state back.
|
|
glBindFramebuffer(GL_READ_FRAMEBUFFER, current_read_fbo);
|
|
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, current_draw_fbo);
|
|
glBindFramebuffer(GL_FRAMEBUFFER, current_fbo);
|
|
}
|
|
|
|
// Fixes using render targets on a texture that has mipmaps.
|
|
if (GetTexture()->GetFilteringMode() == FilteringMode::MIPMAP_NEAREST
|
|
|| GetTexture()->GetFilteringMode() == FilteringMode::MIPMAP_BILINEAR ||
|
|
GetTexture()->GetFilteringMode() == FilteringMode::MIPMAP_TRILINEAR) {
|
|
GLint current_texture = 0;
|
|
glGetIntegerv(GL_TEXTURE_BINDING_2D, ¤t_texture);
|
|
|
|
glBindTexture(GL_TEXTURE_2D, GetTexture()->GetHandle());
|
|
glGenerateMipmap(GL_TEXTURE_2D);
|
|
|
|
glBindTexture(GL_TEXTURE_2D, current_texture);
|
|
}
|
|
}
|
|
|
|
void JGL::RenderTarget::Blit(const JGL::RenderTarget& source, JGL::RenderTarget* destination, const Vector2i& position) {
|
|
if (source.GetDimensions().x > destination->GetDimensions().x || source.GetDimensions().y > destination->GetDimensions().y)
|
|
Logger::Warning("Blitting a Render Target onto another Render Target but the destination Render Target is too small?");
|
|
|
|
// Save the GL state.
|
|
GLuint current_fbo = GetActiveGLFramebufferHandle();
|
|
GLint current_draw_fbo = 0;
|
|
GLint current_read_fbo = 0;
|
|
glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, ¤t_read_fbo);
|
|
glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, ¤t_draw_fbo);
|
|
|
|
// Draw the contents of one into the other.
|
|
glBindFramebuffer(GL_READ_FRAMEBUFFER, source.framebuffer_object);
|
|
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, destination->framebuffer_object);
|
|
glBlitFramebuffer(0, 0, source.size.x, source.size.y, position.x, position.y, position.x + source.size.x, position.y + source.size.y, GL_COLOR_BUFFER_BIT, GL_NEAREST);
|
|
|
|
// Put the GL state back.
|
|
glBindFramebuffer(GL_READ_FRAMEBUFFER, current_read_fbo);
|
|
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, current_draw_fbo);
|
|
glBindFramebuffer(GL_FRAMEBUFFER, current_fbo);
|
|
}
|
|
|
|
void JGL::RenderTarget::Blit(const Texture* source, RenderTarget* destination, const Vector2i& position) {
|
|
auto temp = new RenderTarget(source);
|
|
Blit(*temp, destination);
|
|
delete temp;
|
|
}
|
|
|
|
// To avoid repeatedly allocating and deallocating.
|
|
JGL::RenderTarget* pixel = nullptr;
|
|
void JGL::RenderTarget::Blit(const Color4& color, const Vector2i& position, JGL::RenderTarget* destination) {
|
|
if (position.x > destination->size.x || position.y > destination->size.y)
|
|
Logger::Warning("Blitting outside of the renderable area of the destination.");
|
|
|
|
if (pixel == nullptr)
|
|
pixel = new RenderTarget({1,1});
|
|
|
|
GLint current_draw_fbo = 0;
|
|
GLint current_read_fbo = 0;
|
|
GLint viewport[4];
|
|
GLfloat clear_color[4];
|
|
GLuint current_fbo = GetActiveGLFramebufferHandle();
|
|
glGetIntegerv(GL_VIEWPORT, viewport);
|
|
glGetFloatv(GL_COLOR_CLEAR_VALUE, clear_color);
|
|
glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, ¤t_read_fbo);
|
|
glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, ¤t_draw_fbo);
|
|
|
|
SetActiveGLRenderTarget(*pixel);
|
|
glClearColor(color.RedChannelNormalized(), color.GreenChannelNormalized(), color.BlueChannelNormalized(), color.AlphaChannelNormalized());
|
|
glClear(GL_COLOR_BUFFER_BIT);
|
|
|
|
// Invert so it's relative to the top left corner.
|
|
int target_y = destination->size.y - position.y - 1;
|
|
|
|
glBindFramebuffer(GL_READ_FRAMEBUFFER, pixel->framebuffer_object);
|
|
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, destination->framebuffer_object);
|
|
glBlitFramebuffer(0, 0, 1, 1, position.x, target_y, position.x + 1, target_y + 1, GL_COLOR_BUFFER_BIT, GL_NEAREST);
|
|
|
|
glBindFramebuffer(GL_READ_FRAMEBUFFER, current_read_fbo);
|
|
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, current_draw_fbo);
|
|
glBindFramebuffer(GL_FRAMEBUFFER, current_fbo);
|
|
glViewport(viewport[0], viewport[1], viewport[2], viewport[3]);
|
|
glClearColor(clear_color[0], clear_color[1], clear_color[2], clear_color[3]);
|
|
}
|
|
|
|
JGL::RenderTarget::RenderTarget(const JGL::RenderTarget& rhs) {
|
|
auto* this_render_target = new RenderTarget(rhs.size, rhs.clear_color, rhs.using_depth, rhs.msaa_sample_rate);
|
|
RenderTarget::Blit(rhs, this_render_target);
|
|
|
|
this->clear_color = this_render_target->clear_color;
|
|
this->size = this_render_target->size;
|
|
this->using_depth = this_render_target->using_depth;
|
|
this->texture_created_by_us = true;
|
|
this->texture = this_render_target->texture;
|
|
this->framebuffer_object = this_render_target->framebuffer_object;
|
|
this->depth_buffer = this_render_target->depth_buffer;
|
|
this->msaa_sample_rate = this_render_target->msaa_sample_rate;
|
|
this->msaa_framebuffer_object = this_render_target->msaa_framebuffer_object;
|
|
this->msaa_depth_buffer = this_render_target->msaa_depth_buffer;
|
|
this->msaa_render_buffer = this_render_target->msaa_render_buffer;
|
|
|
|
operator delete(this_render_target);
|
|
}
|
|
|
|
Vector2i JGL::RenderTarget::MaximumSize() {
|
|
return Texture::MaximumSize();
|
|
}
|