#pragma once #include "PICA/regs.hpp" #include "boost/icl/interval.hpp" #include "helpers.hpp" #include "math_util.hpp" #include "opengl.hpp" template using Interval = boost::icl::right_open_interval; struct ColourBuffer { u32 location; PICA::ColorFmt format; OpenGL::uvec2 size; bool valid; // Range of VRAM taken up by buffer Interval range; // OpenGL resources allocated to buffer OpenGL::Texture texture; OpenGL::Framebuffer fbo; ColourBuffer() : valid(false) {} ColourBuffer(u32 loc, PICA::ColorFmt format, u32 x, u32 y, bool valid = true) : location(loc), format(format), size({x, y}), valid(valid) { u64 endLoc = (u64)loc + sizeInBytes(); // Check if start and end are valid here range = Interval(loc, (u32)endLoc); } void allocate() { // Create texture for the FBO, setting up filters and the like // Reading back the current texture is slow, but allocate calls should be few and far between. // If this becomes a bottleneck, we can fix it semi-easily auto prevTexture = OpenGL::getTex2D(); texture.create(size.x(), size.y(), GL_RGBA8); texture.bind(); texture.setMinFilter(OpenGL::Linear); texture.setMagFilter(OpenGL::Linear); glBindTexture(GL_TEXTURE_2D, prevTexture); #ifdef GPU_DEBUG_INFO const auto name = Helpers::format("Surface %dx%d %s from 0x%08X", size.x(), size.y(), PICA::textureFormatToString(format), location); OpenGL::setObjectLabel(GL_TEXTURE, texture.handle(), name.c_str()); #endif fbo.createWithDrawTexture(texture); fbo.bind(OpenGL::DrawAndReadFramebuffer); if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { Helpers::panic("Incomplete framebuffer"); } // TODO: This should not clear the framebuffer contents. It should load them from VRAM. GLint oldViewport[4]; GLfloat oldClearColour[4]; glGetIntegerv(GL_VIEWPORT, oldViewport); glGetFloatv(GL_COLOR_CLEAR_VALUE, oldClearColour); OpenGL::setViewport(size.x(), size.y()); OpenGL::setClearColor(0.0, 0.0, 0.0, 1.0); OpenGL::clearColor(); OpenGL::setViewport(oldViewport[0], oldViewport[1], oldViewport[2], oldViewport[3]); OpenGL::setClearColor(oldClearColour[0], oldClearColour[1], oldClearColour[2], oldClearColour[3]); } void free() { valid = false; if (texture.exists() || fbo.exists()) { texture.free(); fbo.free(); } } Math::Rect getSubRect(u32 inputAddress, u32 width, u32 height) { // PICA textures have top-left origin while OpenGL has bottom-left origin. // Flip the rectangle on the x axis to account for this. const u32 startOffset = (inputAddress - location) / sizePerPixel(format); const u32 x0 = (startOffset % (size.x() * 8)) / 8; const u32 y0 = (startOffset / (size.x() * 8)) * 8; return Math::Rect{x0, size.y() - y0, x0 + width, size.y() - height - y0}; } bool matches(ColourBuffer& other) { return location == other.location && format == other.format && size.x() == other.size.x() && size.y() == other.size.y(); } size_t sizeInBytes() { return (size_t)size.x() * (size_t)size.y() * PICA::sizePerPixel(format); } }; struct DepthBuffer { u32 location; PICA::DepthFmt format; OpenGL::uvec2 size; // Implicitly set to the size of the framebuffer bool valid; // Range of VRAM taken up by buffer Interval range; // OpenGL texture used for storing depth/stencil OpenGL::Texture texture; OpenGL::Framebuffer fbo; DepthBuffer() : valid(false) {} DepthBuffer(u32 loc, PICA::DepthFmt format, u32 x, u32 y, bool valid = true) : location(loc), format(format), size({x, y}), valid(valid) { u64 endLoc = (u64)loc + sizeInBytes(); // Check if start and end are valid here range = Interval(loc, (u32)endLoc); } void allocate() { // Create texture for the FBO, setting up filters and the like // Reading back the current texture is slow, but allocate calls should be few and far between. // If this becomes a bottleneck, we can fix it semi-easily auto prevTexture = OpenGL::getTex2D(); // Internal formats for the texture based on format static constexpr std::array internalFormats = { GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT24, GL_DEPTH_COMPONENT24, GL_DEPTH24_STENCIL8, }; // Format of the texture static constexpr std::array formats = { GL_DEPTH_COMPONENT, GL_DEPTH_COMPONENT, GL_DEPTH_COMPONENT, GL_DEPTH_STENCIL, }; static constexpr std::array types = { GL_UNSIGNED_SHORT, GL_UNSIGNED_INT, GL_UNSIGNED_INT, GL_UNSIGNED_INT_24_8, }; auto internalFormat = internalFormats[(int)format]; auto fmt = formats[(int)format]; auto type = types[(int)format]; texture.createDSTexture(size.x(), size.y(), internalFormat, fmt, nullptr, type, GL_TEXTURE_2D); texture.bind(); texture.setMinFilter(OpenGL::Nearest); texture.setMagFilter(OpenGL::Nearest); glBindTexture(GL_TEXTURE_2D, prevTexture); fbo.createWithDrawTexture(texture, fmt == GL_DEPTH_STENCIL ? GL_DEPTH_STENCIL_ATTACHMENT : GL_DEPTH_ATTACHMENT); if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { Helpers::panic("Incomplete framebuffer"); } } void free() { valid = false; if (texture.exists()) { texture.free(); } } bool matches(DepthBuffer& other) { return location == other.location && format == other.format && size.x() == other.size.x() && size.y() == other.size.y(); } size_t sizeInBytes() { return (size_t)size.x() * (size_t)size.y() * PICA::sizePerPixel(format); } };