mirror of
https://github.com/open-goal/jak-project
synced 2026-07-04 05:20:56 -04:00
d3cc739e43
This attempts to get into master whatever work was done in this PR / it's earlier PR https://github.com/open-goal/jak-project/pull/3965 I don't want this work to be lost / floating around in massive PRs. However the changes are: - switch to ntsc_v1 instead of PAL as the development target, as we have done for all other games - remove most of the copied-from-jak2/3 changes as they need to be confirmed during the decompilation process not just assumed - avoids committing any changes to `game/kernel/common` as it was not clear to me if these were changes made in jak x's kernel that were not properly broken out into it's own functions. We don't want to accidentally introduce bugs into jak1-3's kernel code. - in other words, if the change in the kernel only happens in jak x...it should likely be specific to jak x's kernel, not common. --------- Co-authored-by: VodBox <dillon@vodbox.io> Co-authored-by: yodah <greenboyyodah@gmail.com>
1685 lines
75 KiB
C++
1685 lines
75 KiB
C++
#include "OpenGLRenderer.h"
|
|
|
|
#include "common/goal_constants.h"
|
|
#include "common/log/log.h"
|
|
#include "common/util/FileUtil.h"
|
|
|
|
#include "game/graphics/opengl_renderer/BlitDisplays.h"
|
|
#include "game/graphics/opengl_renderer/DepthCue.h"
|
|
#include "game/graphics/opengl_renderer/DirectRenderer.h"
|
|
#include "game/graphics/opengl_renderer/EyeRenderer.h"
|
|
#include "game/graphics/opengl_renderer/ProgressRenderer.h"
|
|
#include "game/graphics/opengl_renderer/ShadowRenderer.h"
|
|
#include "game/graphics/opengl_renderer/SkyRenderer.h"
|
|
#include "game/graphics/opengl_renderer/TextureUploadHandler.h"
|
|
#include "game/graphics/opengl_renderer/VisDataHandler.h"
|
|
#include "game/graphics/opengl_renderer/Warp.h"
|
|
#include "game/graphics/opengl_renderer/background/Hfrag.h"
|
|
#include "game/graphics/opengl_renderer/background/Shrub.h"
|
|
#include "game/graphics/opengl_renderer/background/TFragment.h"
|
|
#include "game/graphics/opengl_renderer/background/Tie3.h"
|
|
#include "game/graphics/opengl_renderer/foreground/Generic2.h"
|
|
#include "game/graphics/opengl_renderer/foreground/Generic2BucketRenderer.h"
|
|
#include "game/graphics/opengl_renderer/foreground/Merc2BucketRenderer.h"
|
|
#include "game/graphics/opengl_renderer/foreground/Shadow2.h"
|
|
#include "game/graphics/opengl_renderer/ocean/OceanMidAndFar.h"
|
|
#include "game/graphics/opengl_renderer/ocean/OceanNear.h"
|
|
#include "game/graphics/opengl_renderer/sprite/Sprite3.h"
|
|
#include "game/graphics/pipelines/opengl.h"
|
|
|
|
#include "third-party/imgui/imgui.h"
|
|
#include "third-party/imgui/imgui_stdlib.h"
|
|
|
|
// for the vif callback
|
|
#include "game/kernel/common/kmachine.h"
|
|
#include "game/runtime.h"
|
|
|
|
#ifdef _WIN32
|
|
#include <Windows.h>
|
|
#endif
|
|
#include "common/util/string_util.h"
|
|
|
|
namespace {
|
|
std::string g_current_renderer;
|
|
}
|
|
|
|
/*!
|
|
* OpenGL Error callback. If we do something invalid, this will be called.
|
|
*/
|
|
void GLAPIENTRY opengl_error_callback(GLenum source,
|
|
GLenum type,
|
|
GLuint id,
|
|
GLenum severity,
|
|
GLsizei /*length*/,
|
|
const GLchar* message,
|
|
const void* /*userParam*/) {
|
|
if (severity == GL_DEBUG_SEVERITY_NOTIFICATION) {
|
|
lg::debug("[{}] OpenGL notification 0x{:X} S{:X} T{:X}: {}", g_current_renderer, id, source,
|
|
type, message);
|
|
} else if (severity == GL_DEBUG_SEVERITY_LOW) {
|
|
lg::info("[{}] OpenGL message 0x{:X} S{:X} T{:X}: {}", g_current_renderer, id, source, type,
|
|
message);
|
|
} else if (severity == GL_DEBUG_SEVERITY_MEDIUM) {
|
|
lg::warn("[{}] OpenGL warn 0x{:X} S{:X} T{:X}: {}", g_current_renderer, id, source, type,
|
|
message);
|
|
} else if (severity == GL_DEBUG_SEVERITY_HIGH) {
|
|
lg::error("[{}] OpenGL error 0x{:X} S{:X} T{:X}: {}", g_current_renderer, id, source, type,
|
|
message);
|
|
// ASSERT(false);
|
|
}
|
|
}
|
|
|
|
OpenGLRenderer::OpenGLRenderer(std::shared_ptr<TexturePool> texture_pool,
|
|
std::shared_ptr<Loader> loader,
|
|
GameVersion version)
|
|
: m_render_state(texture_pool, loader, version),
|
|
m_collide_renderer(version),
|
|
m_version(version) {
|
|
// requires OpenGL 4.3
|
|
#ifndef __APPLE__
|
|
// setup OpenGL errors
|
|
glEnable(GL_DEBUG_OUTPUT);
|
|
glDebugMessageCallback(opengl_error_callback, nullptr);
|
|
// disable specific errors
|
|
const GLuint gl_error_ignores_api_other[1] = {0x20071}; // some annoying nvidia message
|
|
glDebugMessageControl(GL_DEBUG_SOURCE_API, GL_DEBUG_TYPE_OTHER, GL_DONT_CARE, 1,
|
|
&gl_error_ignores_api_other[0], GL_FALSE);
|
|
#endif
|
|
|
|
lg::info("OpenGL context version: {}", (const char*)glGetString(GL_VERSION));
|
|
lg::info("OpenGL context renderer: {}", (const char*)glGetString(GL_RENDERER));
|
|
lg::info("OpenGL context vendor: {}", (const char*)glGetString(GL_VENDOR));
|
|
lg::info("OpenGL context shading language version: {}",
|
|
(const char*)glGetString(GL_SHADING_LANGUAGE_VERSION));
|
|
|
|
// set up screen draw
|
|
|
|
glGenVertexArrays(1, &screen_vao);
|
|
glGenBuffers(1, &screen_vbo);
|
|
|
|
struct Vertex {
|
|
float x, y;
|
|
};
|
|
constexpr std::array<Vertex, 4> vertices = {
|
|
Vertex{-1, -1},
|
|
Vertex{-1, 1},
|
|
Vertex{1, -1},
|
|
Vertex{1, 1},
|
|
};
|
|
|
|
glBindVertexArray(screen_vao);
|
|
glBindBuffer(GL_ARRAY_BUFFER, screen_vbo);
|
|
glBufferData(GL_ARRAY_BUFFER, sizeof(Vertex) * 4, vertices.data(), GL_STATIC_DRAW);
|
|
glEnableVertexAttribArray(0);
|
|
glVertexAttribPointer(0, // location 0 in the shader
|
|
2, // 2 floats per vert
|
|
GL_FLOAT, // floats
|
|
GL_TRUE, // normalized, ignored,
|
|
sizeof(Vertex), //
|
|
nullptr //
|
|
);
|
|
|
|
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
|
glBindVertexArray(0);
|
|
|
|
// end set up screen draw
|
|
|
|
const tfrag3::Level* common_level = nullptr;
|
|
{
|
|
auto p = scoped_prof("load-common");
|
|
common_level = &m_render_state.loader->load_common(*m_render_state.texture_pool, "GAME");
|
|
}
|
|
|
|
// initialize all renderers
|
|
switch (m_version) {
|
|
case GameVersion::Jak1:
|
|
break;
|
|
case GameVersion::Jak2:
|
|
case GameVersion::Jak3:
|
|
case GameVersion::JakX:
|
|
m_texture_animator =
|
|
std::make_shared<TextureAnimator>(m_render_state.shaders, common_level, m_version);
|
|
break;
|
|
default:
|
|
ASSERT(false);
|
|
}
|
|
|
|
m_merc2 = std::make_shared<Merc2>(m_render_state.shaders, anim_slot_array());
|
|
m_generic2 = std::make_shared<Generic2>(m_render_state.shaders);
|
|
|
|
// initialize all renderers
|
|
auto p = scoped_prof("init-bucket-renderers");
|
|
switch (m_version) {
|
|
case GameVersion::Jak1:
|
|
init_bucket_renderers_jak1();
|
|
break;
|
|
case GameVersion::Jak2:
|
|
init_bucket_renderers_jak2();
|
|
break;
|
|
case GameVersion::Jak3:
|
|
case GameVersion::JakX:
|
|
init_bucket_renderers_jak3();
|
|
break;
|
|
default:
|
|
ASSERT(false);
|
|
}
|
|
}
|
|
|
|
void OpenGLRenderer::init_bucket_renderers_jak3() {
|
|
using namespace jak3;
|
|
m_bucket_renderers.resize((int)BucketId::MAX_BUCKETS);
|
|
m_bucket_categories.resize((int)BucketId::MAX_BUCKETS, BucketCategory::OTHER);
|
|
{
|
|
auto p = scoped_prof("render-inits");
|
|
|
|
m_blit_displays =
|
|
init_bucket_renderer<BlitDisplays>("blit", BucketCategory::OTHER, BucketId::BLIT_START);
|
|
init_bucket_renderer<VisDataHandler>("vis", BucketCategory::OTHER, BucketId::BUCKET_2);
|
|
|
|
// 4
|
|
init_bucket_renderer<TextureUploadHandler>("tex-lcom-sky-pre", BucketCategory::TEX,
|
|
BucketId::TEX_LCOM_SKY_PRE, m_texture_animator);
|
|
init_bucket_renderer<DirectRenderer>("sky", BucketCategory::OTHER, BucketId::SKY, 1024 * 8);
|
|
|
|
init_bucket_renderer<OceanMidAndFar>("ocean-mid-far", BucketCategory::OCEAN,
|
|
BucketId::OCEAN_MID_FAR);
|
|
// 7 (hack for progress menu box)
|
|
init_bucket_renderer<DirectRenderer>("progress-hack", BucketCategory::OTHER,
|
|
BucketId::PROGRESS_HACK, 0x8000);
|
|
|
|
// 8 (in tfrag category for now, just for stat reporting.)
|
|
init_bucket_renderer<Hfrag>("hfrag", BucketCategory::TFRAG, BucketId::HFRAG);
|
|
|
|
// 10
|
|
for (int i = 0; i < LEVEL_MAX; i++) {
|
|
#define GET_BUCKET_ID_FOR_LIST(bkt1, bkt2, idx) ((int)(bkt1) + ((int)(bkt2) - (int)(bkt1)) * (idx))
|
|
// 10
|
|
init_bucket_renderer<TextureUploadHandler>(
|
|
fmt::format("tex-l{}-tfrag", i), BucketCategory::TEX,
|
|
GET_BUCKET_ID_FOR_LIST(BucketId::TEX_L0_TFRAG, BucketId::TEX_L1_TFRAG, i),
|
|
m_texture_animator);
|
|
// 11
|
|
init_bucket_renderer<TFragment>(
|
|
fmt::format("tfrag-l{}-tfrag", i), BucketCategory::TFRAG,
|
|
GET_BUCKET_ID_FOR_LIST(BucketId::TFRAG_L0_TFRAG, BucketId::TFRAG_L1_TFRAG, i),
|
|
std::vector{tfrag3::TFragmentTreeKind::NORMAL}, false, i, anim_slot_array());
|
|
Tie3* tie = init_bucket_renderer<Tie3>(
|
|
fmt::format("tie-l{}-tfrag", i), BucketCategory::TIE,
|
|
GET_BUCKET_ID_FOR_LIST(BucketId::TIE_L0_TFRAG, BucketId::TIE_L1_TFRAG, i), i,
|
|
anim_slot_array());
|
|
init_bucket_renderer<Tie3AnotherCategory>(
|
|
fmt::format("etie-l{}-tfrag", i), BucketCategory::TIE,
|
|
GET_BUCKET_ID_FOR_LIST(BucketId::ETIE_L0_TFRAG, BucketId::ETIE_L1_TFRAG, i), tie,
|
|
tfrag3::TieCategory::NORMAL_ENVMAP);
|
|
|
|
// 17
|
|
init_bucket_renderer<Merc2BucketRenderer>(
|
|
fmt::format("merc-l{}-tfrag", i), BucketCategory::MERC,
|
|
GET_BUCKET_ID_FOR_LIST(BucketId::MERC_L0_TFRAG, BucketId::MERC_L1_TFRAG, i), m_merc2);
|
|
init_bucket_renderer<Generic2BucketRenderer>(
|
|
fmt::format("gmerc-l{}-tfrag", i), BucketCategory::MERC,
|
|
GET_BUCKET_ID_FOR_LIST(BucketId::GMERC_L0_TFRAG, BucketId::GMERC_L1_TFRAG, i), m_generic2,
|
|
Generic2::Mode::NORMAL);
|
|
|
|
init_bucket_renderer<TextureUploadHandler>(
|
|
fmt::format("tex-l{}-shrub", i), BucketCategory::TEX,
|
|
GET_BUCKET_ID_FOR_LIST(BucketId::TEX_L0_SHRUB, BucketId::TEX_L1_SHRUB, i),
|
|
m_texture_animator);
|
|
init_bucket_renderer<Shrub>(
|
|
fmt::format("shrub-l{}-shrub", i), BucketCategory::SHRUB,
|
|
GET_BUCKET_ID_FOR_LIST(BucketId::SHRUB_L0_SHRUB, BucketId::SHRUB_L1_SHRUB, i));
|
|
init_bucket_renderer<Merc2BucketRenderer>(
|
|
fmt::format("merc-l{}-shrub", i), BucketCategory::MERC,
|
|
GET_BUCKET_ID_FOR_LIST(BucketId::MERC_L0_SHRUB, BucketId::MERC_L1_SHRUB, i), m_merc2);
|
|
init_bucket_renderer<Generic2BucketRenderer>(
|
|
fmt::format("gmerc-l{}-shrub", i), BucketCategory::MERC,
|
|
GET_BUCKET_ID_FOR_LIST(BucketId::GMERC_L0_SHRUB, BucketId::GMERC_L1_SHRUB, i), m_generic2,
|
|
Generic2::Mode::NORMAL);
|
|
|
|
// 230
|
|
init_bucket_renderer<TextureUploadHandler>(
|
|
fmt::format("tex-l{}-alpha", i), BucketCategory::TEX,
|
|
GET_BUCKET_ID_FOR_LIST(BucketId::TEX_L0_ALPHA, BucketId::TEX_L1_ALPHA, i),
|
|
m_texture_animator);
|
|
init_bucket_renderer<TFragment>(
|
|
fmt::format("tfrag-t-l{}-alpha", i), BucketCategory::TFRAG,
|
|
GET_BUCKET_ID_FOR_LIST(BucketId::TFRAG_L0_ALPHA, BucketId::TFRAG_L1_ALPHA, i),
|
|
std::vector{tfrag3::TFragmentTreeKind::TRANS}, false, i, anim_slot_array());
|
|
init_bucket_renderer<Tie3AnotherCategory>(
|
|
fmt::format("tie-t-l{}-alpha", i), BucketCategory::TIE,
|
|
GET_BUCKET_ID_FOR_LIST(BucketId::TIE_L0_ALPHA, BucketId::TIE_L1_ALPHA, i), tie,
|
|
tfrag3::TieCategory::TRANS);
|
|
init_bucket_renderer<Tie3AnotherCategory>(
|
|
fmt::format("etie-l{}-alpha", i), BucketCategory::TIE,
|
|
GET_BUCKET_ID_FOR_LIST(BucketId::ETIE_L0_ALPHA, BucketId::ETIE_L1_ALPHA, i), tie,
|
|
tfrag3::TieCategory::TRANS_ENVMAP);
|
|
init_bucket_renderer<Merc2BucketRenderer>(
|
|
fmt::format("merc-l{}-alpha", i), BucketCategory::MERC,
|
|
GET_BUCKET_ID_FOR_LIST(BucketId::MERC_L0_ALPHA, BucketId::MERC_L1_ALPHA, i), m_merc2);
|
|
init_bucket_renderer<Generic2BucketRenderer>(
|
|
fmt::format("gmerc-l{}-alpha", i), BucketCategory::GENERIC,
|
|
GET_BUCKET_ID_FOR_LIST(BucketId::GMERC_L0_ALPHA, BucketId::GMERC_L1_ALPHA, i), m_generic2,
|
|
Generic2::Mode::NORMAL);
|
|
init_bucket_renderer<Tie3AnotherCategory>(
|
|
fmt::format("tie-w-l{}-water", i), BucketCategory::TIE,
|
|
GET_BUCKET_ID_FOR_LIST(BucketId::TIE_L0_WATER, BucketId::TIE_L1_WATER, i), tie,
|
|
tfrag3::TieCategory::WATER);
|
|
init_bucket_renderer<Tie3AnotherCategory>(
|
|
fmt::format("etie-l{}-water", i), BucketCategory::TIE,
|
|
GET_BUCKET_ID_FOR_LIST(BucketId::ETIE_L0_WATER, BucketId::ETIE_L1_WATER, i), tie,
|
|
tfrag3::TieCategory::WATER_ENVMAP);
|
|
}
|
|
|
|
// 340
|
|
init_bucket_renderer<TextureUploadHandler>("tex-lcom-tfrag", BucketCategory::TEX,
|
|
BucketId::TEX_LCOM_TFRAG, m_texture_animator);
|
|
init_bucket_renderer<Merc2BucketRenderer>("merc-lcom-tfrag", BucketCategory::MERC,
|
|
BucketId::MERC_LCOM_TFRAG, m_merc2);
|
|
init_bucket_renderer<Generic2BucketRenderer>("gmerc-lcom-tfrag", BucketCategory::GENERIC,
|
|
BucketId::GMERC_LCOM_TFRAG, m_generic2,
|
|
Generic2::Mode::NORMAL);
|
|
// 345
|
|
init_bucket_renderer<TextureUploadHandler>("tex-lcom-shrub", BucketCategory::TEX,
|
|
BucketId::TEX_LCOM_SHRUB, m_texture_animator);
|
|
init_bucket_renderer<Merc2BucketRenderer>("merc-lcom-shrub", BucketCategory::MERC,
|
|
BucketId::MERC_LCOM_SHRUB, m_merc2);
|
|
|
|
// 350
|
|
init_bucket_renderer<Shadow2>("shadow", BucketCategory::OTHER, BucketId::SHADOW);
|
|
|
|
// 351
|
|
for (int i = 0; i < LEVEL_MAX; i++) {
|
|
#define GET_BUCKET_ID_FOR_LIST(bkt1, bkt2, idx) ((int)(bkt1) + ((int)(bkt2) - (int)(bkt1)) * (idx))
|
|
init_bucket_renderer<TextureUploadHandler>(
|
|
fmt::format("tex-l{}-pris", i), BucketCategory::TEX,
|
|
GET_BUCKET_ID_FOR_LIST(BucketId::TEX_L0_PRIS, BucketId::TEX_L1_PRIS, i),
|
|
m_texture_animator);
|
|
init_bucket_renderer<TextureUploadHandler>(
|
|
fmt::format("tex-l{}-pris2", i), BucketCategory::TEX,
|
|
GET_BUCKET_ID_FOR_LIST(BucketId::TEX_L0_PRIS2, BucketId::TEX_L1_PRIS2, i),
|
|
m_texture_animator);
|
|
|
|
init_bucket_renderer<Merc2BucketRenderer>(
|
|
fmt::format("merc-l{}-pris", i), BucketCategory::MERC,
|
|
GET_BUCKET_ID_FOR_LIST(BucketId::MERC_L0_PRIS, BucketId::MERC_L1_PRIS, i), m_merc2);
|
|
init_bucket_renderer<Generic2BucketRenderer>(
|
|
fmt::format("gmerc-l{}-pris", i), BucketCategory::GENERIC,
|
|
GET_BUCKET_ID_FOR_LIST(BucketId::GMERC_L0_PRIS, BucketId::GMERC_L1_PRIS, i), m_generic2,
|
|
Generic2::Mode::NORMAL);
|
|
|
|
init_bucket_renderer<Generic2BucketRenderer>(
|
|
fmt::format("gmerc2-l{}-pris", i), BucketCategory::GENERIC,
|
|
GET_BUCKET_ID_FOR_LIST(BucketId::GMERC2_L0_PRIS, BucketId::GMERC2_L1_PRIS, i), m_generic2,
|
|
Generic2::Mode::PRIM);
|
|
|
|
init_bucket_renderer<Merc2BucketRenderer>(
|
|
fmt::format("merc-l{}-pris2", i), BucketCategory::MERC,
|
|
GET_BUCKET_ID_FOR_LIST(BucketId::MERC_L0_PRIS2, BucketId::MERC_L1_PRIS2, i), m_merc2);
|
|
init_bucket_renderer<Generic2BucketRenderer>(
|
|
fmt::format("gmerc-l{}-pris2", i), BucketCategory::GENERIC,
|
|
GET_BUCKET_ID_FOR_LIST(BucketId::GMERC_L0_PRIS2, BucketId::GMERC_L1_PRIS2, i), m_generic2,
|
|
Generic2::Mode::NORMAL);
|
|
init_bucket_renderer<Generic2BucketRenderer>(
|
|
fmt::format("gmerc2-l{}-pris2", i), BucketCategory::GENERIC,
|
|
GET_BUCKET_ID_FOR_LIST(BucketId::GMERC2_L0_PRIS2, BucketId::GMERC2_L1_PRIS2, i),
|
|
m_generic2, Generic2::Mode::PRIM);
|
|
}
|
|
|
|
// 401
|
|
init_bucket_renderer<TextureUploadHandler>("tex-lcom-pris", BucketCategory::TEX,
|
|
BucketId::TEX_LCOM_PRIS, m_texture_animator);
|
|
init_bucket_renderer<Merc2BucketRenderer>("merc-lcom-pris", BucketCategory::MERC,
|
|
BucketId::MERC_LCOM_PRIS, m_merc2);
|
|
init_bucket_renderer<Generic2BucketRenderer>("gmerc-lcom-pris", BucketCategory::GENERIC,
|
|
BucketId::GMERC_LCOM_PRIS, m_generic2,
|
|
Generic2::Mode::NORMAL);
|
|
|
|
init_bucket_renderer<Generic2BucketRenderer>("gmerc2-lcom-pris", BucketCategory::GENERIC,
|
|
BucketId::GMERC2_LCOM_PRIS, m_generic2,
|
|
Generic2::Mode::PRIM);
|
|
|
|
// 461
|
|
init_bucket_renderer<TextureUploadHandler>("tex-lcom-sky-post", BucketCategory::TEX,
|
|
BucketId::TEX_LCOM_SKY_POST, m_texture_animator);
|
|
init_bucket_renderer<OceanNear>("ocean-near", BucketCategory::OCEAN, BucketId::OCEAN_NEAR);
|
|
|
|
// 463
|
|
for (int i = 0; i < LEVEL_MAX; i++) {
|
|
#define GET_BUCKET_ID_FOR_LIST(bkt1, bkt2, idx) ((int)(bkt1) + ((int)(bkt2) - (int)(bkt1)) * (idx))
|
|
// 463
|
|
init_bucket_renderer<TextureUploadHandler>(
|
|
fmt::format("tex-l{}-water", i), BucketCategory::TEX,
|
|
GET_BUCKET_ID_FOR_LIST(BucketId::TEX_L0_WATER, BucketId::TEX_L1_WATER, i),
|
|
m_texture_animator);
|
|
init_bucket_renderer<Merc2BucketRenderer>(
|
|
fmt::format("merc-l{}-water", i), BucketCategory::MERC,
|
|
GET_BUCKET_ID_FOR_LIST(BucketId::MERC_L0_WATER, BucketId::MERC_L1_WATER, i), m_merc2);
|
|
init_bucket_renderer<Generic2BucketRenderer>(
|
|
fmt::format("gmerc-l{}-water", i), BucketCategory::GENERIC,
|
|
GET_BUCKET_ID_FOR_LIST(BucketId::GMERC_L0_WATER, BucketId::GMERC_L1_WATER, i), m_generic2,
|
|
Generic2::Mode::NORMAL);
|
|
// 466
|
|
init_bucket_renderer<TFragment>(
|
|
fmt::format("tfrag-l{}-water", i), BucketCategory::TFRAG,
|
|
GET_BUCKET_ID_FOR_LIST(BucketId::TFRAG_L0_WATER, BucketId::TFRAG_L1_WATER, i),
|
|
std::vector{tfrag3::TFragmentTreeKind::WATER}, false, i, anim_slot_array());
|
|
init_bucket_renderer<Generic2BucketRenderer>(
|
|
fmt::format("gmerc2-l{}-water", i), BucketCategory::GENERIC,
|
|
GET_BUCKET_ID_FOR_LIST(BucketId::GMERC2_L0_WATER, BucketId::GMERC2_L1_WATER, i),
|
|
m_generic2, Generic2::Mode::PRIM);
|
|
}
|
|
|
|
// 563
|
|
init_bucket_renderer<TextureUploadHandler>("tex-lcom-water", BucketCategory::TEX,
|
|
BucketId::TEX_LCOM_WATER, m_texture_animator);
|
|
init_bucket_renderer<Merc2BucketRenderer>("merc-lcom-water", BucketCategory::MERC,
|
|
BucketId::MERC_LCOM_WATER, m_merc2);
|
|
init_bucket_renderer<Generic2BucketRenderer>("generic2-lcom-water", BucketCategory::GENERIC,
|
|
BucketId::GMERC2_LCOM_WATER, m_generic2,
|
|
Generic2::Mode::PRIM);
|
|
|
|
// 568
|
|
init_bucket_renderer<TextureUploadHandler>("tex-sprite", BucketCategory::TEX,
|
|
BucketId::TEX_SPRITE, m_texture_animator);
|
|
init_bucket_renderer<Generic2BucketRenderer>("generic-sprite-1", BucketCategory::GENERIC,
|
|
BucketId::GENERIC_SPRITE_1, m_generic2,
|
|
Generic2::Mode::PRIM);
|
|
init_bucket_renderer<Sprite3>("particles", BucketCategory::SPRITE, BucketId::PARTICLES);
|
|
init_bucket_renderer<Generic2BucketRenderer>("generic-sprite-2", BucketCategory::GENERIC,
|
|
BucketId::GENERIC_SPRITE_2, m_generic2,
|
|
Generic2::Mode::PRIM);
|
|
init_bucket_renderer<Generic2BucketRenderer>("generic-sprite-3", BucketCategory::OTHER,
|
|
BucketId::GENERIC_SPRITE_3, m_generic2,
|
|
Generic2::Mode::LIGHTNING);
|
|
|
|
init_bucket_renderer<Shadow2>("shadow2", BucketCategory::OTHER, BucketId::SHADOW2);
|
|
init_bucket_renderer<Shadow2>("shadow3", BucketCategory::OTHER, BucketId::SHADOW3);
|
|
// 575
|
|
init_bucket_renderer<TextureUploadHandler>("tex-warp", BucketCategory::TEX, BucketId::TEX_WARP,
|
|
m_texture_animator);
|
|
init_bucket_renderer<Warp>("generic-warp", BucketCategory::GENERIC, BucketId::GENERIC_WARP,
|
|
m_generic2);
|
|
|
|
init_bucket_renderer<TextureUploadHandler>("debug-no-zbuf1", BucketCategory::OTHER,
|
|
BucketId::DEBUG_NO_ZBUF1, m_texture_animator, true);
|
|
// 578
|
|
init_bucket_renderer<TextureUploadHandler>("tex-hud-hud-alpha", BucketCategory::TEX,
|
|
BucketId::TEX_HUD_HUD_ALPHA, m_texture_animator,
|
|
true);
|
|
init_bucket_renderer<ProgressRenderer>("hud-draw-hud-alpha", BucketCategory::OTHER,
|
|
BucketId::HUD_DRAW_HUD_ALPHA, 0x8000);
|
|
init_bucket_renderer<TextureUploadHandler>("tex-hud-pris2", BucketCategory::TEX,
|
|
BucketId::TEX_HUD_PRIS2, m_texture_animator, true);
|
|
init_bucket_renderer<ProgressRenderer>("hud-draw-pris2", BucketCategory::OTHER,
|
|
BucketId::HUD_DRAW_PRIS2, 0x8000);
|
|
init_bucket_renderer<ProgressRenderer>("progress", BucketCategory::OTHER, BucketId::BUCKET582,
|
|
0x8000);
|
|
|
|
// 583
|
|
init_bucket_renderer<DirectRenderer>("debug", BucketCategory::OTHER, BucketId::DEBUG, 0x8000);
|
|
// 584
|
|
init_bucket_renderer<DirectRenderer>("debug-no-zbuf2", BucketCategory::OTHER,
|
|
BucketId::DEBUG_NO_ZBUF2, 0x8000);
|
|
init_bucket_renderer<DirectRenderer>("debug-menu", BucketCategory::OTHER, BucketId::DEBUG_MENU,
|
|
0x8000);
|
|
|
|
auto eye_renderer = std::make_unique<EyeRenderer>("eyes", 0);
|
|
m_render_state.eye_renderer = eye_renderer.get();
|
|
m_jak3_eye_renderer = std::move(eye_renderer);
|
|
|
|
// for any unset renderers, just set them to an EmptyBucketRenderer.
|
|
for (size_t i = 0; i < m_bucket_renderers.size(); i++) {
|
|
if (!m_bucket_renderers[i]) {
|
|
init_bucket_renderer<EmptyBucketRenderer>(fmt::format("bucket-{}", i),
|
|
BucketCategory::OTHER, i);
|
|
}
|
|
|
|
m_bucket_renderers[i]->init_shaders(m_render_state.shaders);
|
|
m_bucket_renderers[i]->init_textures(*m_render_state.texture_pool, GameVersion::Jak3);
|
|
}
|
|
m_jak3_eye_renderer->init_shaders(m_render_state.shaders);
|
|
m_jak3_eye_renderer->init_textures(*m_render_state.texture_pool, GameVersion::Jak3);
|
|
}
|
|
}
|
|
|
|
void OpenGLRenderer::init_bucket_renderers_jak2() {
|
|
using namespace jak2;
|
|
m_bucket_renderers.resize((int)BucketId::MAX_BUCKETS);
|
|
m_bucket_categories.resize((int)BucketId::MAX_BUCKETS, BucketCategory::OTHER);
|
|
|
|
// 0
|
|
init_bucket_renderer<VisDataHandler>("vis", BucketCategory::OTHER, BucketId::BUCKET_2);
|
|
m_blit_displays =
|
|
init_bucket_renderer<BlitDisplays>("blit", BucketCategory::OTHER, BucketId::BUCKET_3);
|
|
init_bucket_renderer<TextureUploadHandler>("tex-lcom-sky-pre", BucketCategory::TEX,
|
|
BucketId::TEX_LCOM_SKY_PRE, m_texture_animator);
|
|
init_bucket_renderer<DirectRenderer>("sky-draw", BucketCategory::OTHER, BucketId::SKY_DRAW, 1024);
|
|
init_bucket_renderer<OceanMidAndFar>("ocean-mid-far", BucketCategory::OCEAN,
|
|
BucketId::OCEAN_MID_FAR);
|
|
|
|
for (int i = 0; i < LEVEL_MAX; ++i) {
|
|
#define GET_BUCKET_ID_FOR_LIST(bkt1, bkt2, idx) ((int)(bkt1) + ((int)(bkt2) - (int)(bkt1)) * (idx))
|
|
init_bucket_renderer<TextureUploadHandler>(
|
|
fmt::format("tex-l{}-tfrag", i), BucketCategory::TEX,
|
|
GET_BUCKET_ID_FOR_LIST(BucketId::TEX_L0_TFRAG, BucketId::TEX_L1_TFRAG, i),
|
|
m_texture_animator);
|
|
init_bucket_renderer<TFragment>(
|
|
fmt::format("tfrag-l{}-tfrag", i), BucketCategory::TFRAG,
|
|
GET_BUCKET_ID_FOR_LIST(BucketId::TFRAG_L0_TFRAG, BucketId::TFRAG_L1_TFRAG, i),
|
|
std::vector{tfrag3::TFragmentTreeKind::NORMAL}, false, i, anim_slot_array());
|
|
Tie3* tie = init_bucket_renderer<Tie3>(
|
|
fmt::format("tie-l{}-tfrag", i), BucketCategory::TIE,
|
|
GET_BUCKET_ID_FOR_LIST(BucketId::TIE_L0_TFRAG, BucketId::TIE_L1_TFRAG, i), i,
|
|
anim_slot_array());
|
|
init_bucket_renderer<Tie3AnotherCategory>(
|
|
fmt::format("etie-l{}-tfrag", i), BucketCategory::TIE,
|
|
GET_BUCKET_ID_FOR_LIST(BucketId::ETIE_L0_TFRAG, BucketId::ETIE_L1_TFRAG, i), tie,
|
|
tfrag3::TieCategory::NORMAL_ENVMAP);
|
|
init_bucket_renderer<Merc2BucketRenderer>(
|
|
fmt::format("merc-l{}-tfrag", i), BucketCategory::MERC,
|
|
GET_BUCKET_ID_FOR_LIST(BucketId::MERC_L0_TFRAG, BucketId::MERC_L1_TFRAG, i), m_merc2);
|
|
init_bucket_renderer<Generic2BucketRenderer>(
|
|
fmt::format("gmerc-l{}-tfrag", i), BucketCategory::MERC,
|
|
GET_BUCKET_ID_FOR_LIST(BucketId::GMERC_L0_TFRAG, BucketId::GMERC_L1_TFRAG, i), m_generic2,
|
|
Generic2::Mode::NORMAL);
|
|
|
|
init_bucket_renderer<TextureUploadHandler>(
|
|
fmt::format("tex-l{}-shrub", i), BucketCategory::TEX,
|
|
GET_BUCKET_ID_FOR_LIST(BucketId::TEX_L0_SHRUB, BucketId::TEX_L1_SHRUB, i),
|
|
m_texture_animator);
|
|
init_bucket_renderer<Shrub>(
|
|
fmt::format("shrub-l{}-shrub", i), BucketCategory::SHRUB,
|
|
GET_BUCKET_ID_FOR_LIST(BucketId::SHRUB_L0_SHRUB, BucketId::SHRUB_L1_SHRUB, i));
|
|
init_bucket_renderer<Merc2BucketRenderer>(
|
|
fmt::format("merc-l{}-shrub", i), BucketCategory::MERC,
|
|
GET_BUCKET_ID_FOR_LIST(BucketId::MERC_L0_SHRUB, BucketId::MERC_L1_SHRUB, i), m_merc2);
|
|
init_bucket_renderer<Generic2BucketRenderer>(
|
|
fmt::format("gmerc-l{}-shrub", i), BucketCategory::MERC,
|
|
GET_BUCKET_ID_FOR_LIST(BucketId::GMERC_L0_SHRUB, BucketId::GMERC_L1_SHRUB, i), m_generic2,
|
|
Generic2::Mode::NORMAL);
|
|
|
|
init_bucket_renderer<TextureUploadHandler>(
|
|
fmt::format("tex-l{}-alpha", i), BucketCategory::TEX,
|
|
GET_BUCKET_ID_FOR_LIST(BucketId::TEX_L0_ALPHA, BucketId::TEX_L1_ALPHA, i),
|
|
m_texture_animator);
|
|
init_bucket_renderer<TFragment>(
|
|
fmt::format("tfrag-t-l{}-alpha", i), BucketCategory::TFRAG,
|
|
GET_BUCKET_ID_FOR_LIST(BucketId::TFRAG_T_L0_ALPHA, BucketId::TFRAG_T_L1_ALPHA, i),
|
|
std::vector{tfrag3::TFragmentTreeKind::TRANS}, false, i, anim_slot_array());
|
|
init_bucket_renderer<Tie3AnotherCategory>(
|
|
fmt::format("tie-t-l{}-alpha", i), BucketCategory::TIE,
|
|
GET_BUCKET_ID_FOR_LIST(BucketId::TIE_T_L0_ALPHA, BucketId::TIE_T_L1_ALPHA, i), tie,
|
|
tfrag3::TieCategory::TRANS);
|
|
init_bucket_renderer<Tie3AnotherCategory>(
|
|
fmt::format("etie-t-l{}-alpha", i), BucketCategory::TIE,
|
|
GET_BUCKET_ID_FOR_LIST(BucketId::ETIE_T_L0_ALPHA, BucketId::ETIE_T_L1_ALPHA, i), tie,
|
|
tfrag3::TieCategory::TRANS_ENVMAP);
|
|
init_bucket_renderer<Merc2BucketRenderer>(
|
|
fmt::format("merc-l{}-alpha", i), BucketCategory::MERC,
|
|
GET_BUCKET_ID_FOR_LIST(BucketId::MERC_L0_ALPHA, BucketId::MERC_L1_ALPHA, i), m_merc2);
|
|
init_bucket_renderer<Generic2BucketRenderer>(
|
|
fmt::format("gmerc-l{}-alpha", i), BucketCategory::GENERIC,
|
|
GET_BUCKET_ID_FOR_LIST(BucketId::GMERC_L0_ALPHA, BucketId::GMERC_L1_ALPHA, i), m_generic2,
|
|
Generic2::Mode::NORMAL);
|
|
|
|
init_bucket_renderer<TextureUploadHandler>(
|
|
fmt::format("tex-l{}-pris", i), BucketCategory::TEX,
|
|
GET_BUCKET_ID_FOR_LIST(BucketId::TEX_L0_PRIS, BucketId::TEX_L1_PRIS, i),
|
|
m_texture_animator);
|
|
init_bucket_renderer<Merc2BucketRenderer>(
|
|
fmt::format("merc-l{}-pris", i), BucketCategory::MERC,
|
|
GET_BUCKET_ID_FOR_LIST(BucketId::MERC_L0_PRIS, BucketId::MERC_L1_PRIS, i), m_merc2);
|
|
init_bucket_renderer<Generic2BucketRenderer>(
|
|
fmt::format("gmerc-l{}-pris", i), BucketCategory::GENERIC,
|
|
GET_BUCKET_ID_FOR_LIST(BucketId::GMERC_L0_PRIS, BucketId::GMERC_L1_PRIS, i), m_generic2,
|
|
Generic2::Mode::NORMAL);
|
|
|
|
init_bucket_renderer<TextureUploadHandler>(
|
|
fmt::format("tex-l{}-pris2", i), BucketCategory::TEX,
|
|
GET_BUCKET_ID_FOR_LIST(BucketId::TEX_L0_PRIS2, BucketId::TEX_L1_PRIS2, i),
|
|
m_texture_animator);
|
|
init_bucket_renderer<Merc2BucketRenderer>(
|
|
fmt::format("merc-l{}-pris2", i), BucketCategory::MERC,
|
|
GET_BUCKET_ID_FOR_LIST(BucketId::MERC_L0_PRIS2, BucketId::MERC_L1_PRIS2, i), m_merc2);
|
|
init_bucket_renderer<Generic2BucketRenderer>(
|
|
fmt::format("gmerc-l{}-pris2", i), BucketCategory::GENERIC,
|
|
GET_BUCKET_ID_FOR_LIST(BucketId::GMERC_L0_PRIS2, BucketId::GMERC_L1_PRIS2, i), m_generic2,
|
|
Generic2::Mode::NORMAL);
|
|
|
|
init_bucket_renderer<TextureUploadHandler>(
|
|
fmt::format("tex-l{}-water", i), BucketCategory::TEX,
|
|
GET_BUCKET_ID_FOR_LIST(BucketId::TEX_L0_WATER, BucketId::TEX_L1_WATER, i),
|
|
m_texture_animator);
|
|
init_bucket_renderer<Merc2BucketRenderer>(
|
|
fmt::format("merc-l{}-water", i), BucketCategory::MERC,
|
|
GET_BUCKET_ID_FOR_LIST(BucketId::MERC_L0_WATER, BucketId::MERC_L1_WATER, i), m_merc2);
|
|
init_bucket_renderer<Generic2BucketRenderer>(
|
|
fmt::format("gmerc-l{}-water", i), BucketCategory::GENERIC,
|
|
GET_BUCKET_ID_FOR_LIST(BucketId::GMERC_L0_WATER, BucketId::GMERC_L1_WATER, i), m_generic2,
|
|
Generic2::Mode::NORMAL);
|
|
init_bucket_renderer<TFragment>(
|
|
fmt::format("tfrag-w-l{}-alpha", i), BucketCategory::TFRAG,
|
|
GET_BUCKET_ID_FOR_LIST(BucketId::TFRAG_W_L0_WATER, BucketId::TFRAG_W_L1_WATER, i),
|
|
std::vector{tfrag3::TFragmentTreeKind::WATER}, false, i, anim_slot_array());
|
|
init_bucket_renderer<Tie3AnotherCategory>(
|
|
fmt::format("tie-w-l{}-water", i), BucketCategory::TIE,
|
|
GET_BUCKET_ID_FOR_LIST(BucketId::TIE_W_L0_WATER, BucketId::TIE_W_L1_WATER, i), tie,
|
|
tfrag3::TieCategory::WATER);
|
|
init_bucket_renderer<Tie3AnotherCategory>(
|
|
fmt::format("etie-w-l{}-water", i), BucketCategory::TIE,
|
|
GET_BUCKET_ID_FOR_LIST(BucketId::ETIE_W_L0_WATER, BucketId::ETIE_W_L1_WATER, i), tie,
|
|
tfrag3::TieCategory::WATER_ENVMAP);
|
|
#undef GET_BUCKET_ID_FOR_LIST
|
|
}
|
|
// 180
|
|
init_bucket_renderer<TextureUploadHandler>("tex-lcom-tfrag", BucketCategory::TEX,
|
|
BucketId::TEX_LCOM_TFRAG, m_texture_animator);
|
|
init_bucket_renderer<Merc2BucketRenderer>("merc-lcom-tfrag", BucketCategory::MERC,
|
|
BucketId::MERC_LCOM_TFRAG, m_merc2);
|
|
// 190
|
|
init_bucket_renderer<TextureUploadHandler>("tex-lcom-shrub", BucketCategory::TEX,
|
|
BucketId::TEX_LCOM_SHRUB, m_texture_animator);
|
|
init_bucket_renderer<Merc2BucketRenderer>("merc-lcom-shrub", BucketCategory::MERC,
|
|
BucketId::MERC_LCOM_SHRUB, m_merc2);
|
|
init_bucket_renderer<Generic2BucketRenderer>("gmerc-lcom-tfrag", BucketCategory::GENERIC,
|
|
BucketId::GMERC_LCOM_TFRAG, m_generic2,
|
|
Generic2::Mode::NORMAL);
|
|
init_bucket_renderer<Shadow2>("shadow", BucketCategory::OTHER, BucketId::SHADOW);
|
|
// 220
|
|
init_bucket_renderer<TextureUploadHandler>("tex-lcom-pris", BucketCategory::TEX,
|
|
BucketId::TEX_LCOM_PRIS, m_texture_animator);
|
|
init_bucket_renderer<Merc2BucketRenderer>("merc-lcom-pris", BucketCategory::MERC,
|
|
BucketId::MERC_LCOM_PRIS, m_merc2);
|
|
init_bucket_renderer<Generic2BucketRenderer>("gmerc-lcom-pris", BucketCategory::GENERIC,
|
|
BucketId::GMERC_LCOM_PRIS, m_generic2,
|
|
Generic2::Mode::NORMAL);
|
|
init_bucket_renderer<TextureUploadHandler>("tex-lcom-water", BucketCategory::TEX,
|
|
BucketId::TEX_LCOM_WATER, m_texture_animator);
|
|
init_bucket_renderer<Merc2BucketRenderer>("merc-lcom-water", BucketCategory::MERC,
|
|
BucketId::MERC_LCOM_WATER, m_merc2);
|
|
init_bucket_renderer<TextureUploadHandler>("tex-lcom-sky-post", BucketCategory::TEX,
|
|
BucketId::TEX_LCOM_SKY_POST, m_texture_animator);
|
|
// 310
|
|
init_bucket_renderer<OceanNear>("ocean-near", BucketCategory::OCEAN, BucketId::OCEAN_NEAR);
|
|
init_bucket_renderer<TextureUploadHandler>("tex-all-sprite", BucketCategory::TEX,
|
|
BucketId::TEX_ALL_SPRITE, m_texture_animator);
|
|
init_bucket_renderer<Sprite3>("particles", BucketCategory::SPRITE, BucketId::PARTICLES);
|
|
init_bucket_renderer<Shadow2>("shadow2", BucketCategory::OTHER, BucketId::SHADOW2);
|
|
init_bucket_renderer<Generic2BucketRenderer>("effects", BucketCategory::OTHER, BucketId::EFFECTS,
|
|
m_generic2, Generic2::Mode::LIGHTNING);
|
|
init_bucket_renderer<TextureUploadHandler>("tex-all-warp", BucketCategory::TEX,
|
|
BucketId::TEX_ALL_WARP, m_texture_animator);
|
|
init_bucket_renderer<Warp>("warp", BucketCategory::GENERIC, BucketId::GMERC_WARP, m_generic2);
|
|
init_bucket_renderer<TextureUploadHandler>("debug-no-zbuf1", BucketCategory::OTHER,
|
|
BucketId::DEBUG_NO_ZBUF1, m_texture_animator, true);
|
|
init_bucket_renderer<TextureUploadHandler>("tex-all-map", BucketCategory::TEX,
|
|
BucketId::TEX_ALL_MAP, m_texture_animator, true);
|
|
// 320
|
|
init_bucket_renderer<ProgressRenderer>("progress", BucketCategory::OTHER, BucketId::PROGRESS,
|
|
0x1000);
|
|
init_bucket_renderer<DirectRenderer>("screen-filter", BucketCategory::OTHER,
|
|
BucketId::SCREEN_FILTER, 256);
|
|
init_bucket_renderer<TextureUploadHandler>("subtitle", BucketCategory::OTHER, BucketId::SUBTITLE,
|
|
m_texture_animator, true);
|
|
init_bucket_renderer<DirectRenderer>("debug2", BucketCategory::OTHER, BucketId::DEBUG2, 0x8000);
|
|
init_bucket_renderer<DirectRenderer>("debug-no-zbuf2", BucketCategory::OTHER,
|
|
BucketId::DEBUG_NO_ZBUF2, 0x8000);
|
|
init_bucket_renderer<DirectRenderer>("debug3", BucketCategory::OTHER, BucketId::DEBUG3, 0x2000);
|
|
|
|
auto eye_renderer = std::make_unique<EyeRenderer>("eyes", 0);
|
|
m_render_state.eye_renderer = eye_renderer.get();
|
|
m_jak2_eye_renderer = std::move(eye_renderer);
|
|
|
|
{
|
|
auto p = scoped_prof("render-inits");
|
|
// for now, for any unset renderers, just set them to an EmptyBucketRenderer.
|
|
for (size_t i = 0; i < m_bucket_renderers.size(); i++) {
|
|
if (!m_bucket_renderers[i]) {
|
|
init_bucket_renderer<EmptyBucketRenderer>(fmt::format("bucket-{}", i),
|
|
BucketCategory::OTHER, i);
|
|
}
|
|
|
|
m_bucket_renderers[i]->init_shaders(m_render_state.shaders);
|
|
m_bucket_renderers[i]->init_textures(*m_render_state.texture_pool, GameVersion::Jak2);
|
|
}
|
|
m_jak2_eye_renderer->init_shaders(m_render_state.shaders);
|
|
m_jak2_eye_renderer->init_textures(*m_render_state.texture_pool, GameVersion::Jak2);
|
|
}
|
|
}
|
|
|
|
/*!
|
|
* Construct bucket renderers. We can specify different renderers for different buckets
|
|
*/
|
|
void OpenGLRenderer::init_bucket_renderers_jak1() {
|
|
using namespace jak1;
|
|
m_bucket_renderers.resize((int)BucketId::MAX_BUCKETS);
|
|
m_bucket_categories.resize((int)BucketId::MAX_BUCKETS, BucketCategory::OTHER);
|
|
|
|
std::vector<tfrag3::TFragmentTreeKind> normal_tfrags = {tfrag3::TFragmentTreeKind::NORMAL,
|
|
tfrag3::TFragmentTreeKind::LOWRES};
|
|
std::vector<tfrag3::TFragmentTreeKind> dirt_tfrags = {tfrag3::TFragmentTreeKind::DIRT};
|
|
std::vector<tfrag3::TFragmentTreeKind> ice_tfrags = {tfrag3::TFragmentTreeKind::ICE};
|
|
auto sky_gpu_blender = std::make_shared<SkyBlendGPU>();
|
|
auto sky_cpu_blender = std::make_shared<SkyBlendCPU>();
|
|
|
|
//-------------
|
|
// PRE TEXTURE
|
|
//-------------
|
|
// 0 : ??
|
|
// 1 : ??
|
|
// 2 : ??
|
|
// 3 : SKY_DRAW
|
|
init_bucket_renderer<SkyRenderer>("sky", BucketCategory::OTHER, BucketId::SKY_DRAW);
|
|
// 4 : OCEAN_MID_AND_FAR
|
|
init_bucket_renderer<OceanMidAndFar>("ocean-mid-far", BucketCategory::OCEAN,
|
|
BucketId::OCEAN_MID_AND_FAR);
|
|
|
|
//-----------------------
|
|
// LEVEL 0 tfrag texture
|
|
//-----------------------
|
|
// 5 : TFRAG_TEX_LEVEL0
|
|
init_bucket_renderer<TextureUploadHandler>("l0-tfrag-tex", BucketCategory::TEX,
|
|
BucketId::TFRAG_TEX_LEVEL0, m_texture_animator);
|
|
// 6 : TFRAG_LEVEL0
|
|
init_bucket_renderer<TFragment>("l0-tfrag-tfrag", BucketCategory::TFRAG, BucketId::TFRAG_LEVEL0,
|
|
normal_tfrags, false, 0, anim_slot_array());
|
|
// 7 : TFRAG_NEAR_LEVEL0
|
|
// 8 : TIE_NEAR_LEVEL0
|
|
// 9 : TIE_LEVEL0
|
|
init_bucket_renderer<Tie3WithEnvmapJak1>("l0-tfrag-tie", BucketCategory::TIE,
|
|
BucketId::TIE_LEVEL0, 0);
|
|
// 10 : MERC_TFRAG_TEX_LEVEL0
|
|
init_bucket_renderer<Merc2BucketRenderer>("l0-tfrag-merc", BucketCategory::MERC,
|
|
BucketId::MERC_TFRAG_TEX_LEVEL0, m_merc2);
|
|
// 11 : GMERC_TFRAG_TEX_LEVEL0
|
|
init_bucket_renderer<Generic2BucketRenderer>("l0-tfrag-generic", BucketCategory::GENERIC,
|
|
BucketId::GENERIC_TFRAG_TEX_LEVEL0, m_generic2,
|
|
Generic2::Mode::NORMAL);
|
|
|
|
//-----------------------
|
|
// LEVEL 1 tfrag texture
|
|
//-----------------------
|
|
// 12 : TFRAG_TEX_LEVEL1
|
|
init_bucket_renderer<TextureUploadHandler>("l1-tfrag-tex", BucketCategory::TEX,
|
|
BucketId::TFRAG_TEX_LEVEL1, m_texture_animator);
|
|
// 13 : TFRAG_LEVEL1
|
|
init_bucket_renderer<TFragment>("l1-tfrag-tfrag", BucketCategory::TFRAG, BucketId::TFRAG_LEVEL1,
|
|
normal_tfrags, false, 1, anim_slot_array());
|
|
// 14 : TFRAG_NEAR_LEVEL1
|
|
// 15 : TIE_NEAR_LEVEL1
|
|
// 16 : TIE_LEVEL1
|
|
init_bucket_renderer<Tie3WithEnvmapJak1>("l1-tfrag-tie", BucketCategory::TIE,
|
|
BucketId::TIE_LEVEL1, 1);
|
|
// 17 : MERC_TFRAG_TEX_LEVEL1
|
|
init_bucket_renderer<Merc2BucketRenderer>("l1-tfrag-merc", BucketCategory::MERC,
|
|
BucketId::MERC_TFRAG_TEX_LEVEL1, m_merc2);
|
|
// 18 : GMERC_TFRAG_TEX_LEVEL1
|
|
init_bucket_renderer<Generic2BucketRenderer>("l1-tfrag-generic", BucketCategory::GENERIC,
|
|
BucketId::GENERIC_TFRAG_TEX_LEVEL1, m_generic2,
|
|
Generic2::Mode::NORMAL);
|
|
|
|
//-----------------------
|
|
// LEVEL 0 shrub texture
|
|
//-----------------------
|
|
// 19 : SHRUB_TEX_LEVEL0
|
|
init_bucket_renderer<TextureUploadHandler>("l0-shrub-tex", BucketCategory::TEX,
|
|
BucketId::SHRUB_TEX_LEVEL0, m_texture_animator);
|
|
// 20 : SHRUB_NORMAL_LEVEL0
|
|
init_bucket_renderer<Shrub>("l0-shrub", BucketCategory::SHRUB, BucketId::SHRUB_NORMAL_LEVEL0);
|
|
// 21 : ???
|
|
// 22 : SHRUB_BILLBOARD_LEVEL0
|
|
// 23 : SHRUB_TRANS_LEVEL0
|
|
// 24 : SHRUB_GENERIC_LEVEL0
|
|
init_bucket_renderer<Generic2BucketRenderer>("l0-shrub-generic", BucketCategory::GENERIC,
|
|
BucketId::SHRUB_GENERIC_LEVEL0, m_generic2,
|
|
Generic2::Mode::NORMAL);
|
|
|
|
//-----------------------
|
|
// LEVEL 1 shrub texture
|
|
//-----------------------
|
|
// 25 : SHRUB_TEX_LEVEL1
|
|
init_bucket_renderer<TextureUploadHandler>("l1-shrub-tex", BucketCategory::TEX,
|
|
BucketId::SHRUB_TEX_LEVEL1, m_texture_animator);
|
|
// 26 : SHRUB_NORMAL_LEVEL1
|
|
init_bucket_renderer<Shrub>("l1-shrub", BucketCategory::SHRUB, BucketId::SHRUB_NORMAL_LEVEL1);
|
|
// 27 : ???
|
|
// 28 : SHRUB_BILLBOARD_LEVEL1
|
|
// 29 : SHRUB_TRANS_LEVEL1
|
|
// 30 : SHRUB_GENERIC_LEVEL1
|
|
init_bucket_renderer<Generic2BucketRenderer>("l1-shrub-generic", BucketCategory::GENERIC,
|
|
BucketId::SHRUB_GENERIC_LEVEL1, m_generic2,
|
|
Generic2::Mode::NORMAL);
|
|
|
|
//-----------------------
|
|
// LEVEL 0 alpha texture
|
|
//-----------------------
|
|
init_bucket_renderer<TextureUploadHandler>("l0-alpha-tex", BucketCategory::TEX,
|
|
BucketId::ALPHA_TEX_LEVEL0, m_texture_animator); // 31
|
|
init_bucket_renderer<SkyBlendHandler>("l0-alpha-sky-blend-and-tfrag-trans", BucketCategory::OTHER,
|
|
BucketId::TFRAG_TRANS0_AND_SKY_BLEND_LEVEL0, 0,
|
|
sky_gpu_blender, sky_cpu_blender, anim_slot_array()); // 32
|
|
// 33
|
|
init_bucket_renderer<TFragment>("l0-alpha-tfrag", BucketCategory::TFRAG,
|
|
BucketId::TFRAG_DIRT_LEVEL0, dirt_tfrags, false, 0,
|
|
anim_slot_array()); // 34
|
|
// 35
|
|
init_bucket_renderer<TFragment>("l0-alpha-tfrag-ice", BucketCategory::TFRAG,
|
|
BucketId::TFRAG_ICE_LEVEL0, ice_tfrags, false, 0,
|
|
anim_slot_array());
|
|
// 37
|
|
|
|
//-----------------------
|
|
// LEVEL 1 alpha texture
|
|
//-----------------------
|
|
init_bucket_renderer<TextureUploadHandler>("l1-alpha-tex", BucketCategory::TEX,
|
|
BucketId::ALPHA_TEX_LEVEL1, m_texture_animator); // 38
|
|
init_bucket_renderer<SkyBlendHandler>("l1-alpha-sky-blend-and-tfrag-trans", BucketCategory::OTHER,
|
|
BucketId::TFRAG_TRANS1_AND_SKY_BLEND_LEVEL1, 1,
|
|
sky_gpu_blender, sky_cpu_blender, anim_slot_array()); // 39
|
|
// 40
|
|
init_bucket_renderer<TFragment>("l1-alpha-tfrag-dirt", BucketCategory::TFRAG,
|
|
BucketId::TFRAG_DIRT_LEVEL1, dirt_tfrags, false, 1,
|
|
anim_slot_array()); // 41
|
|
// 42
|
|
init_bucket_renderer<TFragment>("l1-alpha-tfrag-ice", BucketCategory::TFRAG,
|
|
BucketId::TFRAG_ICE_LEVEL1, ice_tfrags, false, 1,
|
|
anim_slot_array());
|
|
// 44
|
|
|
|
init_bucket_renderer<Merc2BucketRenderer>("common-alpha-merc", BucketCategory::MERC,
|
|
BucketId::MERC_AFTER_ALPHA, m_merc2);
|
|
|
|
init_bucket_renderer<Generic2BucketRenderer>("common-alpha-generic", BucketCategory::GENERIC,
|
|
BucketId::GENERIC_ALPHA, m_generic2,
|
|
Generic2::Mode::NORMAL); // 46
|
|
init_bucket_renderer<ShadowRenderer>("shadow", BucketCategory::OTHER, BucketId::SHADOW); // 47
|
|
|
|
//-----------------------
|
|
// LEVEL 0 pris texture
|
|
//-----------------------
|
|
init_bucket_renderer<TextureUploadHandler>("l0-pris-tex", BucketCategory::TEX,
|
|
BucketId::PRIS_TEX_LEVEL0, m_texture_animator); // 48
|
|
init_bucket_renderer<Merc2BucketRenderer>("l0-pris-merc", BucketCategory::MERC,
|
|
BucketId::MERC_PRIS_LEVEL0, m_merc2); // 49
|
|
init_bucket_renderer<Generic2BucketRenderer>("l0-pris-generic", BucketCategory::GENERIC,
|
|
BucketId::GENERIC_PRIS_LEVEL0, m_generic2,
|
|
Generic2::Mode::NORMAL); // 50
|
|
|
|
//-----------------------
|
|
// LEVEL 1 pris texture
|
|
//-----------------------
|
|
init_bucket_renderer<TextureUploadHandler>("l1-pris-tex", BucketCategory::TEX,
|
|
BucketId::PRIS_TEX_LEVEL1, m_texture_animator); // 51
|
|
init_bucket_renderer<Merc2BucketRenderer>("l1-pris-merc", BucketCategory::MERC,
|
|
BucketId::MERC_PRIS_LEVEL1, m_merc2); // 52
|
|
init_bucket_renderer<Generic2BucketRenderer>("l1-pris-generic", BucketCategory::GENERIC,
|
|
BucketId::GENERIC_PRIS_LEVEL1, m_generic2,
|
|
Generic2::Mode::NORMAL); // 53
|
|
|
|
// other renderers may output to the eye renderer
|
|
m_render_state.eye_renderer = init_bucket_renderer<EyeRenderer>(
|
|
"common-pris-eyes", BucketCategory::OTHER, BucketId::MERC_EYES_AFTER_PRIS); // 54
|
|
|
|
// hack: set to merc2 for debugging
|
|
init_bucket_renderer<Merc2BucketRenderer>("common-pris-merc", BucketCategory::MERC,
|
|
BucketId::MERC_AFTER_PRIS, m_merc2); // 55
|
|
init_bucket_renderer<Generic2BucketRenderer>("common-pris-generic", BucketCategory::GENERIC,
|
|
BucketId::GENERIC_PRIS, m_generic2,
|
|
Generic2::Mode::NORMAL); // 56
|
|
|
|
//-----------------------
|
|
// LEVEL 0 water texture
|
|
//-----------------------
|
|
init_bucket_renderer<TextureUploadHandler>("l0-water-tex", BucketCategory::TEX,
|
|
BucketId::WATER_TEX_LEVEL0, m_texture_animator); // 57
|
|
init_bucket_renderer<Merc2BucketRenderer>("l0-water-merc", BucketCategory::MERC,
|
|
BucketId::MERC_WATER_LEVEL0, m_merc2); // 58
|
|
init_bucket_renderer<Generic2BucketRenderer>("l0-water-generic", BucketCategory::GENERIC,
|
|
BucketId::GENERIC_WATER_LEVEL0, m_generic2,
|
|
Generic2::Mode::NORMAL); // 59
|
|
|
|
//-----------------------
|
|
// LEVEL 1 water texture
|
|
//-----------------------
|
|
init_bucket_renderer<TextureUploadHandler>("l1-water-tex", BucketCategory::TEX,
|
|
BucketId::WATER_TEX_LEVEL1, m_texture_animator); // 60
|
|
init_bucket_renderer<Merc2BucketRenderer>("l1-water-merc", BucketCategory::MERC,
|
|
BucketId::MERC_WATER_LEVEL1, m_merc2); // 61
|
|
init_bucket_renderer<Generic2BucketRenderer>("l1-water-generic", BucketCategory::GENERIC,
|
|
BucketId::GENERIC_WATER_LEVEL1, m_generic2,
|
|
Generic2::Mode::NORMAL); // 62
|
|
|
|
init_bucket_renderer<OceanNear>("ocean-near", BucketCategory::OCEAN, BucketId::OCEAN_NEAR); // 63
|
|
|
|
//-----------------------
|
|
// DEPTH CUE
|
|
//-----------------------
|
|
init_bucket_renderer<DepthCue>("depth-cue", BucketCategory::OTHER, BucketId::DEPTH_CUE); // 64
|
|
|
|
//-----------------------
|
|
// COMMON texture
|
|
//-----------------------
|
|
init_bucket_renderer<TextureUploadHandler>("common-tex", BucketCategory::TEX,
|
|
BucketId::PRE_SPRITE_TEX, m_texture_animator); // 65
|
|
|
|
init_bucket_renderer<Sprite3>("sprite", BucketCategory::SPRITE, BucketId::SPRITE); // 66
|
|
|
|
init_bucket_renderer<DirectRenderer>("debug", BucketCategory::OTHER, BucketId::DEBUG, 0x20000);
|
|
init_bucket_renderer<DirectRenderer>("debug-no-zbuf", BucketCategory::OTHER,
|
|
BucketId::DEBUG_NO_ZBUF, 0x8000);
|
|
// an extra custom bucket!
|
|
init_bucket_renderer<DirectRenderer>("subtitle", BucketCategory::OTHER, BucketId::SUBTITLE, 6000);
|
|
|
|
// for now, for any unset renderers, just set them to an EmptyBucketRenderer.
|
|
for (size_t i = 0; i < m_bucket_renderers.size(); i++) {
|
|
if (!m_bucket_renderers[i]) {
|
|
init_bucket_renderer<EmptyBucketRenderer>(fmt::format("bucket-{}", i), BucketCategory::OTHER,
|
|
(BucketId)i);
|
|
}
|
|
|
|
m_bucket_renderers[i]->init_shaders(m_render_state.shaders);
|
|
m_bucket_renderers[i]->init_textures(*m_render_state.texture_pool, GameVersion::Jak1);
|
|
}
|
|
sky_cpu_blender->init_textures(*m_render_state.texture_pool, m_version);
|
|
sky_gpu_blender->init_textures(*m_render_state.texture_pool, m_version);
|
|
}
|
|
|
|
namespace {
|
|
Fbo make_fbo(int w, int h, int msaa, bool make_zbuf_and_stencil) {
|
|
Fbo result;
|
|
bool use_multisample = msaa > 1;
|
|
|
|
// make framebuffer object
|
|
glGenFramebuffers(1, &result.fbo_id);
|
|
glBindFramebuffer(GL_FRAMEBUFFER, result.fbo_id);
|
|
result.valid = true;
|
|
|
|
// make texture that will hold the colors of the framebuffer
|
|
GLuint tex;
|
|
glGenTextures(1, &tex);
|
|
result.tex_id = tex;
|
|
glActiveTexture(GL_TEXTURE0);
|
|
if (use_multisample) {
|
|
glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, tex);
|
|
glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, msaa, GL_RGBA8, w, h, GL_TRUE);
|
|
} else {
|
|
glBindTexture(GL_TEXTURE_2D, tex);
|
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
|
|
}
|
|
// make depth and stencil buffers that will hold the... depth and stencil buffers
|
|
if (make_zbuf_and_stencil) {
|
|
GLuint zbuf;
|
|
glGenRenderbuffers(1, &zbuf);
|
|
result.zbuf_stencil_id = zbuf;
|
|
glBindRenderbuffer(GL_RENDERBUFFER, zbuf);
|
|
if (use_multisample) {
|
|
glRenderbufferStorageMultisample(GL_RENDERBUFFER, msaa, GL_DEPTH24_STENCIL8, w, h);
|
|
} else {
|
|
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, w, h);
|
|
}
|
|
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, zbuf);
|
|
}
|
|
// attach texture to framebuffer as target for colors
|
|
|
|
if (use_multisample) {
|
|
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D_MULTISAMPLE, tex, 0);
|
|
} else {
|
|
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex, 0);
|
|
}
|
|
|
|
GLenum render_targets[1] = {GL_COLOR_ATTACHMENT0};
|
|
glDrawBuffers(1, render_targets);
|
|
auto status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
|
|
|
|
if (status != GL_FRAMEBUFFER_COMPLETE) {
|
|
lg::error("Failed to setup framebuffer: {} {} {} {} ", w, h, msaa, make_zbuf_and_stencil);
|
|
switch (status) {
|
|
case GL_FRAMEBUFFER_UNDEFINED:
|
|
lg::print("GL_FRAMEBUFFER_UNDEFINED\n");
|
|
break;
|
|
case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT:
|
|
lg::print("GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT\n");
|
|
break;
|
|
case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT:
|
|
lg::print("GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT\n");
|
|
break;
|
|
case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER:
|
|
lg::print("GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER\n");
|
|
break;
|
|
case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER:
|
|
lg::print("GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER\n");
|
|
break;
|
|
case GL_FRAMEBUFFER_UNSUPPORTED:
|
|
lg::print("GL_FRAMEBUFFER_UNSUPPORTED\n");
|
|
break;
|
|
case GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE:
|
|
lg::print("GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE\n");
|
|
break;
|
|
case GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS:
|
|
lg::print("GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS\n");
|
|
break;
|
|
}
|
|
ASSERT(false);
|
|
}
|
|
|
|
result.multisample_count = msaa;
|
|
result.multisampled = use_multisample;
|
|
result.is_window = false;
|
|
result.width = w;
|
|
result.height = h;
|
|
|
|
return result;
|
|
}
|
|
} // namespace
|
|
|
|
void OpenGLRenderer::blit_display(ScopedProfilerNode& prof) {
|
|
if (m_blit_displays) {
|
|
m_blit_displays->do_copy_back(&m_render_state, prof);
|
|
}
|
|
}
|
|
|
|
/*!
|
|
* Main render function. This is called from the gfx loop with the chain passed from the game.
|
|
*/
|
|
void OpenGLRenderer::render(DmaFollower dma, const RenderOptions& settings) {
|
|
m_profiler.clear();
|
|
m_render_state.reset();
|
|
m_render_state.ee_main_memory = g_ee_main_mem;
|
|
m_render_state.offset_of_s7 = offset_of_s7();
|
|
|
|
{
|
|
g_current_renderer = "frame-setup";
|
|
auto prof = m_profiler.root()->make_scoped_child("frame-setup");
|
|
setup_frame(settings);
|
|
if (settings.gpu_sync) {
|
|
glFinish();
|
|
}
|
|
}
|
|
|
|
{
|
|
g_current_renderer = "loader";
|
|
auto prof = m_profiler.root()->make_scoped_child("loader");
|
|
if (m_last_pmode_alp == 0 && settings.pmode_alp_register != 0 && m_enable_fast_blackout_loads) {
|
|
// blackout, load everything and don't worry about frame rate
|
|
m_render_state.loader->update_blocking(*m_render_state.texture_pool);
|
|
|
|
} else {
|
|
m_render_state.loader->update(*m_render_state.texture_pool);
|
|
}
|
|
}
|
|
|
|
// render the buckets!
|
|
{
|
|
auto prof = m_profiler.root()->make_scoped_child("buckets");
|
|
dispatch_buckets(dma, prof, settings.gpu_sync);
|
|
if (m_texture_animator) {
|
|
// if animation requests weren't made, assume the level is unloaded and the textures should
|
|
// reset.
|
|
m_texture_animator->clear_stale_textures(m_render_state.frame_idx);
|
|
}
|
|
}
|
|
|
|
// blit framebuffer so that it can be used as a texture by the game later
|
|
{
|
|
g_current_renderer = "blit-display";
|
|
auto prof = m_profiler.root()->make_scoped_child("blit-display");
|
|
blit_display(prof);
|
|
}
|
|
|
|
// apply effects done with PCRTC registers, as well as blit the framebuffer to the window and
|
|
// apply brightness/contrast
|
|
{
|
|
g_current_renderer = "pcrtc";
|
|
auto prof = m_profiler.root()->make_scoped_child("pcrtc");
|
|
do_pcrtc_effects(settings.pmode_alp_register, settings.brightness_contrast_color,
|
|
settings.brightness_contrast_alpha, &m_render_state, prof);
|
|
if (settings.gpu_sync) {
|
|
glFinish();
|
|
}
|
|
}
|
|
|
|
m_last_pmode_alp = settings.pmode_alp_register;
|
|
|
|
if (settings.save_screenshot) {
|
|
g_current_renderer = "screenshot";
|
|
auto prof = m_profiler.root()->make_scoped_child("screenshot");
|
|
int read_buffer;
|
|
int x, y, w, h, fbo_id;
|
|
|
|
if (settings.internal_res_screenshot) {
|
|
Fbo* screenshot_src;
|
|
// can't screenshot from a multisampled buffer directly -
|
|
if (m_fbo_state.resources.resolve_buffer.valid) {
|
|
screenshot_src = &m_fbo_state.resources.resolve_buffer;
|
|
read_buffer = GL_COLOR_ATTACHMENT0;
|
|
} else {
|
|
screenshot_src = m_fbo_state.render_fbo;
|
|
read_buffer = GL_FRONT;
|
|
}
|
|
w = screenshot_src->width;
|
|
h = screenshot_src->height;
|
|
x = 0;
|
|
y = 0;
|
|
fbo_id = screenshot_src->fbo_id;
|
|
} else {
|
|
read_buffer = GL_BACK;
|
|
w = settings.draw_region_width;
|
|
h = settings.draw_region_height;
|
|
x = m_render_state.draw_offset_x;
|
|
y = m_render_state.draw_offset_y;
|
|
fbo_id = 0; // window
|
|
}
|
|
finish_screenshot(settings.screenshot_path, w, h, x, y, fbo_id, read_buffer,
|
|
settings.quick_screenshot);
|
|
}
|
|
|
|
if (settings.draw_render_debug_window) {
|
|
g_current_renderer = "render-window";
|
|
auto prof = m_profiler.root()->make_scoped_child("render-window");
|
|
draw_renderer_selection_window();
|
|
// add a profile bar for the imgui stuff
|
|
// vif_interrupt_callback(0);
|
|
if (settings.gpu_sync) {
|
|
glFinish();
|
|
}
|
|
}
|
|
|
|
if (settings.draw_loader_window) {
|
|
g_current_renderer = "loader-window";
|
|
m_render_state.loader->draw_debug_window();
|
|
}
|
|
|
|
m_profiler.finish();
|
|
// if (m_profiler.root_time() > 0.018) {
|
|
// fmt::print("Slow frame: {:.2f} ms\n", m_profiler.root_time() * 1000);
|
|
// fmt::print("{}\n", m_profiler.to_string());
|
|
// }
|
|
|
|
if (settings.draw_profiler_window) {
|
|
g_current_renderer = "profiler-window";
|
|
m_profiler.draw();
|
|
}
|
|
|
|
if (settings.draw_small_profiler_window) {
|
|
g_current_renderer = "small-profiler-window";
|
|
SmallProfilerStats stats;
|
|
stats.draw_calls = m_profiler.root()->stats().draw_calls;
|
|
stats.triangles = m_profiler.root()->stats().triangles;
|
|
for (int i = 0; i < (int)BucketCategory::MAX_CATEGORIES; i++) {
|
|
stats.time_per_category[i] = m_category_times[i];
|
|
}
|
|
m_small_profiler.draw(m_render_state.load_status_debug, stats);
|
|
}
|
|
|
|
if (settings.draw_subtitle_editor_window) {
|
|
g_current_renderer = "subtitle-editor-window";
|
|
if (m_subtitle_editor == nullptr) {
|
|
m_subtitle_editor = new SubtitleEditor();
|
|
}
|
|
m_subtitle_editor->draw_window();
|
|
}
|
|
|
|
if (settings.draw_filters_window) {
|
|
g_current_renderer = "filters-window";
|
|
m_filters_menu.draw_window();
|
|
}
|
|
|
|
if (settings.gpu_sync) {
|
|
g_current_renderer = "gpu-sync";
|
|
glFinish();
|
|
}
|
|
|
|
g_current_renderer = "end";
|
|
}
|
|
|
|
/*!
|
|
* Draw the per-renderer debug window
|
|
*/
|
|
void OpenGLRenderer::draw_renderer_selection_window() {
|
|
ImGui::Begin("Renderer Debug");
|
|
|
|
ImGui::Checkbox("Use old single-draw", &m_render_state.no_multidraw);
|
|
ImGui::SliderFloat("Fog Adjust", &m_render_state.fog_intensity, 0, 10);
|
|
ImGui::Checkbox("Sky CPU", &m_render_state.use_sky_cpu);
|
|
ImGui::Checkbox("Occlusion Cull", &m_render_state.use_occlusion_culling);
|
|
ImGui::Checkbox("Blackout Loads", &m_enable_fast_blackout_loads);
|
|
|
|
if (m_texture_animator && ImGui::TreeNode("Texture Animator")) {
|
|
m_texture_animator->draw_debug_window();
|
|
ImGui::TreePop();
|
|
}
|
|
|
|
ImGui::InputText("Renderer Filter", &m_renderer_filter);
|
|
|
|
for (size_t i = 0; i < m_bucket_renderers.size(); i++) {
|
|
auto renderer = m_bucket_renderers[i].get();
|
|
if (!m_renderer_filter.empty() && !str_util::contains(renderer->name(), m_renderer_filter)) {
|
|
continue;
|
|
}
|
|
if (renderer && !renderer->empty()) {
|
|
ImGui::PushID(i);
|
|
if (ImGui::TreeNode(renderer->name_and_id().c_str())) {
|
|
ImGui::Checkbox("Enable", &renderer->enabled());
|
|
renderer->draw_debug_window();
|
|
ImGui::TreePop();
|
|
}
|
|
ImGui::PopID();
|
|
}
|
|
}
|
|
if (ImGui::TreeNode("Texture Pool")) {
|
|
m_render_state.texture_pool->draw_debug_window();
|
|
ImGui::TreePop();
|
|
}
|
|
if (m_jak2_eye_renderer) {
|
|
if (ImGui::TreeNode("Eyes")) {
|
|
m_jak2_eye_renderer->draw_debug_window();
|
|
ImGui::TreePop();
|
|
}
|
|
}
|
|
if (m_jak3_eye_renderer) {
|
|
if (ImGui::TreeNode("Eyes")) {
|
|
m_jak3_eye_renderer->draw_debug_window();
|
|
ImGui::TreePop();
|
|
}
|
|
}
|
|
ImGui::End();
|
|
}
|
|
|
|
/*!
|
|
* Pre-render frame setup.
|
|
*/
|
|
void OpenGLRenderer::setup_frame(const RenderOptions& settings) {
|
|
// SDL controls the window framebuffer, so we just update the size:
|
|
auto& window_fb = m_fbo_state.resources.window;
|
|
|
|
bool window_resized = window_fb.width != settings.window_framebuffer_width ||
|
|
window_fb.height != settings.window_framebuffer_height;
|
|
window_fb.valid = true;
|
|
window_fb.is_window = true;
|
|
window_fb.fbo_id = 0;
|
|
window_fb.width = settings.window_framebuffer_width;
|
|
window_fb.height = settings.window_framebuffer_height;
|
|
window_fb.multisample_count = 1;
|
|
window_fb.multisampled = false;
|
|
|
|
// see if the render FBO is still applicable
|
|
if (settings.save_screenshot || window_resized || !m_fbo_state.render_fbo ||
|
|
!m_fbo_state.render_fbo->matches(settings.game_res_w, settings.game_res_h,
|
|
settings.msaa_samples)) {
|
|
// doesn't match, set up a new one for these settings
|
|
lg::info("FBO Setup: requested {}x{}, msaa {}", settings.game_res_w, settings.game_res_h,
|
|
settings.msaa_samples);
|
|
|
|
// clear old framebuffers
|
|
m_fbo_state.resources.render_buffer.clear();
|
|
m_fbo_state.resources.resolve_buffer.clear();
|
|
|
|
// NOTE: we will ALWAYS render the game to a separate framebuffer instead of directly to the
|
|
// window framebuffer.
|
|
|
|
// create a fbo to render to, with the desired settings
|
|
m_fbo_state.resources.render_buffer =
|
|
make_fbo(settings.game_res_w, settings.game_res_h, settings.msaa_samples, true);
|
|
m_fbo_state.render_fbo = &m_fbo_state.resources.render_buffer;
|
|
|
|
if (settings.msaa_samples != 1) {
|
|
lg::info("FBO Setup: using second temporary buffer: res: {}x{}", settings.game_res_w,
|
|
settings.game_res_h);
|
|
|
|
// we'll need a temporary fbo to do the msaa resolve step
|
|
// non-multisampled, and doesn't need z/stencil
|
|
m_fbo_state.resources.resolve_buffer =
|
|
make_fbo(settings.game_res_w, settings.game_res_h, 1, false);
|
|
} else {
|
|
lg::info("FBO Setup: not using second temporary buffer");
|
|
}
|
|
}
|
|
|
|
ASSERT_MSG(settings.game_res_w > 0 && settings.game_res_h > 0,
|
|
fmt::format("Bad viewport size from game_res: {}x{}\n", settings.game_res_w,
|
|
settings.game_res_h));
|
|
|
|
ASSERT_MSG(!m_fbo_state.render_fbo->is_window, "window fbo");
|
|
|
|
if (m_version == GameVersion::Jak1) {
|
|
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
|
glViewport(0, 0, m_fbo_state.resources.window.width, m_fbo_state.resources.window.height);
|
|
glClearColor(0.0, 0.0, 0.0, 0.0);
|
|
glClearDepth(0.0);
|
|
glDepthMask(GL_TRUE);
|
|
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
|
|
glDisable(GL_BLEND);
|
|
|
|
glBindFramebuffer(GL_FRAMEBUFFER, m_fbo_state.render_fbo->fbo_id);
|
|
glClearColor(0.0, 0.0, 0.0, 0.0);
|
|
glClearDepth(0.0);
|
|
glClearStencil(0);
|
|
glDepthMask(GL_TRUE);
|
|
// Note: could rely on sky renderer to clear depth and color, but this causes problems with
|
|
// letterboxing
|
|
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
|
|
glDisable(GL_BLEND);
|
|
m_render_state.stencil_dirty = false;
|
|
}
|
|
// jak 2 does the clear in BlitDisplays.cpp
|
|
|
|
// setup the draw region to letterbox later
|
|
m_render_state.draw_region_w = settings.draw_region_width;
|
|
m_render_state.draw_region_h = settings.draw_region_height;
|
|
|
|
// center the letterbox
|
|
m_render_state.draw_offset_x =
|
|
(settings.window_framebuffer_width - m_render_state.draw_region_w) / 2;
|
|
m_render_state.draw_offset_y =
|
|
(settings.window_framebuffer_height - m_render_state.draw_region_h) / 2;
|
|
|
|
m_render_state.render_fb = m_fbo_state.render_fbo->fbo_id;
|
|
|
|
if (m_render_state.draw_region_w <= 0 || m_render_state.draw_region_h <= 0) {
|
|
// trying to draw to 0 size region... opengl doesn't like this.
|
|
m_render_state.draw_region_w = 320;
|
|
m_render_state.draw_region_h = 240;
|
|
}
|
|
|
|
m_render_state.render_fb_x = 0;
|
|
m_render_state.render_fb_y = 0;
|
|
m_render_state.render_fb_w = settings.game_res_w;
|
|
m_render_state.render_fb_h = settings.game_res_h;
|
|
glViewport(0, 0, settings.game_res_w, settings.game_res_h);
|
|
}
|
|
|
|
void OpenGLRenderer::dispatch_buckets_jak1(DmaFollower dma,
|
|
ScopedProfilerNode& prof,
|
|
bool sync_after_buckets) {
|
|
// The first thing the DMA chain should be a call to a common default-registers chain.
|
|
// this chain resets the state of the GS. After this is buckets
|
|
m_category_times.fill(0);
|
|
|
|
m_render_state.buckets_base =
|
|
dma.current_tag_offset() + 16; // offset by 1 qw for the initial call
|
|
m_render_state.next_bucket = m_render_state.buckets_base;
|
|
m_render_state.bucket_for_vis_copy = (int)jak1::BucketId::TFRAG_LEVEL0;
|
|
m_render_state.num_vis_to_copy = jak1::LEVEL_MAX;
|
|
|
|
// Find the default regs buffer
|
|
auto initial_call_tag = dma.current_tag();
|
|
ASSERT(initial_call_tag.kind == DmaTag::Kind::CALL);
|
|
auto initial_call_default_regs = dma.read_and_advance();
|
|
ASSERT(initial_call_default_regs.transferred_tag == 0); // should be a nop.
|
|
m_render_state.default_regs_buffer = dma.current_tag_offset();
|
|
auto default_regs_tag = dma.current_tag();
|
|
ASSERT(default_regs_tag.kind == DmaTag::Kind::CNT);
|
|
ASSERT(default_regs_tag.qwc == 10);
|
|
// TODO verify data in here.
|
|
auto default_data = dma.read_and_advance();
|
|
ASSERT(default_data.size_bytes > 148);
|
|
memcpy(m_render_state.fog_color.data(), default_data.data + 144, 4);
|
|
auto default_ret_tag = dma.current_tag();
|
|
ASSERT(default_ret_tag.qwc == 0);
|
|
ASSERT(default_ret_tag.kind == DmaTag::Kind::RET);
|
|
dma.read_and_advance();
|
|
|
|
// now we should point to the first bucket!
|
|
ASSERT(dma.current_tag_offset() == m_render_state.next_bucket);
|
|
m_render_state.next_bucket += 16;
|
|
|
|
// loop over the buckets!
|
|
for (size_t bucket_id = 0; bucket_id < m_bucket_renderers.size(); bucket_id++) {
|
|
auto& renderer = m_bucket_renderers[bucket_id];
|
|
auto bucket_prof = prof.make_scoped_child(renderer->name_and_id());
|
|
g_current_renderer = renderer->name_and_id();
|
|
// lg::info("Render: {} start", g_current_renderer);
|
|
renderer->render(dma, &m_render_state, bucket_prof);
|
|
if (sync_after_buckets) {
|
|
auto pp = scoped_prof("finish");
|
|
glFinish();
|
|
}
|
|
|
|
// lg::info("Render: {} end", g_current_renderer);
|
|
// should have ended at the start of the next chain
|
|
ASSERT(dma.current_tag_offset() == m_render_state.next_bucket);
|
|
m_render_state.next_bucket += 16;
|
|
vif_interrupt_callback(bucket_id);
|
|
m_category_times[(int)m_bucket_categories[bucket_id]] += bucket_prof.get_elapsed_time();
|
|
|
|
// hack to draw the collision mesh in the middle the drawing
|
|
if (bucket_id == 31 - 1 && Gfx::g_global_settings.collision_enable) {
|
|
auto p = prof.make_scoped_child("collision-draw");
|
|
m_collide_renderer.render(&m_render_state, p);
|
|
}
|
|
}
|
|
|
|
// TODO ending data.
|
|
}
|
|
|
|
void OpenGLRenderer::dispatch_buckets_jak2(DmaFollower dma,
|
|
ScopedProfilerNode& prof,
|
|
bool sync_after_buckets) {
|
|
// The first thing the DMA chain should be a call to a common default-registers chain.
|
|
// this chain resets the state of the GS. After this is buckets
|
|
m_category_times.fill(0);
|
|
|
|
m_render_state.buckets_base = dma.current_tag_offset(); // starts at 0 in jak 2
|
|
m_render_state.next_bucket = m_render_state.buckets_base + 16;
|
|
m_render_state.bucket_for_vis_copy = (int)jak2::BucketId::BUCKET_2;
|
|
m_render_state.num_vis_to_copy = jak2::LEVEL_MAX;
|
|
|
|
for (size_t bucket_id = 0; bucket_id < m_bucket_renderers.size(); bucket_id++) {
|
|
auto& renderer = m_bucket_renderers[bucket_id];
|
|
auto bucket_prof = prof.make_scoped_child(renderer->name_and_id());
|
|
g_current_renderer = renderer->name_and_id();
|
|
// lg::info("Render: {} start", g_current_renderer);
|
|
renderer->render(dma, &m_render_state, bucket_prof);
|
|
if (sync_after_buckets) {
|
|
auto pp = scoped_prof("finish");
|
|
glFinish();
|
|
}
|
|
|
|
// lg::info("Render: {} end", g_current_renderer);
|
|
// should have ended at the start of the next chain
|
|
ASSERT(dma.current_tag_offset() == m_render_state.next_bucket);
|
|
m_render_state.next_bucket += 16;
|
|
vif_interrupt_callback(bucket_id + 1);
|
|
m_category_times[(int)m_bucket_categories[bucket_id]] += bucket_prof.get_elapsed_time();
|
|
|
|
// hack to draw the collision mesh in the middle the drawing
|
|
if (bucket_id + 1 == (int)jak2::BucketId::TEX_L0_ALPHA &&
|
|
Gfx::g_global_settings.collision_enable) {
|
|
auto p = prof.make_scoped_child("collision-draw");
|
|
m_collide_renderer.render(&m_render_state, p);
|
|
}
|
|
}
|
|
vif_interrupt_callback(m_bucket_renderers.size());
|
|
|
|
// TODO ending data.
|
|
}
|
|
|
|
void OpenGLRenderer::dispatch_buckets_jak3(DmaFollower dma,
|
|
ScopedProfilerNode& prof,
|
|
bool sync_after_buckets) {
|
|
// The first thing the DMA chain should be a call to a common default-registers chain.
|
|
// this chain resets the state of the GS. After this is buckets
|
|
m_category_times.fill(0);
|
|
|
|
m_render_state.buckets_base = dma.current_tag_offset(); // starts at 0 in jak 2
|
|
m_render_state.next_bucket = m_render_state.buckets_base + 16;
|
|
m_render_state.bucket_for_vis_copy = (int)jak3::BucketId::BUCKET_2;
|
|
m_render_state.num_vis_to_copy = jak3::LEVEL_MAX;
|
|
|
|
for (size_t bucket_id = 0; bucket_id < m_bucket_renderers.size(); bucket_id++) {
|
|
auto& renderer = m_bucket_renderers[bucket_id];
|
|
auto bucket_prof = prof.make_scoped_child(renderer->name_and_id());
|
|
g_current_renderer = renderer->name_and_id();
|
|
// lg::info("Render: {} start", g_current_renderer);
|
|
renderer->render(dma, &m_render_state, bucket_prof);
|
|
if (sync_after_buckets) {
|
|
auto pp = scoped_prof("finish");
|
|
glFinish();
|
|
}
|
|
|
|
// lg::info("Render: {} end", g_current_renderer);
|
|
// should have ended at the start of the next chain
|
|
ASSERT(dma.current_tag_offset() == m_render_state.next_bucket);
|
|
m_render_state.next_bucket += 16;
|
|
vif_interrupt_callback(bucket_id + 1);
|
|
m_category_times[(int)m_bucket_categories[bucket_id]] += bucket_prof.get_elapsed_time();
|
|
|
|
// hack to draw the collision mesh in the middle the drawing
|
|
if (bucket_id + 1 == (int)jak3::BucketId::TEX_L0_ALPHA &&
|
|
Gfx::g_global_settings.collision_enable) {
|
|
auto p = prof.make_scoped_child("collision-draw");
|
|
m_collide_renderer.render(&m_render_state, p);
|
|
}
|
|
|
|
if (bucket_id == (int)jak3::BucketId::TEX_HUD_HUD_ALPHA) {
|
|
auto p = prof.make_scoped_child("color-filter");
|
|
m_blit_displays->apply_color_filter(&m_render_state, p);
|
|
}
|
|
}
|
|
vif_interrupt_callback(m_bucket_renderers.size());
|
|
|
|
// TODO ending data.
|
|
}
|
|
|
|
/*!
|
|
* This function finds buckets and dispatches them to the appropriate part.
|
|
*/
|
|
void OpenGLRenderer::dispatch_buckets(DmaFollower dma,
|
|
ScopedProfilerNode& prof,
|
|
bool sync_after_buckets) {
|
|
g_current_renderer = "dispatch-buckets pre";
|
|
|
|
m_render_state.version = m_version;
|
|
m_render_state.frame_idx++;
|
|
switch (m_version) {
|
|
case GameVersion::Jak1:
|
|
dispatch_buckets_jak1(dma, prof, sync_after_buckets);
|
|
break;
|
|
case GameVersion::Jak2:
|
|
dispatch_buckets_jak2(dma, prof, sync_after_buckets);
|
|
break;
|
|
case GameVersion::Jak3:
|
|
case GameVersion::JakX:
|
|
dispatch_buckets_jak3(dma, prof, sync_after_buckets);
|
|
break;
|
|
default:
|
|
ASSERT(false);
|
|
}
|
|
|
|
g_current_renderer = "dispatch-buckets post";
|
|
}
|
|
|
|
#ifdef _WIN32
|
|
void win_print_last_error(const std::string& msg) {
|
|
LPSTR lpMsgBuf;
|
|
|
|
FormatMessage(
|
|
FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS,
|
|
NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&lpMsgBuf, 0, NULL);
|
|
|
|
lg::error("[OpenGLRenderer] {} Win Err: {}", msg, lpMsgBuf);
|
|
}
|
|
|
|
void copy_texture_to_clipboard(int width, int height, const std::vector<u32>& texture_data) {
|
|
std::vector<u32> data(texture_data);
|
|
|
|
// BGR -> RGB
|
|
for (auto& px : data) {
|
|
u8 r = px >> 0;
|
|
u8 g = px >> 8;
|
|
u8 b = px >> 16;
|
|
u8 a = px >> 24;
|
|
px = (a << 24) | (r << 16) | (g << 8) | (b << 0);
|
|
}
|
|
|
|
// Calculate the total size of the image data
|
|
size_t image_size = data.size() * sizeof(u32);
|
|
|
|
// BMP/DIB file header
|
|
BITMAPINFOHEADER header;
|
|
header.biSize = sizeof(header);
|
|
header.biWidth = width;
|
|
header.biHeight = height;
|
|
header.biPlanes = 1;
|
|
header.biBitCount = 32;
|
|
header.biCompression = BI_RGB;
|
|
header.biSizeImage = 0;
|
|
header.biXPelsPerMeter = 0;
|
|
header.biYPelsPerMeter = 0;
|
|
header.biClrUsed = 0;
|
|
header.biClrImportant = 0;
|
|
|
|
// Open the clipboard
|
|
if (!OpenClipboard(NULL)) {
|
|
win_print_last_error("Failed to open the clipboard.");
|
|
return;
|
|
}
|
|
|
|
// Empty the clipboard
|
|
if (!EmptyClipboard()) {
|
|
win_print_last_error("Failed to empty the clipboard.");
|
|
CloseClipboard();
|
|
return;
|
|
}
|
|
|
|
// Create a global memory object to hold the image data
|
|
HGLOBAL hClipboardData = GlobalAlloc(GMEM_MOVEABLE, sizeof(header) + image_size);
|
|
if (hClipboardData == NULL) {
|
|
win_print_last_error("Failed to allocate memory for clipboard data.");
|
|
CloseClipboard();
|
|
return;
|
|
}
|
|
|
|
// Get a pointer to the global memory object
|
|
void* pData = GlobalLock(hClipboardData);
|
|
if (pData == NULL) {
|
|
win_print_last_error("Failed to lock clipboard memory.");
|
|
CloseClipboard();
|
|
GlobalFree(hClipboardData);
|
|
return;
|
|
}
|
|
|
|
// Copy the image data into the global memory object
|
|
memcpy(pData, &header, sizeof(header));
|
|
memcpy((char*)pData + sizeof(header), data.data(), image_size);
|
|
|
|
// Unlock the global memory object
|
|
if (!GlobalUnlock(hClipboardData) && GetLastError() != NO_ERROR) {
|
|
win_print_last_error("Failed to unlock memory.");
|
|
CloseClipboard();
|
|
GlobalFree(hClipboardData);
|
|
return;
|
|
}
|
|
|
|
// Set the image data to clipboard
|
|
if (!SetClipboardData(CF_DIB, hClipboardData)) {
|
|
win_print_last_error("Failed to set clipboard data.");
|
|
CloseClipboard();
|
|
GlobalFree(hClipboardData);
|
|
return;
|
|
}
|
|
|
|
// Close the clipboard
|
|
CloseClipboard();
|
|
GlobalFree(hClipboardData);
|
|
|
|
lg::info("Image data copied to clipboard successfully!");
|
|
}
|
|
#endif
|
|
|
|
/*!
|
|
* Take a screenshot!
|
|
*/
|
|
void OpenGLRenderer::finish_screenshot(const std::string& output_name,
|
|
int width,
|
|
int height,
|
|
int x,
|
|
int y,
|
|
GLuint fbo,
|
|
int read_buffer,
|
|
bool quick_screenshot) {
|
|
std::vector<u32> buffer(width * height);
|
|
glPixelStorei(GL_PACK_ALIGNMENT, 1);
|
|
GLint oldbuf, oldreadbuf;
|
|
glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &oldbuf);
|
|
glGetIntegerv(GL_READ_BUFFER, &oldreadbuf);
|
|
glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo);
|
|
glReadBuffer(read_buffer);
|
|
glReadPixels(x, y, width, height, GL_RGBA, GL_UNSIGNED_BYTE, buffer.data());
|
|
|
|
// set alpha. our renderers mess this up in a way that isn't relevant to the final framebuffer.
|
|
for (auto& px : buffer) {
|
|
px |= 0xff000000;
|
|
}
|
|
|
|
#ifdef _WIN32
|
|
if (quick_screenshot) {
|
|
// copy to clipboard (windows only)
|
|
copy_texture_to_clipboard(width, height, buffer);
|
|
}
|
|
#else
|
|
(void)quick_screenshot;
|
|
lg::error("screenshot to clipboard NYI non-Windows");
|
|
#endif
|
|
|
|
// flip upside down in place
|
|
for (int h = 0; h < height / 2; h++) {
|
|
for (int w = 0; w < width; w++) {
|
|
std::swap(buffer[h * width + w], buffer[(height - h - 1) * width + w]);
|
|
}
|
|
}
|
|
|
|
file_util::write_rgba_png(output_name, buffer.data(), width, height);
|
|
glReadBuffer(oldreadbuf);
|
|
glBindFramebuffer(GL_READ_FRAMEBUFFER, oldbuf);
|
|
}
|
|
|
|
void OpenGLRenderer::do_pcrtc_effects(float alp,
|
|
int brightness_contrast_color,
|
|
int brightness_contrast_alpha,
|
|
SharedRenderState* render_state,
|
|
ScopedProfilerNode& prof) {
|
|
Fbo* window_blit_src = nullptr;
|
|
if (m_fbo_state.resources.resolve_buffer.valid) {
|
|
glBindFramebuffer(GL_READ_FRAMEBUFFER, m_fbo_state.render_fbo->fbo_id);
|
|
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_fbo_state.resources.resolve_buffer.fbo_id);
|
|
glBlitFramebuffer(0, // srcX0
|
|
0, // srcY0
|
|
m_fbo_state.render_fbo->width, // srcX1
|
|
m_fbo_state.render_fbo->height, // srcY1
|
|
0, // dstX0
|
|
0, // dstY0
|
|
m_fbo_state.resources.resolve_buffer.width, // dstX1
|
|
m_fbo_state.resources.resolve_buffer.height, // dstY1
|
|
GL_COLOR_BUFFER_BIT, // mask
|
|
GL_LINEAR // filter
|
|
);
|
|
window_blit_src = &m_fbo_state.resources.resolve_buffer;
|
|
} else {
|
|
window_blit_src = &m_fbo_state.resources.render_buffer;
|
|
}
|
|
|
|
glDisable(GL_DEPTH_TEST);
|
|
glDisable(GL_BLEND);
|
|
glViewport(render_state->draw_offset_x, render_state->draw_offset_y, render_state->draw_region_w,
|
|
render_state->draw_region_h);
|
|
glBindTexture(GL_TEXTURE_2D, *window_blit_src->tex_id);
|
|
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
|
|
|
glBindVertexArray(screen_vao);
|
|
glBindBuffer(GL_ARRAY_BUFFER, screen_vbo);
|
|
|
|
float color = (float)brightness_contrast_color / 128.0f;
|
|
float alpha = (float)brightness_contrast_alpha / 128.0f;
|
|
auto& shader = render_state->shaders[ShaderId::POST_PROCESSING];
|
|
shader.activate();
|
|
glUniform1i(glGetUniformLocation(shader.id(), "tex_T0"), 0);
|
|
if (brightness_contrast_color < 0) {
|
|
// subtractive blend - note that color is already negative
|
|
float color_neg = color * alpha;
|
|
glUniform4f(glGetUniformLocation(shader.id(), "color_mult"), 1.0f, 1.0f, 1.0f, alpha);
|
|
glUniform4f(glGetUniformLocation(shader.id(), "color_add"), color_neg, color_neg, color_neg,
|
|
0.0f);
|
|
} else {
|
|
// additive blend
|
|
glUniform4f(glGetUniformLocation(shader.id(), "color_mult"), 1.0f, 1.0f, 1.0f, alpha);
|
|
glUniform4f(glGetUniformLocation(shader.id(), "color_add"), color, color, color, 0.0f);
|
|
}
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
|
glActiveTexture(GL_TEXTURE0);
|
|
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
|
|
|
|
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
|
glBindVertexArray(0);
|
|
|
|
glEnable(GL_BLEND);
|
|
if (alp < 1) {
|
|
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ZERO);
|
|
glBlendEquation(GL_FUNC_ADD);
|
|
|
|
m_blackout_renderer.draw(Vector4f(0, 0, 0, 1.f - alp), render_state, prof);
|
|
}
|
|
glEnable(GL_DEPTH_TEST);
|
|
}
|