implement custom texture preload
This commit is contained in:
parent
657a129b60
commit
59b475a4b9
|
@ -165,6 +165,8 @@ void Config::ReadValues() {
|
|||
// Utility
|
||||
Settings::values.dump_textures = sdl2_config->GetBoolean("Utility", "dump_textures", false);
|
||||
Settings::values.custom_textures = sdl2_config->GetBoolean("Utility", "custom_textures", false);
|
||||
Settings::values.preload_textures =
|
||||
sdl2_config->GetBoolean("Utility", "preload_textures", false);
|
||||
|
||||
// Audio
|
||||
Settings::values.enable_dsp_lle = sdl2_config->GetBoolean("Audio", "enable_dsp_lle", false);
|
||||
|
|
|
@ -186,6 +186,10 @@ dump_textures =
|
|||
# 0 (default): Off, 1: On
|
||||
custom_textures =
|
||||
|
||||
# Loads all custom textures into memory before booting.
|
||||
# 0 (default): Off, 1: On
|
||||
preload_textures =
|
||||
|
||||
[Audio]
|
||||
# Whether or not to enable DSP LLE
|
||||
# 0 (default): No, 1: Yes
|
||||
|
|
|
@ -17,8 +17,6 @@ ConfigureGraphics::ConfigureGraphics(QWidget* parent)
|
|||
ui->setupUi(this);
|
||||
SetConfiguration();
|
||||
|
||||
ui->layoutBox->setEnabled(!Settings::values.custom_layout);
|
||||
|
||||
ui->hw_renderer_group->setEnabled(ui->toggle_hw_renderer->isChecked());
|
||||
connect(ui->toggle_hw_renderer, &QCheckBox::toggled, ui->hw_renderer_group,
|
||||
&QWidget::setEnabled);
|
||||
|
@ -36,21 +34,6 @@ ConfigureGraphics::ConfigureGraphics(QWidget* parent)
|
|||
}
|
||||
});
|
||||
#endif
|
||||
|
||||
connect(ui->render_3d_combobox,
|
||||
static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
|
||||
[this](int currentIndex) {
|
||||
updateShaders(static_cast<Settings::StereoRenderOption>(currentIndex) ==
|
||||
Settings::StereoRenderOption::Anaglyph);
|
||||
});
|
||||
|
||||
connect(ui->bg_button, &QPushButton::clicked, this, [this] {
|
||||
const QColor new_bg_color = QColorDialog::getColor(bg_color);
|
||||
if (!new_bg_color.isValid()) {
|
||||
return;
|
||||
}
|
||||
UpdateBackgroundColorButton(new_bg_color);
|
||||
});
|
||||
}
|
||||
|
||||
ConfigureGraphics::~ConfigureGraphics() = default;
|
||||
|
@ -60,15 +43,6 @@ void ConfigureGraphics::SetConfiguration() {
|
|||
ui->toggle_hw_shader->setChecked(Settings::values.use_hw_shader);
|
||||
ui->toggle_accurate_mul->setChecked(Settings::values.shaders_accurate_mul);
|
||||
ui->toggle_shader_jit->setChecked(Settings::values.use_shader_jit);
|
||||
ui->resolution_factor_combobox->setCurrentIndex(Settings::values.resolution_factor);
|
||||
ui->render_3d_combobox->setCurrentIndex(static_cast<int>(Settings::values.render_3d));
|
||||
ui->factor_3d->setValue(Settings::values.factor_3d);
|
||||
updateShaders(Settings::values.render_3d == Settings::StereoRenderOption::Anaglyph);
|
||||
ui->toggle_linear_filter->setChecked(Settings::values.filter_mode);
|
||||
ui->layout_combobox->setCurrentIndex(static_cast<int>(Settings::values.layout_option));
|
||||
ui->swap_screen->setChecked(Settings::values.swap_screen);
|
||||
UpdateBackgroundColorButton(QColor::fromRgbF(Settings::values.bg_red, Settings::values.bg_green,
|
||||
Settings::values.bg_blue));
|
||||
}
|
||||
|
||||
void ConfigureGraphics::ApplyConfiguration() {
|
||||
|
@ -76,49 +50,6 @@ void ConfigureGraphics::ApplyConfiguration() {
|
|||
Settings::values.use_hw_shader = ui->toggle_hw_shader->isChecked();
|
||||
Settings::values.shaders_accurate_mul = ui->toggle_accurate_mul->isChecked();
|
||||
Settings::values.use_shader_jit = ui->toggle_shader_jit->isChecked();
|
||||
Settings::values.resolution_factor =
|
||||
static_cast<u16>(ui->resolution_factor_combobox->currentIndex());
|
||||
Settings::values.render_3d =
|
||||
static_cast<Settings::StereoRenderOption>(ui->render_3d_combobox->currentIndex());
|
||||
Settings::values.factor_3d = ui->factor_3d->value();
|
||||
Settings::values.pp_shader_name =
|
||||
ui->shader_combobox->itemText(ui->shader_combobox->currentIndex()).toStdString();
|
||||
Settings::values.filter_mode = ui->toggle_linear_filter->isChecked();
|
||||
Settings::values.layout_option =
|
||||
static_cast<Settings::LayoutOption>(ui->layout_combobox->currentIndex());
|
||||
Settings::values.swap_screen = ui->swap_screen->isChecked();
|
||||
Settings::values.dump_textures = ui->toggle_dump_textures->isChecked();
|
||||
Settings::values.custom_textures = ui->toggle_custom_textures->isChecked();
|
||||
Settings::values.bg_red = static_cast<float>(bg_color.redF());
|
||||
Settings::values.bg_green = static_cast<float>(bg_color.greenF());
|
||||
Settings::values.bg_blue = static_cast<float>(bg_color.blueF());
|
||||
}
|
||||
|
||||
void ConfigureGraphics::updateShaders(bool anaglyph) {
|
||||
ui->shader_combobox->clear();
|
||||
|
||||
if (anaglyph)
|
||||
ui->shader_combobox->addItem("dubois (builtin)");
|
||||
else
|
||||
ui->shader_combobox->addItem("none (builtin)");
|
||||
|
||||
ui->shader_combobox->setCurrentIndex(0);
|
||||
|
||||
for (const auto& shader : OpenGL::GetPostProcessingShaderList(anaglyph)) {
|
||||
ui->shader_combobox->addItem(QString::fromStdString(shader));
|
||||
if (Settings::values.pp_shader_name == shader)
|
||||
ui->shader_combobox->setCurrentIndex(ui->shader_combobox->count() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
void ConfigureGraphics::UpdateBackgroundColorButton(const QColor& color) {
|
||||
bg_color = color;
|
||||
|
||||
QPixmap pixmap(ui->bg_button->size());
|
||||
pixmap.fill(bg_color);
|
||||
|
||||
const QIcon color_icon(pixmap);
|
||||
ui->bg_button->setIcon(color_icon);
|
||||
}
|
||||
|
||||
void ConfigureGraphics::RetranslateUI() {
|
||||
|
|
|
@ -354,6 +354,16 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="toggle_preload_textures">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Load all custom textures into memory on boot, instead of loading them when the game requires them.</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Preload Custom Textures</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
|
|
|
@ -161,7 +161,8 @@ ConfigureInput::ConfigureInput(QWidget* parent)
|
|||
continue;
|
||||
button_map[button_id]->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
connect(button_map[button_id], &QPushButton::clicked, [=]() {
|
||||
HandleClick(button_map[button_id],
|
||||
HandleClick(
|
||||
button_map[button_id],
|
||||
[=](const Common::ParamPackage& params) {
|
||||
buttons_param[button_id] = params;
|
||||
// If the user closes the dialog, the changes are reverted in
|
||||
|
@ -198,7 +199,8 @@ ConfigureInput::ConfigureInput(QWidget* parent)
|
|||
analog_map_buttons[analog_id][sub_button_id]->setContextMenuPolicy(
|
||||
Qt::CustomContextMenu);
|
||||
connect(analog_map_buttons[analog_id][sub_button_id], &QPushButton::clicked, [=]() {
|
||||
HandleClick(analog_map_buttons[analog_id][sub_button_id],
|
||||
HandleClick(
|
||||
analog_map_buttons[analog_id][sub_button_id],
|
||||
[=](const Common::ParamPackage& params) {
|
||||
SetAnalogButton(params, analogs_param[analog_id],
|
||||
analog_sub_buttons[sub_button_id]);
|
||||
|
|
|
@ -84,6 +84,8 @@ add_library(common STATIC
|
|||
swap.h
|
||||
telemetry.cpp
|
||||
telemetry.h
|
||||
texture.cpp
|
||||
texture.h
|
||||
thread.cpp
|
||||
thread.h
|
||||
thread_queue_list.h
|
||||
|
|
|
@ -34,8 +34,7 @@ void DetachedTasks::AddTask(std::function<void()> task) {
|
|||
std::unique_lock lock{instance->mutex};
|
||||
--instance->count;
|
||||
std::notify_all_at_thread_exit(instance->cv, std::move(lock));
|
||||
})
|
||||
.detach();
|
||||
}).detach();
|
||||
}
|
||||
|
||||
} // namespace Common
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
#include <vector>
|
||||
#include "common/assert.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Common {
|
||||
void FlipRGBA8Texture(std::vector<u8>& tex, u64 width, u64 height) {
|
||||
ASSERT(tex.size() == width * height * 4);
|
||||
const u64 line_size = width * 4;
|
||||
u8* temp_row = new u8[line_size];
|
||||
u32 offset_1;
|
||||
u32 offset_2;
|
||||
for (u64 line = 0; line < height / 2; line++) {
|
||||
offset_1 = line * line_size;
|
||||
offset_2 = (height - line - 1) * line_size;
|
||||
// Swap lines
|
||||
std::memcpy(temp_row, &tex[offset_1], line_size);
|
||||
std::memcpy(&tex[offset_1], &tex[offset_2], line_size);
|
||||
std::memcpy(&tex[offset_2], temp_row, line_size);
|
||||
}
|
||||
delete[] temp_row;
|
||||
}
|
||||
} // namespace Common
|
|
@ -0,0 +1,8 @@
|
|||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Common {
|
||||
void FlipRGBA8Texture(std::vector<u8>& tex, u64 width, u64 height);
|
||||
}
|
|
@ -460,7 +460,7 @@ endif()
|
|||
create_target_directory_groups(core)
|
||||
|
||||
target_link_libraries(core PUBLIC common PRIVATE audio_core network video_core)
|
||||
target_link_libraries(core PUBLIC Boost::boost PRIVATE cryptopp fmt open_source_archives)
|
||||
target_link_libraries(core PUBLIC Boost::boost PRIVATE cryptopp fmt open_source_archives lodepng)
|
||||
if (ENABLE_WEB_SERVICE)
|
||||
target_compile_definitions(core PRIVATE -DENABLE_WEB_SERVICE)
|
||||
target_link_libraries(core PRIVATE web_service)
|
||||
|
|
|
@ -4,10 +4,12 @@
|
|||
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <lodepng.h>
|
||||
#include "audio_core/dsp_interface.h"
|
||||
#include "audio_core/hle/hle.h"
|
||||
#include "audio_core/lle/lle.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/texture.h"
|
||||
#include "core/arm/arm_interface.h"
|
||||
#ifdef ARCHITECTURE_x86_64
|
||||
#include "core/arm/dynarmic/arm_dynarmic.h"
|
||||
|
@ -159,7 +161,52 @@ System::ResultStatus System::Load(Frontend::EmuWindow& emu_window, const std::st
|
|||
perf_stats = std::make_unique<PerfStats>(title_id);
|
||||
=======
|
||||
custom_tex_cache = std::make_unique<Core::CustomTexCache>();
|
||||
<<<<<<< HEAD
|
||||
>>>>>>> 387a49d7... fix crashes, add custom texture cache, load textures from load directory
|
||||
=======
|
||||
if (Settings::values.preload_textures) {
|
||||
// Custom textures are currently stored as
|
||||
// load/textures/[TitleID]/tex1_[width]x[height]_[64-bit hash]_[format].png
|
||||
const std::string load_path =
|
||||
fmt::format("{}textures/{:016X}/", FileUtil::GetUserPath(FileUtil::UserPath::LoadDir),
|
||||
process->codeset->program_id);
|
||||
|
||||
if (FileUtil::Exists(load_path)) {
|
||||
FileUtil::FSTEntry texture_files;
|
||||
FileUtil::ScanDirectoryTree(load_path, texture_files);
|
||||
for (const auto& file : texture_files.children) {
|
||||
if (file.isDirectory)
|
||||
continue;
|
||||
if (file.virtualName.substr(0, 5) != "tex1_")
|
||||
continue;
|
||||
|
||||
u32 width;
|
||||
u32 height;
|
||||
u64 hash;
|
||||
u32 format; // unused
|
||||
// TODO: more modern way of doing this
|
||||
if (std::sscanf(file.virtualName.c_str(), "tex1_%ux%u_%llX_%u.png", &width, &height,
|
||||
&hash, &format) == 4) {
|
||||
u32 png_width;
|
||||
u32 png_height;
|
||||
std::vector<u8> decoded_png;
|
||||
|
||||
u32 lodepng_ret =
|
||||
lodepng::decode(decoded_png, png_width, png_height, file.physicalName);
|
||||
if (lodepng_ret)
|
||||
LOG_CRITICAL(Render_OpenGL, "Failed to preload custom texture: {}",
|
||||
lodepng_error_text(lodepng_ret));
|
||||
else {
|
||||
LOG_INFO(Render_OpenGL, "Preloaded custom texture from {}",
|
||||
file.physicalName);
|
||||
Common::FlipRGBA8Texture(decoded_png, png_width, png_height);
|
||||
custom_tex_cache->CacheTexture(hash, decoded_png, png_width, png_height);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
>>>>>>> 015582b2... implement custom texture preload
|
||||
status = ResultStatus::Success;
|
||||
m_emu_window = &emu_window;
|
||||
m_filepath = filepath;
|
||||
|
@ -195,8 +242,8 @@ System::ResultStatus System::Init(Frontend::EmuWindow& emu_window, u32 system_mo
|
|||
|
||||
timing = std::make_unique<Timing>();
|
||||
|
||||
kernel = std::make_unique<Kernel::KernelSystem>(*memory, *timing,
|
||||
[this] { PrepareReschedule(); }, system_mode);
|
||||
kernel = std::make_unique<Kernel::KernelSystem>(
|
||||
*memory, *timing, [this] { PrepareReschedule(); }, system_mode);
|
||||
|
||||
if (Settings::values.use_cpu_jit) {
|
||||
#ifdef ARCHITECTURE_x86_64
|
||||
|
|
|
@ -1340,7 +1340,8 @@ void Module::CheckAndUpdateFile(const CecDataPathType path_type, const u32 ncch_
|
|||
case CecDataPathType::MboxData:
|
||||
case CecDataPathType::MboxIcon:
|
||||
case CecDataPathType::MboxTitle:
|
||||
default: {}
|
||||
default: {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -41,8 +41,8 @@ public:
|
|||
return s_instance;
|
||||
}
|
||||
|
||||
void StartPlayback(const std::string& movie_file,
|
||||
std::function<void()> completion_callback = [] {});
|
||||
void StartPlayback(
|
||||
const std::string& movie_file, std::function<void()> completion_callback = [] {});
|
||||
void StartRecording(const std::string& movie_file);
|
||||
|
||||
/// Prepare to override the clock before playing back movies
|
||||
|
|
|
@ -172,6 +172,7 @@ struct Values {
|
|||
|
||||
bool dump_textures;
|
||||
bool custom_textures;
|
||||
bool preload_textures;
|
||||
|
||||
// Audio
|
||||
bool enable_dsp_lle;
|
||||
|
|
|
@ -217,8 +217,7 @@ void TestCommunication(const std::string& host, u16 port, u8 pad_index, u32 clie
|
|||
success_callback();
|
||||
else
|
||||
failure_callback();
|
||||
})
|
||||
.detach();
|
||||
}).detach();
|
||||
}
|
||||
|
||||
CalibrationConfigurationJob::CalibrationConfigurationJob(
|
||||
|
@ -269,8 +268,7 @@ CalibrationConfigurationJob::CalibrationConfigurationJob(
|
|||
complete_event.Wait();
|
||||
socket.Stop();
|
||||
worker_thread.join();
|
||||
})
|
||||
.detach();
|
||||
}).detach();
|
||||
}
|
||||
|
||||
CalibrationConfigurationJob::~CalibrationConfigurationJob() {
|
||||
|
|
|
@ -17,7 +17,8 @@ TestEnvironment::TestEnvironment(bool mutable_memory_)
|
|||
|
||||
timing = std::make_unique<Core::Timing>();
|
||||
memory = std::make_unique<Memory::MemorySystem>();
|
||||
kernel = std::make_unique<Kernel::KernelSystem>(*memory, *timing, [] {}, 0);
|
||||
kernel = std::make_unique<Kernel::KernelSystem>(
|
||||
*memory, *timing, [] {}, 0);
|
||||
|
||||
kernel->SetCurrentProcess(kernel->CreateProcess(kernel->CreateCodeSet("", 0)));
|
||||
page_table = &kernel->GetCurrentProcess()->vm_manager.page_table;
|
||||
|
|
|
@ -23,7 +23,8 @@ static std::shared_ptr<Object> MakeObject(Kernel::KernelSystem& kernel) {
|
|||
TEST_CASE("HLERequestContext::PopulateFromIncomingCommandBuffer", "[core][kernel]") {
|
||||
Core::Timing timing;
|
||||
Memory::MemorySystem memory;
|
||||
Kernel::KernelSystem kernel(memory, timing, [] {}, 0);
|
||||
Kernel::KernelSystem kernel(
|
||||
memory, timing, [] {}, 0);
|
||||
auto [server, client] = kernel.CreateSessionPair();
|
||||
HLERequestContext context(kernel, std::move(server), nullptr);
|
||||
|
||||
|
@ -235,7 +236,8 @@ TEST_CASE("HLERequestContext::PopulateFromIncomingCommandBuffer", "[core][kernel
|
|||
TEST_CASE("HLERequestContext::WriteToOutgoingCommandBuffer", "[core][kernel]") {
|
||||
Core::Timing timing;
|
||||
Memory::MemorySystem memory;
|
||||
Kernel::KernelSystem kernel(memory, timing, [] {}, 0);
|
||||
Kernel::KernelSystem kernel(
|
||||
memory, timing, [] {}, 0);
|
||||
auto [server, client] = kernel.CreateSessionPair();
|
||||
HLERequestContext context(kernel, std::move(server), nullptr);
|
||||
|
||||
|
|
|
@ -13,7 +13,8 @@
|
|||
TEST_CASE("Memory::IsValidVirtualAddress", "[core][memory]") {
|
||||
Core::Timing timing;
|
||||
Memory::MemorySystem memory;
|
||||
Kernel::KernelSystem kernel(memory, timing, [] {}, 0);
|
||||
Kernel::KernelSystem kernel(
|
||||
memory, timing, [] {}, 0);
|
||||
SECTION("these regions should not be mapped on an empty process") {
|
||||
auto process = kernel.CreateProcess(kernel.CreateCodeSet("", 0));
|
||||
CHECK(Memory::IsValidVirtualAddress(*process, Memory::PROCESS_IMAGE_VADDR) == false);
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
#include "common/math_util.h"
|
||||
#include "common/microprofile.h"
|
||||
#include "common/scope_exit.h"
|
||||
#include "common/texture.h"
|
||||
#include "common/vector_math.h"
|
||||
#include "core/core.h"
|
||||
#include "core/custom_tex_cache.h"
|
||||
|
@ -855,25 +856,6 @@ void CachedSurface::FlushGLBuffer(PAddr flush_start, PAddr flush_end) {
|
|||
}
|
||||
}
|
||||
|
||||
// TODO: move this function to a better place
|
||||
void FlipRGBA8Texture(std::vector<u8>& tex, u64 width, u64 height) {
|
||||
ASSERT(tex.size() == width * height * 4);
|
||||
const u64 line_size = width * 4;
|
||||
// Thanks MSVC for not being able to make variable length arrays
|
||||
u8* temp_row = new u8[line_size];
|
||||
u32 offset_1;
|
||||
u32 offset_2;
|
||||
for (u64 line = 0; line < height / 2; line++) {
|
||||
offset_1 = line * line_size;
|
||||
offset_2 = (height - line - 1) * line_size;
|
||||
// Swap lines
|
||||
std::memcpy(temp_row, &tex[offset_1], line_size);
|
||||
std::memcpy(&tex[offset_1], &tex[offset_2], line_size);
|
||||
std::memcpy(&tex[offset_2], temp_row, line_size);
|
||||
}
|
||||
delete[] temp_row;
|
||||
}
|
||||
|
||||
MICROPROFILE_DEFINE(OpenGL_TextureUL, "OpenGL", "Texture Upload", MP_RGB(128, 192, 64));
|
||||
void CachedSurface::UploadGLTexture(const Common::Rectangle<u32>& rect, GLuint read_fb_handle,
|
||||
GLuint draw_fb_handle) {
|
||||
|
@ -893,7 +875,8 @@ void CachedSurface::UploadGLTexture(const Common::Rectangle<u32>& rect, GLuint r
|
|||
u32 png_width = 0;
|
||||
u32 png_height = 0;
|
||||
u64 tex_hash = 0;
|
||||
Common::Rectangle custom_rect = rect; // Required for rect to function properly with custom textures
|
||||
Common::Rectangle custom_rect =
|
||||
rect; // Required for rect to function properly with custom textures
|
||||
|
||||
if (Settings::values.dump_textures || Settings::values.custom_textures)
|
||||
tex_hash = Common::ComputeHash64(gl_buffer.get(), gl_buffer_size);
|
||||
|
@ -913,7 +896,7 @@ void CachedSurface::UploadGLTexture(const Common::Rectangle<u32>& rect, GLuint r
|
|||
lodepng_error_text(lodepng_ret));
|
||||
else {
|
||||
LOG_INFO(Render_OpenGL, "Loaded custom texture from {}", load_path);
|
||||
FlipRGBA8Texture(decoded_png, png_width, png_height);
|
||||
Common::FlipRGBA8Texture(decoded_png, png_width, png_height);
|
||||
custom_tex_cache.CacheTexture(tex_hash, decoded_png, png_width, png_height);
|
||||
use_custom_tex = true;
|
||||
}
|
||||
|
@ -1014,7 +997,7 @@ void CachedSurface::UploadGLTexture(const Common::Rectangle<u32>& rect, GLuint r
|
|||
glBindTexture(GL_TEXTURE_2D, target_tex);
|
||||
glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, &decoded_texture[0]);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
FlipRGBA8Texture(decoded_texture, width, height);
|
||||
Common::FlipRGBA8Texture(decoded_texture, width, height);
|
||||
u32 png_error = lodepng::encode(dump_path, decoded_texture, width, height);
|
||||
if (png_error) {
|
||||
LOG_CRITICAL(Render_OpenGL, "Failed to save decoded texture! {}",
|
||||
|
@ -1034,8 +1017,7 @@ void CachedSurface::UploadGLTexture(const Common::Rectangle<u32>& rect, GLuint r
|
|||
scaled_rect.bottom *= res_scale;
|
||||
|
||||
BlitTextures(unscaled_tex.handle, {0, custom_rect.GetHeight(), custom_rect.GetWidth(), 0},
|
||||
texture.handle,
|
||||
scaled_rect, type, read_fb_handle, draw_fb_handle);
|
||||
texture.handle, scaled_rect, type, read_fb_handle, draw_fb_handle);
|
||||
}
|
||||
|
||||
InvalidateAllWatcher();
|
||||
|
|
Reference in New Issue