mirror of
https://github.com/open-goal/jak-project.git
synced 2024-10-20 21:27:52 -04:00
b5d21be9c5
* temp * temp * before cleaning up * cleanup merge * fix warnings * merge fix * clang format
685 lines
28 KiB
C++
685 lines
28 KiB
C++
#include "DepthCue.h"
|
|
|
|
#include "game/graphics/opengl_renderer/dma_helpers.h"
|
|
|
|
#include "third-party/fmt/core.h"
|
|
#include "third-party/imgui/imgui.h"
|
|
|
|
namespace {
|
|
// Converts fixed point (with 4 bits for decimal) to floating point.
|
|
float fixed_to_floating_point(int fixed) {
|
|
return fixed / 16.0f;
|
|
}
|
|
|
|
math::Vector2f fixed_to_floating_point(const math::Vector<s32, 2>& fixed_vec) {
|
|
return math::Vector2f(fixed_to_floating_point(fixed_vec.x()),
|
|
fixed_to_floating_point(fixed_vec.y()));
|
|
}
|
|
} // namespace
|
|
|
|
// Total number of loops depth-cue performs to draw to the framebuffer
|
|
constexpr int TOTAL_DRAW_SLICES = 16;
|
|
|
|
DepthCue::DepthCue(const std::string& name, int my_id) : BucketRenderer(name, my_id) {
|
|
opengl_setup();
|
|
|
|
m_draw_slices.resize(TOTAL_DRAW_SLICES);
|
|
}
|
|
|
|
void DepthCue::opengl_setup() {
|
|
// Gen texture for sampling the framebuffer
|
|
glGenFramebuffers(1, &m_ogl.framebuffer_sample_fbo);
|
|
glBindFramebuffer(GL_FRAMEBUFFER, m_ogl.framebuffer_sample_fbo);
|
|
|
|
glGenTextures(1, &m_ogl.framebuffer_sample_tex);
|
|
glBindTexture(GL_TEXTURE_2D, m_ogl.framebuffer_sample_tex);
|
|
|
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL);
|
|
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
|
|
|
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
|
|
m_ogl.framebuffer_sample_tex, 0);
|
|
|
|
ASSERT(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE);
|
|
|
|
glBindTexture(GL_TEXTURE_2D, 0);
|
|
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
|
|
|
// Gen framebuffer for depth-cue-base-page
|
|
glGenFramebuffers(1, &m_ogl.fbo);
|
|
glBindFramebuffer(GL_FRAMEBUFFER, m_ogl.fbo);
|
|
|
|
glGenTextures(1, &m_ogl.fbo_texture);
|
|
glBindTexture(GL_TEXTURE_2D, m_ogl.fbo_texture);
|
|
|
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL);
|
|
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
|
|
|
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_ogl.fbo_texture, 0);
|
|
|
|
ASSERT(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE);
|
|
|
|
glBindTexture(GL_TEXTURE_2D, 0);
|
|
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
|
|
|
// Gen vertex array for drawing to depth-cue-base-page
|
|
glGenVertexArrays(1, &m_ogl.depth_cue_page_vao);
|
|
glBindVertexArray(m_ogl.depth_cue_page_vao);
|
|
glGenBuffers(1, &m_ogl.depth_cue_page_vertex_buffer);
|
|
glBindBuffer(GL_ARRAY_BUFFER, m_ogl.depth_cue_page_vertex_buffer);
|
|
|
|
glBufferData(GL_ARRAY_BUFFER, 4 * sizeof(SpriteVertex), nullptr, GL_STATIC_DRAW);
|
|
|
|
glEnableVertexAttribArray(0);
|
|
glVertexAttribPointer(0, // location 0 in the shader
|
|
2, // 2 floats per vert
|
|
GL_FLOAT, // floats
|
|
GL_FALSE, // don't normalize, ignored
|
|
sizeof(SpriteVertex), //
|
|
(void*)offsetof(SpriteVertex, xy) // offset in array
|
|
);
|
|
glEnableVertexAttribArray(1);
|
|
glVertexAttribPointer(1, // location 1 in the shader
|
|
2, // 2 floats per vert
|
|
GL_FLOAT, // floats
|
|
GL_FALSE, // don't normalize, ignored
|
|
sizeof(SpriteVertex), //
|
|
(void*)offsetof(SpriteVertex, st) // offset in array
|
|
);
|
|
|
|
// Gen vertex array for drawing to on-screen framebuffer
|
|
glGenVertexArrays(1, &m_ogl.on_screen_vao);
|
|
glBindVertexArray(m_ogl.on_screen_vao);
|
|
glGenBuffers(1, &m_ogl.on_screen_vertex_buffer);
|
|
glBindBuffer(GL_ARRAY_BUFFER, m_ogl.on_screen_vertex_buffer);
|
|
|
|
glBufferData(GL_ARRAY_BUFFER, 4 * sizeof(SpriteVertex), nullptr, GL_STATIC_DRAW);
|
|
|
|
glEnableVertexAttribArray(0);
|
|
glVertexAttribPointer(0, // location 0 in the shader
|
|
2, // 2 floats per vert
|
|
GL_FLOAT, // floats
|
|
GL_FALSE, // don't normalize, ignored
|
|
sizeof(SpriteVertex), //
|
|
(void*)offsetof(SpriteVertex, xy) // offset in array
|
|
);
|
|
glEnableVertexAttribArray(1);
|
|
glVertexAttribPointer(1, // location 1 in the shader
|
|
2, // 2 floats per vert
|
|
GL_FLOAT, // floats
|
|
GL_FALSE, // don't normalize, ignored
|
|
sizeof(SpriteVertex), //
|
|
(void*)offsetof(SpriteVertex, st) // offset in array
|
|
);
|
|
|
|
// Done
|
|
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
|
glBindVertexArray(0);
|
|
}
|
|
|
|
void DepthCue::render(DmaFollower& dma, SharedRenderState* render_state, ScopedProfilerNode& prof) {
|
|
// First thing should be a NEXT with two nops. this is a jump from buckets to depth-cue
|
|
auto data0 = dma.read_and_advance();
|
|
ASSERT(data0.vif1() == 0);
|
|
ASSERT(data0.vif0() == 0);
|
|
ASSERT(data0.size_bytes == 0);
|
|
|
|
if (dma.current_tag().kind == DmaTag::Kind::CALL) {
|
|
// depth-cue renderer didn't run, let's just get out of here.
|
|
for (int i = 0; i < 4; i++) {
|
|
dma.read_and_advance();
|
|
}
|
|
ASSERT(dma.current_tag_offset() == render_state->next_bucket);
|
|
return;
|
|
}
|
|
|
|
// Read DMA
|
|
{
|
|
auto prof_node = prof.make_scoped_child("dma");
|
|
read_dma(dma, render_state, prof_node);
|
|
}
|
|
|
|
if (!m_enabled) {
|
|
// Renderer disabled, stop early
|
|
return;
|
|
}
|
|
|
|
// Set up draw info
|
|
{
|
|
auto prof_node = prof.make_scoped_child("setup");
|
|
setup(render_state, prof_node);
|
|
}
|
|
|
|
// Draw
|
|
{
|
|
auto prof_node = prof.make_scoped_child("drawing");
|
|
draw(render_state, prof_node);
|
|
}
|
|
}
|
|
|
|
/*!
|
|
* Reads all depth-cue DMA packets.
|
|
*/
|
|
void DepthCue::read_dma(DmaFollower& dma,
|
|
SharedRenderState* render_state,
|
|
ScopedProfilerNode& /*prof*/) {
|
|
// First should be general GS register setup
|
|
{
|
|
auto gs_setup = dma.read_and_advance();
|
|
ASSERT(gs_setup.size_bytes == sizeof(DepthCueGsSetup));
|
|
ASSERT(gs_setup.vifcode0().kind == VifCode::Kind::NOP);
|
|
ASSERT(gs_setup.vifcode1().kind == VifCode::Kind::DIRECT);
|
|
memcpy(&m_gs_setup, gs_setup.data, sizeof(DepthCueGsSetup));
|
|
|
|
ASSERT(m_gs_setup.gif_tag.nreg() == 6);
|
|
ASSERT(m_gs_setup.gif_tag.reg(0) == GifTag::RegisterDescriptor::AD);
|
|
|
|
ASSERT(m_gs_setup.test1.ztest() == GsTest::ZTest::ALWAYS);
|
|
ASSERT(m_gs_setup.zbuf1.zmsk() == true);
|
|
ASSERT(m_gs_setup.tex1.mmag() == true);
|
|
ASSERT(m_gs_setup.tex1.mmin() == 1);
|
|
ASSERT(m_gs_setup.miptbp1 == 0);
|
|
ASSERT(m_gs_setup.alpha1.b_mode() == GsAlpha::BlendMode::DEST);
|
|
ASSERT(m_gs_setup.alpha1.d_mode() == GsAlpha::BlendMode::DEST);
|
|
}
|
|
|
|
// Next is 64 DMAs to draw to the depth-cue-base-page and back to the on-screen framebuffer
|
|
// We'll group these by each slice of the framebuffer being drawn to
|
|
for (int i = 0; i < TOTAL_DRAW_SLICES; i++) {
|
|
// Each 'slice' should be:
|
|
// 1. GS setup for drawing from on-screen framebuffer to depth-cue-base-page
|
|
// 2. Draw to depth-cue-base-page
|
|
// 3. GS setup for drawing from depth-cue-base-page back to on-screen framebuffer
|
|
// 4. Draw to on-screen framebuffer
|
|
DrawSlice& slice = m_draw_slices.at(i);
|
|
|
|
// depth-cue-base-page setup
|
|
{
|
|
auto depth_cue_page_setup = dma.read_and_advance();
|
|
ASSERT(depth_cue_page_setup.size_bytes == sizeof(DepthCuePageGsSetup));
|
|
ASSERT(depth_cue_page_setup.vifcode0().kind == VifCode::Kind::NOP);
|
|
ASSERT(depth_cue_page_setup.vifcode1().kind == VifCode::Kind::DIRECT);
|
|
memcpy(&slice.depth_cue_page_setup, depth_cue_page_setup.data, sizeof(DepthCuePageGsSetup));
|
|
|
|
ASSERT(slice.depth_cue_page_setup.gif_tag.nreg() == 5);
|
|
ASSERT(slice.depth_cue_page_setup.gif_tag.reg(0) == GifTag::RegisterDescriptor::AD);
|
|
|
|
ASSERT(slice.depth_cue_page_setup.tex01.tcc() == 1);
|
|
ASSERT(slice.depth_cue_page_setup.test1.ztest() == GsTest::ZTest::ALWAYS);
|
|
ASSERT(slice.depth_cue_page_setup.alpha1.b_mode() == GsAlpha::BlendMode::SOURCE);
|
|
ASSERT(slice.depth_cue_page_setup.alpha1.d_mode() == GsAlpha::BlendMode::SOURCE);
|
|
}
|
|
|
|
// depth-cue-base-page draw
|
|
{
|
|
auto depth_cue_page_draw = dma.read_and_advance();
|
|
ASSERT(depth_cue_page_draw.size_bytes == sizeof(DepthCuePageDraw));
|
|
ASSERT(depth_cue_page_draw.vifcode0().kind == VifCode::Kind::NOP);
|
|
ASSERT(depth_cue_page_draw.vifcode1().kind == VifCode::Kind::DIRECT);
|
|
memcpy(&slice.depth_cue_page_draw, depth_cue_page_draw.data, sizeof(DepthCuePageDraw));
|
|
|
|
ASSERT(slice.depth_cue_page_draw.gif_tag.nloop() == 1);
|
|
ASSERT(slice.depth_cue_page_draw.gif_tag.pre() == true);
|
|
ASSERT(slice.depth_cue_page_draw.gif_tag.prim() == 6);
|
|
ASSERT(slice.depth_cue_page_draw.gif_tag.flg() == GifTag::Format::PACKED);
|
|
ASSERT(slice.depth_cue_page_draw.gif_tag.nreg() == 5);
|
|
ASSERT(slice.depth_cue_page_draw.gif_tag.reg(0) == GifTag::RegisterDescriptor::RGBAQ);
|
|
}
|
|
|
|
// on-screen setup
|
|
{
|
|
auto on_screen_setup = dma.read_and_advance();
|
|
ASSERT(on_screen_setup.size_bytes == sizeof(OnScreenGsSetup));
|
|
ASSERT(on_screen_setup.vifcode0().kind == VifCode::Kind::NOP);
|
|
ASSERT(on_screen_setup.vifcode1().kind == VifCode::Kind::DIRECT);
|
|
memcpy(&slice.on_screen_setup, on_screen_setup.data, sizeof(OnScreenGsSetup));
|
|
|
|
ASSERT(slice.on_screen_setup.gif_tag.nreg() == 5);
|
|
ASSERT(slice.on_screen_setup.gif_tag.reg(0) == GifTag::RegisterDescriptor::AD);
|
|
|
|
ASSERT(slice.on_screen_setup.tex01.tcc() == 0);
|
|
ASSERT(slice.on_screen_setup.texa.ta0() == 0x80);
|
|
ASSERT(slice.on_screen_setup.texa.ta1() == 0x80);
|
|
ASSERT(slice.on_screen_setup.alpha1.b_mode() == GsAlpha::BlendMode::DEST);
|
|
ASSERT(slice.on_screen_setup.alpha1.d_mode() == GsAlpha::BlendMode::DEST);
|
|
}
|
|
|
|
// on-screen draw
|
|
{
|
|
auto on_screen_draw = dma.read_and_advance();
|
|
ASSERT(on_screen_draw.size_bytes == sizeof(OnScreenDraw));
|
|
ASSERT(on_screen_draw.vifcode0().kind == VifCode::Kind::NOP);
|
|
ASSERT(on_screen_draw.vifcode1().kind == VifCode::Kind::DIRECT);
|
|
memcpy(&slice.on_screen_draw, on_screen_draw.data, sizeof(OnScreenDraw));
|
|
|
|
ASSERT(slice.on_screen_draw.gif_tag.nloop() == 1);
|
|
ASSERT(slice.on_screen_draw.gif_tag.pre() == true);
|
|
ASSERT(slice.on_screen_draw.gif_tag.prim() == 6);
|
|
ASSERT(slice.on_screen_draw.gif_tag.flg() == GifTag::Format::PACKED);
|
|
ASSERT(slice.on_screen_draw.gif_tag.nreg() == 5);
|
|
ASSERT(slice.on_screen_draw.gif_tag.reg(0) == GifTag::RegisterDescriptor::RGBAQ);
|
|
}
|
|
}
|
|
|
|
// Finally, a packet to restore GS state
|
|
{
|
|
auto gs_restore = dma.read_and_advance();
|
|
ASSERT(gs_restore.size_bytes == sizeof(DepthCueGsRestore));
|
|
ASSERT(gs_restore.vifcode0().kind == VifCode::Kind::NOP);
|
|
ASSERT(gs_restore.vifcode1().kind == VifCode::Kind::DIRECT);
|
|
memcpy(&m_gs_restore, gs_restore.data, sizeof(DepthCueGsRestore));
|
|
|
|
ASSERT(m_gs_restore.gif_tag.nreg() == 2);
|
|
ASSERT(m_gs_restore.gif_tag.reg(0) == GifTag::RegisterDescriptor::AD);
|
|
}
|
|
|
|
// End with 'NEXT'
|
|
{
|
|
ASSERT(dma.current_tag().kind == DmaTag::Kind::NEXT);
|
|
|
|
while (dma.current_tag_offset() != render_state->next_bucket) {
|
|
dma.read_and_advance();
|
|
}
|
|
}
|
|
}
|
|
|
|
void DepthCue::setup(SharedRenderState* render_state, ScopedProfilerNode& /*prof*/) {
|
|
if (m_debug.cache_setup && (m_ogl.last_draw_region_w == render_state->draw_region_w &&
|
|
m_ogl.last_draw_region_h == render_state->draw_region_h &&
|
|
// Also recompute when certain debug settings change
|
|
m_ogl.last_override_sharpness == m_debug.override_sharpness &&
|
|
m_ogl.last_custom_sharpness == m_debug.sharpness &&
|
|
m_ogl.last_force_original_res == m_debug.force_original_res &&
|
|
m_ogl.last_res_scale == m_debug.res_scale)) {
|
|
// Draw region didn't change, everything is already set up
|
|
return;
|
|
}
|
|
|
|
m_ogl.last_draw_region_w = render_state->draw_region_w;
|
|
m_ogl.last_draw_region_h = render_state->draw_region_h;
|
|
m_ogl.last_override_sharpness = m_debug.override_sharpness;
|
|
m_ogl.last_custom_sharpness = m_debug.sharpness;
|
|
m_ogl.last_force_original_res = m_debug.force_original_res;
|
|
m_ogl.last_res_scale = m_debug.res_scale;
|
|
|
|
// ASSUMPTIONS
|
|
// --------------------------
|
|
// Assert some assumptions that most of the data for each depth-cue draw is the same.
|
|
// The way the game wants to render this effect is very inefficient in OpenGL, we can use these
|
|
// assumptions to only alter state once, do setup once, and group multiple draw calls.
|
|
const DrawSlice& first_slice = m_draw_slices[0];
|
|
|
|
// 1. Assume each draw slice has the exact same width of 32
|
|
// We'll compare each slice to the first as we go
|
|
float slice_width = fixed_to_floating_point(first_slice.on_screen_draw.xyzf2_2.x() -
|
|
first_slice.on_screen_draw.xyzf2_1.x());
|
|
// NOTE: Y-coords will range between [0,1/2 output res], usually 224 but not always.
|
|
// We'll capture it here so we can convert to coords to [0,1] ranges later.
|
|
float slice_height = fixed_to_floating_point(first_slice.on_screen_draw.xyzf2_2.y());
|
|
|
|
ASSERT(slice_width == 32.0f);
|
|
ASSERT(first_slice.on_screen_draw.xyzf2_1.y() == 0);
|
|
|
|
// 2. Assume that the framebuffer is sampled as a 1024x256 texel view and that the game thinks the
|
|
// framebuffer is 512 pixels wide.
|
|
int fb_sample_width = (int)pow(2, first_slice.depth_cue_page_setup.tex01.tw());
|
|
int fb_sample_height = (int)pow(2, first_slice.depth_cue_page_setup.tex01.th());
|
|
int fb_width = first_slice.depth_cue_page_setup.tex01.tbw() * 64;
|
|
|
|
ASSERT(fb_sample_width == 1024);
|
|
ASSERT(fb_sample_height == 256);
|
|
ASSERT(fb_width == 512);
|
|
ASSERT(fb_width * 2 == fb_sample_width);
|
|
|
|
// 3. Finally, assert that all slices match the above assumptions
|
|
for (const DrawSlice& slice : m_draw_slices) {
|
|
float _slice_width = fixed_to_floating_point(slice.on_screen_draw.xyzf2_2.x() -
|
|
slice.on_screen_draw.xyzf2_1.x());
|
|
float _slice_height = fixed_to_floating_point(slice.on_screen_draw.xyzf2_2.y());
|
|
|
|
ASSERT(slice_width == _slice_width);
|
|
ASSERT(slice_height == _slice_height);
|
|
ASSERT(slice.on_screen_draw.xyzf2_1.y() == 0);
|
|
|
|
int _fb_sample_width = (int)pow(2, slice.depth_cue_page_setup.tex01.tw());
|
|
int _fb_sample_height = (int)pow(2, slice.depth_cue_page_setup.tex01.th());
|
|
int _fb_width = slice.depth_cue_page_setup.tex01.tbw() * 64;
|
|
|
|
ASSERT(fb_sample_width == _fb_sample_width);
|
|
ASSERT(fb_sample_height == _fb_sample_height);
|
|
ASSERT(fb_width == _fb_width);
|
|
}
|
|
|
|
// FRAMEBUFFER SAMPLE TEXTURE
|
|
// --------------------------
|
|
// We need a copy of the framebuffer to sample from. If the framebuffer wasn't using
|
|
// multisampling, this wouldn't be necessary and the framebuffer could just be bound. Instead,
|
|
// we'll just blit to a new texture.
|
|
//
|
|
// The original game code would have created this as a view into the framebuffer whose width is 2x
|
|
// as large, however this isn't necessary for the effect to work.
|
|
int pc_fb_sample_width = render_state->draw_region_w;
|
|
int pc_fb_sample_height = render_state->draw_region_h;
|
|
|
|
m_ogl.framebuffer_sample_width = pc_fb_sample_width;
|
|
m_ogl.framebuffer_sample_height = pc_fb_sample_height;
|
|
|
|
glBindTexture(GL_TEXTURE_2D, m_ogl.framebuffer_sample_tex);
|
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, pc_fb_sample_width, pc_fb_sample_height, 0, GL_RGB,
|
|
GL_UNSIGNED_BYTE, NULL);
|
|
glBindTexture(GL_TEXTURE_2D, 0);
|
|
|
|
// DEPTH CUE BASE PAGE FRAMEBUFFER
|
|
// --------------------------
|
|
// Next, we need a framebuffer to draw slices of the sample texture to. The depth-cue effect
|
|
// usually does this in 16 vertical slices that are 32 pixels wide each. The destination
|
|
// drawn to is smaller than the source by a very small amount (defined by sharpness in the
|
|
// GOAL code), which kicks in the bilinear filtering effect. Normally, a 32x224 texture will
|
|
// be reused for each slice but for the sake of efficient rendering, we'll create a framebuffer
|
|
// that can store all 16 slices side-by-side and draw all slices to it all at once.
|
|
int pc_depth_cue_fb_width = render_state->draw_region_w;
|
|
int pc_depth_cue_fb_height = render_state->draw_region_h;
|
|
|
|
if (m_debug.force_original_res) {
|
|
pc_depth_cue_fb_width = 512;
|
|
}
|
|
|
|
pc_depth_cue_fb_width *= m_debug.res_scale;
|
|
|
|
m_ogl.fbo_width = pc_depth_cue_fb_width;
|
|
m_ogl.fbo_height = pc_depth_cue_fb_height;
|
|
|
|
glBindTexture(GL_TEXTURE_2D, m_ogl.fbo_texture);
|
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, pc_depth_cue_fb_width, pc_depth_cue_fb_height, 0, GL_RGB,
|
|
GL_UNSIGNED_BYTE, NULL);
|
|
glBindTexture(GL_TEXTURE_2D, 0);
|
|
|
|
// DEPTH CUE BASE PAGE VERTEX DATA
|
|
// --------------------------
|
|
// Now that we have a framebuffer to draw each slice to, we need the actual vertex data.
|
|
// We'll take the exact data DMA'd and scale it up the PC dimensions.
|
|
std::vector<SpriteVertex> depth_cue_page_vertices;
|
|
|
|
// U-coordinates here will range from [0,512], but the maximum U value in the original is
|
|
// 1024 since the sample texel width is usually 1024. Since we're not using a texture with
|
|
// 2x the width, the maximum U value used to convert UVs to [0,1] should be 512.
|
|
float max_u = fb_sample_width / 2.0f;
|
|
ASSERT(max_u == 512.0f);
|
|
|
|
for (const auto& slice : m_draw_slices) {
|
|
math::Vector2f xyoffset = fixed_to_floating_point(
|
|
math::Vector2<s32>((s32)slice.depth_cue_page_setup.xyoffset1.ofx(),
|
|
(s32)slice.depth_cue_page_setup.xyoffset1.ofy()));
|
|
|
|
math::Vector2f xy1 = fixed_to_floating_point(slice.depth_cue_page_draw.xyzf2_1.xy());
|
|
math::Vector2f xy2 = fixed_to_floating_point(slice.depth_cue_page_draw.xyzf2_2.xy());
|
|
math::Vector2f uv1 = fixed_to_floating_point(slice.depth_cue_page_draw.uv_1.xy());
|
|
math::Vector2f uv2 = fixed_to_floating_point(slice.depth_cue_page_draw.uv_2.xy());
|
|
|
|
ASSERT(xy1.x() == 0);
|
|
ASSERT(xy1.y() == 0);
|
|
ASSERT(xy2.x() <= 32.0f);
|
|
ASSERT(xy2.y() <= slice_height);
|
|
|
|
if (m_debug.override_sharpness) {
|
|
// Undo sharpness from GOAL code and apply custom
|
|
xy2.x() = 32.0f * m_debug.sharpness;
|
|
xy2.y() = 224.0f * m_debug.sharpness;
|
|
}
|
|
|
|
// Apply xyoffset GS register
|
|
xy1.x() += xyoffset.x() / 4096.0f;
|
|
xy1.y() += xyoffset.y() / 4096.0f;
|
|
xy2.x() += xyoffset.x() / 4096.0f;
|
|
xy2.y() += xyoffset.y() / 4096.0f;
|
|
|
|
// U-coord will range from [0,512], which is half of the original framebuffer sample width
|
|
// Let's also use it to determine the X offset into the depth-cue framebuffer since the
|
|
// original draw assumes each slice is at 0,0.
|
|
float x_offset = (uv1.x() / 512.0f) * (xy2.x() / 32.0f);
|
|
|
|
build_sprite(depth_cue_page_vertices,
|
|
// Top-left
|
|
(xy1.x() / 512.0f) + x_offset, // x1
|
|
xy1.y() / slice_height, // y1
|
|
uv1.x() / max_u, // s1
|
|
uv1.y() / slice_height, // t1
|
|
// Bottom-right
|
|
(xy2.x() / 512.0f) + x_offset, // x2
|
|
xy2.y() / slice_height, // y2
|
|
uv2.x() / max_u, // s2
|
|
uv2.y() / slice_height // t2
|
|
);
|
|
}
|
|
|
|
glBindBuffer(GL_ARRAY_BUFFER, m_ogl.depth_cue_page_vertex_buffer);
|
|
glBufferData(GL_ARRAY_BUFFER, depth_cue_page_vertices.size() * sizeof(SpriteVertex),
|
|
depth_cue_page_vertices.data(), GL_STATIC_DRAW);
|
|
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
|
|
|
// ON SCREEN VERTEX DATA
|
|
// --------------------------
|
|
// Finally, we need to draw pixels from the depth-cue-base-page back to the on-screen
|
|
// framebuffer. We'll take the same approach as above.
|
|
std::vector<SpriteVertex> on_screen_vertices;
|
|
|
|
for (const auto& slice : m_draw_slices) {
|
|
math::Vector2f xyoffset = fixed_to_floating_point(math::Vector2<s32>(
|
|
(s32)slice.on_screen_setup.xyoffset1.ofx(), (s32)slice.on_screen_setup.xyoffset1.ofy()));
|
|
|
|
math::Vector2f xy1 = fixed_to_floating_point(slice.on_screen_draw.xyzf2_1.xy());
|
|
math::Vector2f xy2 = fixed_to_floating_point(slice.on_screen_draw.xyzf2_2.xy());
|
|
math::Vector2f uv1 = fixed_to_floating_point(slice.on_screen_draw.uv_1.xy());
|
|
math::Vector2f uv2 = fixed_to_floating_point(slice.on_screen_draw.uv_2.xy());
|
|
|
|
ASSERT(uv1.x() == 0);
|
|
ASSERT(uv1.y() == 0);
|
|
ASSERT(uv2.x() <= 32.0f);
|
|
ASSERT(uv2.y() <= slice_height);
|
|
|
|
if (m_debug.override_sharpness) {
|
|
// Undo sharpness from GOAL code and apply custom
|
|
uv2.x() = 32.0f * m_debug.sharpness;
|
|
uv2.y() = 224.0f * m_debug.sharpness;
|
|
}
|
|
|
|
// Apply xyoffset GS register
|
|
xy1.x() += xyoffset.x() / 4096.0f;
|
|
xy1.y() += xyoffset.y() / 4096.0f;
|
|
xy2.x() += xyoffset.x() / 4096.0f;
|
|
xy2.y() += xyoffset.y() / 4096.0f;
|
|
|
|
// X-coord will range from [0,512], which is half of the original framebuffer sample width
|
|
// Let's also use it to determine the U offset into the on-screen framebuffer since the
|
|
// original draw assumes each slice is at 0,0.
|
|
float u_offset = (xy1.x() / 512.0f) * (uv2.x() / 32.0f);
|
|
|
|
build_sprite(on_screen_vertices,
|
|
// Top-left
|
|
xy1.x() / 512.0f, // x1
|
|
xy1.y() / slice_height, // y1
|
|
(uv1.x() / 512.0f) + u_offset, // s1
|
|
uv1.y() / slice_height, // t1
|
|
// Bottom-right
|
|
xy2.x() / 512.0f, // x2
|
|
xy2.y() / slice_height, // y2
|
|
(uv2.x() / 512.0f) + u_offset, // s2
|
|
uv2.y() / slice_height // t2
|
|
);
|
|
}
|
|
|
|
glBindBuffer(GL_ARRAY_BUFFER, m_ogl.on_screen_vertex_buffer);
|
|
glBufferData(GL_ARRAY_BUFFER, on_screen_vertices.size() * sizeof(SpriteVertex),
|
|
on_screen_vertices.data(), GL_STATIC_DRAW);
|
|
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
|
}
|
|
|
|
void DepthCue::draw(SharedRenderState* render_state, ScopedProfilerNode& prof) {
|
|
// Disable depth writing but keep test
|
|
glEnable(GL_DEPTH_TEST);
|
|
glDepthMask(GL_FALSE);
|
|
|
|
// Activate shader
|
|
auto shader = &render_state->shaders[ShaderId::DEPTH_CUE];
|
|
shader->activate();
|
|
|
|
glUniform1i(glGetUniformLocation(shader->id(), "tex"), 0);
|
|
|
|
// First, we need to copy the framebuffer into the framebuffer sample texture
|
|
glBindFramebuffer(GL_READ_FRAMEBUFFER, render_state->render_fb);
|
|
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_ogl.framebuffer_sample_fbo);
|
|
|
|
glBlitFramebuffer(render_state->draw_offset_x, // srcX0
|
|
render_state->draw_offset_y, // srcY0
|
|
render_state->draw_offset_x + render_state->draw_region_w, // srcX1
|
|
render_state->draw_offset_y + render_state->draw_region_h, // srcY1
|
|
0, // dstX0
|
|
0, // dstY0
|
|
m_ogl.framebuffer_sample_width, // dstX1
|
|
m_ogl.framebuffer_sample_height, // dstY1
|
|
GL_COLOR_BUFFER_BIT, // mask
|
|
GL_NEAREST // filter
|
|
);
|
|
|
|
glBindFramebuffer(GL_FRAMEBUFFER, render_state->render_fb);
|
|
|
|
// Next, we need to draw from the framebuffer sample texture to the depth-cue-base-page
|
|
// framebuffer
|
|
{
|
|
const auto& depth_cue_page_draw = m_draw_slices[0].depth_cue_page_draw;
|
|
|
|
math::Vector4f colorf = math::Vector4f(
|
|
depth_cue_page_draw.rgbaq.x() / 255.0f, depth_cue_page_draw.rgbaq.y() / 255.0f,
|
|
depth_cue_page_draw.rgbaq.z() / 255.0f, depth_cue_page_draw.rgbaq.w() / 255.0f);
|
|
glUniform4fv(glGetUniformLocation(shader->id(), "u_color"), 1, colorf.data());
|
|
|
|
glUniform1f(glGetUniformLocation(shader->id(), "u_depth"), 1.0f);
|
|
|
|
glBindFramebuffer(GL_FRAMEBUFFER, m_ogl.fbo);
|
|
|
|
glActiveTexture(GL_TEXTURE0);
|
|
glBindTexture(GL_TEXTURE_2D, m_ogl.framebuffer_sample_tex);
|
|
|
|
glBlendEquation(GL_FUNC_ADD);
|
|
glBlendFunc(GL_ONE, GL_ZERO);
|
|
|
|
glViewport(0, 0, m_ogl.fbo_width, m_ogl.fbo_height);
|
|
|
|
prof.add_draw_call();
|
|
prof.add_tri(2 * TOTAL_DRAW_SLICES);
|
|
|
|
glBindVertexArray(m_ogl.depth_cue_page_vao);
|
|
glDrawArrays(GL_TRIANGLES, 0, 6 * TOTAL_DRAW_SLICES); // 6 verts per slice
|
|
}
|
|
|
|
// Finally, the contents of depth-cue-base-page need to be overlayed onto the on-screen
|
|
// framebuffer
|
|
{
|
|
const auto& on_screen_draw = m_draw_slices[0].on_screen_draw;
|
|
|
|
math::Vector4f colorf =
|
|
math::Vector4f(on_screen_draw.rgbaq.x() / 255.0f, on_screen_draw.rgbaq.y() / 255.0f,
|
|
on_screen_draw.rgbaq.z() / 255.0f, on_screen_draw.rgbaq.w() / 255.0f);
|
|
if (m_debug.override_alpha) {
|
|
colorf.w() = m_debug.draw_alpha / 2.0f;
|
|
}
|
|
glUniform4fv(glGetUniformLocation(shader->id(), "u_color"), 1, colorf.data());
|
|
|
|
if (m_debug.depth == 1.0f) {
|
|
glUniform1f(glGetUniformLocation(shader->id(), "u_depth"), m_debug.depth);
|
|
} else {
|
|
// Scale debug depth expontentially to make the slider easier to use
|
|
glUniform1f(glGetUniformLocation(shader->id(), "u_depth"), pow(m_debug.depth, 8));
|
|
}
|
|
|
|
glBindFramebuffer(GL_FRAMEBUFFER, render_state->render_fb);
|
|
|
|
glActiveTexture(GL_TEXTURE0);
|
|
glBindTexture(GL_TEXTURE_2D, m_ogl.fbo_texture);
|
|
|
|
glBlendEquation(GL_FUNC_ADD);
|
|
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ZERO, GL_ONE);
|
|
|
|
glViewport(render_state->draw_offset_x, render_state->draw_offset_y,
|
|
render_state->draw_region_w, render_state->draw_region_h);
|
|
|
|
prof.add_draw_call();
|
|
prof.add_tri(2 * TOTAL_DRAW_SLICES);
|
|
|
|
glBindVertexArray(m_ogl.on_screen_vao);
|
|
glDrawArrays(GL_TRIANGLES, 0, 6 * TOTAL_DRAW_SLICES); // 6 verts per slice
|
|
}
|
|
|
|
// Done
|
|
glDepthMask(GL_TRUE);
|
|
glBindTexture(GL_TEXTURE_2D, 0);
|
|
glBindVertexArray(0);
|
|
}
|
|
|
|
void DepthCue::build_sprite(std::vector<SpriteVertex>& vertices,
|
|
float x1,
|
|
float y1,
|
|
float s1,
|
|
float t1,
|
|
float x2,
|
|
float y2,
|
|
float s2,
|
|
float t2) {
|
|
// First triangle
|
|
// -------------
|
|
// Top-left
|
|
vertices.push_back(SpriteVertex(x1, y1, s1, t1));
|
|
|
|
// Top-right
|
|
vertices.push_back(SpriteVertex(x2, y1, s2, t1));
|
|
|
|
// Bottom-left
|
|
vertices.push_back(SpriteVertex(x1, y2, s1, t2));
|
|
|
|
// Second triangle
|
|
// -------------
|
|
// Top-right
|
|
vertices.push_back(SpriteVertex(x2, y1, s2, t1));
|
|
|
|
// Bottom-left
|
|
vertices.push_back(SpriteVertex(x1, y2, s1, t2));
|
|
|
|
// Bottom-right
|
|
vertices.push_back(SpriteVertex(x2, y2, s2, t2));
|
|
}
|
|
|
|
void DepthCue::draw_debug_window() {
|
|
ImGui::Text("NOTE: depth-cue may be disabled by '*vu1-enable-user-menu*'!");
|
|
|
|
ImGui::Checkbox("Cache setup", &m_debug.cache_setup);
|
|
ImGui::Checkbox("Force original resolution", &m_debug.force_original_res);
|
|
|
|
ImGui::Checkbox("Override alpha", &m_debug.override_alpha);
|
|
if (m_debug.override_alpha) {
|
|
ImGui::SliderFloat("Alpha", &m_debug.draw_alpha, 0.0f, 1.0f);
|
|
}
|
|
|
|
ImGui::Checkbox("Override sharpness", &m_debug.override_sharpness);
|
|
if (m_debug.override_sharpness) {
|
|
ImGui::SliderFloat("Sharpness", &m_debug.sharpness, 0.001f, 1.0f);
|
|
}
|
|
|
|
ImGui::SliderFloat("Depth", &m_debug.depth, 0.0f, 1.0f);
|
|
ImGui::SliderFloat("Resolution scale", &m_debug.res_scale, 0.001f, 2.0f);
|
|
|
|
if (ImGui::Button("Reset")) {
|
|
m_debug.draw_alpha = 0.4f;
|
|
m_debug.sharpness = 0.999f;
|
|
m_debug.depth = 1.0f;
|
|
m_debug.res_scale = 1.0f;
|
|
}
|
|
}
|