commit
4f19d380f5
@ -0,0 +1,7 @@
|
|||||||
|
add_library(lodepng
|
||||||
|
lodepng/lodepng.cpp
|
||||||
|
lodepng/lodepng.h
|
||||||
|
)
|
||||||
|
|
||||||
|
create_target_directory_groups(lodepng)
|
||||||
|
target_include_directories(lodepng INTERFACE lodepng)
|
@ -0,0 +1 @@
|
|||||||
|
Subproject commit 31d9704fdcca0b68fb9656d4764fa0fb60e460c2
|
@ -0,0 +1,29 @@
|
|||||||
|
// Copyright 2019 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include <lodepng.h>
|
||||||
|
#include "citra/lodepng_image_interface.h"
|
||||||
|
#include "common/logging/log.h"
|
||||||
|
|
||||||
|
bool LodePNGImageInterface::DecodePNG(std::vector<u8>& dst, u32& width, u32& height,
|
||||||
|
const std::string& path) {
|
||||||
|
u32 lodepng_ret = lodepng::decode(dst, width, height, path);
|
||||||
|
if (lodepng_ret) {
|
||||||
|
LOG_CRITICAL(Frontend, "Failed to decode {} because {}", path,
|
||||||
|
lodepng_error_text(lodepng_ret));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LodePNGImageInterface::EncodePNG(const std::string& path, const std::vector<u8>& src,
|
||||||
|
u32 width, u32 height) {
|
||||||
|
u32 lodepng_ret = lodepng::encode(path, src, width, height);
|
||||||
|
if (lodepng_ret) {
|
||||||
|
LOG_CRITICAL(Frontend, "Failed to encode {} because {}", path,
|
||||||
|
lodepng_error_text(lodepng_ret));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
// Copyright 2019 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "core/frontend/image_interface.h"
|
||||||
|
|
||||||
|
class LodePNGImageInterface final : public Frontend::ImageInterface {
|
||||||
|
public:
|
||||||
|
bool DecodePNG(std::vector<u8>& dst, u32& width, u32& height, const std::string& path) override;
|
||||||
|
bool EncodePNG(const std::string& path, const std::vector<u8>& src, u32 width,
|
||||||
|
u32 height) override;
|
||||||
|
};
|
@ -0,0 +1,110 @@
|
|||||||
|
// Copyright 2019 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include <QColorDialog>
|
||||||
|
#include "citra_qt/configuration/configure_enhancements.h"
|
||||||
|
#include "core/core.h"
|
||||||
|
#include "core/settings.h"
|
||||||
|
#include "ui_configure_enhancements.h"
|
||||||
|
#include "video_core/renderer_opengl/post_processing_opengl.h"
|
||||||
|
|
||||||
|
ConfigureEnhancements::ConfigureEnhancements(QWidget* parent)
|
||||||
|
: QWidget(parent), ui(new Ui::ConfigureEnhancements) {
|
||||||
|
ui->setupUi(this);
|
||||||
|
SetConfiguration();
|
||||||
|
|
||||||
|
ui->layoutBox->setEnabled(!Settings::values.custom_layout);
|
||||||
|
|
||||||
|
ui->resolution_factor_combobox->setEnabled(Settings::values.use_hw_renderer);
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
bg_color = new_bg_color;
|
||||||
|
QPixmap pixmap(ui->bg_button->size());
|
||||||
|
pixmap.fill(bg_color);
|
||||||
|
const QIcon color_icon(pixmap);
|
||||||
|
ui->bg_button->setIcon(color_icon);
|
||||||
|
});
|
||||||
|
|
||||||
|
ui->toggle_preload_textures->setEnabled(ui->toggle_custom_textures->isChecked());
|
||||||
|
connect(ui->toggle_custom_textures, &QCheckBox::toggled, this, [this] {
|
||||||
|
ui->toggle_preload_textures->setEnabled(ui->toggle_custom_textures->isChecked());
|
||||||
|
if (!ui->toggle_preload_textures->isEnabled())
|
||||||
|
ui->toggle_preload_textures->setChecked(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConfigureEnhancements::SetConfiguration() {
|
||||||
|
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);
|
||||||
|
ui->toggle_dump_textures->setChecked(Settings::values.dump_textures);
|
||||||
|
ui->toggle_custom_textures->setChecked(Settings::values.custom_textures);
|
||||||
|
ui->toggle_preload_textures->setChecked(Settings::values.preload_textures);
|
||||||
|
bg_color = QColor::fromRgbF(Settings::values.bg_red, Settings::values.bg_green,
|
||||||
|
Settings::values.bg_blue);
|
||||||
|
QPixmap pixmap(ui->bg_button->size());
|
||||||
|
pixmap.fill(bg_color);
|
||||||
|
const QIcon color_icon(pixmap);
|
||||||
|
ui->bg_button->setIcon(color_icon);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConfigureEnhancements::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 ConfigureEnhancements::RetranslateUI() {
|
||||||
|
ui->retranslateUi(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConfigureEnhancements::ApplyConfiguration() {
|
||||||
|
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.preload_textures = ui->toggle_preload_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());
|
||||||
|
}
|
||||||
|
|
||||||
|
ConfigureEnhancements::~ConfigureEnhancements() {
|
||||||
|
delete ui;
|
||||||
|
}
|
@ -0,0 +1,29 @@
|
|||||||
|
// Copyright 2019 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QWidget>
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
class ConfigureEnhancements;
|
||||||
|
}
|
||||||
|
|
||||||
|
class ConfigureEnhancements : public QWidget {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit ConfigureEnhancements(QWidget* parent = nullptr);
|
||||||
|
~ConfigureEnhancements();
|
||||||
|
|
||||||
|
void ApplyConfiguration();
|
||||||
|
void RetranslateUI();
|
||||||
|
void SetConfiguration();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void updateShaders(bool anaglyph);
|
||||||
|
|
||||||
|
Ui::ConfigureEnhancements* ui;
|
||||||
|
QColor bg_color;
|
||||||
|
};
|
@ -0,0 +1,317 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>ConfigureEnhancements</class>
|
||||||
|
<widget class="QWidget" name="ConfigureEnhancements">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>400</width>
|
||||||
|
<height>595</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>0</width>
|
||||||
|
<height>0</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>Form</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QGroupBox" name="rendererBox">
|
||||||
|
<property name="title">
|
||||||
|
<string>Renderer</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout_6">
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label">
|
||||||
|
<property name="text">
|
||||||
|
<string>Internal Resolution</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QComboBox" name="resolution_factor_combobox">
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Auto (Window Size)</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Native (400x240)</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>2x Native (800x480)</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>3x Native (1200x720)</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>4x Native (1600x960)</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>5x Native (2000x1200)</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>6x Native (2400x1440)</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>7x Native (2800x1680)</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>8x Native (3200x1920)</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>9x Native (3600x2160)</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>10x Native (4000x2400)</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QCheckBox" name="toggle_linear_filter">
|
||||||
|
<property name="text">
|
||||||
|
<string>Enable Linear Filtering</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_11">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_2">
|
||||||
|
<property name="text">
|
||||||
|
<string>Post-Processing Shader</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QComboBox" name="shader_combobox"/>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QGroupBox" name="groupBox">
|
||||||
|
<property name="title">
|
||||||
|
<string>Stereoscopy</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout_4">
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_5">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_4">
|
||||||
|
<property name="text">
|
||||||
|
<string>Stereoscopic 3D Mode</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QComboBox" name="render_3d_combobox">
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Off</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Side by Side</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Anaglyph</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_7">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_3">
|
||||||
|
<property name="text">
|
||||||
|
<string>Depth</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QSpinBox" name="factor_3d">
|
||||||
|
<property name="suffix">
|
||||||
|
<string>%</string>
|
||||||
|
</property>
|
||||||
|
<property name="minimum">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="maximum">
|
||||||
|
<number>100</number>
|
||||||
|
</property>
|
||||||
|
<property name="value">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QGroupBox" name="layoutBox">
|
||||||
|
<property name="title">
|
||||||
|
<string>Layout</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_4">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label1">
|
||||||
|
<property name="text">
|
||||||
|
<string>Screen Layout:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QComboBox" name="layout_combobox">
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Default</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Single Screen</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Large Screen</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Side by Side</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QCheckBox" name="swap_screen">
|
||||||
|
<property name="text">
|
||||||
|
<string>Swap Screens</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_6">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="bg_label">
|
||||||
|
<property name="text">
|
||||||
|
<string>Background Color:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="bg_button">
|
||||||
|
<property name="maximumSize">
|
||||||
|
<size>
|
||||||
|
<width>40</width>
|
||||||
|
<height>16777215</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QGroupBox" name="utilityBox">
|
||||||
|
<property name="title">
|
||||||
|
<string>Utility</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout_8">
|
||||||
|
<item>
|
||||||
|
<widget class="QCheckBox" name="toggle_custom_textures">
|
||||||
|
<property name="toolTip">
|
||||||
|
<string><html><head/><body><p>Replace textures with PNG files.</p><p>Textures are loaded from load/textures/[Title ID]/.</p></body></html></string>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Use Custom Textures</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QCheckBox" name="toggle_dump_textures">
|
||||||
|
<property name="toolTip">
|
||||||
|
<string><html><head/><body><p>Dump textures to PNG files.</p><p>Textures are dumped to dump/textures/[Title ID]/.</p></body></html></string>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Dump Textures</string>
|
||||||
|
</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>
|
||||||
|
<item>
|
||||||
|
<spacer name="verticalSpacer">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Vertical</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>20</width>
|
||||||
|
<height>165</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<resources/>
|
||||||
|
<connections/>
|
||||||
|
</ui>
|
@ -0,0 +1,38 @@
|
|||||||
|
// Copyright 2019 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include <QImage>
|
||||||
|
#include <QString>
|
||||||
|
#include "citra_qt/qt_image_interface.h"
|
||||||
|
#include "common/logging/log.h"
|
||||||
|
|
||||||
|
bool QtImageInterface::DecodePNG(std::vector<u8>& dst, u32& width, u32& height,
|
||||||
|
const std::string& path) {
|
||||||
|
QImage image(QString::fromStdString(path));
|
||||||
|
|
||||||
|
if (image.isNull()) {
|
||||||
|
LOG_ERROR(Frontend, "Failed to open {} for decoding", path);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
width = image.width();
|
||||||
|
height = image.height();
|
||||||
|
|
||||||
|
image = image.convertToFormat(QImage::Format_RGBA8888);
|
||||||
|
|
||||||
|
// Write RGBA8 to vector
|
||||||
|
dst = std::vector<u8>(image.constBits(), image.constBits() + (width * height * 4));
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool QtImageInterface::EncodePNG(const std::string& path, const std::vector<u8>& src, u32 width,
|
||||||
|
u32 height) {
|
||||||
|
QImage image(src.data(), width, height, QImage::Format_RGBA8888);
|
||||||
|
|
||||||
|
if (!image.save(QString::fromStdString(path), "PNG")) {
|
||||||
|
LOG_ERROR(Frontend, "Failed to save {}", path);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
// Copyright 2019 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "core/frontend/image_interface.h"
|
||||||
|
|
||||||
|
class QtImageInterface final : public Frontend::ImageInterface {
|
||||||
|
public:
|
||||||
|
bool DecodePNG(std::vector<u8>& dst, u32& width, u32& height, const std::string& path) override;
|
||||||
|
bool EncodePNG(const std::string& path, const std::vector<u8>& src, u32 width,
|
||||||
|
u32 height) override;
|
||||||
|
};
|
@ -0,0 +1,22 @@
|
|||||||
|
// Copyright 2019 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#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;
|
||||||
|
for (u64 line = 0; line < height / 2; line++) {
|
||||||
|
const u32 offset_1 = line * line_size;
|
||||||
|
const u32 offset_2 = (height - line - 1) * line_size;
|
||||||
|
// Swap lines
|
||||||
|
std::swap_ranges(tex.begin() + offset_1, tex.begin() + offset_1 + line_size,
|
||||||
|
tex.begin() + offset_2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // namespace Common
|
@ -0,0 +1,12 @@
|
|||||||
|
// Copyright 2019 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include "common/common_types.h"
|
||||||
|
|
||||||
|
namespace Common {
|
||||||
|
void FlipRGBA8Texture(std::vector<u8>& tex, u64 width, u64 height);
|
||||||
|
}
|
@ -0,0 +1,111 @@
|
|||||||
|
// Copyright 2019 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include <fmt/format.h>
|
||||||
|
#include "common/file_util.h"
|
||||||
|
#include "common/texture.h"
|
||||||
|
#include "core.h"
|
||||||
|
#include "core/custom_tex_cache.h"
|
||||||
|
|
||||||
|
namespace Core {
|
||||||
|
CustomTexCache::CustomTexCache() = default;
|
||||||
|
|
||||||
|
CustomTexCache::~CustomTexCache() = default;
|
||||||
|
|
||||||
|
bool CustomTexCache::IsTextureDumped(u64 hash) const {
|
||||||
|
return dumped_textures.count(hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CustomTexCache::SetTextureDumped(const u64 hash) {
|
||||||
|
dumped_textures.insert(hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CustomTexCache::IsTextureCached(u64 hash) const {
|
||||||
|
return custom_textures.count(hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
const CustomTexInfo& CustomTexCache::LookupTexture(u64 hash) const {
|
||||||
|
return custom_textures.at(hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CustomTexCache::CacheTexture(u64 hash, const std::vector<u8>& tex, u32 width, u32 height) {
|
||||||
|
custom_textures[hash] = {width, height, tex};
|
||||||
|
}
|
||||||
|
|
||||||
|
void CustomTexCache::AddTexturePath(u64 hash, const std::string& path) {
|
||||||
|
if (custom_texture_paths.count(hash))
|
||||||
|
LOG_ERROR(Core, "Textures {} and {} conflict!", custom_texture_paths[hash].path, path);
|
||||||
|
else
|
||||||
|
custom_texture_paths[hash] = {path, hash};
|
||||||
|
}
|
||||||
|
|
||||||
|
void CustomTexCache::FindCustomTextures() {
|
||||||
|
// Custom textures are currently stored as
|
||||||
|
// [TitleID]/tex1_[width]x[height]_[64-bit hash]_[format].png
|
||||||
|
|
||||||
|
const std::string load_path =
|
||||||
|
fmt::format("{}textures/{:016X}/", FileUtil::GetUserPath(FileUtil::UserPath::LoadDir),
|
||||||
|
Core::System::GetInstance().Kernel().GetCurrentProcess()->codeset->program_id);
|
||||||
|
|
||||||
|
if (FileUtil::Exists(load_path)) {
|
||||||
|
FileUtil::FSTEntry texture_dir;
|
||||||
|
std::vector<FileUtil::FSTEntry> textures;
|
||||||
|
// 64 nested folders should be plenty for most cases
|
||||||
|
FileUtil::ScanDirectoryTree(load_path, texture_dir, 64);
|
||||||
|
FileUtil::GetAllFilesFromNestedEntries(texture_dir, textures);
|
||||||
|
|
||||||
|
for (const auto& file : textures) {
|
||||||
|
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) {
|
||||||
|
AddTexturePath(hash, file.physicalName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CustomTexCache::PreloadTextures() {
|
||||||
|
for (const auto& path : custom_texture_paths) {
|
||||||
|
const auto& image_interface = Core::System::GetInstance().GetImageInterface();
|
||||||
|
const auto& path_info = path.second;
|
||||||
|
Core::CustomTexInfo tex_info;
|
||||||
|
if (image_interface->DecodePNG(tex_info.tex, tex_info.width, tex_info.height,
|
||||||
|
path_info.path)) {
|
||||||
|
// Make sure the texture size is a power of 2
|
||||||
|
std::bitset<32> width_bits(tex_info.width);
|
||||||
|
std::bitset<32> height_bits(tex_info.height);
|
||||||
|
if (width_bits.count() == 1 && height_bits.count() == 1) {
|
||||||
|
LOG_DEBUG(Render_OpenGL, "Loaded custom texture from {}", path_info.path);
|
||||||
|
Common::FlipRGBA8Texture(tex_info.tex, tex_info.width, tex_info.height);
|
||||||
|
CacheTexture(path_info.hash, tex_info.tex, tex_info.width, tex_info.height);
|
||||||
|
} else {
|
||||||
|
LOG_ERROR(Render_OpenGL, "Texture {} size is not a power of 2", path_info.path);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
LOG_ERROR(Render_OpenGL, "Failed to load custom texture {}", path_info.path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CustomTexCache::CustomTextureExists(u64 hash) const {
|
||||||
|
return custom_texture_paths.count(hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
const CustomTexPathInfo& CustomTexCache::LookupTexturePathInfo(u64 hash) const {
|
||||||
|
return custom_texture_paths.at(hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CustomTexCache::IsTexturePathMapEmpty() const {
|
||||||
|
return custom_texture_paths.size() == 0;
|
||||||
|
}
|
||||||
|
} // namespace Core
|
@ -0,0 +1,51 @@
|
|||||||
|
// Copyright 2019 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <unordered_set>
|
||||||
|
#include <vector>
|
||||||
|
#include "common/common_types.h"
|
||||||
|
|
||||||
|
namespace Core {
|
||||||
|
struct CustomTexInfo {
|
||||||
|
u32 width;
|
||||||
|
u32 height;
|
||||||
|
std::vector<u8> tex;
|
||||||
|
};
|
||||||
|
|
||||||
|
// This is to avoid parsing the filename multiple times
|
||||||
|
struct CustomTexPathInfo {
|
||||||
|
std::string path;
|
||||||
|
u64 hash;
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: think of a better name for this class...
|
||||||
|
class CustomTexCache {
|
||||||
|
public:
|
||||||
|
explicit CustomTexCache();
|
||||||
|
~CustomTexCache();
|
||||||
|
|
||||||
|
bool IsTextureDumped(u64 hash) const;
|
||||||
|
void SetTextureDumped(u64 hash);
|
||||||
|
|
||||||
|
bool IsTextureCached(u64 hash) const;
|
||||||
|
const CustomTexInfo& LookupTexture(u64 hash) const;
|
||||||
|
void CacheTexture(u64 hash, const std::vector<u8>& tex, u32 width, u32 height);
|
||||||
|
|
||||||
|
void AddTexturePath(u64 hash, const std::string& path);
|
||||||
|
void FindCustomTextures();
|
||||||
|
void PreloadTextures();
|
||||||
|
bool CustomTextureExists(u64 hash) const;
|
||||||
|
const CustomTexPathInfo& LookupTexturePathInfo(u64 hash) const;
|
||||||
|
bool IsTexturePathMapEmpty() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::unordered_set<u64> dumped_textures;
|
||||||
|
std::unordered_map<u64, CustomTexInfo> custom_textures;
|
||||||
|
std::unordered_map<u64, CustomTexPathInfo> custom_texture_paths;
|
||||||
|
};
|
||||||
|
} // namespace Core
|
@ -0,0 +1,24 @@
|
|||||||
|
// Copyright 2019 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include "common/common_types.h"
|
||||||
|
|
||||||
|
namespace Frontend {
|
||||||
|
|
||||||
|
class ImageInterface {
|
||||||
|
public:
|
||||||
|
virtual ~ImageInterface() = default;
|
||||||
|
|
||||||
|
// Error logging should be handled by the frontend
|
||||||
|
virtual bool DecodePNG(std::vector<u8>& dst, u32& width, u32& height,
|
||||||
|
const std::string& path) = 0;
|
||||||
|
virtual bool EncodePNG(const std::string& path, const std::vector<u8>& src, u32 width,
|
||||||
|
u32 height) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Frontend
|
Loading…
Reference in New Issue