mirror of
https://github.com/open-goal/jak-project.git
synced 2024-11-23 14:20:07 +00:00
[jak 2] Clouds V2, Clean up texture animator (#2921)
Some general improvements for the texture animator: - Clouds are special cased, saving about 1 ms per frame - Adjusting the amount of clouds now actually works. - Fixed an issue with the brightness of clouds, and the way that they fade out around the edges.
This commit is contained in:
parent
66e48195cb
commit
3b29da919b
@ -19,7 +19,9 @@ class Vector {
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
explicit Vector(Args... args) : m_data{T(args)...} {}
|
||||
constexpr Vector(Args... args) : m_data{T(args)...} {
|
||||
static_assert(sizeof...(args) == Size, "Incorrect number of args");
|
||||
}
|
||||
|
||||
T* begin() { return &m_data[0]; }
|
||||
T* end() { return &m_data[Size]; }
|
||||
|
@ -1,5 +1,6 @@
|
||||
#include "CollideMeshRenderer.h"
|
||||
|
||||
#include "game/graphics/gfx.h"
|
||||
#include "game/graphics/opengl_renderer/background/background_common.h"
|
||||
|
||||
float material_colors_jak1[23 * 3] = {
|
||||
|
@ -1234,8 +1234,8 @@ void DirectRenderer::handle_xyzf2_common(u32 x,
|
||||
auto& corner2_stq = m_prim_building.building_stq[1];
|
||||
|
||||
// should use most recent vertex z.
|
||||
math::Vector<u32, 4> corner3_vert{corner1_vert[0], corner2_vert[1], corner2_vert[2]};
|
||||
math::Vector<u32, 4> corner4_vert{corner2_vert[0], corner1_vert[1], corner2_vert[2]};
|
||||
math::Vector<u32, 4> corner3_vert{corner1_vert[0], corner2_vert[1], corner2_vert[2], 0};
|
||||
math::Vector<u32, 4> corner4_vert{corner2_vert[0], corner1_vert[1], corner2_vert[2], 0};
|
||||
math::Vector<float, 3> corner3_stq{corner1_stq[0], corner2_stq[1], corner2_stq[2]};
|
||||
math::Vector<float, 3> corner4_stq{corner2_stq[0], corner1_stq[1], corner2_stq[2]};
|
||||
|
||||
|
@ -355,7 +355,9 @@ TextureAnimator::TextureAnimator(ShaderLibrary& shaders, const tfrag3::Level* co
|
||||
m_psm32_to_psm8_8_8(8, 8, 8, 64),
|
||||
m_psm32_to_psm8_16_16(16, 16, 16, 64),
|
||||
m_psm32_to_psm8_32_32(32, 32, 16, 64),
|
||||
m_psm32_to_psm8_64_64(64, 64, 64, 64) {
|
||||
m_psm32_to_psm8_64_64(64, 64, 64, 64),
|
||||
m_sky_blend_texture(kFinalSkyTextureSize, kFinalSkyTextureSize, GL_UNSIGNED_INT_8_8_8_8_REV),
|
||||
m_sky_final_texture(kFinalSkyTextureSize, kFinalSkyTextureSize, GL_UNSIGNED_INT_8_8_8_8_REV) {
|
||||
glGenVertexArrays(1, &m_vao);
|
||||
glGenBuffers(1, &m_vertex_buffer);
|
||||
glBindVertexArray(m_vao);
|
||||
@ -389,6 +391,8 @@ TextureAnimator::TextureAnimator(ShaderLibrary& shaders, const tfrag3::Level* co
|
||||
m_uniforms.channel_scramble = glGetUniformLocation(shader.id(), "channel_scramble");
|
||||
m_uniforms.tcc = glGetUniformLocation(shader.id(), "tcc");
|
||||
m_uniforms.alpha_multiply = glGetUniformLocation(shader.id(), "alpha_multiply");
|
||||
m_uniforms.minimum = glGetUniformLocation(shader.id(), "minimum");
|
||||
m_uniforms.maximum = glGetUniformLocation(shader.id(), "maximum");
|
||||
|
||||
// create a single "dummy texture" with all 0 data.
|
||||
// this is faster and easier than switching shaders to one without texturing, and is used
|
||||
@ -433,6 +437,8 @@ TextureAnimator::TextureAnimator(ShaderLibrary& shaders, const tfrag3::Level* co
|
||||
|
||||
// animation-specific stuff
|
||||
setup_texture_anims();
|
||||
|
||||
setup_sky();
|
||||
}
|
||||
|
||||
/*!
|
||||
@ -478,6 +484,25 @@ int TextureAnimator::create_fixed_anim_array(const std::vector<FixedAnimDef>& de
|
||||
void TextureAnimator::draw_debug_window() {
|
||||
ImGui::Checkbox("fast-scrambler", &m_debug.use_fast_scrambler);
|
||||
|
||||
ImGui::Text("Sky:");
|
||||
ImGui::Text("Fog Height: %f", m_debug_sky_input.fog_height);
|
||||
ImGui::Text("Cloud minmax: %f %f", m_debug_sky_input.cloud_min, m_debug_sky_input.cloud_max);
|
||||
for (int i = 0; i < 9; i++) {
|
||||
ImGui::Text("Time[%d]: %f", i, m_debug_sky_input.times[i]);
|
||||
}
|
||||
ImGui::Text("Dest %d", m_debug_sky_input.cloud_dest);
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, m_sky_blend_texture.texture());
|
||||
int w, h;
|
||||
glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &w);
|
||||
glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &h);
|
||||
ImGui::Image((void*)(u64)m_sky_blend_texture.texture(), ImVec2(w, h));
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, m_sky_final_texture.texture());
|
||||
glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &w);
|
||||
glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &h);
|
||||
ImGui::Image((void*)(u64)m_sky_final_texture.texture(), ImVec2(w, h));
|
||||
|
||||
auto& slots = jak2_animated_texture_slots();
|
||||
for (size_t i = 0; i < slots.size(); i++) {
|
||||
ImGui::Text("Slot %d %s (%d)", (int)i, slots[i].c_str(), (int)m_private_output_slots[i]);
|
||||
@ -552,9 +577,6 @@ enum PcTextureAnimCodes {
|
||||
GENERIC_UPLOAD = 16,
|
||||
SET_SHADER = 17,
|
||||
DRAW = 18,
|
||||
MOVE_RG_TO_BA = 19,
|
||||
SET_CLUT_ALPHA = 20,
|
||||
COPY_CLUT_ALPHA = 21,
|
||||
DARKJAK = 22,
|
||||
PRISON_JAK = 23,
|
||||
ORACLE_JAK = 24,
|
||||
@ -574,6 +596,7 @@ enum PcTextureAnimCodes {
|
||||
METKOR = 38,
|
||||
SHIELD = 39,
|
||||
KREW_HOLO = 40,
|
||||
CLOUDS_AND_FOG = 41,
|
||||
};
|
||||
|
||||
// metadata for an upload from GOAL memory
|
||||
@ -616,7 +639,7 @@ void TextureAnimator::handle_texture_anim_data(DmaFollower& dma,
|
||||
m_opengl_texture_pool.free(t.tex, t.w, t.h);
|
||||
}
|
||||
m_in_use_temp_textures.clear(); // reset temp texture allocator.
|
||||
m_erased_on_this_frame.clear();
|
||||
m_force_to_gpu.clear();
|
||||
m_skip_tbps.clear();
|
||||
|
||||
// loop over DMA, and do the appropriate texture operations.
|
||||
@ -653,18 +676,6 @@ void TextureAnimator::handle_texture_anim_data(DmaFollower& dma,
|
||||
case FINISH_ARRAY:
|
||||
done = true;
|
||||
break;
|
||||
case MOVE_RG_TO_BA: {
|
||||
auto p = scoped_prof("rg-to-ba");
|
||||
handle_rg_to_ba(tf);
|
||||
} break;
|
||||
case SET_CLUT_ALPHA: {
|
||||
auto p = scoped_prof("set-clut-alpha");
|
||||
handle_set_clut_alpha(tf);
|
||||
} break;
|
||||
case COPY_CLUT_ALPHA: {
|
||||
auto p = scoped_prof("copy-clut-alpha");
|
||||
handle_copy_clut_alpha(tf);
|
||||
} break;
|
||||
case DARKJAK: {
|
||||
auto p = scoped_prof("darkjak");
|
||||
run_clut_blender_group(tf, m_darkjak_clut_blender_idx, frame_idx);
|
||||
@ -741,6 +752,10 @@ void TextureAnimator::handle_texture_anim_data(DmaFollower& dma,
|
||||
auto p = scoped_prof("krew-holo");
|
||||
run_fixed_animation_array(m_krew_holo_anim_array_idx, tf, texture_pool);
|
||||
} break;
|
||||
case CLOUDS_AND_FOG: {
|
||||
auto p = scoped_prof("clouds-and-fog");
|
||||
handle_clouds_and_fog(tf, texture_pool);
|
||||
} break;
|
||||
default:
|
||||
fmt::print("bad imm: {}\n", vif0.immediate);
|
||||
ASSERT_NOT_REACHED();
|
||||
@ -754,11 +769,10 @@ void TextureAnimator::handle_texture_anim_data(DmaFollower& dma,
|
||||
}
|
||||
|
||||
// The steps above will populate m_textures with some combination of GPU/CPU textures.
|
||||
// we need to make sure that all final textures end up on the GPU. For now, we detect this by
|
||||
// seeing if the "erase" operation ran on an tbp, indicating that it was cleared, which is
|
||||
// always done to all textures by the GOAL code.
|
||||
for (auto tbp : m_erased_on_this_frame) {
|
||||
auto p = scoped_prof("handle-one-erased");
|
||||
// we need to make sure that all final textures end up on the GPU, if desired. (todo: move this to
|
||||
// happen somewhere else)?
|
||||
for (auto tbp : m_force_to_gpu) {
|
||||
auto p = scoped_prof("force-to-gpu");
|
||||
force_to_gpu(tbp);
|
||||
}
|
||||
|
||||
@ -937,133 +951,6 @@ void debug_save_opengl_texture(const std::string& out, GLuint texture) {
|
||||
file_util::write_rgba_png(out, data.data(), w, h);
|
||||
}
|
||||
|
||||
/*!
|
||||
* Copy rg channels to ba from src to dst.
|
||||
* The PS2 implementation is confusing, and this is just a guess at how it works.
|
||||
*/
|
||||
void TextureAnimator::handle_rg_to_ba(const DmaTransfer& tf) {
|
||||
dprintf("[tex anim] rg -> ba\n");
|
||||
ASSERT(tf.size_bytes == sizeof(TextureAnimPcTransform));
|
||||
auto* data = (const TextureAnimPcTransform*)(tf.data);
|
||||
dprintf(" src: %d, dest: %d\n", data->src_tbp, data->dst_tbp);
|
||||
const auto& src = m_textures.find(data->src_tbp);
|
||||
const auto& dst = m_textures.find(data->dst_tbp);
|
||||
if (src != m_textures.end() && dst != m_textures.end()) {
|
||||
ASSERT(src->second.kind == VramEntry::Kind::GPU);
|
||||
ASSERT(dst->second.kind == VramEntry::Kind::GPU);
|
||||
ASSERT(src->second.tex.value().texture() != dst->second.tex.value().texture());
|
||||
FramebufferTexturePairContext ctxt(dst->second.tex.value());
|
||||
float positions[3 * 4] = {0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0};
|
||||
float uvs[2 * 4] = {0, 0, 1, 0, 1, 1, 0, 1};
|
||||
glUniform3fv(m_uniforms.positions, 4, positions);
|
||||
glUniform2fv(m_uniforms.uvs, 4, uvs);
|
||||
glUniform1i(m_uniforms.enable_tex, 1);
|
||||
glUniform4f(m_uniforms.rgba, 256, 256, 256, 128); // TODO - seems wrong.
|
||||
glUniform4i(m_uniforms.channel_scramble, 0, 1, 0, 1);
|
||||
// not sure if this is right or not: the entire cloud stuff is kinda broken.
|
||||
glUniform1f(m_uniforms.alpha_multiply, 1.f);
|
||||
glBindTexture(GL_TEXTURE_2D, src->second.tex.value().texture());
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glDisable(GL_BLEND);
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
glColorMask(true, true, true, true);
|
||||
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
|
||||
|
||||
} else {
|
||||
ASSERT_NOT_REACHED();
|
||||
}
|
||||
|
||||
// const auto& vram_src = m_vram_entries.find(data->src_tbp);
|
||||
// if (vram_src != m_vram_entries.end()) {
|
||||
// ASSERT(vram_src->second.kind == VramEntry::Kind::GENERIC_PSM32);
|
||||
// // no idea if this is right, but lets try.
|
||||
// int w = vram_src->second.width;
|
||||
// int h = vram_src->second.height;
|
||||
// u8* tdata = vram_src->second.data_psm32.data();
|
||||
//
|
||||
// // file_util::write_rgba_png("./before_transform.png", tdata, w, h);
|
||||
//
|
||||
// for (int i = 0; i < w * h; i++) {
|
||||
// tdata[i * 4 + 2] = tdata[i * 4];
|
||||
// tdata[i * 4 + 3] = tdata[i * 4 + 1];
|
||||
// }
|
||||
//
|
||||
// // file_util::write_rgba_png("./after_transform.png", tdata, w, h);
|
||||
//
|
||||
// } else {
|
||||
// ASSERT_NOT_REACHED();
|
||||
// }
|
||||
}
|
||||
|
||||
void TextureAnimator::handle_set_clut_alpha(const DmaTransfer& tf) {
|
||||
ASSERT_NOT_REACHED(); // TODO: if re-enabling, needs alpha multiplier stuff
|
||||
glUniform1f(m_uniforms.alpha_multiply, 1.f);
|
||||
|
||||
dprintf("[tex anim] set clut alpha\n");
|
||||
ASSERT(tf.size_bytes == sizeof(TextureAnimPcTransform));
|
||||
auto* data = (const TextureAnimPcTransform*)(tf.data);
|
||||
dprintf(" src: %d, dest: %d\n", data->src_tbp, data->dst_tbp);
|
||||
const auto& tex = m_textures.find(data->dst_tbp);
|
||||
ASSERT(tex != m_textures.end());
|
||||
|
||||
ASSERT(tex->second.kind == VramEntry::Kind::GPU);
|
||||
FramebufferTexturePairContext ctxt(tex->second.tex.value());
|
||||
float positions[3 * 4] = {0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0};
|
||||
float uvs[2 * 4] = {0, 0, 1, 0, 1, 1, 0, 1};
|
||||
glUniform3fv(m_uniforms.positions, 4, positions);
|
||||
glUniform2fv(m_uniforms.uvs, 4, uvs);
|
||||
glUniform1i(m_uniforms.enable_tex, 0); // NO TEXTURE!
|
||||
glUniform4f(m_uniforms.rgba, 128, 128, 128, 128);
|
||||
glUniform4i(m_uniforms.channel_scramble, 0, 1, 2, 3);
|
||||
glBindTexture(GL_TEXTURE_2D, m_dummy_texture);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glDisable(GL_BLEND);
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
glColorMask(false, false, false, true);
|
||||
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
|
||||
glColorMask(true, true, true, true);
|
||||
}
|
||||
|
||||
void TextureAnimator::handle_copy_clut_alpha(const DmaTransfer& tf) {
|
||||
ASSERT_NOT_REACHED(); // TODO: if re-enabling, needs alpha multiplier stuff
|
||||
glUniform1f(m_uniforms.alpha_multiply, 1.f);
|
||||
|
||||
dprintf("[tex anim] __copy__ clut alpha\n");
|
||||
ASSERT(tf.size_bytes == sizeof(TextureAnimPcTransform));
|
||||
auto* data = (const TextureAnimPcTransform*)(tf.data);
|
||||
dprintf(" src: %d, dest: %d\n", data->src_tbp, data->dst_tbp);
|
||||
const auto& dst_tex = m_textures.find(data->dst_tbp);
|
||||
const auto& src_tex = m_textures.find(data->src_tbp);
|
||||
ASSERT(dst_tex != m_textures.end());
|
||||
if (src_tex == m_textures.end()) {
|
||||
lg::error("Skipping copy clut alpha because source texture at {} wasn't found", data->src_tbp);
|
||||
return;
|
||||
}
|
||||
ASSERT(src_tex != m_textures.end());
|
||||
|
||||
ASSERT(dst_tex->second.kind == VramEntry::Kind::GPU);
|
||||
ASSERT(src_tex->second.kind == VramEntry::Kind::GPU);
|
||||
|
||||
FramebufferTexturePairContext ctxt(dst_tex->second.tex.value());
|
||||
float positions[3 * 4] = {0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0};
|
||||
float uvs[2 * 4] = {0, 0, 1, 0, 1, 1, 0, 1};
|
||||
glUniform3fv(m_uniforms.positions, 4, positions);
|
||||
glUniform2fv(m_uniforms.uvs, 4, uvs);
|
||||
glUniform1i(m_uniforms.enable_tex, 1);
|
||||
glUniform4f(m_uniforms.rgba, 128, 128, 128, 128); // TODO - seems wrong.
|
||||
glUniform4i(m_uniforms.channel_scramble, 0, 1, 2, 3);
|
||||
glBindTexture(GL_TEXTURE_2D, src_tex->second.tex.value().texture());
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glDisable(GL_BLEND);
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
glColorMask(false, false, false, true);
|
||||
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
|
||||
glColorMask(true, true, true, true);
|
||||
}
|
||||
|
||||
void TextureAnimator::run_clut_blender_group(DmaTransfer& tf, int idx, u64 frame_idx) {
|
||||
float f;
|
||||
ASSERT(tf.size_bytes == 16);
|
||||
@ -1076,6 +963,27 @@ void TextureAnimator::run_clut_blender_group(DmaTransfer& tf, int idx, u64 frame
|
||||
}
|
||||
}
|
||||
|
||||
void TextureAnimator::handle_clouds_and_fog(const DmaTransfer& tf, TexturePool* texture_pool) {
|
||||
ASSERT(tf.size_bytes >= sizeof(SkyInput));
|
||||
SkyInput input;
|
||||
memcpy(&input, tf.data, sizeof(SkyInput));
|
||||
auto tex = run_clouds(input);
|
||||
|
||||
if (m_sky_pool_gpu_tex) {
|
||||
texture_pool->move_existing_to_vram(m_sky_pool_gpu_tex, input.cloud_dest);
|
||||
ASSERT(texture_pool->lookup(input.cloud_dest).value() == tex);
|
||||
} else {
|
||||
TextureInput in;
|
||||
in.gpu_texture = tex;
|
||||
in.w = kFinalSkyTextureSize;
|
||||
in.h = kFinalSkyTextureSize;
|
||||
in.debug_page_name = "PC-ANIM";
|
||||
in.debug_name = "clouds";
|
||||
in.id = get_id_for_tbp(texture_pool, input.cloud_dest, 777);
|
||||
m_sky_pool_gpu_tex = texture_pool->give_texture_and_load_to_vram(in, input.cloud_dest);
|
||||
}
|
||||
}
|
||||
|
||||
void TextureAnimator::clear_stale_textures(u64 frame_idx) {
|
||||
for (auto& group : m_clut_blender_groups) {
|
||||
if (frame_idx > group.last_updated_frame) {
|
||||
@ -1138,7 +1046,7 @@ void TextureAnimator::handle_generic_upload(const DmaTransfer& tf, const u8* ee_
|
||||
}
|
||||
m_tex_looking_for_clut = nullptr;
|
||||
if (upload->force_to_gpu) {
|
||||
m_erased_on_this_frame.insert(upload->dest);
|
||||
m_force_to_gpu.insert(upload->dest);
|
||||
}
|
||||
break;
|
||||
case (int)GsTex0::PSM::PSMT8:
|
||||
@ -1149,7 +1057,7 @@ void TextureAnimator::handle_generic_upload(const DmaTransfer& tf, const u8* ee_
|
||||
memcpy(vram.data.data(), ee_mem + upload->data, vram.data.size());
|
||||
m_tex_looking_for_clut = &vram;
|
||||
if (upload->force_to_gpu) {
|
||||
m_erased_on_this_frame.insert(upload->dest);
|
||||
m_force_to_gpu.insert(upload->dest);
|
||||
}
|
||||
break;
|
||||
case (int)GsTex0::PSM::PSMT4:
|
||||
@ -1160,7 +1068,7 @@ void TextureAnimator::handle_generic_upload(const DmaTransfer& tf, const u8* ee_
|
||||
memcpy(vram.data.data(), ee_mem + upload->data, vram.data.size());
|
||||
m_tex_looking_for_clut = &vram;
|
||||
if (upload->force_to_gpu) {
|
||||
m_erased_on_this_frame.insert(upload->dest);
|
||||
m_force_to_gpu.insert(upload->dest);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
@ -1176,7 +1084,6 @@ void TextureAnimator::handle_generic_upload(const DmaTransfer& tf, const u8* ee_
|
||||
* These may be modified by animation functions, but most of the time they aren't.
|
||||
*/
|
||||
void TextureAnimator::handle_erase_dest(DmaFollower& dma) {
|
||||
dprintf("[tex anim] erase destination texture\n");
|
||||
// auto& out = m_new_dest_textures.emplace_back();
|
||||
VramEntry* entry = nullptr;
|
||||
|
||||
@ -1265,67 +1172,7 @@ void TextureAnimator::handle_erase_dest(DmaFollower& dma) {
|
||||
|
||||
// set as active
|
||||
m_current_dest_tbp = entry->dest_texture_address;
|
||||
m_erased_on_this_frame.insert(entry->dest_texture_address);
|
||||
}
|
||||
|
||||
/*!
|
||||
* Set up this texture as a GPU texture. This does a few things:
|
||||
* - sets the Kind to GPU
|
||||
* - makes sure the texture resource points to a valid OpenGL texture of the right size, without
|
||||
* triggering the resize/delete sync issue mentioned above.
|
||||
* - sets flags to indicate if this GPU texture needs to be updated in the pool.
|
||||
*/
|
||||
VramEntry* TextureAnimator::setup_vram_entry_for_gpu_texture(int w, int h, int tbp) {
|
||||
auto pp = scoped_prof("setup-vram-entry");
|
||||
const auto& existing_dest = m_textures.find(tbp);
|
||||
|
||||
// see if we have an existing OpenGL texture at all
|
||||
bool existing_opengl = existing_dest != m_textures.end() && existing_dest->second.tex.has_value();
|
||||
|
||||
// see if we can reuse it (same size)
|
||||
bool can_reuse = true;
|
||||
if (existing_opengl) {
|
||||
if (existing_dest->second.tex->height() != h || existing_dest->second.tex->width() != w) {
|
||||
dprintf(" can't reuse, size mismatch\n");
|
||||
can_reuse = false;
|
||||
}
|
||||
} else {
|
||||
dprintf(" can't reuse, first time using this address\n");
|
||||
can_reuse = false;
|
||||
}
|
||||
|
||||
VramEntry* entry = nullptr;
|
||||
if (can_reuse) {
|
||||
// texture is the right size, just use it again.
|
||||
entry = &existing_dest->second;
|
||||
} else {
|
||||
if (existing_opengl) {
|
||||
// we have a texture, but it's the wrong type. Remember that we need to update the pool
|
||||
entry = &existing_dest->second;
|
||||
entry->needs_pool_update = true;
|
||||
} else {
|
||||
// create the entry. Also need to update the pool
|
||||
entry = &m_textures[tbp];
|
||||
entry->reset();
|
||||
entry->needs_pool_update = true;
|
||||
}
|
||||
|
||||
// if we already have a texture, try to swap it with an OpenGL texture of the right size.
|
||||
if (entry->tex.has_value()) {
|
||||
// gross
|
||||
m_opengl_texture_pool.free(entry->tex->texture(), entry->tex->width(), entry->tex->height());
|
||||
entry->tex->update_texture_size(w, h);
|
||||
entry->tex->update_texture_unsafe(m_opengl_texture_pool.allocate(w, h));
|
||||
} else {
|
||||
entry->tex.emplace(w, h, GL_UNSIGNED_INT_8_8_8_8_REV);
|
||||
}
|
||||
}
|
||||
|
||||
entry->kind = VramEntry::Kind::GPU;
|
||||
entry->tex_width = w;
|
||||
entry->tex_height = h;
|
||||
entry->dest_texture_address = tbp;
|
||||
return entry;
|
||||
m_force_to_gpu.insert(entry->dest_texture_address);
|
||||
}
|
||||
|
||||
/*!
|
||||
@ -1432,24 +1279,6 @@ void TextureAnimator::handle_draw(DmaFollower& dma, TexturePool& texture_pool) {
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* Get a 16x16 CLUT texture, stored in psm32 (in-memory format, not vram). Fatal if it doesn't
|
||||
* exist.
|
||||
*/
|
||||
const u32* TextureAnimator::get_clut_16_16_psm32(int cbp) {
|
||||
const auto& clut_lookup = m_textures.find(cbp);
|
||||
if (clut_lookup == m_textures.end()) {
|
||||
printf("get_clut_16_16_psm32 referenced an unknown clut texture in %d\n", cbp);
|
||||
ASSERT_NOT_REACHED();
|
||||
}
|
||||
|
||||
if (clut_lookup->second.kind != VramEntry::Kind::CLUT16_16_IN_PSM32) {
|
||||
ASSERT_NOT_REACHED();
|
||||
}
|
||||
|
||||
return (const u32*)clut_lookup->second.data.data();
|
||||
}
|
||||
|
||||
/*!
|
||||
* Using the current shader settings, load the CLUT table to the texture coverter "VRAM".
|
||||
*/
|
||||
@ -1506,7 +1335,7 @@ GLuint TextureAnimator::make_or_get_gpu_texture_for_current_shader(TexturePool&
|
||||
case VramEntry::Kind::GPU:
|
||||
// already on the GPU, just return it.
|
||||
return lookup->second.tex->texture();
|
||||
// data on the CPU, in PSM32
|
||||
// data on the CPU, in PSM32
|
||||
case VramEntry::Kind::GENERIC_PSM32:
|
||||
// see how we're reading it:
|
||||
switch (m_current_shader.tex0.psm()) {
|
||||
@ -1597,74 +1426,6 @@ GLuint TextureAnimator::make_or_get_gpu_texture_for_current_shader(TexturePool&
|
||||
}
|
||||
}
|
||||
|
||||
void TextureAnimator::set_up_opengl_for_fixed(const FixedLayerDef& def,
|
||||
std::optional<GLint> texture) {
|
||||
if (texture) {
|
||||
glBindTexture(GL_TEXTURE_2D, *texture);
|
||||
glUniform1i(m_uniforms.enable_tex, 1);
|
||||
} else {
|
||||
glBindTexture(GL_TEXTURE_2D, m_dummy_texture);
|
||||
glUniform1i(m_uniforms.enable_tex, 0);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
}
|
||||
// tex0
|
||||
// assuming default-texture-anim-layer-func, which sets 1.
|
||||
glUniform1i(m_uniforms.tcc, 1);
|
||||
|
||||
// ASSERT(shader.tex0.tfx() == GsTex0::TextureFunction::MODULATE);
|
||||
// tex1
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
|
||||
glColorMask(def.channel_masks[0], def.channel_masks[1], def.channel_masks[2],
|
||||
def.channel_masks[3]);
|
||||
if (def.z_test) {
|
||||
ASSERT_NOT_REACHED();
|
||||
} else {
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
}
|
||||
|
||||
if (def.clamp_u) {
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
} else {
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
|
||||
}
|
||||
|
||||
if (def.clamp_v) {
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
} else {
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
|
||||
}
|
||||
|
||||
if (def.blend_enable) {
|
||||
auto blend_a = def.blend_modes[0];
|
||||
auto blend_b = def.blend_modes[1];
|
||||
auto blend_c = def.blend_modes[2];
|
||||
auto blend_d = def.blend_modes[3];
|
||||
glEnable(GL_BLEND);
|
||||
|
||||
// 0 2 0 1
|
||||
if (blend_a == GsAlpha::BlendMode::SOURCE && blend_b == GsAlpha::BlendMode::ZERO_OR_FIXED &&
|
||||
blend_c == GsAlpha::BlendMode::SOURCE && blend_d == GsAlpha::BlendMode::DEST) {
|
||||
glBlendEquation(GL_FUNC_ADD);
|
||||
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE, GL_ONE, GL_ZERO);
|
||||
} else if (blend_a == GsAlpha::BlendMode::SOURCE && blend_b == GsAlpha::BlendMode::DEST &&
|
||||
blend_c == GsAlpha::BlendMode::SOURCE && blend_d == GsAlpha::BlendMode::DEST) {
|
||||
glBlendEquation(GL_FUNC_ADD);
|
||||
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ZERO);
|
||||
} else {
|
||||
fmt::print("unhandled blend: {} {} {} {}\n", (int)blend_a, (int)blend_b, (int)blend_c,
|
||||
(int)blend_d);
|
||||
ASSERT_NOT_REACHED();
|
||||
}
|
||||
|
||||
} else {
|
||||
glDisable(GL_BLEND);
|
||||
}
|
||||
glUniform4i(m_uniforms.channel_scramble, 0, 1, 2, 3);
|
||||
}
|
||||
|
||||
bool TextureAnimator::set_up_opengl_for_shader(const ShaderContext& shader,
|
||||
std::optional<GLuint> texture,
|
||||
bool prim_abe) {
|
||||
@ -1795,6 +1556,152 @@ bool TextureAnimator::set_up_opengl_for_shader(const ShaderContext& shader,
|
||||
return writes_alpha;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Set up this texture as a GPU texture. This does a few things:
|
||||
* - sets the Kind to GPU
|
||||
* - makes sure the texture resource points to a valid OpenGL texture of the right size, without
|
||||
* triggering the resize/delete sync issue mentioned above.
|
||||
* - sets flags to indicate if this GPU texture needs to be updated in the pool.
|
||||
*/
|
||||
VramEntry* TextureAnimator::setup_vram_entry_for_gpu_texture(int w, int h, int tbp) {
|
||||
auto pp = scoped_prof("setup-vram-entry");
|
||||
const auto& existing_dest = m_textures.find(tbp);
|
||||
|
||||
// see if we have an existing OpenGL texture at all
|
||||
bool existing_opengl = existing_dest != m_textures.end() && existing_dest->second.tex.has_value();
|
||||
|
||||
// see if we can reuse it (same size)
|
||||
bool can_reuse = true;
|
||||
if (existing_opengl) {
|
||||
if (existing_dest->second.tex->height() != h || existing_dest->second.tex->width() != w) {
|
||||
dprintf(" can't reuse, size mismatch\n");
|
||||
can_reuse = false;
|
||||
}
|
||||
} else {
|
||||
dprintf(" can't reuse, first time using this address\n");
|
||||
can_reuse = false;
|
||||
}
|
||||
|
||||
VramEntry* entry = nullptr;
|
||||
if (can_reuse) {
|
||||
// texture is the right size, just use it again.
|
||||
entry = &existing_dest->second;
|
||||
} else {
|
||||
if (existing_opengl) {
|
||||
// we have a texture, but it's the wrong type. Remember that we need to update the pool
|
||||
entry = &existing_dest->second;
|
||||
entry->needs_pool_update = true;
|
||||
} else {
|
||||
// create the entry. Also need to update the pool
|
||||
entry = &m_textures[tbp];
|
||||
entry->reset();
|
||||
entry->needs_pool_update = true;
|
||||
}
|
||||
|
||||
// if we already have a texture, try to swap it with an OpenGL texture of the right size.
|
||||
if (entry->tex.has_value()) {
|
||||
// gross
|
||||
m_opengl_texture_pool.free(entry->tex->texture(), entry->tex->width(), entry->tex->height());
|
||||
entry->tex->update_texture_size(w, h);
|
||||
entry->tex->update_texture_unsafe(m_opengl_texture_pool.allocate(w, h));
|
||||
} else {
|
||||
entry->tex.emplace(w, h, GL_UNSIGNED_INT_8_8_8_8_REV);
|
||||
}
|
||||
}
|
||||
|
||||
entry->kind = VramEntry::Kind::GPU;
|
||||
entry->tex_width = w;
|
||||
entry->tex_height = h;
|
||||
entry->dest_texture_address = tbp;
|
||||
return entry;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Get a 16x16 CLUT texture, stored in psm32 (in-memory format, not vram). Fatal if it doesn't
|
||||
* exist.
|
||||
*/
|
||||
const u32* TextureAnimator::get_clut_16_16_psm32(int cbp) {
|
||||
const auto& clut_lookup = m_textures.find(cbp);
|
||||
if (clut_lookup == m_textures.end()) {
|
||||
printf("get_clut_16_16_psm32 referenced an unknown clut texture in %d\n", cbp);
|
||||
ASSERT_NOT_REACHED();
|
||||
}
|
||||
|
||||
if (clut_lookup->second.kind != VramEntry::Kind::CLUT16_16_IN_PSM32) {
|
||||
ASSERT_NOT_REACHED();
|
||||
}
|
||||
|
||||
return (const u32*)clut_lookup->second.data.data();
|
||||
}
|
||||
|
||||
void TextureAnimator::set_up_opengl_for_fixed(const FixedLayerDef& def,
|
||||
std::optional<GLint> texture) {
|
||||
if (texture) {
|
||||
glBindTexture(GL_TEXTURE_2D, *texture);
|
||||
glUniform1i(m_uniforms.enable_tex, 1);
|
||||
} else {
|
||||
glBindTexture(GL_TEXTURE_2D, m_dummy_texture);
|
||||
glUniform1i(m_uniforms.enable_tex, 0);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
}
|
||||
// tex0
|
||||
// assuming default-texture-anim-layer-func, which sets 1.
|
||||
glUniform1i(m_uniforms.tcc, 1);
|
||||
|
||||
// ASSERT(shader.tex0.tfx() == GsTex0::TextureFunction::MODULATE);
|
||||
// tex1
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
|
||||
glColorMask(def.channel_masks[0], def.channel_masks[1], def.channel_masks[2],
|
||||
def.channel_masks[3]);
|
||||
if (def.z_test) {
|
||||
ASSERT_NOT_REACHED();
|
||||
} else {
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
}
|
||||
|
||||
if (def.clamp_u) {
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
} else {
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
|
||||
}
|
||||
|
||||
if (def.clamp_v) {
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
} else {
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
|
||||
}
|
||||
|
||||
if (def.blend_enable) {
|
||||
auto blend_a = def.blend_modes[0];
|
||||
auto blend_b = def.blend_modes[1];
|
||||
auto blend_c = def.blend_modes[2];
|
||||
auto blend_d = def.blend_modes[3];
|
||||
glEnable(GL_BLEND);
|
||||
|
||||
// 0 2 0 1
|
||||
if (blend_a == GsAlpha::BlendMode::SOURCE && blend_b == GsAlpha::BlendMode::ZERO_OR_FIXED &&
|
||||
blend_c == GsAlpha::BlendMode::SOURCE && blend_d == GsAlpha::BlendMode::DEST) {
|
||||
glBlendEquation(GL_FUNC_ADD);
|
||||
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE, GL_ONE, GL_ZERO);
|
||||
} else if (blend_a == GsAlpha::BlendMode::SOURCE && blend_b == GsAlpha::BlendMode::DEST &&
|
||||
blend_c == GsAlpha::BlendMode::SOURCE && blend_d == GsAlpha::BlendMode::DEST) {
|
||||
glBlendEquation(GL_FUNC_ADD);
|
||||
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ZERO);
|
||||
} else {
|
||||
fmt::print("unhandled blend: {} {} {} {}\n", (int)blend_a, (int)blend_b, (int)blend_c,
|
||||
(int)blend_d);
|
||||
ASSERT_NOT_REACHED();
|
||||
}
|
||||
|
||||
} else {
|
||||
glDisable(GL_BLEND);
|
||||
}
|
||||
glUniform4i(m_uniforms.channel_scramble, 0, 1, 2, 3);
|
||||
}
|
||||
|
||||
namespace {
|
||||
void set_uniform(GLuint uniform, const math::Vector<float, 4>& vf) {
|
||||
glUniform4f(uniform, vf.x(), vf.y(), vf.z(), vf.w());
|
||||
@ -2503,3 +2410,187 @@ void TextureAnimator::setup_texture_anims() {
|
||||
m_krew_holo_anim_array_idx = create_fixed_anim_array({def});
|
||||
}
|
||||
}
|
||||
|
||||
// initial values of the random table for cloud texture generation.
|
||||
constexpr Vector16ub kInitialRandomTable[TextureAnimator::kRandomTableSize] = {
|
||||
{0x20, 0x19, 0x18, 0x17, 0x16, 0x15, 0x14, 0x13, 0x12, 0x11, 0x10, 0x89, 0x67, 0x45, 0x23, 0x1},
|
||||
{0x37, 0x82, 0x87, 0x23, 0x78, 0x87, 0x4, 0x32, 0x97, 0x91, 0x48, 0x98, 0x30, 0x38, 0x89, 0x87},
|
||||
{0x62, 0x47, 0x2, 0x62, 0x78, 0x92, 0x28, 0x90, 0x81, 0x47, 0x72, 0x28, 0x83, 0x29, 0x71, 0x68},
|
||||
{0x28, 0x61, 0x17, 0x62, 0x87, 0x74, 0x38, 0x12, 0x83, 0x9, 0x78, 0x12, 0x76, 0x31, 0x72, 0x80},
|
||||
{0x39, 0x72, 0x98, 0x34, 0x72, 0x98, 0x69, 0x78, 0x65, 0x71, 0x98, 0x83, 0x97, 0x23, 0x98, 0x1},
|
||||
{0x97, 0x38, 0x72, 0x98, 0x23, 0x87, 0x23, 0x98, 0x93, 0x72, 0x98, 0x20, 0x81, 0x29, 0x10,
|
||||
0x62},
|
||||
{0x28, 0x75, 0x38, 0x82, 0x99, 0x30, 0x72, 0x87, 0x83, 0x9, 0x14, 0x98, 0x10, 0x43, 0x87, 0x29},
|
||||
{0x87, 0x23, 0x0, 0x87, 0x18, 0x98, 0x12, 0x98, 0x10, 0x98, 0x21, 0x83, 0x90, 0x37, 0x62,
|
||||
0x71}};
|
||||
|
||||
/*!
|
||||
* Update dest and random_table.
|
||||
*/
|
||||
int make_noise_texture(u8* dest, Vector16ub* random_table, int dim, int random_index_in) {
|
||||
ASSERT(dim % 16 == 0);
|
||||
const int qw_per_row = dim / 16;
|
||||
for (int row = 0; row < dim; row++) {
|
||||
for (int qw_in_row = 0; qw_in_row < qw_per_row; qw_in_row++) {
|
||||
const int row_start_qwi = row * qw_per_row;
|
||||
Vector16ub* rand_rows[4] = {
|
||||
random_table + (random_index_in + 0) % TextureAnimator::kRandomTableSize,
|
||||
random_table + (random_index_in + 3) % TextureAnimator::kRandomTableSize,
|
||||
random_table + (random_index_in + 5) % TextureAnimator::kRandomTableSize,
|
||||
random_table + (random_index_in + 7) % TextureAnimator::kRandomTableSize,
|
||||
};
|
||||
const int qwi = row_start_qwi + qw_in_row;
|
||||
*rand_rows[3] = *rand_rows[0] + *rand_rows[1] + *rand_rows[2];
|
||||
memcpy(dest + 16 * qwi, rand_rows[3]->data(), 16);
|
||||
random_index_in = (random_index_in + 1) % TextureAnimator::kRandomTableSize;
|
||||
}
|
||||
}
|
||||
return random_index_in;
|
||||
}
|
||||
|
||||
int update_opengl_noise_texture(GLuint texture,
|
||||
u8* temp,
|
||||
Vector16ub* random_table,
|
||||
int dim,
|
||||
int random_index_in) {
|
||||
int ret = make_noise_texture(temp, random_table, dim, random_index_in);
|
||||
glBindTexture(GL_TEXTURE_2D, texture);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, dim, dim, 0, GL_RED, GL_UNSIGNED_BYTE, temp);
|
||||
glGenerateMipmap(GL_TEXTURE_2D);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void debug_save_opengl_u8_texture(const std::string& out, GLuint texture) {
|
||||
glBindTexture(GL_TEXTURE_2D, texture);
|
||||
int w, h;
|
||||
glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &w);
|
||||
glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &h);
|
||||
fmt::print("saving texture with size {} x {}\n", w, h);
|
||||
std::vector<u8> data_r(w * h);
|
||||
glGetTexImage(GL_TEXTURE_2D, 0, GL_RED, GL_UNSIGNED_BYTE, data_r.data());
|
||||
std::vector<u8> data(w * h * 4);
|
||||
for (int i = 0; i < w * h; i++) {
|
||||
data[i * 4] = data_r[i];
|
||||
data[i * 4 + 1] = data_r[i];
|
||||
data[i * 4 + 2] = data_r[i];
|
||||
data[i * 4 + 3] = 255;
|
||||
}
|
||||
file_util::write_rgba_png(out, data.data(), w, h);
|
||||
}
|
||||
|
||||
void TextureAnimator::setup_sky() {
|
||||
// sky
|
||||
// initialize random table with values from the game.
|
||||
for (int i = 0; i < kRandomTableSize; i++) {
|
||||
m_random_table[i] = kInitialRandomTable[i];
|
||||
}
|
||||
|
||||
const float max_times[4] = {4800.f, 2400.f, 1200.f, 600.f};
|
||||
const float scales[4] = {0.5, 0.2, 0.15, 0.0075f};
|
||||
for (int i = 0, dim = kFinalSkyTextureSize >> (kNumSkyNoiseLayers - 1); i < kNumSkyNoiseLayers;
|
||||
i++, dim *= 2) {
|
||||
auto& tex = m_sky_noise_textures[i];
|
||||
tex.temp_data.resize(dim * dim);
|
||||
tex.max_time = max_times[i];
|
||||
tex.scale = scales[i];
|
||||
tex.dim = dim;
|
||||
glGenTextures(1, &tex.new_tex);
|
||||
m_random_index = update_opengl_noise_texture(tex.new_tex, tex.temp_data.data(), m_random_table,
|
||||
dim, m_random_index);
|
||||
glGenTextures(1, &tex.old_tex);
|
||||
m_random_index = update_opengl_noise_texture(tex.old_tex, tex.temp_data.data(), m_random_table,
|
||||
dim, m_random_index);
|
||||
// debug_save_opengl_u8_texture(fmt::format("{}_old.png", dim), tex.old_tex);
|
||||
// debug_save_opengl_u8_texture(fmt::format("{}_new.png", dim), tex.new_tex);
|
||||
}
|
||||
}
|
||||
|
||||
GLint TextureAnimator::run_clouds(const SkyInput& input) {
|
||||
m_debug_sky_input = input;
|
||||
|
||||
// anim 0 creates a clut with rgba = 128, 128, 128, i, at tbp = (24 * 32)
|
||||
// (this has alphas from 0 to 256).
|
||||
// This step is eliminated on OpenGL because we don't need this simple ramp CLUT.
|
||||
|
||||
// the next anim uses that clut with noise textures.
|
||||
// so we expect those textures to have values like (128, 128, 128, x) where 0 <= x <= 255.
|
||||
// (in OpenGL, we create these with a single-channel texture, with that channel in 0 - 1)
|
||||
|
||||
// this repeats for different resolutions (4 times in total)
|
||||
|
||||
// Next, these are blended together into a single texture
|
||||
// The blend mode is 0, 2, 0, 1
|
||||
// [(CSource - 0) * Asource] >> 7 + CDest
|
||||
// in the PS2, CSource is 128, so the >> 7 cancels entirely.
|
||||
|
||||
int times_idx = 0;
|
||||
// Anim 0:
|
||||
// this create a 16x16 CLUT with RGB = 128, 128, 128 and alpha = i
|
||||
// (texture-anim-alpha-ramp-clut-init)
|
||||
// it's uploaded 24 * 32 = 768. (texture-anim-alpha-ramp-clut-upload)
|
||||
times_idx++;
|
||||
{
|
||||
FramebufferTexturePairContext ctxt(m_sky_blend_texture);
|
||||
glClearColor(0.0, 0.0, 0.0, 0.0);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
glUniform1i(m_uniforms.tcc, 1);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glColorMask(true, true, true, true);
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
glEnable(GL_BLEND);
|
||||
glBlendEquation(GL_FUNC_ADD);
|
||||
glBlendFuncSeparate(GL_ONE, GL_ONE, GL_ZERO, GL_ZERO);
|
||||
glUniform4i(m_uniforms.channel_scramble, 0, 0, 0, 0);
|
||||
glUniform1f(m_uniforms.alpha_multiply, 1.f);
|
||||
glUniform1i(m_uniforms.enable_tex, 1);
|
||||
|
||||
float positions[3 * 4] = {0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0};
|
||||
glUniform3fv(m_uniforms.positions, 4, positions);
|
||||
float uv[2 * 4] = {0, 0, 1, 0, 1, 1, 0, 1};
|
||||
glUniform2fv(m_uniforms.uvs, 4, uv);
|
||||
|
||||
// Anim 1:
|
||||
// noise (16x16)
|
||||
// while (noise_layer_idx) {
|
||||
for (int noise_layer_idx = 0; noise_layer_idx < kNumSkyNoiseLayers; noise_layer_idx++) {
|
||||
const float new_time = input.times[times_idx];
|
||||
auto& ntp = m_sky_noise_textures[noise_layer_idx];
|
||||
|
||||
if (new_time < ntp.last_time) {
|
||||
std::swap(ntp.new_tex, ntp.old_tex);
|
||||
m_random_index = update_opengl_noise_texture(ntp.new_tex, ntp.temp_data.data(),
|
||||
m_random_table, ntp.dim, m_random_index);
|
||||
}
|
||||
ntp.last_time = new_time;
|
||||
float new_interp = ntp.last_time / ntp.max_time;
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, ntp.new_tex);
|
||||
float s = new_interp * ntp.scale * 128.f;
|
||||
set_uniform(m_uniforms.rgba, math::Vector4f(s, s, s, 256));
|
||||
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, ntp.old_tex);
|
||||
s = (1.f - new_interp) * ntp.scale * 128.f;
|
||||
set_uniform(m_uniforms.rgba, math::Vector4f(s, s, s, 256));
|
||||
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
|
||||
times_idx++;
|
||||
}
|
||||
}
|
||||
|
||||
FramebufferTexturePairContext ctxt(m_sky_final_texture);
|
||||
glClearColor(0.0, 0.0, 0.0, 0.0);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
glUniform1i(m_uniforms.enable_tex, 2);
|
||||
glBindTexture(GL_TEXTURE_2D, m_sky_blend_texture.texture());
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glUniform1f(m_uniforms.minimum, input.cloud_min);
|
||||
glUniform1f(m_uniforms.maximum, input.cloud_max);
|
||||
glDisable(GL_BLEND);
|
||||
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
|
||||
|
||||
return m_sky_final_texture.texture();
|
||||
}
|
@ -35,7 +35,6 @@ struct VramEntry {
|
||||
int dest_texture_address = 0;
|
||||
int cbp = 0;
|
||||
std::optional<FramebufferTexturePair> tex;
|
||||
// math::Vector<u8, 4> rgba_clear;
|
||||
|
||||
bool needs_pool_update = false;
|
||||
GpuTexture* pool_gpu_tex = nullptr;
|
||||
@ -46,9 +45,7 @@ struct VramEntry {
|
||||
tex_height = 0;
|
||||
tex_width = 0;
|
||||
cbp = 0;
|
||||
// tex.reset();
|
||||
needs_pool_update = false;
|
||||
// pool_gpu_tex = nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
@ -205,6 +202,37 @@ struct FixedAnimArray {
|
||||
std::vector<FixedAnim> anims;
|
||||
};
|
||||
|
||||
/*
|
||||
(deftype sky-input (structure)
|
||||
((fog-height float)
|
||||
(cloud-min float)
|
||||
(cloud-max float)
|
||||
(times float 9)
|
||||
(cloud-dest int32)
|
||||
)
|
||||
)
|
||||
*/
|
||||
|
||||
struct SkyInput {
|
||||
float fog_height;
|
||||
float cloud_min;
|
||||
float cloud_max;
|
||||
float times[9];
|
||||
int32_t cloud_dest;
|
||||
};
|
||||
|
||||
using Vector16ub = math::Vector<u8, 16>;
|
||||
|
||||
struct NoiseTexturePair {
|
||||
GLuint old_tex = 0;
|
||||
GLuint new_tex = 0;
|
||||
std::vector<u8> temp_data;
|
||||
int dim = 0;
|
||||
float scale = 0;
|
||||
float last_time = 0;
|
||||
float max_time = 0;
|
||||
};
|
||||
|
||||
class TexturePool;
|
||||
|
||||
class TextureAnimator {
|
||||
@ -223,28 +251,24 @@ class TextureAnimator {
|
||||
private:
|
||||
void copy_private_to_public();
|
||||
void setup_texture_anims();
|
||||
void setup_sky();
|
||||
void handle_upload_clut_16_16(const DmaTransfer& tf, const u8* ee_mem);
|
||||
void handle_generic_upload(const DmaTransfer& tf, const u8* ee_mem);
|
||||
void handle_clouds_and_fog(const DmaTransfer& tf, TexturePool* texture_pool);
|
||||
void handle_erase_dest(DmaFollower& dma);
|
||||
void handle_set_shader(DmaFollower& dma);
|
||||
void handle_draw(DmaFollower& dma, TexturePool& texture_pool);
|
||||
void handle_rg_to_ba(const DmaTransfer& tf);
|
||||
void handle_set_clut_alpha(const DmaTransfer& tf);
|
||||
void handle_copy_clut_alpha(const DmaTransfer& tf);
|
||||
|
||||
VramEntry* setup_vram_entry_for_gpu_texture(int w, int h, int tbp);
|
||||
|
||||
void set_up_opengl_for_fixed(const FixedLayerDef& def, std::optional<GLint> texture);
|
||||
bool set_up_opengl_for_shader(const ShaderContext& shader,
|
||||
std::optional<GLuint> texture,
|
||||
bool prim_abe);
|
||||
void set_up_opengl_for_fixed(const FixedLayerDef& def, std::optional<GLint> texture);
|
||||
|
||||
void load_clut_to_converter();
|
||||
const u32* get_clut_16_16_psm32(int cbp);
|
||||
|
||||
GLuint make_temp_gpu_texture(const u32* data, u32 width, u32 height);
|
||||
|
||||
GLuint make_or_get_gpu_texture_for_current_shader(TexturePool& texture_pool);
|
||||
const u32* get_clut_16_16_psm32(int cbp);
|
||||
void load_clut_to_converter();
|
||||
void force_to_gpu(int tbp);
|
||||
|
||||
int create_fixed_anim_array(const std::vector<FixedAnimDef>& defs);
|
||||
@ -278,18 +302,15 @@ class TextureAnimator {
|
||||
std::unordered_map<u32, VramEntry> m_textures;
|
||||
std::unordered_map<u64, PcTextureId> m_ids_by_vram;
|
||||
|
||||
std::set<u32> m_erased_on_this_frame;
|
||||
std::set<u32> m_force_to_gpu; // rename? or rework to not need?
|
||||
|
||||
struct TempTexture {
|
||||
GLuint tex;
|
||||
u32 w, h;
|
||||
};
|
||||
std::vector<TempTexture> m_in_use_temp_textures;
|
||||
|
||||
ShaderContext m_current_shader;
|
||||
TextureConverter m_converter;
|
||||
int m_current_dest_tbp = -1;
|
||||
|
||||
std::vector<TempTexture> m_in_use_temp_textures;
|
||||
ShaderContext m_current_shader;
|
||||
GLuint m_vao;
|
||||
GLuint m_vertex_buffer;
|
||||
struct Vertex {
|
||||
@ -307,6 +328,7 @@ class TextureAnimator {
|
||||
GLuint channel_scramble;
|
||||
GLuint tcc;
|
||||
GLuint alpha_multiply;
|
||||
GLuint minimum, maximum;
|
||||
} m_uniforms;
|
||||
|
||||
struct {
|
||||
@ -318,6 +340,7 @@ class TextureAnimator {
|
||||
|
||||
u8 m_index_to_clut_addr[256];
|
||||
OpenGLTexturePool m_opengl_texture_pool;
|
||||
int m_current_dest_tbp = -1;
|
||||
|
||||
std::vector<GLuint> m_private_output_slots;
|
||||
std::vector<GLuint> m_public_output_slots;
|
||||
@ -351,6 +374,7 @@ class TextureAnimator {
|
||||
const std::string& suffix1,
|
||||
const std::optional<std::string>& dgo);
|
||||
void run_clut_blender_group(DmaTransfer& tf, int idx, u64 frame_idx);
|
||||
GLint run_clouds(const SkyInput& input);
|
||||
|
||||
Psm32ToPsm8Scrambler m_psm32_to_psm8_8_8, m_psm32_to_psm8_16_16, m_psm32_to_psm8_32_32,
|
||||
m_psm32_to_psm8_64_64;
|
||||
@ -372,4 +396,23 @@ class TextureAnimator {
|
||||
int m_krew_holo_anim_array_idx = -1;
|
||||
|
||||
std::vector<FixedAnimArray> m_fixed_anim_arrays;
|
||||
|
||||
public:
|
||||
// must be power of 2 - number of 16-byte rows in random table. (original game has 8)
|
||||
static constexpr int kRandomTableSize = 8;
|
||||
|
||||
// must be power of 2 - dimensions of the final clouds textures
|
||||
static constexpr int kFinalSkyTextureSize = 128;
|
||||
|
||||
// number of small sub-textures. Must be less than log2(kFinalTextureSize).
|
||||
static constexpr int kNumSkyNoiseLayers = 4;
|
||||
|
||||
private:
|
||||
SkyInput m_debug_sky_input;
|
||||
Vector16ub m_random_table[kRandomTableSize];
|
||||
int m_random_index = 0;
|
||||
NoiseTexturePair m_sky_noise_textures[kNumSkyNoiseLayers];
|
||||
FramebufferTexturePair m_sky_blend_texture;
|
||||
FramebufferTexturePair m_sky_final_texture;
|
||||
GpuTexture* m_sky_pool_gpu_tex = nullptr;
|
||||
};
|
||||
|
@ -1,6 +1,7 @@
|
||||
#include "common/log/log.h"
|
||||
|
||||
#include "Generic2.h"
|
||||
#include "game/graphics/gfx.h"
|
||||
|
||||
void Generic2::opengl_setup(ShaderLibrary& shaders) {
|
||||
// create OpenGL objects
|
||||
|
@ -9,7 +9,6 @@
|
||||
#include "common/util/Timer.h"
|
||||
|
||||
#include "game/graphics/opengl_renderer/loader/common.h"
|
||||
#include "game/graphics/pipelines/opengl.h"
|
||||
#include "game/graphics/texture/TexturePool.h"
|
||||
|
||||
class Loader {
|
||||
|
@ -7,13 +7,28 @@ uniform int enable_tex;
|
||||
uniform int tcc;
|
||||
uniform ivec4 channel_scramble;
|
||||
uniform float alpha_multiply;
|
||||
uniform float minimum;
|
||||
uniform float maximum;
|
||||
|
||||
in vec2 uv;
|
||||
|
||||
uniform sampler2D tex_T0;
|
||||
|
||||
void main() {
|
||||
float cloud_lookup(float v, float minimum, float maximum) {
|
||||
maximum = max(minimum, maximum);
|
||||
if (v <= minimum) {
|
||||
return 0;
|
||||
}
|
||||
if (v >= maximum) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
float alpha = (v - minimum) / (maximum - minimum);
|
||||
float sin_alpha = sin(alpha * 3.1415926 / 2.f);
|
||||
return sin_alpha * sin_alpha;
|
||||
}
|
||||
|
||||
void main() {
|
||||
if (enable_tex == 1) {
|
||||
vec4 tex_color = texture(tex_T0, uv);
|
||||
vec4 unscambled_tex = vec4(tex_color[channel_scramble[0]],
|
||||
@ -26,9 +41,15 @@ void main() {
|
||||
} else {
|
||||
color.xyz *= unscambled_tex.xyz;
|
||||
}
|
||||
} else if (enable_tex == 2) {
|
||||
// cloud version
|
||||
vec4 tex_color = texture(tex_T0, uv);
|
||||
color.x = 0.5;
|
||||
color.y = 0.5;
|
||||
color.z = 0.5;
|
||||
color.a = 0.5 * cloud_lookup(tex_color.r, minimum, maximum);
|
||||
} else {
|
||||
color = (rgba / 128.);
|
||||
}
|
||||
|
||||
color.a *= alpha_multiply;
|
||||
}
|
@ -12,10 +12,11 @@
|
||||
#include "common/util/SmallVector.h"
|
||||
#include "common/versions/versions.h"
|
||||
|
||||
#include "game/graphics/pipelines/opengl.h"
|
||||
#include "game/graphics/texture/TextureConverter.h"
|
||||
#include "game/graphics/texture/TextureID.h"
|
||||
|
||||
#include "third-party/glad/include/glad/glad.h"
|
||||
|
||||
// verify all texture lookups.
|
||||
// will make texture lookups slower and likely caused dropped frames when loading
|
||||
constexpr bool EXTRA_TEX_DEBUG = false;
|
||||
|
@ -230,7 +230,7 @@
|
||||
(set! (-> upload-record height) (-> s5-1 h))
|
||||
(set! (-> upload-record dest) (-> s5-1 dest 0))
|
||||
(set! (-> upload-record format) (-> s5-1 psm))
|
||||
(set! (-> upload-record force-to-gpu) 0)
|
||||
(set! (-> upload-record force-to-gpu) 1)
|
||||
)
|
||||
(&+! (-> arg0 base) 16)
|
||||
|
||||
|
@ -196,6 +196,7 @@
|
||||
(metkor 38)
|
||||
(shield 39)
|
||||
(krew-holo 40)
|
||||
(clouds-and-fog 41)
|
||||
)
|
||||
|
||||
(deftype texture-anim-pc-upload (structure)
|
||||
@ -614,6 +615,22 @@
|
||||
(define-extern *metkor-texture-anim-array* (texture-anim-array texture-anim))
|
||||
(define-extern *shield-texture-anim-array* (texture-anim-array texture-anim))
|
||||
(define-extern *krew-holo-texture-anim-array* (texture-anim-array texture-anim))
|
||||
(define-extern *toxic-slime-texture-anim-array* (texture-anim-array texture-anim))
|
||||
|
||||
(defun pc-update-anim-frame-time ((anim texture-anim))
|
||||
(when (not (paused?))
|
||||
(with-pp
|
||||
(let ((f0-2 (+ (-> anim frame-time) (* (-> anim frame-delta) (-> pp clock seconds-per-frame))))
|
||||
(f1-2 (-> anim frame-mod))
|
||||
)
|
||||
(set! (-> anim frame-time) (- f0-2 (* (the float (the int (/ f0-2 f1-2))) f1-2)))
|
||||
)
|
||||
(if (< (-> anim frame-time) 0.0)
|
||||
(+! (-> anim frame-time) (-> anim frame-mod))
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
(defun pc-update-fixed-anim ((bucket bucket-id) (anim-id texture-anim-pc) (anim-array texture-anim-array))
|
||||
"Run a 'fixed' texture-anim, which should run entirely in C++."
|
||||
@ -661,18 +678,7 @@
|
||||
(when (-> anim func)
|
||||
((-> anim func) dma-buf anim)
|
||||
)
|
||||
(when (not (paused?))
|
||||
(with-pp
|
||||
(let ((f0-2 (+ (-> anim frame-time) (* (-> anim frame-delta) (-> pp clock seconds-per-frame))))
|
||||
(f1-2 (-> anim frame-mod))
|
||||
)
|
||||
(set! (-> anim frame-time) (- f0-2 (* (the float (the int (/ f0-2 f1-2))) f1-2)))
|
||||
)
|
||||
(if (< (-> anim frame-time) 0.0)
|
||||
(+! (-> anim frame-time) (-> anim frame-mod))
|
||||
)
|
||||
)
|
||||
)
|
||||
(pc-update-anim-frame-time anim)
|
||||
)
|
||||
)
|
||||
)
|
||||
@ -685,21 +691,58 @@
|
||||
(none)
|
||||
)
|
||||
|
||||
(deftype sky-input (structure)
|
||||
((fog-height float)
|
||||
(cloud-min float)
|
||||
(cloud-max float)
|
||||
(times float 9)
|
||||
(cloud-dest int32)
|
||||
)
|
||||
)
|
||||
|
||||
(defun make-sky-input ((si sky-input))
|
||||
(set! (-> si fog-height) (-> (the-as (array texture-anim) *sky-texture-anim-array*) 8 extra z))
|
||||
(set! (-> si cloud-min) (-> *sky-texture-anim-array* array-data 7 extra y))
|
||||
(set! (-> si cloud-max) (-> *sky-texture-anim-array* array-data 7 extra z))
|
||||
(set! (-> si cloud-dest) (the int (-> *sky-texture-anim-array* array-data 6 tex dest 0)))
|
||||
(dotimes (i 9)
|
||||
(set! (-> si times i)
|
||||
(-> *sky-texture-anim-array* array-data i frame-time)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
(defun update-texture-anim ((bucket bucket-id) (anim-array texture-anim-array))
|
||||
"Generate all DMA to update all textures in the given list for the given bucket."
|
||||
(let ((anim-idx 0))
|
||||
(cond
|
||||
((or (= anim-array *sky-texture-anim-array*)
|
||||
)
|
||||
|
||||
((= anim-array *toxic-slime-texture-anim-array*)
|
||||
;; not yet implemented
|
||||
(return #f)
|
||||
)
|
||||
((= anim-array *sky-texture-anim-array*)
|
||||
(when (= bucket (bucket-id tex-lcom-sky-post))
|
||||
;; skip. I believe this is only used to generate the envmap texture for the ocean.
|
||||
;; it generates the exact same thing, so if we want this on PC one day, we can just
|
||||
;; steal if from the beginning of the frame.
|
||||
(return #f)
|
||||
)
|
||||
;; for sky, we basically emulate the full thing
|
||||
;; (format *stdcon* "doing sky to bucket ~d~%" bucket)
|
||||
(with-dma-buffer-add-bucket ((dma-buf (-> *display* frames (-> *display* on-screen) global-buf))
|
||||
bucket
|
||||
)
|
||||
(pc-texture-anim-flag start-anim-array dma-buf)
|
||||
(pc-texture-anim-flag clouds-and-fog dma-buf :qwc 4)
|
||||
(make-sky-input (the sky-input (-> dma-buf base)))
|
||||
(&+! (-> dma-buf base) 64)
|
||||
(pc-texture-anim-flag finish-anim-array dma-buf)
|
||||
(dotimes (i 8) ;; intentially skipping fog here!!
|
||||
(pc-update-anim-frame-time (-> *sky-texture-anim-array* array-data i))
|
||||
)
|
||||
)
|
||||
;; falling through on purpose
|
||||
(set! anim-idx 8) ;; fog
|
||||
;(return #f)
|
||||
)
|
||||
((= anim-array *skull-gem-texture-anim-array*)
|
||||
(pc-update-fixed-anim bucket (texture-anim-pc skull-gem) anim-array)
|
||||
@ -780,7 +823,6 @@
|
||||
((= anim-array *jakb-prison-texture-anim-array*)
|
||||
;; prison is simple, and we reimplemented it in C++.
|
||||
;; so we just have to send the frame-time value.
|
||||
;; (format *stdcon* "doing prison-jak~%")
|
||||
(with-dma-buffer-add-bucket ((dma-buf (-> *display* frames (-> *display* on-screen) global-buf))
|
||||
bucket
|
||||
)
|
||||
@ -800,7 +842,6 @@
|
||||
((= anim-array *darkjak-hires-texture-anim-array*)
|
||||
;; oracle is simple, and we reimplemented it in C++.
|
||||
;; so we just have to send the frame-time value.
|
||||
;; (format *stdcon* "doing oracle jak~%")
|
||||
(with-dma-buffer-add-bucket ((dma-buf (-> *display* frames (-> *display* on-screen) global-buf))
|
||||
bucket
|
||||
)
|
||||
@ -819,7 +860,6 @@
|
||||
((= anim-array *darkjak-hires-nest-texture-anim-array*)
|
||||
;; oracle is simple, and we reimplemented it in C++.
|
||||
;; so we just have to send the frame-time value.
|
||||
;; (format *stdcon* "doing nest jak~%")
|
||||
(with-dma-buffer-add-bucket ((dma-buf (-> *display* frames (-> *display* on-screen) global-buf))
|
||||
bucket
|
||||
)
|
||||
@ -838,7 +878,6 @@
|
||||
((= anim-array *kor-transform-texture-anim-array*)
|
||||
;; kor is simple, and we reimplemented it in C++.
|
||||
;; so we just have to send the frame-time value.
|
||||
;; (format *stdcon* "doing kor~%")
|
||||
(with-dma-buffer-add-bucket ((dma-buf (-> *display* frames (-> *display* on-screen) global-buf))
|
||||
bucket
|
||||
)
|
||||
@ -855,10 +894,11 @@
|
||||
(return #f)
|
||||
)
|
||||
(else
|
||||
(return #f) ;; HACK!!!
|
||||
(format 0 "Unhandled texture animation!~%")
|
||||
(break!)
|
||||
(return #f)
|
||||
)
|
||||
)
|
||||
|
||||
;;
|
||||
;; (return #f)
|
||||
;;
|
||||
@ -877,11 +917,11 @@
|
||||
(pc-texture-anim-flag start-anim-array dma-buf)
|
||||
|
||||
;; loop over animated textures. Each will produce a single texture.
|
||||
(dotimes (anim-idx (-> anim-array length))
|
||||
;(dotimes (anim-idx (-> anim-array length))
|
||||
(while (< anim-idx (-> anim-array length))
|
||||
(let* ((anim (-> anim-array array-data anim-idx))
|
||||
(dest-tex (-> anim tex))
|
||||
)
|
||||
; (format 0 "texture anim dest tex: ~A~%" dest-tex)
|
||||
(when dest-tex
|
||||
0
|
||||
(let ((tex-width (-> dest-tex w)))
|
||||
@ -901,7 +941,6 @@
|
||||
)
|
||||
)
|
||||
(when (and (nonzero? tex-width) (nonzero? tex-height))
|
||||
; (format 0 " clearing~%")
|
||||
;; configure for drawing to this texture.
|
||||
(pc-texture-anim-flag erase-and-init dma-buf)
|
||||
(dma-buffer-add-gs-set-flusha dma-buf
|
||||
@ -984,6 +1023,7 @@
|
||||
)
|
||||
)
|
||||
)
|
||||
(+! anim-idx 1)
|
||||
)
|
||||
|
||||
;; reset GS registers - we messed with frame/scissor.
|
||||
@ -1001,6 +1041,7 @@
|
||||
(none)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
(defun no-alpha-texture-anim-layer-func ((dma-buf dma-buffer) (fbp-to-draw uint) (width int) (height int) (layer texture-anim-layer) (time float))
|
||||
"Like others, but the tcc value is 0"
|
||||
|
@ -170,7 +170,7 @@ void assign_colors(Node& root, std::vector<Color>& palette_out) {
|
||||
if (n.rgb_sum_count) {
|
||||
n.final_idx = idx++;
|
||||
palette_out.emplace_back(n.r_sum / n.rgb_sum_count, n.g_sum / n.rgb_sum_count,
|
||||
n.b_sum / n.rgb_sum_count);
|
||||
n.b_sum / n.rgb_sum_count, 0);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user