Compare commits
66 Commits
__refs_pul
...
__refs_pul
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4eb9c475d1 | ||
|
|
ac959799e4 | ||
|
|
49b15af054 | ||
|
|
f9e468d891 | ||
|
|
7ddd5b765d | ||
|
|
50c191439d | ||
|
|
3b8a0bc146 | ||
|
|
136040ee15 | ||
|
|
e58855c7a4 | ||
|
|
00131e752d | ||
|
|
223ddb2008 | ||
|
|
fcf81147e7 | ||
|
|
73a2d71f44 | ||
|
|
bd8065295c | ||
|
|
0acf9b351f | ||
|
|
073a21ac0b | ||
|
|
64e45b04e0 | ||
|
|
6d64ecf359 | ||
|
|
500e81429a | ||
|
|
5ff72a48a7 | ||
|
|
82a313a14c | ||
|
|
88897b67aa | ||
|
|
fdb199290b | ||
|
|
af074ee422 | ||
|
|
deff28d3c0 | ||
|
|
3d9776f36a | ||
|
|
a76f0d5d06 | ||
|
|
4048b54ef7 | ||
|
|
9cd79c25ed | ||
|
|
2515d2433b | ||
|
|
8b08cb925b | ||
|
|
975226e7ff | ||
|
|
a8974f0556 | ||
|
|
23ae7cf9db | ||
|
|
fdd5c97a14 | ||
|
|
f165a85398 | ||
|
|
0731383124 | ||
|
|
05f6f59ffb | ||
|
|
ce8291f6c5 | ||
|
|
9dccf7e1fa | ||
|
|
030676b95d | ||
|
|
a439f7b6e1 | ||
|
|
b56e5edafc | ||
|
|
460ebc8187 | ||
|
|
6ac1bd9f5d | ||
|
|
7e9b79955f | ||
|
|
564b7fdc9c | ||
|
|
c08c5d346a | ||
|
|
9382414b20 | ||
|
|
e3af341d5b | ||
|
|
3f17fe7133 | ||
|
|
a164b413fa | ||
|
|
9273c02427 | ||
|
|
b89dda2b98 | ||
|
|
9947c6ad59 | ||
|
|
9b50dca2bb | ||
|
|
009a2cc9cc | ||
|
|
6faf1b0972 | ||
|
|
820f646458 | ||
|
|
948f6c0738 | ||
|
|
8f4e09ba07 | ||
|
|
56ab608044 | ||
|
|
54724fe918 | ||
|
|
b155b3ef81 | ||
|
|
a859a35ec8 | ||
|
|
742f895f8b |
2
externals/dynarmic
vendored
2
externals/dynarmic
vendored
Submodule externals/dynarmic updated: 0435ac2d80...959446573f
2
externals/xbyak
vendored
2
externals/xbyak
vendored
Submodule externals/xbyak updated: 71b75f653f...1de435ed04
@@ -17,10 +17,10 @@ AudioRenderer::AudioRenderer(AudioRendererParameter params,
|
||||
Kernel::SharedPtr<Kernel::Event> buffer_event)
|
||||
: worker_params{params}, buffer_event{buffer_event}, voices(params.voice_count) {
|
||||
|
||||
audio_core = std::make_unique<AudioCore::AudioOut>();
|
||||
stream = audio_core->OpenStream(STREAM_SAMPLE_RATE, STREAM_NUM_CHANNELS, "AudioRenderer",
|
||||
[=]() { buffer_event->Signal(); });
|
||||
audio_core->StartStream(stream);
|
||||
audio_out = std::make_unique<AudioCore::AudioOut>();
|
||||
stream = audio_out->OpenStream(STREAM_SAMPLE_RATE, STREAM_NUM_CHANNELS, "AudioRenderer",
|
||||
[=]() { buffer_event->Signal(); });
|
||||
audio_out->StartStream(stream);
|
||||
|
||||
QueueMixedBuffer(0);
|
||||
QueueMixedBuffer(1);
|
||||
@@ -236,11 +236,11 @@ void AudioRenderer::QueueMixedBuffer(Buffer::Tag tag) {
|
||||
}
|
||||
}
|
||||
}
|
||||
audio_core->QueueBuffer(stream, tag, std::move(buffer));
|
||||
audio_out->QueueBuffer(stream, tag, std::move(buffer));
|
||||
}
|
||||
|
||||
void AudioRenderer::ReleaseAndQueueBuffers() {
|
||||
const auto released_buffers{audio_core->GetTagsAndReleaseBuffers(stream, 2)};
|
||||
const auto released_buffers{audio_out->GetTagsAndReleaseBuffers(stream, 2)};
|
||||
for (const auto& tag : released_buffers) {
|
||||
QueueMixedBuffer(tag);
|
||||
}
|
||||
|
||||
@@ -204,7 +204,7 @@ private:
|
||||
AudioRendererParameter worker_params;
|
||||
Kernel::SharedPtr<Kernel::Event> buffer_event;
|
||||
std::vector<VoiceState> voices;
|
||||
std::unique_ptr<AudioCore::AudioOut> audio_core;
|
||||
std::unique_ptr<AudioCore::AudioOut> audio_out;
|
||||
AudioCore::StreamPtr stream;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
# Generate cpp with Git revision from template
|
||||
# Also if this is a CI build, add the build name (ie: Nightly, Bleeding Edge) to the scm_rev file as well
|
||||
# Also if this is a CI build, add the build name (ie: Nightly, Canary) to the scm_rev file as well
|
||||
set(REPO_NAME "")
|
||||
set(BUILD_VERSION "0")
|
||||
if ($ENV{CI})
|
||||
if ($ENV{TRAVIS})
|
||||
set(BUILD_REPOSITORY $ENV{TRAVIS_REPO_SLUG})
|
||||
set(BUILD_TAG $ENV{TRAVIS_TAG})
|
||||
elseif($ENV{APPVEYOR})
|
||||
set(BUILD_REPOSITORY $ENV{APPVEYOR_REPO_NAME})
|
||||
set(BUILD_TAG $ENV{APPVEYOR_REPO_TAG_NAME})
|
||||
endif()
|
||||
# regex capture the string nightly or bleeding-edge into CMAKE_MATCH_1
|
||||
# regex capture the string nightly or canary into CMAKE_MATCH_1
|
||||
string(REGEX MATCH "yuzu-emu/yuzu-?(.*)" OUTVAR ${BUILD_REPOSITORY})
|
||||
if (${CMAKE_MATCH_COUNT} GREATER 0)
|
||||
# capitalize the first letter of each word in the repo name.
|
||||
@@ -16,10 +19,21 @@ if ($ENV{CI})
|
||||
string(SUBSTRING ${WORD} 0 1 FIRST_LETTER)
|
||||
string(SUBSTRING ${WORD} 1 -1 REMAINDER)
|
||||
string(TOUPPER ${FIRST_LETTER} FIRST_LETTER)
|
||||
# this leaves a trailing space on the last word, but we actually want that
|
||||
# because of how it's styled in the title bar.
|
||||
set(REPO_NAME "${REPO_NAME}${FIRST_LETTER}${REMAINDER} ")
|
||||
set(REPO_NAME "${REPO_NAME}${FIRST_LETTER}${REMAINDER}")
|
||||
endforeach()
|
||||
if (BUILD_TAG)
|
||||
string(REGEX MATCH "${CMAKE_MATCH_1}-([0-9]+)" OUTVAR ${BUILD_TAG})
|
||||
if (${CMAKE_MATCH_COUNT} GREATER 0)
|
||||
set(BUILD_VERSION ${CMAKE_MATCH_1})
|
||||
endif()
|
||||
if (BUILD_VERSION)
|
||||
# This leaves a trailing space on the last word, but we actually want that
|
||||
# because of how it's styled in the title bar.
|
||||
set(BUILD_FULLNAME "${REPO_NAME} #${BUILD_VERSION} ")
|
||||
else()
|
||||
set(BUILD_FULLNAME "")
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/scm_rev.cpp.in" "${CMAKE_CURRENT_SOURCE_DIR}/scm_rev.cpp" @ONLY)
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
#define GIT_DESC "@GIT_DESC@"
|
||||
#define BUILD_NAME "@REPO_NAME@"
|
||||
#define BUILD_DATE "@BUILD_DATE@"
|
||||
#define BUILD_FULLNAME "@BUILD_FULLNAME@"
|
||||
#define BUILD_VERSION "@BUILD_VERSION@"
|
||||
|
||||
namespace Common {
|
||||
|
||||
@@ -17,6 +19,8 @@ const char g_scm_branch[] = GIT_BRANCH;
|
||||
const char g_scm_desc[] = GIT_DESC;
|
||||
const char g_build_name[] = BUILD_NAME;
|
||||
const char g_build_date[] = BUILD_DATE;
|
||||
const char g_build_fullname[] = BUILD_FULLNAME;
|
||||
const char g_build_version[] = BUILD_VERSION;
|
||||
|
||||
} // namespace
|
||||
|
||||
|
||||
@@ -11,5 +11,7 @@ extern const char g_scm_branch[];
|
||||
extern const char g_scm_desc[];
|
||||
extern const char g_build_name[];
|
||||
extern const char g_build_date[];
|
||||
extern const char g_build_fullname[];
|
||||
extern const char g_build_version[];
|
||||
|
||||
} // namespace Common
|
||||
|
||||
@@ -24,7 +24,6 @@
|
||||
#include "core/hle/kernel/scheduler.h"
|
||||
#include "core/hle/kernel/thread.h"
|
||||
#include "core/hle/service/service.h"
|
||||
#include "core/hle/service/sm/controller.h"
|
||||
#include "core/hle/service/sm/sm.h"
|
||||
#include "core/loader/loader.h"
|
||||
#include "core/perf_stats.h"
|
||||
@@ -137,7 +136,7 @@ struct System::Impl {
|
||||
if (virtual_filesystem == nullptr)
|
||||
virtual_filesystem = std::make_shared<FileSys::RealVfsFilesystem>();
|
||||
|
||||
current_process = Kernel::Process::Create(kernel, "main");
|
||||
kernel.MakeCurrentProcess(Kernel::Process::Create(kernel, "main"));
|
||||
|
||||
cpu_barrier = std::make_shared<CpuBarrier>();
|
||||
cpu_exclusive_monitor = Cpu::MakeExclusiveMonitor(cpu_cores.size());
|
||||
@@ -203,7 +202,7 @@ struct System::Impl {
|
||||
return init_result;
|
||||
}
|
||||
|
||||
const Loader::ResultStatus load_result{app_loader->Load(current_process)};
|
||||
const Loader::ResultStatus load_result{app_loader->Load(kernel.CurrentProcess())};
|
||||
if (load_result != Loader::ResultStatus::Success) {
|
||||
LOG_CRITICAL(Core, "Failed to load ROM (Error {})!", static_cast<int>(load_result));
|
||||
Shutdown();
|
||||
@@ -282,7 +281,6 @@ struct System::Impl {
|
||||
std::unique_ptr<VideoCore::RendererBase> renderer;
|
||||
std::unique_ptr<Tegra::GPU> gpu_core;
|
||||
std::shared_ptr<Tegra::DebugContext> debug_context;
|
||||
Kernel::SharedPtr<Kernel::Process> current_process;
|
||||
std::shared_ptr<ExclusiveMonitor> cpu_exclusive_monitor;
|
||||
std::shared_ptr<CpuBarrier> cpu_barrier;
|
||||
std::array<std::shared_ptr<Cpu>, NUM_CPU_CORES> cpu_cores;
|
||||
@@ -364,7 +362,11 @@ const std::shared_ptr<Kernel::Scheduler>& System::Scheduler(size_t core_index) {
|
||||
}
|
||||
|
||||
Kernel::SharedPtr<Kernel::Process>& System::CurrentProcess() {
|
||||
return impl->current_process;
|
||||
return impl->kernel.CurrentProcess();
|
||||
}
|
||||
|
||||
const Kernel::SharedPtr<Kernel::Process>& System::CurrentProcess() const {
|
||||
return impl->kernel.CurrentProcess();
|
||||
}
|
||||
|
||||
ARM_Interface& System::ArmInterface(size_t core_index) {
|
||||
|
||||
@@ -174,9 +174,12 @@ public:
|
||||
/// Gets the scheduler for the CPU core with the specified index
|
||||
const std::shared_ptr<Kernel::Scheduler>& Scheduler(size_t core_index);
|
||||
|
||||
/// Gets the current process
|
||||
/// Provides a reference to the current process
|
||||
Kernel::SharedPtr<Kernel::Process>& CurrentProcess();
|
||||
|
||||
/// Provides a constant reference to the current process.
|
||||
const Kernel::SharedPtr<Kernel::Process>& CurrentProcess() const;
|
||||
|
||||
/// Provides a reference to the kernel instance.
|
||||
Kernel::KernelCore& Kernel();
|
||||
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <cstring>
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "core/crypto/aes_util.h"
|
||||
#include "core/file_sys/nca_patch.h"
|
||||
@@ -13,9 +17,9 @@ BKTR::BKTR(VirtualFile base_romfs_, VirtualFile bktr_romfs_, RelocationBlock rel
|
||||
std::vector<SubsectionBucket> subsection_buckets_, bool is_encrypted_,
|
||||
Core::Crypto::Key128 key_, u64 base_offset_, u64 ivfc_offset_,
|
||||
std::array<u8, 8> section_ctr_)
|
||||
: base_romfs(std::move(base_romfs_)), bktr_romfs(std::move(bktr_romfs_)),
|
||||
relocation(relocation_), relocation_buckets(std::move(relocation_buckets_)),
|
||||
: relocation(relocation_), relocation_buckets(std::move(relocation_buckets_)),
|
||||
subsection(subsection_), subsection_buckets(std::move(subsection_buckets_)),
|
||||
base_romfs(std::move(base_romfs_)), bktr_romfs(std::move(bktr_romfs_)),
|
||||
encrypted(is_encrypted_), key(key_), base_offset(base_offset_), ivfc_offset(ivfc_offset_),
|
||||
section_ctr(section_ctr_) {
|
||||
for (size_t i = 0; i < relocation.number_buckets - 1; ++i) {
|
||||
|
||||
@@ -5,10 +5,13 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <common/common_funcs.h>
|
||||
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/swap.h"
|
||||
#include "core/crypto/key_manager.h"
|
||||
#include "core/file_sys/romfs.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#include "core/file_sys/content_archive.h"
|
||||
#include "core/file_sys/control_metadata.h"
|
||||
#include "core/file_sys/patch_manager.h"
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include "common/common_types.h"
|
||||
#include "core/file_sys/nca_metadata.h"
|
||||
|
||||
@@ -116,6 +116,7 @@ struct KernelCore::Impl {
|
||||
next_thread_id = 1;
|
||||
|
||||
process_list.clear();
|
||||
current_process.reset();
|
||||
|
||||
handle_table.Clear();
|
||||
resource_limits.fill(nullptr);
|
||||
@@ -206,6 +207,7 @@ struct KernelCore::Impl {
|
||||
|
||||
// Lists all processes that exist in the current session.
|
||||
std::vector<SharedPtr<Process>> process_list;
|
||||
SharedPtr<Process> current_process;
|
||||
|
||||
Kernel::HandleTable handle_table;
|
||||
std::array<SharedPtr<ResourceLimit>, 4> resource_limits;
|
||||
@@ -264,6 +266,18 @@ void KernelCore::AppendNewProcess(SharedPtr<Process> process) {
|
||||
impl->process_list.push_back(std::move(process));
|
||||
}
|
||||
|
||||
void KernelCore::MakeCurrentProcess(SharedPtr<Process> process) {
|
||||
impl->current_process = std::move(process);
|
||||
}
|
||||
|
||||
SharedPtr<Process>& KernelCore::CurrentProcess() {
|
||||
return impl->current_process;
|
||||
}
|
||||
|
||||
const SharedPtr<Process>& KernelCore::CurrentProcess() const {
|
||||
return impl->current_process;
|
||||
}
|
||||
|
||||
void KernelCore::AddNamedPort(std::string name, SharedPtr<ClientPort> port) {
|
||||
impl->named_ports.emplace(std::move(name), std::move(port));
|
||||
}
|
||||
|
||||
@@ -65,6 +65,15 @@ public:
|
||||
/// Adds the given shared pointer to an internal list of active processes.
|
||||
void AppendNewProcess(SharedPtr<Process> process);
|
||||
|
||||
/// Makes the given process the new current process.
|
||||
void MakeCurrentProcess(SharedPtr<Process> process);
|
||||
|
||||
/// Retrieves a reference to the current process.
|
||||
SharedPtr<Process>& CurrentProcess();
|
||||
|
||||
/// Retrieves a const reference to the current process.
|
||||
const SharedPtr<Process>& CurrentProcess() const;
|
||||
|
||||
/// Adds a port to the named port table
|
||||
void AddNamedPort(std::string name, SharedPtr<ClientPort> port);
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <atomic>
|
||||
#include "common/logging/log.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
@@ -78,7 +77,7 @@ private:
|
||||
SharedMemory mem{};
|
||||
std::memcpy(&mem, shared_mem->GetPointer(), sizeof(SharedMemory));
|
||||
|
||||
if (is_device_reload_pending.exchange(false))
|
||||
if (Settings::values.is_device_reload_pending.exchange(false))
|
||||
LoadInputDevices();
|
||||
|
||||
// Set up controllers as neon red+blue Joy-Con attached to console
|
||||
@@ -267,7 +266,6 @@ private:
|
||||
CoreTiming::EventType* pad_update_event;
|
||||
|
||||
// Stored input state info
|
||||
std::atomic<bool> is_device_reload_pending{true};
|
||||
std::array<std::unique_ptr<Input::ButtonDevice>, Settings::NativeButton::NUM_BUTTONS_HID>
|
||||
buttons;
|
||||
std::array<std::unique_ptr<Input::AnalogDevice>, Settings::NativeAnalog::NUM_STICKS_HID> sticks;
|
||||
@@ -797,7 +795,9 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
void ReloadInputDevices() {}
|
||||
void ReloadInputDevices() {
|
||||
Settings::values.is_device_reload_pending.store(true);
|
||||
}
|
||||
|
||||
void InstallInterfaces(SM::ServiceManager& service_manager) {
|
||||
std::make_shared<Hid>()->InstallAsService(service_manager);
|
||||
|
||||
@@ -74,8 +74,6 @@ using Kernel::SharedPtr;
|
||||
|
||||
namespace Service {
|
||||
|
||||
std::unordered_map<std::string, SharedPtr<ClientPort>> g_kernel_named_ports;
|
||||
|
||||
/**
|
||||
* Creates a function string for logging, complete with the name (or header code, depending
|
||||
* on what's passed in) the port name, and all the cmd_buff arguments.
|
||||
|
||||
@@ -57,4 +57,6 @@ Controller::Controller() : ServiceFramework("IpcController") {
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
Controller::~Controller() = default;
|
||||
|
||||
} // namespace Service::SM
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace Service::SM {
|
||||
class Controller final : public ServiceFramework<Controller> {
|
||||
public:
|
||||
Controller();
|
||||
~Controller() = default;
|
||||
~Controller() override;
|
||||
|
||||
private:
|
||||
void ConvertSessionToDomain(Kernel::HLERequestContext& ctx);
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
|
||||
namespace Service::SM {
|
||||
|
||||
ServiceManager::ServiceManager() = default;
|
||||
ServiceManager::~ServiceManager() = default;
|
||||
|
||||
void ServiceManager::InvokeControlRequest(Kernel::HLERequestContext& context) {
|
||||
|
||||
@@ -46,6 +46,7 @@ class ServiceManager {
|
||||
public:
|
||||
static void InstallInterfaces(std::shared_ptr<ServiceManager> self);
|
||||
|
||||
ServiceManager();
|
||||
~ServiceManager();
|
||||
|
||||
ResultVal<Kernel::SharedPtr<Kernel::ServerPort>> RegisterService(std::string name,
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <string>
|
||||
#include "common/common_types.h"
|
||||
|
||||
@@ -120,6 +121,7 @@ struct Values {
|
||||
std::array<std::string, NativeAnalog::NumAnalogs> analogs;
|
||||
std::string motion_device;
|
||||
std::string touch_device;
|
||||
std::atomic_bool is_device_reload_pending{true};
|
||||
|
||||
// Core
|
||||
bool use_cpu_jit;
|
||||
|
||||
@@ -23,6 +23,7 @@ add_library(video_core STATIC
|
||||
renderer_base.cpp
|
||||
renderer_base.h
|
||||
renderer_opengl/gl_buffer_cache.cpp
|
||||
renderer_opengl/gl_buffer_cache.h
|
||||
renderer_opengl/gl_rasterizer.cpp
|
||||
renderer_opengl/gl_rasterizer.h
|
||||
renderer_opengl/gl_rasterizer_cache.cpp
|
||||
|
||||
@@ -293,10 +293,6 @@ Texture::TICEntry Maxwell3D::GetTICEntry(u32 tic_index) const {
|
||||
tic_entry.header_version == Texture::TICHeaderVersion::Pitch,
|
||||
"TIC versions other than BlockLinear or Pitch are unimplemented");
|
||||
|
||||
ASSERT_MSG((tic_entry.texture_type == Texture::TextureType::Texture2D) ||
|
||||
(tic_entry.texture_type == Texture::TextureType::Texture2DNoMipmap),
|
||||
"Texture types other than Texture2D are unimplemented");
|
||||
|
||||
auto r_type = tic_entry.r_type.Value();
|
||||
auto g_type = tic_entry.g_type.Value();
|
||||
auto b_type = tic_entry.b_type.Value();
|
||||
|
||||
@@ -533,7 +533,11 @@ public:
|
||||
u32 stencil_back_mask;
|
||||
u32 stencil_back_func_mask;
|
||||
|
||||
INSERT_PADDING_WORDS(0x20);
|
||||
INSERT_PADDING_WORDS(0x13);
|
||||
|
||||
u32 rt_separate_frag_data;
|
||||
|
||||
INSERT_PADDING_WORDS(0xC);
|
||||
|
||||
struct {
|
||||
u32 address_high;
|
||||
@@ -557,7 +561,22 @@ public:
|
||||
struct {
|
||||
union {
|
||||
BitField<0, 4, u32> count;
|
||||
BitField<4, 3, u32> map_0;
|
||||
BitField<7, 3, u32> map_1;
|
||||
BitField<10, 3, u32> map_2;
|
||||
BitField<13, 3, u32> map_3;
|
||||
BitField<16, 3, u32> map_4;
|
||||
BitField<19, 3, u32> map_5;
|
||||
BitField<22, 3, u32> map_6;
|
||||
BitField<25, 3, u32> map_7;
|
||||
};
|
||||
|
||||
u32 GetMap(size_t index) const {
|
||||
const std::array<u32, NumRenderTargets> maps{map_0, map_1, map_2, map_3,
|
||||
map_4, map_5, map_6, map_7};
|
||||
ASSERT(index < maps.size());
|
||||
return maps[index];
|
||||
}
|
||||
} rt_control;
|
||||
|
||||
INSERT_PADDING_WORDS(0x2);
|
||||
@@ -968,6 +987,7 @@ ASSERT_REG_POSITION(clear_stencil, 0x368);
|
||||
ASSERT_REG_POSITION(stencil_back_func_ref, 0x3D5);
|
||||
ASSERT_REG_POSITION(stencil_back_mask, 0x3D6);
|
||||
ASSERT_REG_POSITION(stencil_back_func_mask, 0x3D7);
|
||||
ASSERT_REG_POSITION(rt_separate_frag_data, 0x3EB);
|
||||
ASSERT_REG_POSITION(zeta, 0x3F8);
|
||||
ASSERT_REG_POSITION(vertex_attrib_format, 0x458);
|
||||
ASSERT_REG_POSITION(rt_control, 0x487);
|
||||
|
||||
@@ -41,7 +41,6 @@ void MaxwellDMA::HandleCopy() {
|
||||
|
||||
// TODO(Subv): Perform more research and implement all features of this engine.
|
||||
ASSERT(regs.exec.enable_swizzle == 0);
|
||||
ASSERT(regs.exec.enable_2d == 1);
|
||||
ASSERT(regs.exec.query_mode == Regs::QueryMode::None);
|
||||
ASSERT(regs.exec.query_intr == Regs::QueryIntr::None);
|
||||
ASSERT(regs.exec.copy_mode == Regs::CopyMode::Unk2);
|
||||
@@ -51,10 +50,19 @@ void MaxwellDMA::HandleCopy() {
|
||||
ASSERT(regs.dst_params.pos_y == 0);
|
||||
|
||||
if (regs.exec.is_dst_linear == regs.exec.is_src_linear) {
|
||||
Memory::CopyBlock(dest_cpu, source_cpu, regs.x_count * regs.y_count);
|
||||
size_t copy_size = regs.x_count;
|
||||
|
||||
// When the enable_2d bit is disabled, the copy is performed as if we were copying a 1D
|
||||
// buffer of length `x_count`, otherwise we copy a 2D buffer of size (x_count, y_count).
|
||||
if (regs.exec.enable_2d) {
|
||||
copy_size = copy_size * regs.y_count;
|
||||
}
|
||||
|
||||
Memory::CopyBlock(dest_cpu, source_cpu, copy_size);
|
||||
return;
|
||||
}
|
||||
|
||||
ASSERT(regs.exec.enable_2d == 1);
|
||||
u8* src_buffer = Memory::GetPointer(source_cpu);
|
||||
u8* dst_buffer = Memory::GetPointer(dest_cpu);
|
||||
|
||||
|
||||
@@ -244,6 +244,16 @@ enum class TextureType : u64 {
|
||||
TextureCube = 3,
|
||||
};
|
||||
|
||||
enum class TextureQueryType : u64 {
|
||||
Dimension = 1,
|
||||
TextureType = 2,
|
||||
SamplePosition = 5,
|
||||
Filter = 16,
|
||||
LevelOfDetail = 18,
|
||||
Wrap = 20,
|
||||
BorderColor = 22,
|
||||
};
|
||||
|
||||
enum class IpaInterpMode : u64 { Linear = 0, Perspective = 1, Flat = 2, Sc = 3 };
|
||||
enum class IpaSampleMode : u64 { Default = 0, Centroid = 1, Offset = 2 };
|
||||
|
||||
@@ -518,6 +528,21 @@ union Instruction {
|
||||
}
|
||||
} tex;
|
||||
|
||||
union {
|
||||
BitField<22, 6, TextureQueryType> query_type;
|
||||
BitField<31, 4, u64> component_mask;
|
||||
} txq;
|
||||
|
||||
union {
|
||||
BitField<28, 1, u64> array;
|
||||
BitField<29, 2, TextureType> texture_type;
|
||||
BitField<31, 4, u64> component_mask;
|
||||
|
||||
bool IsComponentEnabled(size_t component) const {
|
||||
return ((1ull << component) & component_mask) != 0;
|
||||
}
|
||||
} tmml;
|
||||
|
||||
union {
|
||||
BitField<28, 1, u64> array;
|
||||
BitField<29, 2, TextureType> texture_type;
|
||||
@@ -670,11 +695,13 @@ public:
|
||||
LDG, // Load from global memory
|
||||
STG, // Store in global memory
|
||||
TEX,
|
||||
TEXQ, // Texture Query
|
||||
TEXS, // Texture Fetch with scalar/non-vec4 source/destinations
|
||||
TLDS, // Texture Load with scalar/non-vec4 source/destinations
|
||||
TLD4, // Texture Load 4
|
||||
TLD4S, // Texture Load 4 with scalar / non - vec4 source / destinations
|
||||
TXQ, // Texture Query
|
||||
TEXS, // Texture Fetch with scalar/non-vec4 source/destinations
|
||||
TLDS, // Texture Load with scalar/non-vec4 source/destinations
|
||||
TLD4, // Texture Load 4
|
||||
TLD4S, // Texture Load 4 with scalar / non - vec4 source / destinations
|
||||
TMML_B, // Texture Mip Map Level
|
||||
TMML, // Texture Mip Map Level
|
||||
EXIT,
|
||||
IPA,
|
||||
FFMA_IMM, // Fused Multiply and Add
|
||||
@@ -894,11 +921,13 @@ private:
|
||||
INST("1110111011010---", Id::LDG, Type::Memory, "LDG"),
|
||||
INST("1110111011011---", Id::STG, Type::Memory, "STG"),
|
||||
INST("110000----111---", Id::TEX, Type::Memory, "TEX"),
|
||||
INST("1101111101001---", Id::TEXQ, Type::Memory, "TEXQ"),
|
||||
INST("1101111101001---", Id::TXQ, Type::Memory, "TXQ"),
|
||||
INST("1101100---------", Id::TEXS, Type::Memory, "TEXS"),
|
||||
INST("1101101---------", Id::TLDS, Type::Memory, "TLDS"),
|
||||
INST("110010----111---", Id::TLD4, Type::Memory, "TLD4"),
|
||||
INST("1101111100------", Id::TLD4S, Type::Memory, "TLD4S"),
|
||||
INST("110111110110----", Id::TMML_B, Type::Memory, "TMML_B"),
|
||||
INST("1101111101011---", Id::TMML, Type::Memory, "TMML"),
|
||||
INST("111000110000----", Id::EXIT, Type::Trivial, "EXIT"),
|
||||
INST("11100000--------", Id::IPA, Type::Trivial, "IPA"),
|
||||
INST("0011001-1-------", Id::FFMA_IMM, Type::Ffma, "FFMA_IMM"),
|
||||
|
||||
@@ -151,24 +151,17 @@ void RasterizerOpenGL::SetupVertexArrays() {
|
||||
Tegra::GPUVAddr start = vertex_array.StartAddress();
|
||||
const Tegra::GPUVAddr end = regs.vertex_array_limit[index].LimitAddress();
|
||||
|
||||
if (regs.instanced_arrays.IsInstancingEnabled(index) && vertex_array.divisor != 0) {
|
||||
start += vertex_array.stride * (gpu.state.current_instance / vertex_array.divisor);
|
||||
}
|
||||
|
||||
ASSERT(end > start);
|
||||
u64 size = end - start + 1;
|
||||
|
||||
GLintptr vertex_buffer_offset = buffer_cache.UploadMemory(start, size);
|
||||
const u64 size = end - start + 1;
|
||||
const GLintptr vertex_buffer_offset = buffer_cache.UploadMemory(start, size);
|
||||
|
||||
// Bind the vertex array to the buffer at the current offset.
|
||||
glBindVertexBuffer(index, buffer_cache.GetHandle(), vertex_buffer_offset,
|
||||
vertex_array.stride);
|
||||
|
||||
if (regs.instanced_arrays.IsInstancingEnabled(index) && vertex_array.divisor != 0) {
|
||||
// Tell OpenGL that this is an instanced vertex buffer to prevent accessing different
|
||||
// indexes on each vertex. We do the instance indexing manually by incrementing the
|
||||
// start address of the vertex buffer.
|
||||
glVertexBindingDivisor(index, 1);
|
||||
// Enable vertex buffer instancing with the specified divisor.
|
||||
glVertexBindingDivisor(index, vertex_array.divisor);
|
||||
} else {
|
||||
// Disable the vertex buffer instancing.
|
||||
glVertexBindingDivisor(index, 0);
|
||||
@@ -178,7 +171,7 @@ void RasterizerOpenGL::SetupVertexArrays() {
|
||||
|
||||
void RasterizerOpenGL::SetupShaders() {
|
||||
MICROPROFILE_SCOPE(OpenGL_Shader);
|
||||
auto& gpu = Core::System::GetInstance().GPU().Maxwell3D();
|
||||
const auto& gpu = Core::System::GetInstance().GPU().Maxwell3D();
|
||||
|
||||
// Next available bindpoints to use when uploading the const buffers and textures to the GLSL
|
||||
// shaders. The constbuffer bindpoint starts after the shader stage configuration bind points.
|
||||
@@ -186,7 +179,7 @@ void RasterizerOpenGL::SetupShaders() {
|
||||
u32 current_texture_bindpoint = 0;
|
||||
|
||||
for (size_t index = 0; index < Maxwell::MaxShaderProgram; ++index) {
|
||||
auto& shader_config = gpu.regs.shader_config[index];
|
||||
const auto& shader_config = gpu.regs.shader_config[index];
|
||||
const Maxwell::ShaderProgram program{static_cast<Maxwell::ShaderProgram>(index)};
|
||||
|
||||
// Skip stages that are not enabled
|
||||
@@ -198,7 +191,7 @@ void RasterizerOpenGL::SetupShaders() {
|
||||
|
||||
GLShader::MaxwellUniformData ubo{};
|
||||
ubo.SetFromRegs(gpu.state.shader_stages[stage]);
|
||||
GLintptr offset = buffer_cache.UploadHostMemory(
|
||||
const GLintptr offset = buffer_cache.UploadHostMemory(
|
||||
&ubo, sizeof(ubo), static_cast<size_t>(uniform_buffer_alignment));
|
||||
|
||||
// Bind the buffer
|
||||
@@ -237,6 +230,8 @@ void RasterizerOpenGL::SetupShaders() {
|
||||
}
|
||||
}
|
||||
|
||||
state.Apply();
|
||||
|
||||
shader_program_manager->UseTrivialGeometryShader();
|
||||
}
|
||||
|
||||
@@ -299,17 +294,10 @@ void RasterizerOpenGL::UpdatePagesCachedCount(VAddr addr, u64 size, int delta) {
|
||||
cached_pages.add({pages_interval, delta});
|
||||
}
|
||||
|
||||
std::pair<Surface, Surface> RasterizerOpenGL::ConfigureFramebuffers(bool using_color_fb,
|
||||
bool using_depth_fb,
|
||||
bool preserve_contents) {
|
||||
void RasterizerOpenGL::ConfigureFramebuffers(bool using_depth_fb, bool preserve_contents) {
|
||||
MICROPROFILE_SCOPE(OpenGL_Framebuffer);
|
||||
const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
|
||||
|
||||
if (regs.rt[0].format == Tegra::RenderTargetFormat::NONE) {
|
||||
LOG_ERROR(HW_GPU, "RenderTargetFormat is not configured");
|
||||
using_color_fb = false;
|
||||
}
|
||||
|
||||
const bool has_stencil = regs.stencil_enable;
|
||||
const bool write_color_fb =
|
||||
state.color_mask.red_enabled == GL_TRUE || state.color_mask.green_enabled == GL_TRUE ||
|
||||
@@ -319,41 +307,52 @@ std::pair<Surface, Surface> RasterizerOpenGL::ConfigureFramebuffers(bool using_c
|
||||
(state.depth.test_enabled && state.depth.write_mask == GL_TRUE) ||
|
||||
(has_stencil && (state.stencil.front.write_mask || state.stencil.back.write_mask));
|
||||
|
||||
Surface color_surface;
|
||||
Surface depth_surface;
|
||||
MathUtil::Rectangle<u32> surfaces_rect;
|
||||
std::tie(color_surface, depth_surface, surfaces_rect) =
|
||||
res_cache.GetFramebufferSurfaces(using_color_fb, using_depth_fb, preserve_contents);
|
||||
if (using_depth_fb) {
|
||||
depth_surface = res_cache.GetDepthBufferSurface(preserve_contents);
|
||||
}
|
||||
|
||||
const MathUtil::Rectangle<s32> viewport_rect{regs.viewport_transform[0].GetRect()};
|
||||
const MathUtil::Rectangle<u32> draw_rect{
|
||||
static_cast<u32>(std::clamp<s32>(static_cast<s32>(surfaces_rect.left) + viewport_rect.left,
|
||||
surfaces_rect.left, surfaces_rect.right)), // Left
|
||||
static_cast<u32>(std::clamp<s32>(static_cast<s32>(surfaces_rect.bottom) + viewport_rect.top,
|
||||
surfaces_rect.bottom, surfaces_rect.top)), // Top
|
||||
static_cast<u32>(std::clamp<s32>(static_cast<s32>(surfaces_rect.left) + viewport_rect.right,
|
||||
surfaces_rect.left, surfaces_rect.right)), // Right
|
||||
static_cast<u32>(
|
||||
std::clamp<s32>(static_cast<s32>(surfaces_rect.bottom) + viewport_rect.bottom,
|
||||
surfaces_rect.bottom, surfaces_rect.top))}; // Bottom
|
||||
// TODO(bunnei): Figure out how the below register works. According to envytools, this should be
|
||||
// used to enable multiple render targets. However, it is left unset on all games that I have
|
||||
// tested.
|
||||
ASSERT_MSG(regs.rt_separate_frag_data == 0, "Unimplemented");
|
||||
|
||||
// Bind the framebuffer surfaces
|
||||
BindFramebufferSurfaces(color_surface, depth_surface, has_stencil);
|
||||
|
||||
SyncViewport(surfaces_rect);
|
||||
|
||||
// Viewport can have negative offsets or larger dimensions than our framebuffer sub-rect. Enable
|
||||
// scissor test to prevent drawing outside of the framebuffer region
|
||||
state.scissor.enabled = true;
|
||||
state.scissor.x = draw_rect.left;
|
||||
state.scissor.y = draw_rect.bottom;
|
||||
state.scissor.width = draw_rect.GetWidth();
|
||||
state.scissor.height = draw_rect.GetHeight();
|
||||
state.draw.draw_framebuffer = framebuffer.handle;
|
||||
state.Apply();
|
||||
|
||||
// Only return the surface to be marked as dirty if writing to it is enabled.
|
||||
return std::make_pair(write_color_fb ? color_surface : nullptr,
|
||||
write_depth_fb ? depth_surface : nullptr);
|
||||
std::array<GLenum, Maxwell::NumRenderTargets> buffers;
|
||||
for (size_t index = 0; index < Maxwell::NumRenderTargets; ++index) {
|
||||
Surface color_surface = res_cache.GetColorBufferSurface(index, preserve_contents);
|
||||
buffers[index] = GL_COLOR_ATTACHMENT0 + regs.rt_control.GetMap(index);
|
||||
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER,
|
||||
GL_COLOR_ATTACHMENT0 + static_cast<GLenum>(index), GL_TEXTURE_2D,
|
||||
color_surface != nullptr ? color_surface->Texture().handle : 0, 0);
|
||||
}
|
||||
|
||||
glDrawBuffers(regs.rt_control.count, buffers.data());
|
||||
|
||||
if (depth_surface) {
|
||||
if (has_stencil) {
|
||||
// Attach both depth and stencil
|
||||
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D,
|
||||
depth_surface->Texture().handle, 0);
|
||||
} else {
|
||||
// Attach depth
|
||||
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D,
|
||||
depth_surface->Texture().handle, 0);
|
||||
// Clear stencil attachment
|
||||
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0);
|
||||
}
|
||||
} else {
|
||||
// Clear both depth and stencil attachment
|
||||
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0,
|
||||
0);
|
||||
}
|
||||
|
||||
SyncViewport();
|
||||
|
||||
state.Apply();
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::Clear() {
|
||||
@@ -412,8 +411,7 @@ void RasterizerOpenGL::Clear() {
|
||||
|
||||
ScopeAcquireGLContext acquire_context{emu_window};
|
||||
|
||||
auto [dirty_color_surface, dirty_depth_surface] =
|
||||
ConfigureFramebuffers(use_color_fb, use_depth_fb, false);
|
||||
ConfigureFramebuffers(use_depth_fb, false);
|
||||
|
||||
clear_state.Apply();
|
||||
|
||||
@@ -430,12 +428,12 @@ void RasterizerOpenGL::DrawArrays() {
|
||||
return;
|
||||
|
||||
MICROPROFILE_SCOPE(OpenGL_Drawing);
|
||||
const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
|
||||
const auto& gpu = Core::System::GetInstance().GPU().Maxwell3D();
|
||||
const auto& regs = gpu.regs;
|
||||
|
||||
ScopeAcquireGLContext acquire_context{emu_window};
|
||||
|
||||
auto [dirty_color_surface, dirty_depth_surface] =
|
||||
ConfigureFramebuffers(true, regs.zeta.Address() != 0 && regs.zeta_enable != 0, true);
|
||||
ConfigureFramebuffers(true, true);
|
||||
|
||||
SyncDepthTestState();
|
||||
SyncStencilTestState();
|
||||
@@ -448,7 +446,8 @@ void RasterizerOpenGL::DrawArrays() {
|
||||
|
||||
// Draw the vertex batch
|
||||
const bool is_indexed = accelerate_draw == AccelDraw::Indexed;
|
||||
const u64 index_buffer_size{regs.index_array.count * regs.index_array.FormatSizeInBytes()};
|
||||
const u64 index_buffer_size{static_cast<u64>(regs.index_array.count) *
|
||||
static_cast<u64>(regs.index_array.FormatSizeInBytes())};
|
||||
|
||||
state.draw.vertex_buffer = buffer_cache.GetHandle();
|
||||
state.Apply();
|
||||
@@ -491,13 +490,29 @@ void RasterizerOpenGL::DrawArrays() {
|
||||
const GLint base_vertex{static_cast<GLint>(regs.vb_element_base)};
|
||||
|
||||
// Adjust the index buffer offset so it points to the first desired index.
|
||||
index_buffer_offset += regs.index_array.first * regs.index_array.FormatSizeInBytes();
|
||||
index_buffer_offset += static_cast<GLintptr>(regs.index_array.first) *
|
||||
static_cast<GLintptr>(regs.index_array.FormatSizeInBytes());
|
||||
|
||||
glDrawElementsBaseVertex(primitive_mode, regs.index_array.count,
|
||||
MaxwellToGL::IndexFormat(regs.index_array.format),
|
||||
reinterpret_cast<const void*>(index_buffer_offset), base_vertex);
|
||||
if (gpu.state.current_instance > 0) {
|
||||
glDrawElementsInstancedBaseVertexBaseInstance(
|
||||
primitive_mode, regs.index_array.count,
|
||||
MaxwellToGL::IndexFormat(regs.index_array.format),
|
||||
reinterpret_cast<const void*>(index_buffer_offset), 1, base_vertex,
|
||||
gpu.state.current_instance);
|
||||
} else {
|
||||
glDrawElementsBaseVertex(primitive_mode, regs.index_array.count,
|
||||
MaxwellToGL::IndexFormat(regs.index_array.format),
|
||||
reinterpret_cast<const void*>(index_buffer_offset),
|
||||
base_vertex);
|
||||
}
|
||||
} else {
|
||||
glDrawArrays(primitive_mode, regs.vertex_buffer.first, regs.vertex_buffer.count);
|
||||
if (gpu.state.current_instance > 0) {
|
||||
glDrawArraysInstancedBaseInstance(primitive_mode, regs.vertex_buffer.first,
|
||||
regs.vertex_buffer.count, 1,
|
||||
gpu.state.current_instance);
|
||||
} else {
|
||||
glDrawArrays(primitive_mode, regs.vertex_buffer.first, regs.vertex_buffer.count);
|
||||
}
|
||||
}
|
||||
|
||||
// Disable scissor test
|
||||
@@ -514,13 +529,9 @@ void RasterizerOpenGL::DrawArrays() {
|
||||
|
||||
void RasterizerOpenGL::NotifyMaxwellRegisterChanged(u32 method) {}
|
||||
|
||||
void RasterizerOpenGL::FlushAll() {
|
||||
MICROPROFILE_SCOPE(OpenGL_CacheManagement);
|
||||
}
|
||||
void RasterizerOpenGL::FlushAll() {}
|
||||
|
||||
void RasterizerOpenGL::FlushRegion(VAddr addr, u64 size) {
|
||||
MICROPROFILE_SCOPE(OpenGL_CacheManagement);
|
||||
}
|
||||
void RasterizerOpenGL::FlushRegion(VAddr addr, u64 size) {}
|
||||
|
||||
void RasterizerOpenGL::InvalidateRegion(VAddr addr, u64 size) {
|
||||
MICROPROFILE_SCOPE(OpenGL_CacheManagement);
|
||||
@@ -530,7 +541,6 @@ void RasterizerOpenGL::InvalidateRegion(VAddr addr, u64 size) {
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::FlushAndInvalidateRegion(VAddr addr, u64 size) {
|
||||
MICROPROFILE_SCOPE(OpenGL_CacheManagement);
|
||||
InvalidateRegion(addr, size);
|
||||
}
|
||||
|
||||
@@ -578,7 +588,7 @@ bool RasterizerOpenGL::AccelerateDisplay(const Tegra::FramebufferConfig& config,
|
||||
void RasterizerOpenGL::SamplerInfo::Create() {
|
||||
sampler.Create();
|
||||
mag_filter = min_filter = Tegra::Texture::TextureFilter::Linear;
|
||||
wrap_u = wrap_v = Tegra::Texture::WrapMode::Wrap;
|
||||
wrap_u = wrap_v = wrap_p = Tegra::Texture::WrapMode::Wrap;
|
||||
|
||||
// default is GL_LINEAR_MIPMAP_LINEAR
|
||||
glSamplerParameteri(sampler.handle, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
@@ -586,7 +596,7 @@ void RasterizerOpenGL::SamplerInfo::Create() {
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::SamplerInfo::SyncWithConfig(const Tegra::Texture::TSCEntry& config) {
|
||||
GLuint s = sampler.handle;
|
||||
const GLuint s = sampler.handle;
|
||||
|
||||
if (mag_filter != config.mag_filter) {
|
||||
mag_filter = config.mag_filter;
|
||||
@@ -605,8 +615,13 @@ void RasterizerOpenGL::SamplerInfo::SyncWithConfig(const Tegra::Texture::TSCEntr
|
||||
wrap_v = config.wrap_v;
|
||||
glSamplerParameteri(s, GL_TEXTURE_WRAP_T, MaxwellToGL::WrapMode(wrap_v));
|
||||
}
|
||||
if (wrap_p != config.wrap_p) {
|
||||
wrap_p = config.wrap_p;
|
||||
glSamplerParameteri(s, GL_TEXTURE_WRAP_R, MaxwellToGL::WrapMode(wrap_p));
|
||||
}
|
||||
|
||||
if (wrap_u == Tegra::Texture::WrapMode::Border || wrap_v == Tegra::Texture::WrapMode::Border) {
|
||||
if (wrap_u == Tegra::Texture::WrapMode::Border || wrap_v == Tegra::Texture::WrapMode::Border ||
|
||||
wrap_p == Tegra::Texture::WrapMode::Border) {
|
||||
const GLvec4 new_border_color = {{config.border_color_r, config.border_color_g,
|
||||
config.border_color_b, config.border_color_a}};
|
||||
if (border_color != new_border_color) {
|
||||
@@ -666,8 +681,6 @@ u32 RasterizerOpenGL::SetupConstBuffers(Maxwell::ShaderStage stage, Shader& shad
|
||||
current_bindpoint + bindpoint);
|
||||
}
|
||||
|
||||
state.Apply();
|
||||
|
||||
return current_bindpoint + static_cast<u32>(entries.size());
|
||||
}
|
||||
|
||||
@@ -682,7 +695,7 @@ u32 RasterizerOpenGL::SetupTextures(Maxwell::ShaderStage stage, Shader& shader,
|
||||
|
||||
for (u32 bindpoint = 0; bindpoint < entries.size(); ++bindpoint) {
|
||||
const auto& entry = entries[bindpoint];
|
||||
u32 current_bindpoint = current_unit + bindpoint;
|
||||
const u32 current_bindpoint = current_unit + bindpoint;
|
||||
|
||||
// Bind the uniform to the sampler.
|
||||
|
||||
@@ -692,14 +705,15 @@ u32 RasterizerOpenGL::SetupTextures(Maxwell::ShaderStage stage, Shader& shader,
|
||||
const auto texture = maxwell3d.GetStageTexture(entry.GetStage(), entry.GetOffset());
|
||||
|
||||
if (!texture.enabled) {
|
||||
state.texture_units[current_bindpoint].texture_2d = 0;
|
||||
state.texture_units[current_bindpoint].texture = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
texture_samplers[current_bindpoint].SyncWithConfig(texture.tsc);
|
||||
Surface surface = res_cache.GetTextureSurface(texture);
|
||||
if (surface != nullptr) {
|
||||
state.texture_units[current_bindpoint].texture_2d = surface->Texture().handle;
|
||||
state.texture_units[current_bindpoint].texture = surface->Texture().handle;
|
||||
state.texture_units[current_bindpoint].target = surface->Target();
|
||||
state.texture_units[current_bindpoint].swizzle.r =
|
||||
MaxwellToGL::SwizzleSource(texture.tic.x_source);
|
||||
state.texture_units[current_bindpoint].swizzle.g =
|
||||
@@ -710,47 +724,19 @@ u32 RasterizerOpenGL::SetupTextures(Maxwell::ShaderStage stage, Shader& shader,
|
||||
MaxwellToGL::SwizzleSource(texture.tic.w_source);
|
||||
} else {
|
||||
// Can occur when texture addr is null or its memory is unmapped/invalid
|
||||
state.texture_units[current_bindpoint].texture_2d = 0;
|
||||
state.texture_units[current_bindpoint].texture = 0;
|
||||
}
|
||||
}
|
||||
|
||||
state.Apply();
|
||||
|
||||
return current_unit + static_cast<u32>(entries.size());
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::BindFramebufferSurfaces(const Surface& color_surface,
|
||||
const Surface& depth_surface, bool has_stencil) {
|
||||
state.draw.draw_framebuffer = framebuffer.handle;
|
||||
state.Apply();
|
||||
|
||||
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
|
||||
color_surface != nullptr ? color_surface->Texture().handle : 0, 0);
|
||||
if (depth_surface != nullptr) {
|
||||
if (has_stencil) {
|
||||
// attach both depth and stencil
|
||||
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D,
|
||||
depth_surface->Texture().handle, 0);
|
||||
} else {
|
||||
// attach depth
|
||||
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D,
|
||||
depth_surface->Texture().handle, 0);
|
||||
// clear stencil attachment
|
||||
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0);
|
||||
}
|
||||
} else {
|
||||
// clear both depth and stencil attachment
|
||||
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0,
|
||||
0);
|
||||
}
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::SyncViewport(const MathUtil::Rectangle<u32>& surfaces_rect) {
|
||||
void RasterizerOpenGL::SyncViewport() {
|
||||
const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
|
||||
const MathUtil::Rectangle<s32> viewport_rect{regs.viewport_transform[0].GetRect()};
|
||||
|
||||
state.viewport.x = static_cast<GLint>(surfaces_rect.left) + viewport_rect.left;
|
||||
state.viewport.y = static_cast<GLint>(surfaces_rect.bottom) + viewport_rect.bottom;
|
||||
state.viewport.x = viewport_rect.left;
|
||||
state.viewport.y = viewport_rect.bottom;
|
||||
state.viewport.width = static_cast<GLsizei>(viewport_rect.GetWidth());
|
||||
state.viewport.height = static_cast<GLsizei>(viewport_rect.GetHeight());
|
||||
}
|
||||
|
||||
@@ -93,17 +93,12 @@ private:
|
||||
Tegra::Texture::TextureFilter min_filter;
|
||||
Tegra::Texture::WrapMode wrap_u;
|
||||
Tegra::Texture::WrapMode wrap_v;
|
||||
Tegra::Texture::WrapMode wrap_p;
|
||||
GLvec4 border_color;
|
||||
};
|
||||
|
||||
/// Configures the color and depth framebuffer states and returns the dirty <Color, Depth>
|
||||
/// surfaces if writing was enabled.
|
||||
std::pair<Surface, Surface> ConfigureFramebuffers(bool using_color_fb, bool using_depth_fb,
|
||||
bool preserve_contents);
|
||||
|
||||
/// Binds the framebuffer color and depth surface
|
||||
void BindFramebufferSurfaces(const Surface& color_surface, const Surface& depth_surface,
|
||||
bool has_stencil);
|
||||
/// Configures the color and depth framebuffer states
|
||||
void ConfigureFramebuffers(bool using_depth_fb, bool preserve_contents);
|
||||
|
||||
/*
|
||||
* Configures the current constbuffers to use for the draw command.
|
||||
@@ -126,7 +121,7 @@ private:
|
||||
u32 current_unit);
|
||||
|
||||
/// Syncs the viewport to match the guest state
|
||||
void SyncViewport(const MathUtil::Rectangle<u32>& surfaces_rect);
|
||||
void SyncViewport();
|
||||
|
||||
/// Syncs the clip enabled status to match the guest state
|
||||
void SyncClipEnabled();
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
#include "common/alignment.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/microprofile.h"
|
||||
#include "common/scope_exit.h"
|
||||
#include "core/core.h"
|
||||
@@ -51,15 +52,17 @@ static VAddr TryGetCpuAddr(Tegra::GPUVAddr gpu_addr) {
|
||||
params.type = GetFormatType(params.pixel_format);
|
||||
params.width = Common::AlignUp(config.tic.Width(), GetCompressionFactor(params.pixel_format));
|
||||
params.height = Common::AlignUp(config.tic.Height(), GetCompressionFactor(params.pixel_format));
|
||||
params.depth = config.tic.Depth();
|
||||
params.unaligned_height = config.tic.Height();
|
||||
params.size_in_bytes = params.SizeInBytes();
|
||||
params.cache_width = Common::AlignUp(params.width, 16);
|
||||
params.cache_height = Common::AlignUp(params.height, 16);
|
||||
params.cache_width = Common::AlignUp(params.width, 8);
|
||||
params.cache_height = Common::AlignUp(params.height, 8);
|
||||
params.target = SurfaceTargetFromTextureType(config.tic.texture_type);
|
||||
return params;
|
||||
}
|
||||
|
||||
/*static*/ SurfaceParams SurfaceParams::CreateForFramebuffer(
|
||||
const Tegra::Engines::Maxwell3D::Regs::RenderTargetConfig& config) {
|
||||
/*static*/ SurfaceParams SurfaceParams::CreateForFramebuffer(size_t index) {
|
||||
const auto& config{Core::System::GetInstance().GPU().Maxwell3D().regs.rt[index]};
|
||||
SurfaceParams params{};
|
||||
params.addr = TryGetCpuAddr(config.Address());
|
||||
params.is_tiled = true;
|
||||
@@ -69,10 +72,12 @@ static VAddr TryGetCpuAddr(Tegra::GPUVAddr gpu_addr) {
|
||||
params.type = GetFormatType(params.pixel_format);
|
||||
params.width = config.width;
|
||||
params.height = config.height;
|
||||
params.depth = 1;
|
||||
params.unaligned_height = config.height;
|
||||
params.size_in_bytes = params.SizeInBytes();
|
||||
params.cache_width = Common::AlignUp(params.width, 16);
|
||||
params.cache_height = Common::AlignUp(params.height, 16);
|
||||
params.cache_width = Common::AlignUp(params.width, 8);
|
||||
params.cache_height = Common::AlignUp(params.height, 8);
|
||||
params.target = SurfaceTarget::Texture2D;
|
||||
return params;
|
||||
}
|
||||
|
||||
@@ -86,13 +91,14 @@ static VAddr TryGetCpuAddr(Tegra::GPUVAddr gpu_addr) {
|
||||
params.pixel_format = PixelFormatFromDepthFormat(format);
|
||||
params.component_type = ComponentTypeFromDepthFormat(format);
|
||||
params.type = GetFormatType(params.pixel_format);
|
||||
params.size_in_bytes = params.SizeInBytes();
|
||||
params.width = zeta_width;
|
||||
params.height = zeta_height;
|
||||
params.depth = 1;
|
||||
params.unaligned_height = zeta_height;
|
||||
params.size_in_bytes = params.SizeInBytes();
|
||||
params.cache_width = Common::AlignUp(params.width, 16);
|
||||
params.cache_height = Common::AlignUp(params.height, 16);
|
||||
params.cache_width = Common::AlignUp(params.width, 8);
|
||||
params.cache_height = Common::AlignUp(params.height, 8);
|
||||
params.target = SurfaceTarget::Texture2D;
|
||||
return params;
|
||||
}
|
||||
|
||||
@@ -166,6 +172,26 @@ static constexpr std::array<FormatTuple, SurfaceParams::MaxPixelFormat> tex_form
|
||||
ComponentType::Float, false}, // Z32FS8
|
||||
}};
|
||||
|
||||
static GLenum SurfaceTargetToGL(SurfaceParams::SurfaceTarget target) {
|
||||
switch (target) {
|
||||
case SurfaceParams::SurfaceTarget::Texture1D:
|
||||
return GL_TEXTURE_1D;
|
||||
case SurfaceParams::SurfaceTarget::Texture2D:
|
||||
return GL_TEXTURE_2D;
|
||||
case SurfaceParams::SurfaceTarget::Texture3D:
|
||||
return GL_TEXTURE_3D;
|
||||
case SurfaceParams::SurfaceTarget::Texture1DArray:
|
||||
return GL_TEXTURE_1D_ARRAY;
|
||||
case SurfaceParams::SurfaceTarget::Texture2DArray:
|
||||
return GL_TEXTURE_2D_ARRAY;
|
||||
case SurfaceParams::SurfaceTarget::TextureCubemap:
|
||||
return GL_TEXTURE_CUBE_MAP;
|
||||
}
|
||||
LOG_CRITICAL(Render_OpenGL, "Unimplemented texture target={}", static_cast<u32>(target));
|
||||
UNREACHABLE();
|
||||
return {};
|
||||
}
|
||||
|
||||
static const FormatTuple& GetFormatTuple(PixelFormat pixel_format, ComponentType component_type) {
|
||||
ASSERT(static_cast<size_t>(pixel_format) < tex_format_tuples.size());
|
||||
auto& format = tex_format_tuples[static_cast<unsigned int>(pixel_format)];
|
||||
@@ -220,7 +246,8 @@ static bool IsFormatBCn(PixelFormat format) {
|
||||
}
|
||||
|
||||
template <bool morton_to_gl, PixelFormat format>
|
||||
void MortonCopy(u32 stride, u32 block_height, u32 height, std::vector<u8>& gl_buffer, VAddr addr) {
|
||||
void MortonCopy(u32 stride, u32 block_height, u32 height, u8* gl_buffer, size_t gl_buffer_size,
|
||||
VAddr addr) {
|
||||
constexpr u32 bytes_per_pixel = SurfaceParams::GetFormatBpp(format) / CHAR_BIT;
|
||||
constexpr u32 gl_bytes_per_pixel = CachedSurface::GetGLBytesPerPixel(format);
|
||||
|
||||
@@ -230,18 +257,18 @@ void MortonCopy(u32 stride, u32 block_height, u32 height, std::vector<u8>& gl_bu
|
||||
const u32 tile_size{IsFormatBCn(format) ? 4U : 1U};
|
||||
const std::vector<u8> data = Tegra::Texture::UnswizzleTexture(
|
||||
addr, tile_size, bytes_per_pixel, stride, height, block_height);
|
||||
const size_t size_to_copy{std::min(gl_buffer.size(), data.size())};
|
||||
gl_buffer.assign(data.begin(), data.begin() + size_to_copy);
|
||||
const size_t size_to_copy{std::min(gl_buffer_size, data.size())};
|
||||
memcpy(gl_buffer, data.data(), size_to_copy);
|
||||
} else {
|
||||
// TODO(bunnei): Assumes the default rendering GOB size of 16 (128 lines). We should
|
||||
// check the configuration for this and perform more generic un/swizzle
|
||||
LOG_WARNING(Render_OpenGL, "need to use correct swizzle/GOB parameters!");
|
||||
VideoCore::MortonCopyPixels128(stride, height, bytes_per_pixel, gl_bytes_per_pixel,
|
||||
Memory::GetPointer(addr), gl_buffer.data(), morton_to_gl);
|
||||
Memory::GetPointer(addr), gl_buffer, morton_to_gl);
|
||||
}
|
||||
}
|
||||
|
||||
static constexpr std::array<void (*)(u32, u32, u32, std::vector<u8>&, VAddr),
|
||||
static constexpr std::array<void (*)(u32, u32, u32, u8*, size_t, VAddr),
|
||||
SurfaceParams::MaxPixelFormat>
|
||||
morton_to_gl_fns = {
|
||||
// clang-format off
|
||||
@@ -298,7 +325,7 @@ static constexpr std::array<void (*)(u32, u32, u32, std::vector<u8>&, VAddr),
|
||||
// clang-format on
|
||||
};
|
||||
|
||||
static constexpr std::array<void (*)(u32, u32, u32, std::vector<u8>&, VAddr),
|
||||
static constexpr std::array<void (*)(u32, u32, u32, u8*, size_t, VAddr),
|
||||
SurfaceParams::MaxPixelFormat>
|
||||
gl_to_morton_fns = {
|
||||
// clang-format off
|
||||
@@ -357,33 +384,6 @@ static constexpr std::array<void (*)(u32, u32, u32, std::vector<u8>&, VAddr),
|
||||
// clang-format on
|
||||
};
|
||||
|
||||
// Allocate an uninitialized texture of appropriate size and format for the surface
|
||||
static void AllocateSurfaceTexture(GLuint texture, const FormatTuple& format_tuple, u32 width,
|
||||
u32 height) {
|
||||
OpenGLState cur_state = OpenGLState::GetCurState();
|
||||
|
||||
// Keep track of previous texture bindings
|
||||
GLuint old_tex = cur_state.texture_units[0].texture_2d;
|
||||
cur_state.texture_units[0].texture_2d = texture;
|
||||
cur_state.Apply();
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
|
||||
if (!format_tuple.compressed) {
|
||||
// Only pre-create the texture for non-compressed textures.
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, format_tuple.internal_format, width, height, 0,
|
||||
format_tuple.format, format_tuple.type, nullptr);
|
||||
}
|
||||
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
|
||||
// Restore previous texture bindings
|
||||
cur_state.texture_units[0].texture_2d = old_tex;
|
||||
cur_state.Apply();
|
||||
}
|
||||
|
||||
static bool BlitTextures(GLuint src_tex, const MathUtil::Rectangle<u32>& src_rect, GLuint dst_tex,
|
||||
const MathUtil::Rectangle<u32>& dst_rect, SurfaceType type,
|
||||
GLuint read_fb_handle, GLuint draw_fb_handle) {
|
||||
@@ -438,12 +438,56 @@ static bool BlitTextures(GLuint src_tex, const MathUtil::Rectangle<u32>& src_rec
|
||||
return true;
|
||||
}
|
||||
|
||||
CachedSurface::CachedSurface(const SurfaceParams& params) : params(params) {
|
||||
CachedSurface::CachedSurface(const SurfaceParams& params)
|
||||
: params(params), gl_target(SurfaceTargetToGL(params.target)) {
|
||||
texture.Create();
|
||||
const auto& rect{params.GetRect()};
|
||||
AllocateSurfaceTexture(texture.handle,
|
||||
GetFormatTuple(params.pixel_format, params.component_type),
|
||||
rect.GetWidth(), rect.GetHeight());
|
||||
|
||||
// Keep track of previous texture bindings
|
||||
OpenGLState cur_state = OpenGLState::GetCurState();
|
||||
const auto& old_tex = cur_state.texture_units[0];
|
||||
SCOPE_EXIT({
|
||||
cur_state.texture_units[0] = old_tex;
|
||||
cur_state.Apply();
|
||||
});
|
||||
|
||||
cur_state.texture_units[0].texture = texture.handle;
|
||||
cur_state.texture_units[0].target = SurfaceTargetToGL(params.target);
|
||||
cur_state.Apply();
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
|
||||
const auto& format_tuple = GetFormatTuple(params.pixel_format, params.component_type);
|
||||
if (!format_tuple.compressed) {
|
||||
// Only pre-create the texture for non-compressed textures.
|
||||
switch (params.target) {
|
||||
case SurfaceParams::SurfaceTarget::Texture1D:
|
||||
glTexImage1D(SurfaceTargetToGL(params.target), 0, format_tuple.internal_format,
|
||||
rect.GetWidth(), 0, format_tuple.format, format_tuple.type, nullptr);
|
||||
break;
|
||||
case SurfaceParams::SurfaceTarget::Texture2D:
|
||||
glTexImage2D(SurfaceTargetToGL(params.target), 0, format_tuple.internal_format,
|
||||
rect.GetWidth(), rect.GetHeight(), 0, format_tuple.format,
|
||||
format_tuple.type, nullptr);
|
||||
break;
|
||||
case SurfaceParams::SurfaceTarget::Texture3D:
|
||||
case SurfaceParams::SurfaceTarget::Texture2DArray:
|
||||
glTexImage3D(SurfaceTargetToGL(params.target), 0, format_tuple.internal_format,
|
||||
rect.GetWidth(), rect.GetHeight(), params.depth, 0, format_tuple.format,
|
||||
format_tuple.type, nullptr);
|
||||
break;
|
||||
default:
|
||||
LOG_CRITICAL(Render_OpenGL, "Unimplemented surface target={}",
|
||||
static_cast<u32>(params.target));
|
||||
UNREACHABLE();
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, format_tuple.internal_format, rect.GetWidth(),
|
||||
rect.GetHeight(), 0, format_tuple.format, format_tuple.type, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
glTexParameteri(SurfaceTargetToGL(params.target), GL_TEXTURE_MAX_LEVEL, 0);
|
||||
glTexParameteri(SurfaceTargetToGL(params.target), GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTexParameteri(SurfaceTargetToGL(params.target), GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(SurfaceTargetToGL(params.target), GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
}
|
||||
|
||||
static void ConvertS8Z24ToZ24S8(std::vector<u8>& data, u32 width, u32 height) {
|
||||
@@ -461,7 +505,7 @@ static void ConvertS8Z24ToZ24S8(std::vector<u8>& data, u32 width, u32 height) {
|
||||
|
||||
S8Z24 input_pixel{};
|
||||
Z24S8 output_pixel{};
|
||||
const auto bpp{CachedSurface::GetGLBytesPerPixel(PixelFormat::S8Z24)};
|
||||
constexpr auto bpp{CachedSurface::GetGLBytesPerPixel(PixelFormat::S8Z24)};
|
||||
for (size_t y = 0; y < height; ++y) {
|
||||
for (size_t x = 0; x < width; ++x) {
|
||||
const size_t offset{bpp * (y * width + x)};
|
||||
@@ -474,7 +518,7 @@ static void ConvertS8Z24ToZ24S8(std::vector<u8>& data, u32 width, u32 height) {
|
||||
}
|
||||
|
||||
static void ConvertG8R8ToR8G8(std::vector<u8>& data, u32 width, u32 height) {
|
||||
const auto bpp{CachedSurface::GetGLBytesPerPixel(PixelFormat::G8R8U)};
|
||||
constexpr auto bpp{CachedSurface::GetGLBytesPerPixel(PixelFormat::G8R8U)};
|
||||
for (size_t y = 0; y < height; ++y) {
|
||||
for (size_t x = 0; x < width; ++x) {
|
||||
const size_t offset{bpp * (y * width + x)};
|
||||
@@ -514,23 +558,6 @@ static void ConvertFormatAsNeeded_LoadGLBuffer(std::vector<u8>& data, PixelForma
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to perform software conversion (as needed) when flushing a buffer to Switch
|
||||
* memory. This is for Maxwell pixel formats that cannot be represented as-is in OpenGL or with
|
||||
* typical desktop GPUs.
|
||||
*/
|
||||
static void ConvertFormatAsNeeded_FlushGLBuffer(std::vector<u8>& /*data*/, PixelFormat pixel_format,
|
||||
u32 /*width*/, u32 /*height*/) {
|
||||
switch (pixel_format) {
|
||||
case PixelFormat::ASTC_2D_4X4:
|
||||
case PixelFormat::S8Z24:
|
||||
LOG_CRITICAL(Render_OpenGL, "Unimplemented pixel_format={}",
|
||||
static_cast<u32>(pixel_format));
|
||||
UNREACHABLE();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
MICROPROFILE_DEFINE(OpenGL_SurfaceLoad, "OpenGL", "Surface Load", MP_RGB(128, 64, 192));
|
||||
void CachedSurface::LoadGLBuffer() {
|
||||
ASSERT(params.type != SurfaceType::Fill);
|
||||
@@ -545,13 +572,25 @@ void CachedSurface::LoadGLBuffer() {
|
||||
MICROPROFILE_SCOPE(OpenGL_SurfaceLoad);
|
||||
|
||||
if (params.is_tiled) {
|
||||
gl_buffer.resize(copy_size);
|
||||
// TODO(bunnei): This only unswizzles and copies a 2D texture - we do not yet know how to do
|
||||
// this for 3D textures, etc.
|
||||
switch (params.target) {
|
||||
case SurfaceParams::SurfaceTarget::Texture2D:
|
||||
// Pass impl. to the fallback code below
|
||||
break;
|
||||
default:
|
||||
LOG_CRITICAL(HW_GPU, "Unimplemented tiled load for target={}",
|
||||
static_cast<u32>(params.target));
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
gl_buffer.resize(static_cast<size_t>(params.depth) * copy_size);
|
||||
morton_to_gl_fns[static_cast<size_t>(params.pixel_format)](
|
||||
params.width, params.block_height, params.height, gl_buffer, params.addr);
|
||||
params.width, params.block_height, params.height, gl_buffer.data(), copy_size,
|
||||
params.addr);
|
||||
} else {
|
||||
const u8* const texture_src_data_end = texture_src_data + copy_size;
|
||||
|
||||
const u8* const texture_src_data_end{texture_src_data +
|
||||
(static_cast<size_t>(params.depth) * copy_size)};
|
||||
gl_buffer.assign(texture_src_data, texture_src_data_end);
|
||||
}
|
||||
|
||||
@@ -560,23 +599,7 @@ void CachedSurface::LoadGLBuffer() {
|
||||
|
||||
MICROPROFILE_DEFINE(OpenGL_SurfaceFlush, "OpenGL", "Surface Flush", MP_RGB(128, 192, 64));
|
||||
void CachedSurface::FlushGLBuffer() {
|
||||
u8* const dst_buffer = Memory::GetPointer(params.addr);
|
||||
|
||||
ASSERT(dst_buffer);
|
||||
ASSERT(gl_buffer.size() ==
|
||||
params.width * params.height * GetGLBytesPerPixel(params.pixel_format));
|
||||
|
||||
MICROPROFILE_SCOPE(OpenGL_SurfaceFlush);
|
||||
|
||||
ConvertFormatAsNeeded_FlushGLBuffer(gl_buffer, params.pixel_format, params.width,
|
||||
params.height);
|
||||
|
||||
if (!params.is_tiled) {
|
||||
std::memcpy(dst_buffer, gl_buffer.data(), params.size_in_bytes);
|
||||
} else {
|
||||
gl_to_morton_fns[static_cast<size_t>(params.pixel_format)](
|
||||
params.width, params.block_height, params.height, gl_buffer, params.addr);
|
||||
}
|
||||
ASSERT_MSG(false, "Unimplemented");
|
||||
}
|
||||
|
||||
MICROPROFILE_DEFINE(OpenGL_TextureUL, "OpenGL", "Texture Upload", MP_RGB(128, 64, 192));
|
||||
@@ -586,22 +609,29 @@ void CachedSurface::UploadGLTexture(GLuint read_fb_handle, GLuint draw_fb_handle
|
||||
|
||||
MICROPROFILE_SCOPE(OpenGL_TextureUL);
|
||||
|
||||
ASSERT(gl_buffer.size() ==
|
||||
params.width * params.height * GetGLBytesPerPixel(params.pixel_format));
|
||||
ASSERT(gl_buffer.size() == static_cast<size_t>(params.width) * params.height *
|
||||
GetGLBytesPerPixel(params.pixel_format) * params.depth);
|
||||
|
||||
const auto& rect{params.GetRect()};
|
||||
|
||||
// Load data from memory to the surface
|
||||
GLint x0 = static_cast<GLint>(rect.left);
|
||||
GLint y0 = static_cast<GLint>(rect.bottom);
|
||||
size_t buffer_offset = (y0 * params.width + x0) * GetGLBytesPerPixel(params.pixel_format);
|
||||
const GLint x0 = static_cast<GLint>(rect.left);
|
||||
const GLint y0 = static_cast<GLint>(rect.bottom);
|
||||
const size_t buffer_offset =
|
||||
static_cast<size_t>(static_cast<size_t>(y0) * params.width + static_cast<size_t>(x0)) *
|
||||
GetGLBytesPerPixel(params.pixel_format);
|
||||
|
||||
const FormatTuple& tuple = GetFormatTuple(params.pixel_format, params.component_type);
|
||||
GLuint target_tex = texture.handle;
|
||||
const GLuint target_tex = texture.handle;
|
||||
OpenGLState cur_state = OpenGLState::GetCurState();
|
||||
|
||||
GLuint old_tex = cur_state.texture_units[0].texture_2d;
|
||||
cur_state.texture_units[0].texture_2d = target_tex;
|
||||
const auto& old_tex = cur_state.texture_units[0];
|
||||
SCOPE_EXIT({
|
||||
cur_state.texture_units[0] = old_tex;
|
||||
cur_state.Apply();
|
||||
});
|
||||
cur_state.texture_units[0].texture = target_tex;
|
||||
cur_state.texture_units[0].target = SurfaceTargetToGL(params.target);
|
||||
cur_state.Apply();
|
||||
|
||||
// Ensure no bad interactions with GL_UNPACK_ALIGNMENT
|
||||
@@ -610,136 +640,102 @@ void CachedSurface::UploadGLTexture(GLuint read_fb_handle, GLuint draw_fb_handle
|
||||
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
if (tuple.compressed) {
|
||||
glCompressedTexImage2D(
|
||||
GL_TEXTURE_2D, 0, tuple.internal_format, static_cast<GLsizei>(params.width),
|
||||
static_cast<GLsizei>(params.height), 0, static_cast<GLsizei>(params.size_in_bytes),
|
||||
&gl_buffer[buffer_offset]);
|
||||
switch (params.target) {
|
||||
case SurfaceParams::SurfaceTarget::Texture2D:
|
||||
glCompressedTexImage2D(
|
||||
SurfaceTargetToGL(params.target), 0, tuple.internal_format,
|
||||
static_cast<GLsizei>(params.width), static_cast<GLsizei>(params.height), 0,
|
||||
static_cast<GLsizei>(params.size_in_bytes), &gl_buffer[buffer_offset]);
|
||||
break;
|
||||
case SurfaceParams::SurfaceTarget::Texture3D:
|
||||
case SurfaceParams::SurfaceTarget::Texture2DArray:
|
||||
glCompressedTexImage3D(
|
||||
SurfaceTargetToGL(params.target), 0, tuple.internal_format,
|
||||
static_cast<GLsizei>(params.width), static_cast<GLsizei>(params.height),
|
||||
static_cast<GLsizei>(params.depth), 0, static_cast<GLsizei>(params.size_in_bytes),
|
||||
&gl_buffer[buffer_offset]);
|
||||
break;
|
||||
default:
|
||||
LOG_CRITICAL(Render_OpenGL, "Unimplemented surface target={}",
|
||||
static_cast<u32>(params.target));
|
||||
UNREACHABLE();
|
||||
glCompressedTexImage2D(
|
||||
GL_TEXTURE_2D, 0, tuple.internal_format, static_cast<GLsizei>(params.width),
|
||||
static_cast<GLsizei>(params.height), 0, static_cast<GLsizei>(params.size_in_bytes),
|
||||
&gl_buffer[buffer_offset]);
|
||||
}
|
||||
} else {
|
||||
glTexSubImage2D(GL_TEXTURE_2D, 0, x0, y0, static_cast<GLsizei>(rect.GetWidth()),
|
||||
static_cast<GLsizei>(rect.GetHeight()), tuple.format, tuple.type,
|
||||
&gl_buffer[buffer_offset]);
|
||||
|
||||
switch (params.target) {
|
||||
case SurfaceParams::SurfaceTarget::Texture1D:
|
||||
glTexSubImage1D(SurfaceTargetToGL(params.target), 0, x0,
|
||||
static_cast<GLsizei>(rect.GetWidth()), tuple.format, tuple.type,
|
||||
&gl_buffer[buffer_offset]);
|
||||
break;
|
||||
case SurfaceParams::SurfaceTarget::Texture2D:
|
||||
glTexSubImage2D(SurfaceTargetToGL(params.target), 0, x0, y0,
|
||||
static_cast<GLsizei>(rect.GetWidth()),
|
||||
static_cast<GLsizei>(rect.GetHeight()), tuple.format, tuple.type,
|
||||
&gl_buffer[buffer_offset]);
|
||||
break;
|
||||
case SurfaceParams::SurfaceTarget::Texture3D:
|
||||
case SurfaceParams::SurfaceTarget::Texture2DArray:
|
||||
glTexSubImage3D(SurfaceTargetToGL(params.target), 0, x0, y0, 0,
|
||||
static_cast<GLsizei>(rect.GetWidth()),
|
||||
static_cast<GLsizei>(rect.GetHeight()), params.depth, tuple.format,
|
||||
tuple.type, &gl_buffer[buffer_offset]);
|
||||
break;
|
||||
default:
|
||||
LOG_CRITICAL(Render_OpenGL, "Unimplemented surface target={}",
|
||||
static_cast<u32>(params.target));
|
||||
UNREACHABLE();
|
||||
glTexSubImage2D(GL_TEXTURE_2D, 0, x0, y0, static_cast<GLsizei>(rect.GetWidth()),
|
||||
static_cast<GLsizei>(rect.GetHeight()), tuple.format, tuple.type,
|
||||
&gl_buffer[buffer_offset]);
|
||||
}
|
||||
}
|
||||
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
|
||||
|
||||
cur_state.texture_units[0].texture_2d = old_tex;
|
||||
cur_state.Apply();
|
||||
}
|
||||
|
||||
MICROPROFILE_DEFINE(OpenGL_TextureDL, "OpenGL", "Texture Download", MP_RGB(128, 192, 64));
|
||||
void CachedSurface::DownloadGLTexture(GLuint read_fb_handle, GLuint draw_fb_handle) {
|
||||
if (params.type == SurfaceType::Fill)
|
||||
return;
|
||||
|
||||
MICROPROFILE_SCOPE(OpenGL_TextureDL);
|
||||
|
||||
gl_buffer.resize(params.width * params.height * GetGLBytesPerPixel(params.pixel_format));
|
||||
|
||||
OpenGLState state = OpenGLState::GetCurState();
|
||||
OpenGLState prev_state = state;
|
||||
SCOPE_EXIT({ prev_state.Apply(); });
|
||||
|
||||
const FormatTuple& tuple = GetFormatTuple(params.pixel_format, params.component_type);
|
||||
|
||||
// Ensure no bad interactions with GL_PACK_ALIGNMENT
|
||||
ASSERT(params.width * GetGLBytesPerPixel(params.pixel_format) % 4 == 0);
|
||||
glPixelStorei(GL_PACK_ROW_LENGTH, static_cast<GLint>(params.width));
|
||||
|
||||
const auto& rect{params.GetRect()};
|
||||
size_t buffer_offset =
|
||||
(rect.bottom * params.width + rect.left) * GetGLBytesPerPixel(params.pixel_format);
|
||||
|
||||
state.UnbindTexture(texture.handle);
|
||||
state.draw.read_framebuffer = read_fb_handle;
|
||||
state.Apply();
|
||||
|
||||
if (params.type == SurfaceType::ColorTexture) {
|
||||
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
|
||||
texture.handle, 0);
|
||||
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0,
|
||||
0);
|
||||
} else if (params.type == SurfaceType::Depth) {
|
||||
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
|
||||
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D,
|
||||
texture.handle, 0);
|
||||
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0);
|
||||
} else {
|
||||
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
|
||||
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D,
|
||||
texture.handle, 0);
|
||||
}
|
||||
glReadPixels(static_cast<GLint>(rect.left), static_cast<GLint>(rect.bottom),
|
||||
static_cast<GLsizei>(rect.GetWidth()), static_cast<GLsizei>(rect.GetHeight()),
|
||||
tuple.format, tuple.type, &gl_buffer[buffer_offset]);
|
||||
|
||||
glPixelStorei(GL_PACK_ROW_LENGTH, 0);
|
||||
}
|
||||
|
||||
RasterizerCacheOpenGL::RasterizerCacheOpenGL() {
|
||||
read_framebuffer.Create();
|
||||
draw_framebuffer.Create();
|
||||
copy_pbo.Create();
|
||||
}
|
||||
|
||||
Surface RasterizerCacheOpenGL::GetTextureSurface(const Tegra::Texture::FullTextureInfo& config) {
|
||||
return GetSurface(SurfaceParams::CreateForTexture(config));
|
||||
}
|
||||
|
||||
SurfaceSurfaceRect_Tuple RasterizerCacheOpenGL::GetFramebufferSurfaces(bool using_color_fb,
|
||||
bool using_depth_fb,
|
||||
bool preserve_contents) {
|
||||
const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
|
||||
|
||||
// TODO(bunnei): This is hard corded to use just the first render buffer
|
||||
LOG_TRACE(Render_OpenGL, "hard-coded for render target 0!");
|
||||
|
||||
// get color and depth surfaces
|
||||
SurfaceParams color_params{};
|
||||
SurfaceParams depth_params{};
|
||||
|
||||
if (using_color_fb) {
|
||||
color_params = SurfaceParams::CreateForFramebuffer(regs.rt[0]);
|
||||
Surface RasterizerCacheOpenGL::GetDepthBufferSurface(bool preserve_contents) {
|
||||
const auto& regs{Core::System::GetInstance().GPU().Maxwell3D().regs};
|
||||
if (!regs.zeta.Address() || !regs.zeta_enable) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (using_depth_fb) {
|
||||
depth_params = SurfaceParams::CreateForDepthBuffer(regs.zeta_width, regs.zeta_height,
|
||||
regs.zeta.Address(), regs.zeta.format);
|
||||
SurfaceParams depth_params{SurfaceParams::CreateForDepthBuffer(
|
||||
regs.zeta_width, regs.zeta_height, regs.zeta.Address(), regs.zeta.format)};
|
||||
|
||||
return GetSurface(depth_params, preserve_contents);
|
||||
}
|
||||
|
||||
Surface RasterizerCacheOpenGL::GetColorBufferSurface(size_t index, bool preserve_contents) {
|
||||
const auto& regs{Core::System::GetInstance().GPU().Maxwell3D().regs};
|
||||
|
||||
ASSERT(index < Tegra::Engines::Maxwell3D::Regs::NumRenderTargets);
|
||||
|
||||
if (index >= regs.rt_control.count) {
|
||||
return {};
|
||||
}
|
||||
|
||||
MathUtil::Rectangle<u32> color_rect{};
|
||||
Surface color_surface;
|
||||
if (using_color_fb) {
|
||||
color_surface = GetSurface(color_params, preserve_contents);
|
||||
if (color_surface) {
|
||||
color_rect = color_surface->GetSurfaceParams().GetRect();
|
||||
}
|
||||
if (regs.rt[index].Address() == 0 || regs.rt[index].format == Tegra::RenderTargetFormat::NONE) {
|
||||
return {};
|
||||
}
|
||||
|
||||
MathUtil::Rectangle<u32> depth_rect{};
|
||||
Surface depth_surface;
|
||||
if (using_depth_fb) {
|
||||
depth_surface = GetSurface(depth_params, preserve_contents);
|
||||
if (depth_surface) {
|
||||
depth_rect = depth_surface->GetSurfaceParams().GetRect();
|
||||
}
|
||||
}
|
||||
const SurfaceParams color_params{SurfaceParams::CreateForFramebuffer(index)};
|
||||
|
||||
MathUtil::Rectangle<u32> fb_rect{};
|
||||
if (color_surface && depth_surface) {
|
||||
fb_rect = color_rect;
|
||||
// Color and Depth surfaces must have the same dimensions and offsets
|
||||
if (color_rect.bottom != depth_rect.bottom || color_rect.top != depth_rect.top ||
|
||||
color_rect.left != depth_rect.left || color_rect.right != depth_rect.right) {
|
||||
color_surface = GetSurface(color_params);
|
||||
depth_surface = GetSurface(depth_params);
|
||||
fb_rect = color_surface->GetSurfaceParams().GetRect();
|
||||
}
|
||||
} else if (color_surface) {
|
||||
fb_rect = color_rect;
|
||||
} else if (depth_surface) {
|
||||
fb_rect = depth_rect;
|
||||
}
|
||||
|
||||
return std::make_tuple(color_surface, depth_surface, fb_rect);
|
||||
return GetSurface(color_params, preserve_contents);
|
||||
}
|
||||
|
||||
void RasterizerCacheOpenGL::LoadSurface(const Surface& surface) {
|
||||
@@ -748,7 +744,6 @@ void RasterizerCacheOpenGL::LoadSurface(const Surface& surface) {
|
||||
}
|
||||
|
||||
void RasterizerCacheOpenGL::FlushSurface(const Surface& surface) {
|
||||
surface->DownloadGLTexture(read_framebuffer.handle, draw_framebuffer.handle);
|
||||
surface->FlushGLBuffer();
|
||||
}
|
||||
|
||||
@@ -809,8 +804,8 @@ Surface RasterizerCacheOpenGL::RecreateSurface(const Surface& surface,
|
||||
// If format is unchanged, we can do a faster blit without reinterpreting pixel data
|
||||
if (params.pixel_format == new_params.pixel_format) {
|
||||
BlitTextures(surface->Texture().handle, params.GetRect(), new_surface->Texture().handle,
|
||||
new_surface->GetSurfaceParams().GetRect(), params.type,
|
||||
read_framebuffer.handle, draw_framebuffer.handle);
|
||||
params.GetRect(), params.type, read_framebuffer.handle,
|
||||
draw_framebuffer.handle);
|
||||
return new_surface;
|
||||
}
|
||||
|
||||
@@ -821,12 +816,7 @@ Surface RasterizerCacheOpenGL::RecreateSurface(const Surface& surface,
|
||||
|
||||
size_t buffer_size = std::max(params.SizeInBytes(), new_params.SizeInBytes());
|
||||
|
||||
// Use a Pixel Buffer Object to download the previous texture and then upload it to the new
|
||||
// one using the new format.
|
||||
OGLBuffer pbo;
|
||||
pbo.Create();
|
||||
|
||||
glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo.handle);
|
||||
glBindBuffer(GL_PIXEL_PACK_BUFFER, copy_pbo.handle);
|
||||
glBufferData(GL_PIXEL_PACK_BUFFER, buffer_size, nullptr, GL_STREAM_DRAW_ARB);
|
||||
if (source_format.compressed) {
|
||||
glGetCompressedTextureImage(surface->Texture().handle, 0,
|
||||
@@ -845,8 +835,8 @@ Surface RasterizerCacheOpenGL::RecreateSurface(const Surface& surface,
|
||||
// of the data in this case. Games like Super Mario Odyssey seem to hit this case
|
||||
// when drawing, it re-uses the memory of a previous texture as a bigger framebuffer
|
||||
// but it doesn't clear it beforehand, the texture is already full of zeros.
|
||||
LOG_CRITICAL(HW_GPU, "Trying to upload extra texture data from the CPU during "
|
||||
"reinterpretation but the texture is tiled.");
|
||||
LOG_DEBUG(HW_GPU, "Trying to upload extra texture data from the CPU during "
|
||||
"reinterpretation but the texture is tiled.");
|
||||
}
|
||||
size_t remaining_size = new_params.SizeInBytes() - params.SizeInBytes();
|
||||
std::vector<u8> data(remaining_size);
|
||||
@@ -859,21 +849,38 @@ Surface RasterizerCacheOpenGL::RecreateSurface(const Surface& surface,
|
||||
|
||||
const auto& dest_rect{new_params.GetRect()};
|
||||
|
||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo.handle);
|
||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, copy_pbo.handle);
|
||||
if (dest_format.compressed) {
|
||||
glCompressedTexSubImage2D(
|
||||
GL_TEXTURE_2D, 0, 0, 0, static_cast<GLsizei>(dest_rect.GetWidth()),
|
||||
static_cast<GLsizei>(dest_rect.GetHeight()), dest_format.format,
|
||||
static_cast<GLsizei>(new_params.SizeInBytes()), nullptr);
|
||||
LOG_CRITICAL(HW_GPU, "Compressed copy is unimplemented!");
|
||||
UNREACHABLE();
|
||||
} else {
|
||||
glTextureSubImage2D(new_surface->Texture().handle, 0, 0, 0,
|
||||
static_cast<GLsizei>(dest_rect.GetWidth()),
|
||||
static_cast<GLsizei>(dest_rect.GetHeight()), dest_format.format,
|
||||
dest_format.type, nullptr);
|
||||
switch (new_params.target) {
|
||||
case SurfaceParams::SurfaceTarget::Texture1D:
|
||||
glTextureSubImage1D(new_surface->Texture().handle, 0, 0,
|
||||
static_cast<GLsizei>(dest_rect.GetWidth()), dest_format.format,
|
||||
dest_format.type, nullptr);
|
||||
break;
|
||||
case SurfaceParams::SurfaceTarget::Texture2D:
|
||||
glTextureSubImage2D(new_surface->Texture().handle, 0, 0, 0,
|
||||
static_cast<GLsizei>(dest_rect.GetWidth()),
|
||||
static_cast<GLsizei>(dest_rect.GetHeight()), dest_format.format,
|
||||
dest_format.type, nullptr);
|
||||
break;
|
||||
case SurfaceParams::SurfaceTarget::Texture3D:
|
||||
case SurfaceParams::SurfaceTarget::Texture2DArray:
|
||||
glTextureSubImage3D(new_surface->Texture().handle, 0, 0, 0, 0,
|
||||
static_cast<GLsizei>(dest_rect.GetWidth()),
|
||||
static_cast<GLsizei>(dest_rect.GetHeight()),
|
||||
static_cast<GLsizei>(new_params.depth), dest_format.format,
|
||||
dest_format.type, nullptr);
|
||||
break;
|
||||
default:
|
||||
LOG_CRITICAL(Render_OpenGL, "Unimplemented surface target={}",
|
||||
static_cast<u32>(params.target));
|
||||
UNREACHABLE();
|
||||
}
|
||||
}
|
||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
||||
|
||||
pbo.Release();
|
||||
}
|
||||
|
||||
return new_surface;
|
||||
|
||||
@@ -109,6 +109,33 @@ struct SurfaceParams {
|
||||
Invalid = 4,
|
||||
};
|
||||
|
||||
enum class SurfaceTarget {
|
||||
Texture1D,
|
||||
Texture2D,
|
||||
Texture3D,
|
||||
Texture1DArray,
|
||||
Texture2DArray,
|
||||
TextureCubemap,
|
||||
};
|
||||
|
||||
static SurfaceTarget SurfaceTargetFromTextureType(Tegra::Texture::TextureType texture_type) {
|
||||
switch (texture_type) {
|
||||
case Tegra::Texture::TextureType::Texture1D:
|
||||
return SurfaceTarget::Texture1D;
|
||||
case Tegra::Texture::TextureType::Texture2D:
|
||||
case Tegra::Texture::TextureType::Texture2DNoMipmap:
|
||||
return SurfaceTarget::Texture2D;
|
||||
case Tegra::Texture::TextureType::Texture1DArray:
|
||||
return SurfaceTarget::Texture1DArray;
|
||||
case Tegra::Texture::TextureType::Texture2DArray:
|
||||
return SurfaceTarget::Texture2DArray;
|
||||
default:
|
||||
LOG_CRITICAL(HW_GPU, "Unimplemented texture_type={}", static_cast<u32>(texture_type));
|
||||
UNREACHABLE();
|
||||
return SurfaceTarget::Texture2D;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the compression factor for the specified PixelFormat. This applies to just the
|
||||
* "compressed width" and "compressed height", not the overall compression factor of a
|
||||
@@ -635,15 +662,14 @@ struct SurfaceParams {
|
||||
ASSERT(width % compression_factor == 0);
|
||||
ASSERT(height % compression_factor == 0);
|
||||
return (width / compression_factor) * (height / compression_factor) *
|
||||
GetFormatBpp(pixel_format) / CHAR_BIT;
|
||||
GetFormatBpp(pixel_format) * depth / CHAR_BIT;
|
||||
}
|
||||
|
||||
/// Creates SurfaceParams from a texture configuration
|
||||
static SurfaceParams CreateForTexture(const Tegra::Texture::FullTextureInfo& config);
|
||||
|
||||
/// Creates SurfaceParams from a framebuffer configuration
|
||||
static SurfaceParams CreateForFramebuffer(
|
||||
const Tegra::Engines::Maxwell3D::Regs::RenderTargetConfig& config);
|
||||
static SurfaceParams CreateForFramebuffer(size_t index);
|
||||
|
||||
/// Creates SurfaceParams for a depth buffer configuration
|
||||
static SurfaceParams CreateForDepthBuffer(u32 zeta_width, u32 zeta_height,
|
||||
@@ -664,8 +690,10 @@ struct SurfaceParams {
|
||||
SurfaceType type;
|
||||
u32 width;
|
||||
u32 height;
|
||||
u32 depth;
|
||||
u32 unaligned_height;
|
||||
size_t size_in_bytes;
|
||||
SurfaceTarget target;
|
||||
|
||||
// Parameters used for caching only
|
||||
u32 cache_width;
|
||||
@@ -709,6 +737,10 @@ public:
|
||||
return texture;
|
||||
}
|
||||
|
||||
GLenum Target() const {
|
||||
return gl_target;
|
||||
}
|
||||
|
||||
static constexpr unsigned int GetGLBytesPerPixel(SurfaceParams::PixelFormat format) {
|
||||
if (format == SurfaceParams::PixelFormat::Invalid)
|
||||
return 0;
|
||||
@@ -724,14 +756,14 @@ public:
|
||||
void LoadGLBuffer();
|
||||
void FlushGLBuffer();
|
||||
|
||||
// Upload/Download data in gl_buffer in/to this surface's texture
|
||||
// Upload data in gl_buffer to this surface's texture
|
||||
void UploadGLTexture(GLuint read_fb_handle, GLuint draw_fb_handle);
|
||||
void DownloadGLTexture(GLuint read_fb_handle, GLuint draw_fb_handle);
|
||||
|
||||
private:
|
||||
OGLTexture texture;
|
||||
std::vector<u8> gl_buffer;
|
||||
SurfaceParams params;
|
||||
GLenum gl_target;
|
||||
};
|
||||
|
||||
class RasterizerCacheOpenGL final : public RasterizerCache<Surface> {
|
||||
@@ -741,9 +773,11 @@ public:
|
||||
/// Get a surface based on the texture configuration
|
||||
Surface GetTextureSurface(const Tegra::Texture::FullTextureInfo& config);
|
||||
|
||||
/// Get the color and depth surfaces based on the framebuffer configuration
|
||||
SurfaceSurfaceRect_Tuple GetFramebufferSurfaces(bool using_color_fb, bool using_depth_fb,
|
||||
bool preserve_contents);
|
||||
/// Get the depth surface based on the framebuffer configuration
|
||||
Surface GetDepthBufferSurface(bool preserve_contents);
|
||||
|
||||
/// Get the color surface based on the framebuffer configuration and the specified render target
|
||||
Surface GetColorBufferSurface(size_t index, bool preserve_contents);
|
||||
|
||||
/// Flushes the surface to Switch memory
|
||||
void FlushSurface(const Surface& surface);
|
||||
@@ -774,6 +808,10 @@ private:
|
||||
|
||||
OGLFramebuffer read_framebuffer;
|
||||
OGLFramebuffer draw_framebuffer;
|
||||
|
||||
/// Use a Pixel Buffer Object to download the previous texture and then upload it to the new one
|
||||
/// using the new format.
|
||||
OGLBuffer copy_pbo;
|
||||
};
|
||||
|
||||
} // namespace OpenGL
|
||||
|
||||
@@ -13,8 +13,8 @@ namespace OpenGL {
|
||||
|
||||
/// Gets the address for the specified shader stage program
|
||||
static VAddr GetShaderAddress(Maxwell::ShaderProgram program) {
|
||||
auto& gpu = Core::System::GetInstance().GPU().Maxwell3D();
|
||||
auto& shader_config = gpu.regs.shader_config[static_cast<size_t>(program)];
|
||||
const auto& gpu = Core::System::GetInstance().GPU().Maxwell3D();
|
||||
const auto& shader_config = gpu.regs.shader_config[static_cast<size_t>(program)];
|
||||
return *gpu.memory_manager.GpuToCpuAddress(gpu.regs.code_address.CodeAddress() +
|
||||
shader_config.offset);
|
||||
}
|
||||
@@ -86,7 +86,7 @@ CachedShader::CachedShader(VAddr addr, Maxwell::ShaderProgram program_type)
|
||||
}
|
||||
|
||||
GLuint CachedShader::GetProgramResourceIndex(const GLShader::ConstBufferEntry& buffer) {
|
||||
auto search{resource_cache.find(buffer.GetHash())};
|
||||
const auto search{resource_cache.find(buffer.GetHash())};
|
||||
if (search == resource_cache.end()) {
|
||||
const GLuint index{
|
||||
glGetProgramResourceIndex(program.handle, GL_UNIFORM_BLOCK, buffer.GetName().c_str())};
|
||||
@@ -98,7 +98,7 @@ GLuint CachedShader::GetProgramResourceIndex(const GLShader::ConstBufferEntry& b
|
||||
}
|
||||
|
||||
GLint CachedShader::GetUniformLocation(const GLShader::SamplerEntry& sampler) {
|
||||
auto search{uniform_cache.find(sampler.GetHash())};
|
||||
const auto search{uniform_cache.find(sampler.GetHash())};
|
||||
if (search == uniform_cache.end()) {
|
||||
const GLint index{glGetUniformLocation(program.handle, sampler.GetName().c_str())};
|
||||
uniform_cache[sampler.GetHash()] = index;
|
||||
|
||||
@@ -113,7 +113,7 @@ private:
|
||||
|
||||
/// Scans a range of code for labels and determines the exit method.
|
||||
ExitMethod Scan(u32 begin, u32 end, std::set<u32>& labels) {
|
||||
auto [iter, inserted] =
|
||||
const auto [iter, inserted] =
|
||||
exit_method_map.emplace(std::make_pair(begin, end), ExitMethod::Undetermined);
|
||||
ExitMethod& exit_method = iter->second;
|
||||
if (!inserted)
|
||||
@@ -131,22 +131,22 @@ private:
|
||||
if (instr.pred.pred_index == static_cast<u64>(Pred::UnusedIndex)) {
|
||||
return exit_method = ExitMethod::AlwaysEnd;
|
||||
} else {
|
||||
ExitMethod not_met = Scan(offset + 1, end, labels);
|
||||
const ExitMethod not_met = Scan(offset + 1, end, labels);
|
||||
return exit_method = ParallelExit(ExitMethod::AlwaysEnd, not_met);
|
||||
}
|
||||
}
|
||||
case OpCode::Id::BRA: {
|
||||
u32 target = offset + instr.bra.GetBranchTarget();
|
||||
const u32 target = offset + instr.bra.GetBranchTarget();
|
||||
labels.insert(target);
|
||||
ExitMethod no_jmp = Scan(offset + 1, end, labels);
|
||||
ExitMethod jmp = Scan(target, end, labels);
|
||||
const ExitMethod no_jmp = Scan(offset + 1, end, labels);
|
||||
const ExitMethod jmp = Scan(target, end, labels);
|
||||
return exit_method = ParallelExit(no_jmp, jmp);
|
||||
}
|
||||
case OpCode::Id::SSY: {
|
||||
// The SSY instruction uses a similar encoding as the BRA instruction.
|
||||
ASSERT_MSG(instr.bra.constant_buffer == 0,
|
||||
"Constant buffer SSY is not supported");
|
||||
u32 target = offset + instr.bra.GetBranchTarget();
|
||||
const u32 target = offset + instr.bra.GetBranchTarget();
|
||||
labels.insert(target);
|
||||
// Continue scanning for an exit method.
|
||||
break;
|
||||
@@ -346,8 +346,8 @@ public:
|
||||
*/
|
||||
void SetRegisterToInputAttibute(const Register& reg, u64 elem, Attribute::Index attribute,
|
||||
const Tegra::Shader::IpaMode& input_mode) {
|
||||
std::string dest = GetRegisterAsFloat(reg);
|
||||
std::string src = GetInputAttribute(attribute, input_mode) + GetSwizzle(elem);
|
||||
const std::string dest = GetRegisterAsFloat(reg);
|
||||
const std::string src = GetInputAttribute(attribute, input_mode) + GetSwizzle(elem);
|
||||
shader.AddLine(dest + " = " + src + ';');
|
||||
}
|
||||
|
||||
@@ -359,8 +359,8 @@ public:
|
||||
* @param reg The register to use as the source value.
|
||||
*/
|
||||
void SetOutputAttributeToRegister(Attribute::Index attribute, u64 elem, const Register& reg) {
|
||||
std::string dest = GetOutputAttribute(attribute);
|
||||
std::string src = GetRegisterAsFloat(reg);
|
||||
const std::string dest = GetOutputAttribute(attribute);
|
||||
const std::string src = GetRegisterAsFloat(reg);
|
||||
|
||||
if (!dest.empty()) {
|
||||
// Can happen with unknown/unimplemented output attributes, in which case we ignore the
|
||||
@@ -393,9 +393,9 @@ public:
|
||||
GLSLRegister::Type type) {
|
||||
declr_const_buffers[cbuf_index].MarkAsUsedIndirect(cbuf_index, stage);
|
||||
|
||||
std::string final_offset = fmt::format("({} + {})", index_str, offset / 4);
|
||||
std::string value = 'c' + std::to_string(cbuf_index) + '[' + final_offset + " / 4][" +
|
||||
final_offset + " % 4]";
|
||||
const std::string final_offset = fmt::format("({} + {})", index_str, offset / 4);
|
||||
const std::string value = 'c' + std::to_string(cbuf_index) + '[' + final_offset + " / 4][" +
|
||||
final_offset + " % 4]";
|
||||
|
||||
if (type == GLSLRegister::Type::Float) {
|
||||
return value;
|
||||
@@ -443,13 +443,12 @@ public:
|
||||
}
|
||||
declarations.AddNewLine();
|
||||
|
||||
// Append the sampler2D array for the used textures.
|
||||
const size_t num_samplers = used_samplers.size();
|
||||
if (num_samplers > 0) {
|
||||
declarations.AddLine("uniform sampler2D " + SamplerEntry::GetArrayName(stage) + '[' +
|
||||
std::to_string(num_samplers) + "];");
|
||||
declarations.AddNewLine();
|
||||
const auto& samplers = GetSamplers();
|
||||
for (const auto& sampler : samplers) {
|
||||
declarations.AddLine("uniform " + sampler.GetTypeString() + ' ' + sampler.GetName() +
|
||||
';');
|
||||
}
|
||||
declarations.AddNewLine();
|
||||
}
|
||||
|
||||
/// Returns a list of constant buffer declarations
|
||||
@@ -461,27 +460,29 @@ public:
|
||||
}
|
||||
|
||||
/// Returns a list of samplers used in the shader
|
||||
std::vector<SamplerEntry> GetSamplers() const {
|
||||
const std::vector<SamplerEntry>& GetSamplers() const {
|
||||
return used_samplers;
|
||||
}
|
||||
|
||||
/// Returns the GLSL sampler used for the input shader sampler, and creates a new one if
|
||||
/// necessary.
|
||||
std::string AccessSampler(const Sampler& sampler) {
|
||||
size_t offset = static_cast<size_t>(sampler.index.Value());
|
||||
std::string AccessSampler(const Sampler& sampler, Tegra::Shader::TextureType type,
|
||||
bool is_array) {
|
||||
const size_t offset = static_cast<size_t>(sampler.index.Value());
|
||||
|
||||
// If this sampler has already been used, return the existing mapping.
|
||||
auto itr =
|
||||
const auto itr =
|
||||
std::find_if(used_samplers.begin(), used_samplers.end(),
|
||||
[&](const SamplerEntry& entry) { return entry.GetOffset() == offset; });
|
||||
|
||||
if (itr != used_samplers.end()) {
|
||||
ASSERT(itr->GetType() == type && itr->IsArray() == is_array);
|
||||
return itr->GetName();
|
||||
}
|
||||
|
||||
// Otherwise create a new mapping for this sampler
|
||||
size_t next_index = used_samplers.size();
|
||||
SamplerEntry entry{stage, offset, next_index};
|
||||
const size_t next_index = used_samplers.size();
|
||||
const SamplerEntry entry{stage, offset, next_index, type, is_array};
|
||||
used_samplers.emplace_back(entry);
|
||||
return entry.GetName();
|
||||
}
|
||||
@@ -698,7 +699,7 @@ private:
|
||||
};
|
||||
|
||||
bool IsColorComponentOutputEnabled(u32 render_target, u32 component) const {
|
||||
u32 bit = render_target * 4 + component;
|
||||
const u32 bit = render_target * 4 + component;
|
||||
return enabled_color_outputs & (1 << bit);
|
||||
}
|
||||
};
|
||||
@@ -706,7 +707,7 @@ private:
|
||||
|
||||
/// Gets the Subroutine object corresponding to the specified address.
|
||||
const Subroutine& GetSubroutine(u32 begin, u32 end) const {
|
||||
auto iter = subroutines.find(Subroutine{begin, end, suffix});
|
||||
const auto iter = subroutines.find(Subroutine{begin, end, suffix});
|
||||
ASSERT(iter != subroutines.end());
|
||||
return *iter;
|
||||
}
|
||||
@@ -722,8 +723,8 @@ private:
|
||||
}
|
||||
|
||||
/// Generates code representing a texture sampler.
|
||||
std::string GetSampler(const Sampler& sampler) {
|
||||
return regs.AccessSampler(sampler);
|
||||
std::string GetSampler(const Sampler& sampler, Tegra::Shader::TextureType type, bool is_array) {
|
||||
return regs.AccessSampler(sampler, type, is_array);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -751,7 +752,7 @@ private:
|
||||
// Can't assign to the constant predicate.
|
||||
ASSERT(pred != static_cast<u64>(Pred::UnusedIndex));
|
||||
|
||||
std::string variable = 'p' + std::to_string(pred) + '_' + suffix;
|
||||
const std::string variable = 'p' + std::to_string(pred) + '_' + suffix;
|
||||
shader.AddLine(variable + " = " + value + ';');
|
||||
declr_predicates.insert(std::move(variable));
|
||||
}
|
||||
@@ -1032,7 +1033,11 @@ private:
|
||||
if (header.writes_depth) {
|
||||
// The depth output is always 2 registers after the last color output, and current_reg
|
||||
// already contains one past the last color register.
|
||||
shader.AddLine("gl_FragDepth = " + regs.GetRegisterAsFloat(current_reg + 1) + ';');
|
||||
|
||||
shader.AddLine(
|
||||
"gl_FragDepth = " +
|
||||
regs.GetRegisterAsFloat(static_cast<Tegra::Shader::Register>(current_reg) + 1) +
|
||||
';');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1434,7 +1439,7 @@ private:
|
||||
if (instr.alu_integer.negate_b)
|
||||
op_b = "-(" + op_b + ')';
|
||||
|
||||
std::string shift = std::to_string(instr.alu_integer.shift_amount.Value());
|
||||
const std::string shift = std::to_string(instr.alu_integer.shift_amount.Value());
|
||||
|
||||
regs.SetRegisterToInteger(instr.gpr0, true, 0,
|
||||
"((" + op_a + " << " + shift + ") + " + op_b + ')', 1, 1);
|
||||
@@ -1452,7 +1457,7 @@ private:
|
||||
case OpCode::Id::SEL_C:
|
||||
case OpCode::Id::SEL_R:
|
||||
case OpCode::Id::SEL_IMM: {
|
||||
std::string condition =
|
||||
const std::string condition =
|
||||
GetPredicateCondition(instr.sel.pred, instr.sel.neg_pred != 0);
|
||||
regs.SetRegisterToInteger(instr.gpr0, true, 0,
|
||||
'(' + condition + ") ? " + op_a + " : " + op_b, 1, 1);
|
||||
@@ -1474,8 +1479,9 @@ private:
|
||||
case OpCode::Id::LOP3_C:
|
||||
case OpCode::Id::LOP3_R:
|
||||
case OpCode::Id::LOP3_IMM: {
|
||||
std::string op_c = regs.GetRegisterAsInteger(instr.gpr39);
|
||||
const std::string op_c = regs.GetRegisterAsInteger(instr.gpr39);
|
||||
std::string lut;
|
||||
|
||||
if (opcode->GetId() == OpCode::Id::LOP3_R) {
|
||||
lut = '(' + std::to_string(instr.alu.lop3.GetImmLut28()) + ')';
|
||||
} else {
|
||||
@@ -1490,9 +1496,9 @@ private:
|
||||
case OpCode::Id::IMNMX_IMM: {
|
||||
ASSERT_MSG(instr.imnmx.exchange == Tegra::Shader::IMinMaxExchange::None,
|
||||
"Unimplemented");
|
||||
std::string condition =
|
||||
const std::string condition =
|
||||
GetPredicateCondition(instr.imnmx.pred, instr.imnmx.negate_pred != 0);
|
||||
std::string parameters = op_a + ',' + op_b;
|
||||
const std::string parameters = op_a + ',' + op_b;
|
||||
regs.SetRegisterToInteger(instr.gpr0, instr.imnmx.is_signed, 0,
|
||||
'(' + condition + ") ? min(" + parameters + ") : max(" +
|
||||
parameters + ')',
|
||||
@@ -1509,7 +1515,7 @@ private:
|
||||
break;
|
||||
}
|
||||
case OpCode::Type::Ffma: {
|
||||
std::string op_a = regs.GetRegisterAsFloat(instr.gpr8);
|
||||
const std::string op_a = regs.GetRegisterAsFloat(instr.gpr8);
|
||||
std::string op_b = instr.ffma.negate_b ? "-" : "";
|
||||
std::string op_c = instr.ffma.negate_c ? "-" : "";
|
||||
|
||||
@@ -1719,7 +1725,7 @@ private:
|
||||
shader.AddLine("uint index = (" + regs.GetRegisterAsInteger(instr.gpr8, 0, false) +
|
||||
" / 4) & (MAX_CONSTBUFFER_ELEMENTS - 1);");
|
||||
|
||||
std::string op_a =
|
||||
const std::string op_a =
|
||||
regs.GetUniformIndirect(instr.cbuf36.index, instr.cbuf36.offset + 0, "index",
|
||||
GLSLRegister::Type::Float);
|
||||
|
||||
@@ -1729,7 +1735,7 @@ private:
|
||||
break;
|
||||
|
||||
case Tegra::Shader::UniformType::Double: {
|
||||
std::string op_b =
|
||||
const std::string op_b =
|
||||
regs.GetUniformIndirect(instr.cbuf36.index, instr.cbuf36.offset + 4,
|
||||
"index", GLSLRegister::Type::Float);
|
||||
regs.SetRegisterToFloat(instr.gpr0, 0, op_a, 1, 1);
|
||||
@@ -1753,10 +1759,35 @@ private:
|
||||
break;
|
||||
}
|
||||
case OpCode::Id::TEX: {
|
||||
const std::string op_a = regs.GetRegisterAsFloat(instr.gpr8);
|
||||
const std::string op_b = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1);
|
||||
const std::string sampler = GetSampler(instr.sampler);
|
||||
const std::string coord = "vec2 coords = vec2(" + op_a + ", " + op_b + ");";
|
||||
ASSERT_MSG(instr.tex.array == 0, "TEX arrays unimplemented");
|
||||
Tegra::Shader::TextureType texture_type{instr.tex.texture_type};
|
||||
std::string coord;
|
||||
|
||||
switch (texture_type) {
|
||||
case Tegra::Shader::TextureType::Texture1D: {
|
||||
const std::string x = regs.GetRegisterAsFloat(instr.gpr8);
|
||||
coord = "float coords = " + x + ';';
|
||||
break;
|
||||
}
|
||||
case Tegra::Shader::TextureType::Texture2D: {
|
||||
const std::string x = regs.GetRegisterAsFloat(instr.gpr8);
|
||||
const std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1);
|
||||
coord = "vec2 coords = vec2(" + x + ", " + y + ");";
|
||||
break;
|
||||
}
|
||||
default:
|
||||
LOG_CRITICAL(HW_GPU, "Unhandled texture type {}",
|
||||
static_cast<u32>(texture_type));
|
||||
UNREACHABLE();
|
||||
|
||||
// Fallback to interpreting as a 2D texture for now
|
||||
const std::string x = regs.GetRegisterAsFloat(instr.gpr8);
|
||||
const std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1);
|
||||
coord = "vec2 coords = vec2(" + x + ", " + y + ");";
|
||||
texture_type = Tegra::Shader::TextureType::Texture2D;
|
||||
}
|
||||
|
||||
const std::string sampler = GetSampler(instr.sampler, texture_type, false);
|
||||
// Add an extra scope and declare the texture coords inside to prevent
|
||||
// overwriting them in case they are used as outputs of the texs instruction.
|
||||
shader.AddLine("{");
|
||||
@@ -1778,20 +1809,65 @@ private:
|
||||
break;
|
||||
}
|
||||
case OpCode::Id::TEXS: {
|
||||
const std::string op_a = regs.GetRegisterAsFloat(instr.gpr8);
|
||||
const std::string op_b = regs.GetRegisterAsFloat(instr.gpr20);
|
||||
const std::string sampler = GetSampler(instr.sampler);
|
||||
const std::string coord = "vec2 coords = vec2(" + op_a + ", " + op_b + ");";
|
||||
std::string coord;
|
||||
Tegra::Shader::TextureType texture_type{instr.texs.GetTextureType()};
|
||||
bool is_array{instr.texs.IsArrayTexture()};
|
||||
|
||||
switch (texture_type) {
|
||||
case Tegra::Shader::TextureType::Texture2D: {
|
||||
if (is_array) {
|
||||
const std::string index = regs.GetRegisterAsInteger(instr.gpr8);
|
||||
const std::string x = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1);
|
||||
const std::string y = regs.GetRegisterAsFloat(instr.gpr20);
|
||||
coord = "vec3 coords = vec3(" + x + ", " + y + ", " + index + ");";
|
||||
} else {
|
||||
const std::string x = regs.GetRegisterAsFloat(instr.gpr8);
|
||||
const std::string y = regs.GetRegisterAsFloat(instr.gpr20);
|
||||
coord = "vec2 coords = vec2(" + x + ", " + y + ");";
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
LOG_CRITICAL(HW_GPU, "Unhandled texture type {}",
|
||||
static_cast<u32>(texture_type));
|
||||
UNREACHABLE();
|
||||
|
||||
// Fallback to interpreting as a 2D texture for now
|
||||
const std::string x = regs.GetRegisterAsFloat(instr.gpr8);
|
||||
const std::string y = regs.GetRegisterAsFloat(instr.gpr20);
|
||||
coord = "vec2 coords = vec2(" + x + ", " + y + ");";
|
||||
texture_type = Tegra::Shader::TextureType::Texture2D;
|
||||
is_array = false;
|
||||
}
|
||||
const std::string sampler = GetSampler(instr.sampler, texture_type, is_array);
|
||||
const std::string texture = "texture(" + sampler + ", coords)";
|
||||
WriteTexsInstruction(instr, coord, texture);
|
||||
break;
|
||||
}
|
||||
case OpCode::Id::TLDS: {
|
||||
const std::string op_a = regs.GetRegisterAsInteger(instr.gpr8);
|
||||
const std::string op_b = regs.GetRegisterAsInteger(instr.gpr20);
|
||||
const std::string sampler = GetSampler(instr.sampler);
|
||||
const std::string coord = "ivec2 coords = ivec2(" + op_a + ", " + op_b + ");";
|
||||
ASSERT(instr.tlds.GetTextureType() == Tegra::Shader::TextureType::Texture2D);
|
||||
ASSERT(instr.tlds.IsArrayTexture() == false);
|
||||
std::string coord;
|
||||
|
||||
switch (instr.tlds.GetTextureType()) {
|
||||
case Tegra::Shader::TextureType::Texture2D: {
|
||||
if (instr.tlds.IsArrayTexture()) {
|
||||
LOG_CRITICAL(HW_GPU, "Unhandled 2d array texture");
|
||||
UNREACHABLE();
|
||||
} else {
|
||||
const std::string x = regs.GetRegisterAsInteger(instr.gpr8);
|
||||
const std::string y = regs.GetRegisterAsInteger(instr.gpr20);
|
||||
coord = "ivec2 coords = ivec2(" + x + ", " + y + ");";
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
LOG_CRITICAL(HW_GPU, "Unhandled texture type {}",
|
||||
static_cast<u32>(instr.tlds.GetTextureType()));
|
||||
UNREACHABLE();
|
||||
}
|
||||
const std::string sampler = GetSampler(instr.sampler, instr.tlds.GetTextureType(),
|
||||
instr.tlds.IsArrayTexture());
|
||||
const std::string texture = "texelFetch(" + sampler + ", coords, 0)";
|
||||
WriteTexsInstruction(instr, coord, texture);
|
||||
break;
|
||||
@@ -1799,12 +1875,12 @@ private:
|
||||
case OpCode::Id::TLD4: {
|
||||
ASSERT(instr.tld4.texture_type == Tegra::Shader::TextureType::Texture2D);
|
||||
ASSERT(instr.tld4.array == 0);
|
||||
std::string coord{};
|
||||
std::string coord;
|
||||
|
||||
switch (instr.tld4.texture_type) {
|
||||
case Tegra::Shader::TextureType::Texture2D: {
|
||||
std::string x = regs.GetRegisterAsFloat(instr.gpr8);
|
||||
std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1);
|
||||
const std::string x = regs.GetRegisterAsFloat(instr.gpr8);
|
||||
const std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1);
|
||||
coord = "vec2 coords = vec2(" + x + ", " + y + ");";
|
||||
break;
|
||||
}
|
||||
@@ -1814,7 +1890,8 @@ private:
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
const std::string sampler = GetSampler(instr.sampler);
|
||||
const std::string sampler =
|
||||
GetSampler(instr.sampler, instr.tld4.texture_type, false);
|
||||
// Add an extra scope and declare the texture coords inside to prevent
|
||||
// overwriting them in case they are used as outputs of the texs instruction.
|
||||
shader.AddLine("{");
|
||||
@@ -1840,13 +1917,82 @@ private:
|
||||
const std::string op_a = regs.GetRegisterAsFloat(instr.gpr8);
|
||||
const std::string op_b = regs.GetRegisterAsFloat(instr.gpr20);
|
||||
// TODO(Subv): Figure out how the sampler type is encoded in the TLD4S instruction.
|
||||
const std::string sampler = GetSampler(instr.sampler);
|
||||
const std::string sampler =
|
||||
GetSampler(instr.sampler, Tegra::Shader::TextureType::Texture2D, false);
|
||||
const std::string coord = "vec2 coords = vec2(" + op_a + ", " + op_b + ");";
|
||||
const std::string texture = "textureGather(" + sampler + ", coords, " +
|
||||
std::to_string(instr.tld4s.component) + ')';
|
||||
WriteTexsInstruction(instr, coord, texture);
|
||||
break;
|
||||
}
|
||||
case OpCode::Id::TXQ: {
|
||||
// TODO: the new commits on the texture refactor, change the way samplers work.
|
||||
// Sadly, not all texture instructions specify the type of texture their sampler
|
||||
// uses. This must be fixed at a later instance.
|
||||
const std::string sampler =
|
||||
GetSampler(instr.sampler, Tegra::Shader::TextureType::Texture2D, false);
|
||||
switch (instr.txq.query_type) {
|
||||
case Tegra::Shader::TextureQueryType::Dimension: {
|
||||
const std::string texture = "textureQueryLevels(" + sampler + ')';
|
||||
regs.SetRegisterToInteger(instr.gpr0, true, 0, texture, 1, 1);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
LOG_CRITICAL(HW_GPU, "Unhandled texture query type: {}",
|
||||
static_cast<u32>(instr.txq.query_type.Value()));
|
||||
UNREACHABLE();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case OpCode::Id::TMML: {
|
||||
const std::string op_a = regs.GetRegisterAsFloat(instr.gpr8);
|
||||
const std::string op_b = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1);
|
||||
const bool is_array = instr.tmml.array != 0;
|
||||
auto texture_type = instr.tmml.texture_type.Value();
|
||||
const std::string sampler = GetSampler(instr.sampler, texture_type, is_array);
|
||||
|
||||
// TODO: add coordinates for different samplers once other texture types are
|
||||
// implemented.
|
||||
std::string coord;
|
||||
switch (texture_type) {
|
||||
case Tegra::Shader::TextureType::Texture1D: {
|
||||
std::string x = regs.GetRegisterAsFloat(instr.gpr8);
|
||||
coord = "float coords = " + x + ';';
|
||||
break;
|
||||
}
|
||||
case Tegra::Shader::TextureType::Texture2D: {
|
||||
std::string x = regs.GetRegisterAsFloat(instr.gpr8);
|
||||
std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1);
|
||||
coord = "vec2 coords = vec2(" + x + ", " + y + ");";
|
||||
break;
|
||||
}
|
||||
default:
|
||||
LOG_CRITICAL(HW_GPU, "Unhandled texture type {}",
|
||||
static_cast<u32>(texture_type));
|
||||
UNREACHABLE();
|
||||
|
||||
// Fallback to interpreting as a 2D texture for now
|
||||
std::string x = regs.GetRegisterAsFloat(instr.gpr8);
|
||||
std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1);
|
||||
coord = "vec2 coords = vec2(" + x + ", " + y + ");";
|
||||
texture_type = Tegra::Shader::TextureType::Texture2D;
|
||||
}
|
||||
// Add an extra scope and declare the texture coords inside to prevent
|
||||
// overwriting them in case they are used as outputs of the texs instruction.
|
||||
shader.AddLine('{');
|
||||
++shader.scope;
|
||||
shader.AddLine(coord);
|
||||
const std::string texture = "textureQueryLod(" + sampler + ", coords)";
|
||||
const std::string tmp = "vec2 tmp = " + texture + "*vec2(256.0, 256.0);";
|
||||
shader.AddLine(tmp);
|
||||
|
||||
regs.SetRegisterToInteger(instr.gpr0, true, 0, "int(tmp.y)", 1, 1);
|
||||
regs.SetRegisterToInteger(instr.gpr0.Value() + 1, false, 0, "uint(tmp.x)", 1, 1);
|
||||
--shader.scope;
|
||||
shader.AddLine('}');
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
LOG_CRITICAL(HW_GPU, "Unhandled memory instruction: {}", opcode->GetName());
|
||||
UNREACHABLE();
|
||||
@@ -1886,12 +2032,12 @@ private:
|
||||
// We can't use the constant predicate as destination.
|
||||
ASSERT(instr.fsetp.pred3 != static_cast<u64>(Pred::UnusedIndex));
|
||||
|
||||
std::string second_pred =
|
||||
const std::string second_pred =
|
||||
GetPredicateCondition(instr.fsetp.pred39, instr.fsetp.neg_pred != 0);
|
||||
|
||||
std::string combiner = GetPredicateCombiner(instr.fsetp.op);
|
||||
const std::string combiner = GetPredicateCombiner(instr.fsetp.op);
|
||||
|
||||
std::string predicate = GetPredicateComparison(instr.fsetp.cond, op_a, op_b);
|
||||
const std::string predicate = GetPredicateComparison(instr.fsetp.cond, op_a, op_b);
|
||||
// Set the primary predicate to the result of Predicate OP SecondPredicate
|
||||
SetPredicate(instr.fsetp.pred3,
|
||||
'(' + predicate + ") " + combiner + " (" + second_pred + ')');
|
||||
@@ -1905,7 +2051,8 @@ private:
|
||||
break;
|
||||
}
|
||||
case OpCode::Type::IntegerSetPredicate: {
|
||||
std::string op_a = regs.GetRegisterAsInteger(instr.gpr8, 0, instr.isetp.is_signed);
|
||||
const std::string op_a =
|
||||
regs.GetRegisterAsInteger(instr.gpr8, 0, instr.isetp.is_signed);
|
||||
std::string op_b;
|
||||
|
||||
if (instr.is_b_imm) {
|
||||
@@ -1922,12 +2069,12 @@ private:
|
||||
// We can't use the constant predicate as destination.
|
||||
ASSERT(instr.isetp.pred3 != static_cast<u64>(Pred::UnusedIndex));
|
||||
|
||||
std::string second_pred =
|
||||
const std::string second_pred =
|
||||
GetPredicateCondition(instr.isetp.pred39, instr.isetp.neg_pred != 0);
|
||||
|
||||
std::string combiner = GetPredicateCombiner(instr.isetp.op);
|
||||
const std::string combiner = GetPredicateCombiner(instr.isetp.op);
|
||||
|
||||
std::string predicate = GetPredicateComparison(instr.isetp.cond, op_a, op_b);
|
||||
const std::string predicate = GetPredicateComparison(instr.isetp.cond, op_a, op_b);
|
||||
// Set the primary predicate to the result of Predicate OP SecondPredicate
|
||||
SetPredicate(instr.isetp.pred3,
|
||||
'(' + predicate + ") " + combiner + " (" + second_pred + ')');
|
||||
@@ -1941,20 +2088,20 @@ private:
|
||||
break;
|
||||
}
|
||||
case OpCode::Type::PredicateSetPredicate: {
|
||||
std::string op_a =
|
||||
const std::string op_a =
|
||||
GetPredicateCondition(instr.psetp.pred12, instr.psetp.neg_pred12 != 0);
|
||||
std::string op_b =
|
||||
const std::string op_b =
|
||||
GetPredicateCondition(instr.psetp.pred29, instr.psetp.neg_pred29 != 0);
|
||||
|
||||
// We can't use the constant predicate as destination.
|
||||
ASSERT(instr.psetp.pred3 != static_cast<u64>(Pred::UnusedIndex));
|
||||
|
||||
std::string second_pred =
|
||||
const std::string second_pred =
|
||||
GetPredicateCondition(instr.psetp.pred39, instr.psetp.neg_pred39 != 0);
|
||||
|
||||
std::string combiner = GetPredicateCombiner(instr.psetp.op);
|
||||
const std::string combiner = GetPredicateCombiner(instr.psetp.op);
|
||||
|
||||
std::string predicate =
|
||||
const std::string predicate =
|
||||
'(' + op_a + ") " + GetPredicateCombiner(instr.psetp.cond) + " (" + op_b + ')';
|
||||
|
||||
// Set the primary predicate to the result of Predicate OP SecondPredicate
|
||||
@@ -1980,7 +2127,7 @@ private:
|
||||
std::string op_b = instr.fset.neg_b ? "-" : "";
|
||||
|
||||
if (instr.is_b_imm) {
|
||||
std::string imm = GetImmediate19(instr);
|
||||
const std::string imm = GetImmediate19(instr);
|
||||
if (instr.fset.neg_imm)
|
||||
op_b += "(-" + imm + ')';
|
||||
else
|
||||
@@ -2000,13 +2147,14 @@ private:
|
||||
|
||||
// The fset instruction sets a register to 1.0 or -1 (depending on the bf bit) if the
|
||||
// condition is true, and to 0 otherwise.
|
||||
std::string second_pred =
|
||||
const std::string second_pred =
|
||||
GetPredicateCondition(instr.fset.pred39, instr.fset.neg_pred != 0);
|
||||
|
||||
std::string combiner = GetPredicateCombiner(instr.fset.op);
|
||||
const std::string combiner = GetPredicateCombiner(instr.fset.op);
|
||||
|
||||
std::string predicate = "((" + GetPredicateComparison(instr.fset.cond, op_a, op_b) +
|
||||
") " + combiner + " (" + second_pred + "))";
|
||||
const std::string predicate = "((" +
|
||||
GetPredicateComparison(instr.fset.cond, op_a, op_b) +
|
||||
") " + combiner + " (" + second_pred + "))";
|
||||
|
||||
if (instr.fset.bf) {
|
||||
regs.SetRegisterToFloat(instr.gpr0, 0, predicate + " ? 1.0 : 0.0", 1, 1);
|
||||
@@ -2017,7 +2165,7 @@ private:
|
||||
break;
|
||||
}
|
||||
case OpCode::Type::IntegerSet: {
|
||||
std::string op_a = regs.GetRegisterAsInteger(instr.gpr8, 0, instr.iset.is_signed);
|
||||
const std::string op_a = regs.GetRegisterAsInteger(instr.gpr8, 0, instr.iset.is_signed);
|
||||
|
||||
std::string op_b;
|
||||
|
||||
@@ -2034,13 +2182,14 @@ private:
|
||||
|
||||
// The iset instruction sets a register to 1.0 or -1 (depending on the bf bit) if the
|
||||
// condition is true, and to 0 otherwise.
|
||||
std::string second_pred =
|
||||
const std::string second_pred =
|
||||
GetPredicateCondition(instr.iset.pred39, instr.iset.neg_pred != 0);
|
||||
|
||||
std::string combiner = GetPredicateCombiner(instr.iset.op);
|
||||
const std::string combiner = GetPredicateCombiner(instr.iset.op);
|
||||
|
||||
std::string predicate = "((" + GetPredicateComparison(instr.iset.cond, op_a, op_b) +
|
||||
") " + combiner + " (" + second_pred + "))";
|
||||
const std::string predicate = "((" +
|
||||
GetPredicateComparison(instr.iset.cond, op_a, op_b) +
|
||||
") " + combiner + " (" + second_pred + "))";
|
||||
|
||||
if (instr.iset.bf) {
|
||||
regs.SetRegisterToFloat(instr.gpr0, 0, predicate + " ? 1.0 : 0.0", 1, 1);
|
||||
@@ -2190,18 +2339,22 @@ private:
|
||||
case OpCode::Id::BRA: {
|
||||
ASSERT_MSG(instr.bra.constant_buffer == 0,
|
||||
"BRA with constant buffers are not implemented");
|
||||
u32 target = offset + instr.bra.GetBranchTarget();
|
||||
const u32 target = offset + instr.bra.GetBranchTarget();
|
||||
shader.AddLine("{ jmp_to = " + std::to_string(target) + "u; break; }");
|
||||
break;
|
||||
}
|
||||
case OpCode::Id::IPA: {
|
||||
const auto& attribute = instr.attribute.fmt28;
|
||||
const auto& reg = instr.gpr0;
|
||||
ASSERT_MSG(instr.ipa.saturate == 0, "IPA saturate not implemented");
|
||||
|
||||
Tegra::Shader::IpaMode input_mode{instr.ipa.interp_mode.Value(),
|
||||
instr.ipa.sample_mode.Value()};
|
||||
regs.SetRegisterToInputAttibute(reg, attribute.element, attribute.index,
|
||||
input_mode);
|
||||
|
||||
if (instr.ipa.saturate) {
|
||||
regs.SetRegisterToFloat(reg, 0, regs.GetRegisterAsFloat(reg), 1, 1, true);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case OpCode::Id::SSY: {
|
||||
@@ -2210,7 +2363,7 @@ private:
|
||||
// has a similar structure to the BRA opcode.
|
||||
ASSERT_MSG(instr.bra.constant_buffer == 0, "Constant buffer SSY is not supported");
|
||||
|
||||
u32 target = offset + instr.bra.GetBranchTarget();
|
||||
const u32 target = offset + instr.bra.GetBranchTarget();
|
||||
EmitPushToSSYStack(target);
|
||||
break;
|
||||
}
|
||||
@@ -2304,10 +2457,10 @@ private:
|
||||
shader.AddLine("case " + std::to_string(label) + "u: {");
|
||||
++shader.scope;
|
||||
|
||||
auto next_it = labels.lower_bound(label + 1);
|
||||
u32 next_label = next_it == labels.end() ? subroutine.end : *next_it;
|
||||
const auto next_it = labels.lower_bound(label + 1);
|
||||
const u32 next_label = next_it == labels.end() ? subroutine.end : *next_it;
|
||||
|
||||
u32 compile_end = CompileRange(label, next_label);
|
||||
const u32 compile_end = CompileRange(label, next_label);
|
||||
if (compile_end > next_label && compile_end != PROGRAM_END) {
|
||||
// This happens only when there is a label inside a IF/LOOP block
|
||||
shader.AddLine(" jmp_to = " + std::to_string(compile_end) + "u; break; }");
|
||||
@@ -2370,7 +2523,8 @@ boost::optional<ProgramResult> DecompileProgram(const ProgramCode& program_code,
|
||||
Maxwell3D::Regs::ShaderStage stage,
|
||||
const std::string& suffix) {
|
||||
try {
|
||||
auto subroutines = ControlFlowAnalyzer(program_code, main_offset, suffix).GetSubroutines();
|
||||
const auto subroutines =
|
||||
ControlFlowAnalyzer(program_code, main_offset, suffix).GetSubroutines();
|
||||
GLSLGenerator generator(subroutines, program_code, main_offset, stage, suffix);
|
||||
return ProgramResult{generator.GetShaderCode(), generator.GetEntries()};
|
||||
} catch (const DecompileFail& exception) {
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "video_core/engines/shader_bytecode.h"
|
||||
|
||||
namespace OpenGL::GLShader {
|
||||
|
||||
@@ -73,8 +74,9 @@ class SamplerEntry {
|
||||
using Maxwell = Tegra::Engines::Maxwell3D::Regs;
|
||||
|
||||
public:
|
||||
SamplerEntry(Maxwell::ShaderStage stage, size_t offset, size_t index)
|
||||
: offset(offset), stage(stage), sampler_index(index) {}
|
||||
SamplerEntry(Maxwell::ShaderStage stage, size_t offset, size_t index,
|
||||
Tegra::Shader::TextureType type, bool is_array)
|
||||
: offset(offset), stage(stage), sampler_index(index), type(type), is_array(is_array) {}
|
||||
|
||||
size_t GetOffset() const {
|
||||
return offset;
|
||||
@@ -89,8 +91,41 @@ public:
|
||||
}
|
||||
|
||||
std::string GetName() const {
|
||||
return std::string(TextureSamplerNames[static_cast<size_t>(stage)]) + '[' +
|
||||
std::to_string(sampler_index) + ']';
|
||||
return std::string(TextureSamplerNames[static_cast<size_t>(stage)]) + '_' +
|
||||
std::to_string(sampler_index);
|
||||
}
|
||||
|
||||
std::string GetTypeString() const {
|
||||
using Tegra::Shader::TextureType;
|
||||
std::string glsl_type;
|
||||
|
||||
switch (type) {
|
||||
case TextureType::Texture1D:
|
||||
glsl_type = "sampler1D";
|
||||
break;
|
||||
case TextureType::Texture2D:
|
||||
glsl_type = "sampler2D";
|
||||
break;
|
||||
case TextureType::Texture3D:
|
||||
glsl_type = "sampler3D";
|
||||
break;
|
||||
case TextureType::TextureCube:
|
||||
glsl_type = "samplerCube";
|
||||
break;
|
||||
default:
|
||||
UNIMPLEMENTED();
|
||||
}
|
||||
if (is_array)
|
||||
glsl_type += "Array";
|
||||
return glsl_type;
|
||||
}
|
||||
|
||||
Tegra::Shader::TextureType GetType() const {
|
||||
return type;
|
||||
}
|
||||
|
||||
bool IsArray() const {
|
||||
return is_array;
|
||||
}
|
||||
|
||||
u32 GetHash() const {
|
||||
@@ -105,11 +140,14 @@ private:
|
||||
static constexpr std::array<const char*, Maxwell::MaxShaderStage> TextureSamplerNames = {
|
||||
"tex_vs", "tex_tessc", "tex_tesse", "tex_gs", "tex_fs",
|
||||
};
|
||||
|
||||
/// Offset in TSC memory from which to read the sampler object, as specified by the sampling
|
||||
/// instruction.
|
||||
size_t offset;
|
||||
Maxwell::ShaderStage stage; ///< Shader stage where this sampler was used.
|
||||
size_t sampler_index; ///< Value used to index into the generated GLSL sampler array.
|
||||
Maxwell::ShaderStage stage; ///< Shader stage where this sampler was used.
|
||||
size_t sampler_index; ///< Value used to index into the generated GLSL sampler array.
|
||||
Tegra::Shader::TextureType type; ///< The type used to sample this texture (Texture2D, etc)
|
||||
bool is_array; ///< Whether the texture is being sampled as an array texture or not.
|
||||
};
|
||||
|
||||
struct ShaderEntries {
|
||||
|
||||
@@ -25,7 +25,7 @@ GLuint LoadShader(const char* source, GLenum type) {
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
GLuint shader_id = glCreateShader(type);
|
||||
const GLuint shader_id = glCreateShader(type);
|
||||
glShaderSource(shader_id, 1, &source, nullptr);
|
||||
LOG_DEBUG(Render_OpenGL, "Compiling {} shader...", debug_type);
|
||||
glCompileShader(shader_id);
|
||||
|
||||
@@ -200,9 +200,9 @@ void OpenGLState::Apply() const {
|
||||
const auto& texture_unit = texture_units[i];
|
||||
const auto& cur_state_texture_unit = cur_state.texture_units[i];
|
||||
|
||||
if (texture_unit.texture_2d != cur_state_texture_unit.texture_2d) {
|
||||
if (texture_unit.texture != cur_state_texture_unit.texture) {
|
||||
glActiveTexture(TextureUnits::MaxwellTexture(static_cast<int>(i)).Enum());
|
||||
glBindTexture(GL_TEXTURE_2D, texture_unit.texture_2d);
|
||||
glBindTexture(texture_unit.target, texture_unit.texture);
|
||||
}
|
||||
if (texture_unit.sampler != cur_state_texture_unit.sampler) {
|
||||
glBindSampler(static_cast<GLuint>(i), texture_unit.sampler);
|
||||
@@ -214,7 +214,7 @@ void OpenGLState::Apply() const {
|
||||
texture_unit.swizzle.a != cur_state_texture_unit.swizzle.a) {
|
||||
std::array<GLint, 4> mask = {texture_unit.swizzle.r, texture_unit.swizzle.g,
|
||||
texture_unit.swizzle.b, texture_unit.swizzle.a};
|
||||
glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_RGBA, mask.data());
|
||||
glTexParameteriv(texture_unit.target, GL_TEXTURE_SWIZZLE_RGBA, mask.data());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -287,7 +287,7 @@ void OpenGLState::Apply() const {
|
||||
|
||||
OpenGLState& OpenGLState::UnbindTexture(GLuint handle) {
|
||||
for (auto& unit : texture_units) {
|
||||
if (unit.texture_2d == handle) {
|
||||
if (unit.texture == handle) {
|
||||
unit.Unbind();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,8 +94,9 @@ public:
|
||||
|
||||
// 3 texture units - one for each that is used in PICA fragment shader emulation
|
||||
struct TextureUnit {
|
||||
GLuint texture_2d; // GL_TEXTURE_BINDING_2D
|
||||
GLuint sampler; // GL_SAMPLER_BINDING
|
||||
GLuint texture; // GL_TEXTURE_BINDING_2D
|
||||
GLuint sampler; // GL_SAMPLER_BINDING
|
||||
GLenum target;
|
||||
struct {
|
||||
GLint r; // GL_TEXTURE_SWIZZLE_R
|
||||
GLint g; // GL_TEXTURE_SWIZZLE_G
|
||||
@@ -104,7 +105,7 @@ public:
|
||||
} swizzle;
|
||||
|
||||
void Unbind() {
|
||||
texture_2d = 0;
|
||||
texture = 0;
|
||||
swizzle.r = GL_RED;
|
||||
swizzle.g = GL_GREEN;
|
||||
swizzle.b = GL_BLUE;
|
||||
@@ -114,6 +115,7 @@ public:
|
||||
void Reset() {
|
||||
Unbind();
|
||||
sampler = 0;
|
||||
target = GL_TEXTURE_2D;
|
||||
}
|
||||
};
|
||||
std::array<TextureUnit, 32> texture_units;
|
||||
|
||||
@@ -29,7 +29,7 @@ OGLStreamBuffer::OGLStreamBuffer(GLenum target, GLsizeiptr size, bool prefer_coh
|
||||
if (GLAD_GL_ARB_buffer_storage) {
|
||||
persistent = true;
|
||||
coherent = prefer_coherent;
|
||||
GLbitfield flags =
|
||||
const GLbitfield flags =
|
||||
GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | (coherent ? GL_MAP_COHERENT_BIT : 0);
|
||||
glBufferStorage(gl_target, allocate_size, nullptr, flags);
|
||||
mapped_ptr = static_cast<u8*>(glMapBufferRange(
|
||||
|
||||
@@ -177,7 +177,7 @@ void RendererOpenGL::LoadFBToScreenInfo(const Tegra::FramebufferConfig& framebuf
|
||||
Memory::GetPointer(framebuffer_addr),
|
||||
gl_framebuffer_data.data(), true);
|
||||
|
||||
state.texture_units[0].texture_2d = screen_info.texture.resource.handle;
|
||||
state.texture_units[0].texture = screen_info.texture.resource.handle;
|
||||
state.Apply();
|
||||
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
@@ -194,7 +194,7 @@ void RendererOpenGL::LoadFBToScreenInfo(const Tegra::FramebufferConfig& framebuf
|
||||
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
|
||||
|
||||
state.texture_units[0].texture_2d = 0;
|
||||
state.texture_units[0].texture = 0;
|
||||
state.Apply();
|
||||
}
|
||||
}
|
||||
@@ -205,7 +205,7 @@ void RendererOpenGL::LoadFBToScreenInfo(const Tegra::FramebufferConfig& framebuf
|
||||
*/
|
||||
void RendererOpenGL::LoadColorToActiveGLTexture(u8 color_r, u8 color_g, u8 color_b, u8 color_a,
|
||||
const TextureInfo& texture) {
|
||||
state.texture_units[0].texture_2d = texture.resource.handle;
|
||||
state.texture_units[0].texture = texture.resource.handle;
|
||||
state.Apply();
|
||||
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
@@ -214,7 +214,7 @@ void RendererOpenGL::LoadColorToActiveGLTexture(u8 color_r, u8 color_g, u8 color
|
||||
// Update existing texture
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, framebuffer_data);
|
||||
|
||||
state.texture_units[0].texture_2d = 0;
|
||||
state.texture_units[0].texture = 0;
|
||||
state.Apply();
|
||||
}
|
||||
|
||||
@@ -260,7 +260,7 @@ void RendererOpenGL::InitOpenGLObjects() {
|
||||
// Allocation of storage is deferred until the first frame, when we
|
||||
// know the framebuffer size.
|
||||
|
||||
state.texture_units[0].texture_2d = screen_info.texture.resource.handle;
|
||||
state.texture_units[0].texture = screen_info.texture.resource.handle;
|
||||
state.Apply();
|
||||
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
@@ -272,7 +272,7 @@ void RendererOpenGL::InitOpenGLObjects() {
|
||||
|
||||
screen_info.display_texture = screen_info.texture.resource.handle;
|
||||
|
||||
state.texture_units[0].texture_2d = 0;
|
||||
state.texture_units[0].texture = 0;
|
||||
state.Apply();
|
||||
|
||||
// Clear screen to black
|
||||
@@ -305,14 +305,14 @@ void RendererOpenGL::ConfigureFramebufferTexture(TextureInfo& texture,
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
state.texture_units[0].texture_2d = texture.resource.handle;
|
||||
state.texture_units[0].texture = texture.resource.handle;
|
||||
state.Apply();
|
||||
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, internal_format, texture.width, texture.height, 0,
|
||||
texture.gl_format, texture.gl_type, nullptr);
|
||||
|
||||
state.texture_units[0].texture_2d = 0;
|
||||
state.texture_units[0].texture = 0;
|
||||
state.Apply();
|
||||
}
|
||||
|
||||
@@ -354,14 +354,14 @@ void RendererOpenGL::DrawScreenTriangles(const ScreenInfo& screen_info, float x,
|
||||
ScreenRectVertex(x + w, y + h, texcoords.bottom * scale_u, right * scale_v),
|
||||
}};
|
||||
|
||||
state.texture_units[0].texture_2d = screen_info.display_texture;
|
||||
state.texture_units[0].texture = screen_info.display_texture;
|
||||
state.texture_units[0].swizzle = {GL_RED, GL_GREEN, GL_BLUE, GL_ALPHA};
|
||||
state.Apply();
|
||||
|
||||
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices.data());
|
||||
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
|
||||
|
||||
state.texture_units[0].texture_2d = 0;
|
||||
state.texture_units[0].texture = 0;
|
||||
state.Apply();
|
||||
}
|
||||
|
||||
|
||||
@@ -170,8 +170,12 @@ struct TICEntry {
|
||||
BitField<0, 16, u32> width_minus_1;
|
||||
BitField<23, 4, TextureType> texture_type;
|
||||
};
|
||||
u16 height_minus_1;
|
||||
INSERT_PADDING_BYTES(10);
|
||||
union {
|
||||
BitField<0, 16, u32> height_minus_1;
|
||||
BitField<16, 15, u32> depth_minus_1;
|
||||
};
|
||||
|
||||
INSERT_PADDING_BYTES(8);
|
||||
|
||||
GPUVAddr Address() const {
|
||||
return static_cast<GPUVAddr>((static_cast<GPUVAddr>(address_high) << 32) | address_low);
|
||||
@@ -192,6 +196,10 @@ struct TICEntry {
|
||||
return height_minus_1 + 1;
|
||||
}
|
||||
|
||||
u32 Depth() const {
|
||||
return depth_minus_1 + 1;
|
||||
}
|
||||
|
||||
u32 BlockHeight() const {
|
||||
ASSERT(header_version == TICHeaderVersion::BlockLinear ||
|
||||
header_version == TICHeaderVersion::BlockLinearColorKey);
|
||||
|
||||
@@ -9,6 +9,8 @@ add_executable(yuzu
|
||||
about_dialog.h
|
||||
bootmanager.cpp
|
||||
bootmanager.h
|
||||
compatibility_list.cpp
|
||||
compatibility_list.h
|
||||
configuration/config.cpp
|
||||
configuration/config.h
|
||||
configuration/configure_audio.cpp
|
||||
@@ -43,6 +45,8 @@ add_executable(yuzu
|
||||
game_list.cpp
|
||||
game_list.h
|
||||
game_list_p.h
|
||||
game_list_worker.cpp
|
||||
game_list_worker.h
|
||||
hotkeys.cpp
|
||||
hotkeys.h
|
||||
main.cpp
|
||||
|
||||
@@ -11,7 +11,7 @@ AboutDialog::AboutDialog(QWidget* parent) : QDialog(parent), ui(new Ui::AboutDia
|
||||
ui->setupUi(this);
|
||||
ui->labelLogo->setPixmap(QIcon::fromTheme("yuzu").pixmap(200));
|
||||
ui->labelBuildInfo->setText(
|
||||
ui->labelBuildInfo->text().arg(Common::g_build_name, Common::g_scm_branch,
|
||||
ui->labelBuildInfo->text().arg(Common::g_build_fullname, Common::g_scm_branch,
|
||||
Common::g_scm_desc, QString(Common::g_build_date).left(10)));
|
||||
}
|
||||
|
||||
|
||||
@@ -256,6 +256,7 @@ void GRenderWindow::InitRenderTarget() {
|
||||
QGLFormat fmt;
|
||||
fmt.setVersion(3, 3);
|
||||
fmt.setProfile(QGLFormat::CoreProfile);
|
||||
fmt.setSwapInterval(false);
|
||||
|
||||
// Requests a forward-compatible context, which is required to get a 3.2+ context on OS X
|
||||
fmt.setOption(QGL::NoDeprecatedFunctions);
|
||||
|
||||
18
src/yuzu/compatibility_list.cpp
Normal file
18
src/yuzu/compatibility_list.cpp
Normal file
@@ -0,0 +1,18 @@
|
||||
// Copyright 2018 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include "yuzu/compatibility_list.h"
|
||||
|
||||
CompatibilityList::const_iterator FindMatchingCompatibilityEntry(
|
||||
const CompatibilityList& compatibility_list, u64 program_id) {
|
||||
return std::find_if(compatibility_list.begin(), compatibility_list.end(),
|
||||
[program_id](const auto& element) {
|
||||
std::string pid = fmt::format("{:016X}", program_id);
|
||||
return element.first == pid;
|
||||
});
|
||||
}
|
||||
17
src/yuzu/compatibility_list.h
Normal file
17
src/yuzu/compatibility_list.h
Normal file
@@ -0,0 +1,17 @@
|
||||
// Copyright 2018 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
#include <QString>
|
||||
|
||||
#include "common/common_types.h"
|
||||
|
||||
using CompatibilityList = std::unordered_map<std::string, std::pair<QString, QString>>;
|
||||
|
||||
CompatibilityList::const_iterator FindMatchingCompatibilityEntry(
|
||||
const CompatibilityList& compatibility_list, u64 program_id);
|
||||
@@ -61,7 +61,7 @@
|
||||
<item>
|
||||
<widget class="QCheckBox" name="use_multi_core">
|
||||
<property name="text">
|
||||
<string>Enable multi-core</string>
|
||||
<string>Enable multi-core (Currently broken.)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
||||
@@ -18,17 +18,11 @@
|
||||
#include "common/common_types.h"
|
||||
#include "common/file_util.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/file_sys/content_archive.h"
|
||||
#include "core/file_sys/control_metadata.h"
|
||||
#include "core/file_sys/nca_metadata.h"
|
||||
#include "core/file_sys/patch_manager.h"
|
||||
#include "core/file_sys/registered_cache.h"
|
||||
#include "core/file_sys/romfs.h"
|
||||
#include "core/file_sys/vfs_real.h"
|
||||
#include "core/hle/service/filesystem/filesystem.h"
|
||||
#include "core/loader/loader.h"
|
||||
#include "yuzu/compatibility_list.h"
|
||||
#include "yuzu/game_list.h"
|
||||
#include "yuzu/game_list_p.h"
|
||||
#include "yuzu/game_list_worker.h"
|
||||
#include "yuzu/main.h"
|
||||
#include "yuzu/ui_settings.h"
|
||||
|
||||
@@ -436,45 +430,6 @@ void GameList::LoadInterfaceLayout() {
|
||||
|
||||
const QStringList GameList::supported_file_extensions = {"nso", "nro", "nca", "xci", "nsp"};
|
||||
|
||||
static bool HasSupportedFileExtension(const std::string& file_name) {
|
||||
const QFileInfo file = QFileInfo(QString::fromStdString(file_name));
|
||||
return GameList::supported_file_extensions.contains(file.suffix(), Qt::CaseInsensitive);
|
||||
}
|
||||
|
||||
static bool IsExtractedNCAMain(const std::string& file_name) {
|
||||
return QFileInfo(QString::fromStdString(file_name)).fileName() == "main";
|
||||
}
|
||||
|
||||
static QString FormatGameName(const std::string& physical_name) {
|
||||
const QString physical_name_as_qstring = QString::fromStdString(physical_name);
|
||||
const QFileInfo file_info(physical_name_as_qstring);
|
||||
|
||||
if (IsExtractedNCAMain(physical_name)) {
|
||||
return file_info.dir().path();
|
||||
}
|
||||
|
||||
return physical_name_as_qstring;
|
||||
}
|
||||
|
||||
static QString FormatPatchNameVersions(const FileSys::PatchManager& patch_manager,
|
||||
bool updatable = true) {
|
||||
QString out;
|
||||
for (const auto& kv : patch_manager.GetPatchVersionNames()) {
|
||||
if (!updatable && kv.first == FileSys::PatchType::Update)
|
||||
continue;
|
||||
|
||||
if (kv.second.empty()) {
|
||||
out.append(fmt::format("{}\n", FileSys::FormatPatchTypeName(kv.first)).c_str());
|
||||
} else {
|
||||
out.append(fmt::format("{} ({})\n", FileSys::FormatPatchTypeName(kv.first), kv.second)
|
||||
.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
out.chop(1);
|
||||
return out;
|
||||
}
|
||||
|
||||
void GameList::RefreshGameDirectory() {
|
||||
if (!UISettings::values.gamedir.isEmpty() && current_worker != nullptr) {
|
||||
LOG_INFO(Frontend, "Change detected in the games directory. Reloading game list.");
|
||||
@@ -482,176 +437,3 @@ void GameList::RefreshGameDirectory() {
|
||||
PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan);
|
||||
}
|
||||
}
|
||||
|
||||
static void GetMetadataFromControlNCA(const FileSys::PatchManager& patch_manager,
|
||||
const std::shared_ptr<FileSys::NCA>& nca,
|
||||
std::vector<u8>& icon, std::string& name) {
|
||||
auto [nacp, icon_file] = patch_manager.ParseControlNCA(nca);
|
||||
if (icon_file != nullptr)
|
||||
icon = icon_file->ReadAllBytes();
|
||||
if (nacp != nullptr)
|
||||
name = nacp->GetApplicationName();
|
||||
}
|
||||
|
||||
GameListWorker::GameListWorker(
|
||||
FileSys::VirtualFilesystem vfs, QString dir_path, bool deep_scan,
|
||||
const std::unordered_map<std::string, std::pair<QString, QString>>& compatibility_list)
|
||||
: vfs(std::move(vfs)), dir_path(std::move(dir_path)), deep_scan(deep_scan),
|
||||
compatibility_list(compatibility_list) {}
|
||||
|
||||
GameListWorker::~GameListWorker() = default;
|
||||
|
||||
void GameListWorker::AddInstalledTitlesToGameList() {
|
||||
const auto cache = Service::FileSystem::GetUnionContents();
|
||||
const auto installed_games = cache->ListEntriesFilter(FileSys::TitleType::Application,
|
||||
FileSys::ContentRecordType::Program);
|
||||
|
||||
for (const auto& game : installed_games) {
|
||||
const auto& file = cache->GetEntryUnparsed(game);
|
||||
std::unique_ptr<Loader::AppLoader> loader = Loader::GetLoader(file);
|
||||
if (!loader)
|
||||
continue;
|
||||
|
||||
std::vector<u8> icon;
|
||||
std::string name;
|
||||
u64 program_id = 0;
|
||||
loader->ReadProgramId(program_id);
|
||||
|
||||
const FileSys::PatchManager patch{program_id};
|
||||
const auto& control = cache->GetEntry(game.title_id, FileSys::ContentRecordType::Control);
|
||||
if (control != nullptr)
|
||||
GetMetadataFromControlNCA(patch, control, icon, name);
|
||||
|
||||
auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id);
|
||||
|
||||
// The game list uses this as compatibility number for untested games
|
||||
QString compatibility("99");
|
||||
if (it != compatibility_list.end())
|
||||
compatibility = it->second.first;
|
||||
|
||||
emit EntryReady({
|
||||
new GameListItemPath(
|
||||
FormatGameName(file->GetFullPath()), icon, QString::fromStdString(name),
|
||||
QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType())),
|
||||
program_id),
|
||||
new GameListItemCompat(compatibility),
|
||||
new GameListItem(FormatPatchNameVersions(patch)),
|
||||
new GameListItem(
|
||||
QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType()))),
|
||||
new GameListItemSize(file->GetSize()),
|
||||
});
|
||||
}
|
||||
|
||||
const auto control_data = cache->ListEntriesFilter(FileSys::TitleType::Application,
|
||||
FileSys::ContentRecordType::Control);
|
||||
|
||||
for (const auto& entry : control_data) {
|
||||
const auto nca = cache->GetEntry(entry);
|
||||
if (nca != nullptr)
|
||||
nca_control_map.insert_or_assign(entry.title_id, nca);
|
||||
}
|
||||
}
|
||||
|
||||
void GameListWorker::FillControlMap(const std::string& dir_path) {
|
||||
const auto nca_control_callback = [this](u64* num_entries_out, const std::string& directory,
|
||||
const std::string& virtual_name) -> bool {
|
||||
std::string physical_name = directory + DIR_SEP + virtual_name;
|
||||
|
||||
if (stop_processing)
|
||||
return false; // Breaks the callback loop.
|
||||
|
||||
bool is_dir = FileUtil::IsDirectory(physical_name);
|
||||
QFileInfo file_info(physical_name.c_str());
|
||||
if (!is_dir && file_info.suffix().toStdString() == "nca") {
|
||||
auto nca =
|
||||
std::make_shared<FileSys::NCA>(vfs->OpenFile(physical_name, FileSys::Mode::Read));
|
||||
if (nca->GetType() == FileSys::NCAContentType::Control)
|
||||
nca_control_map.insert_or_assign(nca->GetTitleId(), nca);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
FileUtil::ForeachDirectoryEntry(nullptr, dir_path, nca_control_callback);
|
||||
}
|
||||
|
||||
void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion) {
|
||||
const auto callback = [this, recursion](u64* num_entries_out, const std::string& directory,
|
||||
const std::string& virtual_name) -> bool {
|
||||
std::string physical_name = directory + DIR_SEP + virtual_name;
|
||||
|
||||
if (stop_processing)
|
||||
return false; // Breaks the callback loop.
|
||||
|
||||
bool is_dir = FileUtil::IsDirectory(physical_name);
|
||||
if (!is_dir &&
|
||||
(HasSupportedFileExtension(physical_name) || IsExtractedNCAMain(physical_name))) {
|
||||
std::unique_ptr<Loader::AppLoader> loader =
|
||||
Loader::GetLoader(vfs->OpenFile(physical_name, FileSys::Mode::Read));
|
||||
if (!loader || ((loader->GetFileType() == Loader::FileType::Unknown ||
|
||||
loader->GetFileType() == Loader::FileType::Error) &&
|
||||
!UISettings::values.show_unknown))
|
||||
return true;
|
||||
|
||||
std::vector<u8> icon;
|
||||
const auto res1 = loader->ReadIcon(icon);
|
||||
|
||||
u64 program_id = 0;
|
||||
const auto res2 = loader->ReadProgramId(program_id);
|
||||
|
||||
std::string name = " ";
|
||||
const auto res3 = loader->ReadTitle(name);
|
||||
|
||||
const FileSys::PatchManager patch{program_id};
|
||||
|
||||
if (res1 != Loader::ResultStatus::Success && res3 != Loader::ResultStatus::Success &&
|
||||
res2 == Loader::ResultStatus::Success) {
|
||||
// Use from metadata pool.
|
||||
if (nca_control_map.find(program_id) != nca_control_map.end()) {
|
||||
const auto nca = nca_control_map[program_id];
|
||||
GetMetadataFromControlNCA(patch, nca, icon, name);
|
||||
}
|
||||
}
|
||||
|
||||
auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id);
|
||||
|
||||
// The game list uses this as compatibility number for untested games
|
||||
QString compatibility("99");
|
||||
if (it != compatibility_list.end())
|
||||
compatibility = it->second.first;
|
||||
|
||||
emit EntryReady({
|
||||
new GameListItemPath(
|
||||
FormatGameName(physical_name), icon, QString::fromStdString(name),
|
||||
QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType())),
|
||||
program_id),
|
||||
new GameListItemCompat(compatibility),
|
||||
new GameListItem(FormatPatchNameVersions(patch, loader->IsRomFSUpdatable())),
|
||||
new GameListItem(
|
||||
QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType()))),
|
||||
new GameListItemSize(FileUtil::GetSize(physical_name)),
|
||||
});
|
||||
} else if (is_dir && recursion > 0) {
|
||||
watch_list.append(QString::fromStdString(physical_name));
|
||||
AddFstEntriesToGameList(physical_name, recursion - 1);
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
FileUtil::ForeachDirectoryEntry(nullptr, dir_path, callback);
|
||||
}
|
||||
|
||||
void GameListWorker::run() {
|
||||
stop_processing = false;
|
||||
watch_list.append(dir_path);
|
||||
FillControlMap(dir_path.toStdString());
|
||||
AddInstalledTitlesToGameList();
|
||||
AddFstEntriesToGameList(dir_path.toStdString(), deep_scan ? 256 : 0);
|
||||
nca_control_map.clear();
|
||||
emit Finished(watch_list);
|
||||
}
|
||||
|
||||
void GameListWorker::Cancel() {
|
||||
this->disconnect();
|
||||
stop_processing = true;
|
||||
}
|
||||
|
||||
@@ -4,8 +4,6 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
#include <QFileSystemWatcher>
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
@@ -21,6 +19,7 @@
|
||||
#include <QWidget>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "yuzu/compatibility_list.h"
|
||||
|
||||
class GameListWorker;
|
||||
class GMainWindow;
|
||||
@@ -90,9 +89,8 @@ signals:
|
||||
void GameChosen(QString game_path);
|
||||
void ShouldCancelWorker();
|
||||
void OpenFolderRequested(u64 program_id, GameListOpenTarget target);
|
||||
void NavigateToGamedbEntryRequested(
|
||||
u64 program_id,
|
||||
std::unordered_map<std::string, std::pair<QString, QString>>& compatibility_list);
|
||||
void NavigateToGamedbEntryRequested(u64 program_id,
|
||||
const CompatibilityList& compatibility_list);
|
||||
|
||||
private slots:
|
||||
void onTextChanged(const QString& newText);
|
||||
@@ -114,7 +112,7 @@ private:
|
||||
QStandardItemModel* item_model = nullptr;
|
||||
GameListWorker* current_worker = nullptr;
|
||||
QFileSystemWatcher* watcher = nullptr;
|
||||
std::unordered_map<std::string, std::pair<QString, QString>> compatibility_list;
|
||||
CompatibilityList compatibility_list;
|
||||
};
|
||||
|
||||
Q_DECLARE_METATYPE(GameListOpenTarget);
|
||||
|
||||
@@ -6,9 +6,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
@@ -16,7 +14,6 @@
|
||||
#include <QCoreApplication>
|
||||
#include <QImage>
|
||||
#include <QObject>
|
||||
#include <QRunnable>
|
||||
#include <QStandardItem>
|
||||
#include <QString>
|
||||
|
||||
@@ -26,12 +23,6 @@
|
||||
#include "yuzu/ui_settings.h"
|
||||
#include "yuzu/util/util.h"
|
||||
|
||||
namespace FileSys {
|
||||
class NCA;
|
||||
class RegisteredCache;
|
||||
class VfsFilesystem;
|
||||
} // namespace FileSys
|
||||
|
||||
/**
|
||||
* Gets the default icon (for games without valid SMDH)
|
||||
* @param large If true, returns large icon (48x48), otherwise returns small icon (24x24)
|
||||
@@ -43,17 +34,6 @@ static QPixmap GetDefaultIcon(u32 size) {
|
||||
return icon;
|
||||
}
|
||||
|
||||
static auto FindMatchingCompatibilityEntry(
|
||||
const std::unordered_map<std::string, std::pair<QString, QString>>& compatibility_list,
|
||||
u64 program_id) {
|
||||
return std::find_if(
|
||||
compatibility_list.begin(), compatibility_list.end(),
|
||||
[program_id](const std::pair<std::string, std::pair<QString, QString>>& element) {
|
||||
std::string pid = fmt::format("{:016X}", program_id);
|
||||
return element.first == pid;
|
||||
});
|
||||
}
|
||||
|
||||
class GameListItem : public QStandardItem {
|
||||
|
||||
public:
|
||||
@@ -196,50 +176,3 @@ public:
|
||||
return data(SizeRole).toULongLong() < other.data(SizeRole).toULongLong();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Asynchronous worker object for populating the game list.
|
||||
* Communicates with other threads through Qt's signal/slot system.
|
||||
*/
|
||||
class GameListWorker : public QObject, public QRunnable {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
GameListWorker(
|
||||
std::shared_ptr<FileSys::VfsFilesystem> vfs, QString dir_path, bool deep_scan,
|
||||
const std::unordered_map<std::string, std::pair<QString, QString>>& compatibility_list);
|
||||
~GameListWorker() override;
|
||||
|
||||
public slots:
|
||||
/// Starts the processing of directory tree information.
|
||||
void run() override;
|
||||
/// Tells the worker that it should no longer continue processing. Thread-safe.
|
||||
void Cancel();
|
||||
|
||||
signals:
|
||||
/**
|
||||
* The `EntryReady` signal is emitted once an entry has been prepared and is ready
|
||||
* to be added to the game list.
|
||||
* @param entry_items a list with `QStandardItem`s that make up the columns of the new entry.
|
||||
*/
|
||||
void EntryReady(QList<QStandardItem*> entry_items);
|
||||
|
||||
/**
|
||||
* After the worker has traversed the game directory looking for entries, this signal is emmited
|
||||
* with a list of folders that should be watched for changes as well.
|
||||
*/
|
||||
void Finished(QStringList watch_list);
|
||||
|
||||
private:
|
||||
std::shared_ptr<FileSys::VfsFilesystem> vfs;
|
||||
std::map<u64, std::shared_ptr<FileSys::NCA>> nca_control_map;
|
||||
QStringList watch_list;
|
||||
QString dir_path;
|
||||
bool deep_scan;
|
||||
const std::unordered_map<std::string, std::pair<QString, QString>>& compatibility_list;
|
||||
std::atomic_bool stop_processing;
|
||||
|
||||
void AddInstalledTitlesToGameList();
|
||||
void FillControlMap(const std::string& dir_path);
|
||||
void AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion = 0);
|
||||
};
|
||||
|
||||
239
src/yuzu/game_list_worker.cpp
Normal file
239
src/yuzu/game_list_worker.cpp
Normal file
@@ -0,0 +1,239 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <QDir>
|
||||
#include <QFileInfo>
|
||||
|
||||
#include "common/common_paths.h"
|
||||
#include "common/file_util.h"
|
||||
#include "core/file_sys/content_archive.h"
|
||||
#include "core/file_sys/control_metadata.h"
|
||||
#include "core/file_sys/mode.h"
|
||||
#include "core/file_sys/nca_metadata.h"
|
||||
#include "core/file_sys/patch_manager.h"
|
||||
#include "core/file_sys/registered_cache.h"
|
||||
#include "core/hle/service/filesystem/filesystem.h"
|
||||
#include "core/loader/loader.h"
|
||||
#include "yuzu/compatibility_list.h"
|
||||
#include "yuzu/game_list.h"
|
||||
#include "yuzu/game_list_p.h"
|
||||
#include "yuzu/game_list_worker.h"
|
||||
#include "yuzu/ui_settings.h"
|
||||
|
||||
namespace {
|
||||
void GetMetadataFromControlNCA(const FileSys::PatchManager& patch_manager,
|
||||
const std::shared_ptr<FileSys::NCA>& nca, std::vector<u8>& icon,
|
||||
std::string& name) {
|
||||
auto [nacp, icon_file] = patch_manager.ParseControlNCA(nca);
|
||||
if (icon_file != nullptr)
|
||||
icon = icon_file->ReadAllBytes();
|
||||
if (nacp != nullptr)
|
||||
name = nacp->GetApplicationName();
|
||||
}
|
||||
|
||||
bool HasSupportedFileExtension(const std::string& file_name) {
|
||||
const QFileInfo file = QFileInfo(QString::fromStdString(file_name));
|
||||
return GameList::supported_file_extensions.contains(file.suffix(), Qt::CaseInsensitive);
|
||||
}
|
||||
|
||||
bool IsExtractedNCAMain(const std::string& file_name) {
|
||||
return QFileInfo(QString::fromStdString(file_name)).fileName() == "main";
|
||||
}
|
||||
|
||||
QString FormatGameName(const std::string& physical_name) {
|
||||
const QString physical_name_as_qstring = QString::fromStdString(physical_name);
|
||||
const QFileInfo file_info(physical_name_as_qstring);
|
||||
|
||||
if (IsExtractedNCAMain(physical_name)) {
|
||||
return file_info.dir().path();
|
||||
}
|
||||
|
||||
return physical_name_as_qstring;
|
||||
}
|
||||
|
||||
QString FormatPatchNameVersions(const FileSys::PatchManager& patch_manager, bool updatable = true) {
|
||||
QString out;
|
||||
for (const auto& kv : patch_manager.GetPatchVersionNames()) {
|
||||
if (!updatable && kv.first == FileSys::PatchType::Update)
|
||||
continue;
|
||||
|
||||
if (kv.second.empty()) {
|
||||
out.append(fmt::format("{}\n", FileSys::FormatPatchTypeName(kv.first)).c_str());
|
||||
} else {
|
||||
out.append(fmt::format("{} ({})\n", FileSys::FormatPatchTypeName(kv.first), kv.second)
|
||||
.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
out.chop(1);
|
||||
return out;
|
||||
}
|
||||
} // Anonymous namespace
|
||||
|
||||
GameListWorker::GameListWorker(FileSys::VirtualFilesystem vfs, QString dir_path, bool deep_scan,
|
||||
const CompatibilityList& compatibility_list)
|
||||
: vfs(std::move(vfs)), dir_path(std::move(dir_path)), deep_scan(deep_scan),
|
||||
compatibility_list(compatibility_list) {}
|
||||
|
||||
GameListWorker::~GameListWorker() = default;
|
||||
|
||||
void GameListWorker::AddInstalledTitlesToGameList() {
|
||||
const auto cache = Service::FileSystem::GetUnionContents();
|
||||
const auto installed_games = cache->ListEntriesFilter(FileSys::TitleType::Application,
|
||||
FileSys::ContentRecordType::Program);
|
||||
|
||||
for (const auto& game : installed_games) {
|
||||
const auto& file = cache->GetEntryUnparsed(game);
|
||||
std::unique_ptr<Loader::AppLoader> loader = Loader::GetLoader(file);
|
||||
if (!loader)
|
||||
continue;
|
||||
|
||||
std::vector<u8> icon;
|
||||
std::string name;
|
||||
u64 program_id = 0;
|
||||
loader->ReadProgramId(program_id);
|
||||
|
||||
const FileSys::PatchManager patch{program_id};
|
||||
const auto& control = cache->GetEntry(game.title_id, FileSys::ContentRecordType::Control);
|
||||
if (control != nullptr)
|
||||
GetMetadataFromControlNCA(patch, control, icon, name);
|
||||
|
||||
auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id);
|
||||
|
||||
// The game list uses this as compatibility number for untested games
|
||||
QString compatibility("99");
|
||||
if (it != compatibility_list.end())
|
||||
compatibility = it->second.first;
|
||||
|
||||
emit EntryReady({
|
||||
new GameListItemPath(
|
||||
FormatGameName(file->GetFullPath()), icon, QString::fromStdString(name),
|
||||
QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType())),
|
||||
program_id),
|
||||
new GameListItemCompat(compatibility),
|
||||
new GameListItem(FormatPatchNameVersions(patch)),
|
||||
new GameListItem(
|
||||
QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType()))),
|
||||
new GameListItemSize(file->GetSize()),
|
||||
});
|
||||
}
|
||||
|
||||
const auto control_data = cache->ListEntriesFilter(FileSys::TitleType::Application,
|
||||
FileSys::ContentRecordType::Control);
|
||||
|
||||
for (const auto& entry : control_data) {
|
||||
const auto nca = cache->GetEntry(entry);
|
||||
if (nca != nullptr)
|
||||
nca_control_map.insert_or_assign(entry.title_id, nca);
|
||||
}
|
||||
}
|
||||
|
||||
void GameListWorker::FillControlMap(const std::string& dir_path) {
|
||||
const auto nca_control_callback = [this](u64* num_entries_out, const std::string& directory,
|
||||
const std::string& virtual_name) -> bool {
|
||||
std::string physical_name = directory + DIR_SEP + virtual_name;
|
||||
|
||||
if (stop_processing)
|
||||
return false; // Breaks the callback loop.
|
||||
|
||||
bool is_dir = FileUtil::IsDirectory(physical_name);
|
||||
QFileInfo file_info(physical_name.c_str());
|
||||
if (!is_dir && file_info.suffix().toStdString() == "nca") {
|
||||
auto nca =
|
||||
std::make_shared<FileSys::NCA>(vfs->OpenFile(physical_name, FileSys::Mode::Read));
|
||||
if (nca->GetType() == FileSys::NCAContentType::Control)
|
||||
nca_control_map.insert_or_assign(nca->GetTitleId(), nca);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
FileUtil::ForeachDirectoryEntry(nullptr, dir_path, nca_control_callback);
|
||||
}
|
||||
|
||||
void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion) {
|
||||
const auto callback = [this, recursion](u64* num_entries_out, const std::string& directory,
|
||||
const std::string& virtual_name) -> bool {
|
||||
std::string physical_name = directory + DIR_SEP + virtual_name;
|
||||
|
||||
if (stop_processing)
|
||||
return false; // Breaks the callback loop.
|
||||
|
||||
bool is_dir = FileUtil::IsDirectory(physical_name);
|
||||
if (!is_dir &&
|
||||
(HasSupportedFileExtension(physical_name) || IsExtractedNCAMain(physical_name))) {
|
||||
std::unique_ptr<Loader::AppLoader> loader =
|
||||
Loader::GetLoader(vfs->OpenFile(physical_name, FileSys::Mode::Read));
|
||||
if (!loader || ((loader->GetFileType() == Loader::FileType::Unknown ||
|
||||
loader->GetFileType() == Loader::FileType::Error) &&
|
||||
!UISettings::values.show_unknown))
|
||||
return true;
|
||||
|
||||
std::vector<u8> icon;
|
||||
const auto res1 = loader->ReadIcon(icon);
|
||||
|
||||
u64 program_id = 0;
|
||||
const auto res2 = loader->ReadProgramId(program_id);
|
||||
|
||||
std::string name = " ";
|
||||
const auto res3 = loader->ReadTitle(name);
|
||||
|
||||
const FileSys::PatchManager patch{program_id};
|
||||
|
||||
if (res1 != Loader::ResultStatus::Success && res3 != Loader::ResultStatus::Success &&
|
||||
res2 == Loader::ResultStatus::Success) {
|
||||
// Use from metadata pool.
|
||||
if (nca_control_map.find(program_id) != nca_control_map.end()) {
|
||||
const auto nca = nca_control_map[program_id];
|
||||
GetMetadataFromControlNCA(patch, nca, icon, name);
|
||||
}
|
||||
}
|
||||
|
||||
auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id);
|
||||
|
||||
// The game list uses this as compatibility number for untested games
|
||||
QString compatibility("99");
|
||||
if (it != compatibility_list.end())
|
||||
compatibility = it->second.first;
|
||||
|
||||
emit EntryReady({
|
||||
new GameListItemPath(
|
||||
FormatGameName(physical_name), icon, QString::fromStdString(name),
|
||||
QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType())),
|
||||
program_id),
|
||||
new GameListItemCompat(compatibility),
|
||||
new GameListItem(FormatPatchNameVersions(patch, loader->IsRomFSUpdatable())),
|
||||
new GameListItem(
|
||||
QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType()))),
|
||||
new GameListItemSize(FileUtil::GetSize(physical_name)),
|
||||
});
|
||||
} else if (is_dir && recursion > 0) {
|
||||
watch_list.append(QString::fromStdString(physical_name));
|
||||
AddFstEntriesToGameList(physical_name, recursion - 1);
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
FileUtil::ForeachDirectoryEntry(nullptr, dir_path, callback);
|
||||
}
|
||||
|
||||
void GameListWorker::run() {
|
||||
stop_processing = false;
|
||||
watch_list.append(dir_path);
|
||||
FillControlMap(dir_path.toStdString());
|
||||
AddInstalledTitlesToGameList();
|
||||
AddFstEntriesToGameList(dir_path.toStdString(), deep_scan ? 256 : 0);
|
||||
nca_control_map.clear();
|
||||
emit Finished(watch_list);
|
||||
}
|
||||
|
||||
void GameListWorker::Cancel() {
|
||||
this->disconnect();
|
||||
stop_processing = true;
|
||||
}
|
||||
72
src/yuzu/game_list_worker.h
Normal file
72
src/yuzu/game_list_worker.h
Normal file
@@ -0,0 +1,72 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
#include <QList>
|
||||
#include <QObject>
|
||||
#include <QRunnable>
|
||||
#include <QString>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "yuzu/compatibility_list.h"
|
||||
|
||||
class QStandardItem;
|
||||
|
||||
namespace FileSys {
|
||||
class NCA;
|
||||
class VfsFilesystem;
|
||||
} // namespace FileSys
|
||||
|
||||
/**
|
||||
* Asynchronous worker object for populating the game list.
|
||||
* Communicates with other threads through Qt's signal/slot system.
|
||||
*/
|
||||
class GameListWorker : public QObject, public QRunnable {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
GameListWorker(std::shared_ptr<FileSys::VfsFilesystem> vfs, QString dir_path, bool deep_scan,
|
||||
const CompatibilityList& compatibility_list);
|
||||
~GameListWorker() override;
|
||||
|
||||
/// Starts the processing of directory tree information.
|
||||
void run() override;
|
||||
|
||||
/// Tells the worker that it should no longer continue processing. Thread-safe.
|
||||
void Cancel();
|
||||
|
||||
signals:
|
||||
/**
|
||||
* The `EntryReady` signal is emitted once an entry has been prepared and is ready
|
||||
* to be added to the game list.
|
||||
* @param entry_items a list with `QStandardItem`s that make up the columns of the new entry.
|
||||
*/
|
||||
void EntryReady(QList<QStandardItem*> entry_items);
|
||||
|
||||
/**
|
||||
* After the worker has traversed the game directory looking for entries, this signal is emitted
|
||||
* with a list of folders that should be watched for changes as well.
|
||||
*/
|
||||
void Finished(QStringList watch_list);
|
||||
|
||||
private:
|
||||
void AddInstalledTitlesToGameList();
|
||||
void FillControlMap(const std::string& dir_path);
|
||||
void AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion = 0);
|
||||
|
||||
std::shared_ptr<FileSys::VfsFilesystem> vfs;
|
||||
std::map<u64, std::shared_ptr<FileSys::NCA>> nca_control_map;
|
||||
QStringList watch_list;
|
||||
QString dir_path;
|
||||
bool deep_scan;
|
||||
const CompatibilityList& compatibility_list;
|
||||
std::atomic_bool stop_processing;
|
||||
};
|
||||
@@ -47,6 +47,7 @@
|
||||
#include "video_core/debug_utils/debug_utils.h"
|
||||
#include "yuzu/about_dialog.h"
|
||||
#include "yuzu/bootmanager.h"
|
||||
#include "yuzu/compatibility_list.h"
|
||||
#include "yuzu/configuration/config.h"
|
||||
#include "yuzu/configuration/configure_dialog.h"
|
||||
#include "yuzu/debugger/console.h"
|
||||
@@ -136,11 +137,11 @@ GMainWindow::GMainWindow()
|
||||
|
||||
ConnectMenuEvents();
|
||||
ConnectWidgetEvents();
|
||||
LOG_INFO(Frontend, "yuzu Version: {} | {}-{}", Common::g_build_name, Common::g_scm_branch,
|
||||
LOG_INFO(Frontend, "yuzu Version: {} | {}-{}", Common::g_build_fullname, Common::g_scm_branch,
|
||||
Common::g_scm_desc);
|
||||
|
||||
setWindowTitle(QString("yuzu %1| %2-%3")
|
||||
.arg(Common::g_build_name, Common::g_scm_branch, Common::g_scm_desc));
|
||||
.arg(Common::g_build_fullname, Common::g_scm_branch, Common::g_scm_desc));
|
||||
show();
|
||||
|
||||
// Necessary to load titles from nand in gamelist.
|
||||
@@ -444,6 +445,8 @@ QStringList GMainWindow::GetUnsupportedGLExtensions() {
|
||||
unsupported_ext.append("ARB_vertex_type_10f_11f_11f_rev");
|
||||
if (!GLAD_GL_ARB_texture_mirror_clamp_to_edge)
|
||||
unsupported_ext.append("ARB_texture_mirror_clamp_to_edge");
|
||||
if (!GLAD_GL_ARB_base_instance)
|
||||
unsupported_ext.append("ARB_base_instance");
|
||||
|
||||
// Extensions required to support some texture formats.
|
||||
if (!GLAD_GL_EXT_texture_compression_s3tc)
|
||||
@@ -606,7 +609,7 @@ void GMainWindow::BootGame(const QString& filename) {
|
||||
}
|
||||
|
||||
setWindowTitle(QString("yuzu %1| %4 | %2-%3")
|
||||
.arg(Common::g_build_name, Common::g_scm_branch, Common::g_scm_desc,
|
||||
.arg(Common::g_build_fullname, Common::g_scm_branch, Common::g_scm_desc,
|
||||
QString::fromStdString(title_name)));
|
||||
|
||||
render_window->show();
|
||||
@@ -641,7 +644,7 @@ void GMainWindow::ShutdownGame() {
|
||||
game_list->show();
|
||||
game_list->setFilterFocus();
|
||||
setWindowTitle(QString("yuzu %1| %2-%3")
|
||||
.arg(Common::g_build_name, Common::g_scm_branch, Common::g_scm_desc));
|
||||
.arg(Common::g_build_fullname, Common::g_scm_branch, Common::g_scm_desc));
|
||||
|
||||
// Disable status bar updates
|
||||
status_bar_update_timer.stop();
|
||||
@@ -723,14 +726,11 @@ void GMainWindow::OnGameListOpenFolder(u64 program_id, GameListOpenTarget target
|
||||
QDesktopServices::openUrl(QUrl::fromLocalFile(qpath));
|
||||
}
|
||||
|
||||
void GMainWindow::OnGameListNavigateToGamedbEntry(
|
||||
u64 program_id,
|
||||
std::unordered_map<std::string, std::pair<QString, QString>>& compatibility_list) {
|
||||
|
||||
auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id);
|
||||
void GMainWindow::OnGameListNavigateToGamedbEntry(u64 program_id,
|
||||
const CompatibilityList& compatibility_list) {
|
||||
const auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id);
|
||||
|
||||
QString directory;
|
||||
|
||||
if (it != compatibility_list.end())
|
||||
directory = it->second.second;
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include "common/common_types.h"
|
||||
#include "core/core.h"
|
||||
#include "ui_main.h"
|
||||
#include "yuzu/compatibility_list.h"
|
||||
#include "yuzu/hotkeys.h"
|
||||
|
||||
class Config;
|
||||
@@ -137,9 +138,8 @@ private slots:
|
||||
/// Called whenever a user selects a game in the game list widget.
|
||||
void OnGameListLoadFile(QString game_path);
|
||||
void OnGameListOpenFolder(u64 program_id, GameListOpenTarget target);
|
||||
void OnGameListNavigateToGamedbEntry(
|
||||
u64 program_id,
|
||||
std::unordered_map<std::string, std::pair<QString, QString>>& compatibility_list);
|
||||
void OnGameListNavigateToGamedbEntry(u64 program_id,
|
||||
const CompatibilityList& compatibility_list);
|
||||
void OnMenuLoadFile();
|
||||
void OnMenuLoadFolder();
|
||||
void OnMenuInstallToNAND();
|
||||
|
||||
@@ -91,6 +91,8 @@ bool EmuWindow_SDL2::SupportsRequiredGLExtensions() {
|
||||
unsupported_ext.push_back("ARB_vertex_type_10f_11f_11f_rev");
|
||||
if (!GLAD_GL_ARB_texture_mirror_clamp_to_edge)
|
||||
unsupported_ext.push_back("ARB_texture_mirror_clamp_to_edge");
|
||||
if (!GLAD_GL_ARB_base_instance)
|
||||
unsupported_ext.push_back("ARB_base_instance");
|
||||
|
||||
// Extensions required to support some texture formats.
|
||||
if (!GLAD_GL_EXT_texture_compression_s3tc)
|
||||
@@ -128,7 +130,7 @@ EmuWindow_SDL2::EmuWindow_SDL2(bool fullscreen) {
|
||||
SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
|
||||
SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 0);
|
||||
|
||||
std::string window_title = fmt::format("yuzu {} | {}-{}", Common::g_build_name,
|
||||
std::string window_title = fmt::format("yuzu {} | {}-{}", Common::g_build_fullname,
|
||||
Common::g_scm_branch, Common::g_scm_desc);
|
||||
render_window =
|
||||
SDL_CreateWindow(window_title.c_str(),
|
||||
@@ -166,7 +168,8 @@ EmuWindow_SDL2::EmuWindow_SDL2(bool fullscreen) {
|
||||
OnResize();
|
||||
OnMinimalClientAreaChangeRequest(GetActiveConfig().min_client_area_size);
|
||||
SDL_PumpEvents();
|
||||
LOG_INFO(Frontend, "yuzu Version: {} | {}-{}", Common::g_build_name, Common::g_scm_branch,
|
||||
SDL_GL_SetSwapInterval(false);
|
||||
LOG_INFO(Frontend, "yuzu Version: {} | {}-{}", Common::g_build_fullname, Common::g_scm_branch,
|
||||
Common::g_scm_desc);
|
||||
|
||||
DoneCurrent();
|
||||
|
||||
@@ -82,6 +82,9 @@ int main(int argc, char** argv) {
|
||||
int option_index = 0;
|
||||
bool use_gdbstub = Settings::values.use_gdbstub;
|
||||
u32 gdb_port = static_cast<u32>(Settings::values.gdbstub_port);
|
||||
|
||||
InitializeLogging();
|
||||
|
||||
char* endarg;
|
||||
#ifdef _WIN32
|
||||
int argc_w;
|
||||
@@ -144,8 +147,6 @@ int main(int argc, char** argv) {
|
||||
LocalFree(argv_w);
|
||||
#endif
|
||||
|
||||
InitializeLogging();
|
||||
|
||||
MicroProfileOnThreadCreate("EmuThread");
|
||||
SCOPE_EXIT({ MicroProfileShutdown(); });
|
||||
|
||||
|
||||
Reference in New Issue
Block a user