Compare commits
23 Commits
android-92
...
android-94
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9ffb91e8f4 | ||
|
|
bd42bba71c | ||
|
|
a27f94830a | ||
|
|
bd6f9f1d91 | ||
|
|
bf15aa093c | ||
|
|
0e9b839b6f | ||
|
|
15a5bdd979 | ||
|
|
fc4cde7513 | ||
|
|
ff3859d482 | ||
|
|
10de8f2c60 | ||
|
|
51b89fddd0 | ||
|
|
f585dec48d | ||
|
|
ad1a9f3d3a | ||
|
|
71044f6def | ||
|
|
a17cde7b2c | ||
|
|
a84c928827 | ||
|
|
9568d3bc60 | ||
|
|
0fe935a5de | ||
|
|
c84c35ac74 | ||
|
|
b32940d3ea | ||
|
|
38394f36d7 | ||
|
|
2f0db2708c | ||
|
|
1a246bf135 |
@@ -1,3 +1,11 @@
|
|||||||
|
| Pull Request | Commit | Title | Author | Merged? |
|
||||||
|
|----|----|----|----|----|
|
||||||
|
|
||||||
|
|
||||||
|
End of merge log. You can find the original README.md below the break.
|
||||||
|
|
||||||
|
-----
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
SPDX-FileCopyrightText: 2018 yuzu Emulator Project
|
SPDX-FileCopyrightText: 2018 yuzu Emulator Project
|
||||||
SPDX-License-Identifier: GPL-2.0-or-later
|
SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|||||||
@@ -218,7 +218,6 @@ public:
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
m_window->OnSurfaceChanged(m_native_window);
|
m_window->OnSurfaceChanged(m_native_window);
|
||||||
m_system.Renderer().NotifySurfaceChanged();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConfigureFilesystemProvider(const std::string& filepath) {
|
void ConfigureFilesystemProvider(const std::string& filepath) {
|
||||||
|
|||||||
@@ -135,6 +135,11 @@ std::u16string UTF8ToUTF16(std::string_view input) {
|
|||||||
return convert.from_bytes(input.data(), input.data() + input.size());
|
return convert.from_bytes(input.data(), input.data() + input.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::u32string UTF8ToUTF32(std::string_view input) {
|
||||||
|
std::wstring_convert<std::codecvt_utf8<char32_t>, char32_t> convert;
|
||||||
|
return convert.from_bytes(input.data(), input.data() + input.size());
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
static std::wstring CPToUTF16(u32 code_page, std::string_view input) {
|
static std::wstring CPToUTF16(u32 code_page, std::string_view input) {
|
||||||
const auto size =
|
const auto size =
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ bool SplitPath(const std::string& full_path, std::string* _pPath, std::string* _
|
|||||||
|
|
||||||
[[nodiscard]] std::string UTF16ToUTF8(std::u16string_view input);
|
[[nodiscard]] std::string UTF16ToUTF8(std::u16string_view input);
|
||||||
[[nodiscard]] std::u16string UTF8ToUTF16(std::string_view input);
|
[[nodiscard]] std::u16string UTF8ToUTF16(std::string_view input);
|
||||||
|
[[nodiscard]] std::u32string UTF8ToUTF32(std::string_view input);
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
[[nodiscard]] std::string UTF16ToUTF8(std::wstring_view input);
|
[[nodiscard]] std::string UTF16ToUTF8(std::wstring_view input);
|
||||||
|
|||||||
@@ -1078,6 +1078,10 @@ void System::ApplySettings() {
|
|||||||
impl->RefreshTime();
|
impl->RefreshTime();
|
||||||
|
|
||||||
if (IsPoweredOn()) {
|
if (IsPoweredOn()) {
|
||||||
|
if (Settings::values.custom_rtc_enabled) {
|
||||||
|
const s64 posix_time{Settings::values.custom_rtc.GetValue()};
|
||||||
|
GetTimeManager().UpdateLocalSystemClockTime(posix_time);
|
||||||
|
}
|
||||||
Renderer().RefreshBaseSettings();
|
Renderer().RefreshBaseSettings();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
|
#include <codecvt>
|
||||||
|
#include <locale>
|
||||||
#include <numeric>
|
#include <numeric>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
@@ -12,6 +14,7 @@
|
|||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
#include "common/scope_exit.h"
|
#include "common/scope_exit.h"
|
||||||
#include "common/settings.h"
|
#include "common/settings.h"
|
||||||
|
#include "common/string_util.h"
|
||||||
#include "core/arm/arm_interface.h"
|
#include "core/arm/arm_interface.h"
|
||||||
#include "core/core.h"
|
#include "core/core.h"
|
||||||
#include "core/debugger/gdbstub.h"
|
#include "core/debugger/gdbstub.h"
|
||||||
@@ -68,10 +71,16 @@ static std::string EscapeGDB(std::string_view data) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static std::string EscapeXML(std::string_view data) {
|
static std::string EscapeXML(std::string_view data) {
|
||||||
|
std::u32string converted = U"[Encoding error]";
|
||||||
|
try {
|
||||||
|
converted = Common::UTF8ToUTF32(data);
|
||||||
|
} catch (std::range_error&) {
|
||||||
|
}
|
||||||
|
|
||||||
std::string escaped;
|
std::string escaped;
|
||||||
escaped.reserve(data.size());
|
escaped.reserve(data.size());
|
||||||
|
|
||||||
for (char c : data) {
|
for (char32_t c : converted) {
|
||||||
switch (c) {
|
switch (c) {
|
||||||
case '&':
|
case '&':
|
||||||
escaped += "&";
|
escaped += "&";
|
||||||
@@ -86,7 +95,11 @@ static std::string EscapeXML(std::string_view data) {
|
|||||||
escaped += ">";
|
escaped += ">";
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
escaped += c;
|
if (c > 0x7f) {
|
||||||
|
escaped += fmt::format("&#{};", static_cast<u32>(c));
|
||||||
|
} else {
|
||||||
|
escaped += static_cast<char>(c);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
|
#include "common/scope_exit.h"
|
||||||
#include "core/file_sys/program_metadata.h"
|
#include "core/file_sys/program_metadata.h"
|
||||||
#include "core/file_sys/vfs.h"
|
#include "core/file_sys/vfs.h"
|
||||||
#include "core/loader/loader.h"
|
#include "core/loader/loader.h"
|
||||||
@@ -95,6 +96,13 @@ Loader::ResultStatus ProgramMetadata::Load(VirtualFile file) {
|
|||||||
return Loader::ResultStatus::Success;
|
return Loader::ResultStatus::Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loader::ResultStatus ProgramMetadata::Reload(VirtualFile file) {
|
||||||
|
const u64 original_program_id = aci_header.title_id;
|
||||||
|
SCOPE_EXIT({ aci_header.title_id = original_program_id; });
|
||||||
|
|
||||||
|
return this->Load(file);
|
||||||
|
}
|
||||||
|
|
||||||
/*static*/ ProgramMetadata ProgramMetadata::GetDefault() {
|
/*static*/ ProgramMetadata ProgramMetadata::GetDefault() {
|
||||||
// Allow use of cores 0~3 and thread priorities 1~63.
|
// Allow use of cores 0~3 and thread priorities 1~63.
|
||||||
constexpr u32 default_thread_info_capability = 0x30007F7;
|
constexpr u32 default_thread_info_capability = 0x30007F7;
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ public:
|
|||||||
static ProgramMetadata GetDefault();
|
static ProgramMetadata GetDefault();
|
||||||
|
|
||||||
Loader::ResultStatus Load(VirtualFile file);
|
Loader::ResultStatus Load(VirtualFile file);
|
||||||
|
Loader::ResultStatus Reload(VirtualFile file);
|
||||||
|
|
||||||
/// Load from parameters instead of NPDM file, used for KIP
|
/// Load from parameters instead of NPDM file, used for KIP
|
||||||
void LoadManual(bool is_64_bit, ProgramAddressSpaceType address_space, s32 main_thread_prio,
|
void LoadManual(bool is_64_bit, ProgramAddressSpaceType address_space, s32 main_thread_prio,
|
||||||
|
|||||||
@@ -118,7 +118,7 @@ AppLoader_DeconstructedRomDirectory::LoadResult AppLoader_DeconstructedRomDirect
|
|||||||
return {ResultStatus::ErrorMissingNPDM, {}};
|
return {ResultStatus::ErrorMissingNPDM, {}};
|
||||||
}
|
}
|
||||||
|
|
||||||
const ResultStatus result2 = metadata.Load(npdm);
|
const ResultStatus result2 = metadata.Reload(npdm);
|
||||||
if (result2 != ResultStatus::Success) {
|
if (result2 != ResultStatus::Success) {
|
||||||
return {result2, {}};
|
return {result2, {}};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ set(SHADER_FILES
|
|||||||
block_linear_unswizzle_2d.comp
|
block_linear_unswizzle_2d.comp
|
||||||
block_linear_unswizzle_3d.comp
|
block_linear_unswizzle_3d.comp
|
||||||
convert_abgr8_to_d24s8.frag
|
convert_abgr8_to_d24s8.frag
|
||||||
|
convert_d32f_to_abgr8.frag
|
||||||
convert_d24s8_to_abgr8.frag
|
convert_d24s8_to_abgr8.frag
|
||||||
convert_depth_to_float.frag
|
convert_depth_to_float.frag
|
||||||
convert_float_to_depth.frag
|
convert_float_to_depth.frag
|
||||||
|
|||||||
14
src/video_core/host_shaders/convert_d32f_to_abgr8.frag
Normal file
14
src/video_core/host_shaders/convert_d32f_to_abgr8.frag
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#version 450
|
||||||
|
|
||||||
|
layout(binding = 0) uniform sampler2D depth_tex;
|
||||||
|
|
||||||
|
layout(location = 0) out vec4 color;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
ivec2 coord = ivec2(gl_FragCoord.xy);
|
||||||
|
float depth = textureLod(depth_tex, coord, 0).r;
|
||||||
|
color = vec4(depth, depth, depth, 1.0);
|
||||||
|
}
|
||||||
@@ -89,9 +89,6 @@ public:
|
|||||||
void RequestScreenshot(void* data, std::function<void(bool)> callback,
|
void RequestScreenshot(void* data, std::function<void(bool)> callback,
|
||||||
const Layout::FramebufferLayout& layout);
|
const Layout::FramebufferLayout& layout);
|
||||||
|
|
||||||
/// This is called to notify the rendering backend of a surface change
|
|
||||||
virtual void NotifySurfaceChanged() {}
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
Core::Frontend::EmuWindow& render_window; ///< Reference to the render window handle.
|
Core::Frontend::EmuWindow& render_window; ///< Reference to the render window handle.
|
||||||
std::unique_ptr<Core::Frontend::GraphicsContext> context;
|
std::unique_ptr<Core::Frontend::GraphicsContext> context;
|
||||||
|
|||||||
@@ -116,6 +116,7 @@ constexpr std::array<FormatTuple, VideoCore::Surface::MaxPixelFormat> FORMAT_TAB
|
|||||||
{GL_RGB9_E5, GL_RGB, GL_UNSIGNED_INT_5_9_9_9_REV}, // E5B9G9R9_FLOAT
|
{GL_RGB9_E5, GL_RGB, GL_UNSIGNED_INT_5_9_9_9_REV}, // E5B9G9R9_FLOAT
|
||||||
{GL_DEPTH_COMPONENT32F, GL_DEPTH_COMPONENT, GL_FLOAT}, // D32_FLOAT
|
{GL_DEPTH_COMPONENT32F, GL_DEPTH_COMPONENT, GL_FLOAT}, // D32_FLOAT
|
||||||
{GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT}, // D16_UNORM
|
{GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT}, // D16_UNORM
|
||||||
|
{GL_DEPTH_COMPONENT24, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT_24_8}, // X8_D24_UNORM
|
||||||
{GL_STENCIL_INDEX8, GL_STENCIL, GL_UNSIGNED_BYTE}, // S8_UINT
|
{GL_STENCIL_INDEX8, GL_STENCIL, GL_UNSIGNED_BYTE}, // S8_UINT
|
||||||
{GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8}, // D24_UNORM_S8_UINT
|
{GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8}, // D24_UNORM_S8_UINT
|
||||||
{GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8}, // S8_UINT_D24_UNORM
|
{GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8}, // S8_UINT_D24_UNORM
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
#include "video_core/host_shaders/blit_color_float_frag_spv.h"
|
#include "video_core/host_shaders/blit_color_float_frag_spv.h"
|
||||||
#include "video_core/host_shaders/convert_abgr8_to_d24s8_frag_spv.h"
|
#include "video_core/host_shaders/convert_abgr8_to_d24s8_frag_spv.h"
|
||||||
#include "video_core/host_shaders/convert_d24s8_to_abgr8_frag_spv.h"
|
#include "video_core/host_shaders/convert_d24s8_to_abgr8_frag_spv.h"
|
||||||
|
#include "video_core/host_shaders/convert_d32f_to_abgr8_frag_spv.h"
|
||||||
#include "video_core/host_shaders/convert_depth_to_float_frag_spv.h"
|
#include "video_core/host_shaders/convert_depth_to_float_frag_spv.h"
|
||||||
#include "video_core/host_shaders/convert_float_to_depth_frag_spv.h"
|
#include "video_core/host_shaders/convert_float_to_depth_frag_spv.h"
|
||||||
#include "video_core/host_shaders/convert_s8d24_to_abgr8_frag_spv.h"
|
#include "video_core/host_shaders/convert_s8d24_to_abgr8_frag_spv.h"
|
||||||
@@ -433,6 +434,7 @@ BlitImageHelper::BlitImageHelper(const Device& device_, Scheduler& scheduler_,
|
|||||||
convert_depth_to_float_frag(BuildShader(device, CONVERT_DEPTH_TO_FLOAT_FRAG_SPV)),
|
convert_depth_to_float_frag(BuildShader(device, CONVERT_DEPTH_TO_FLOAT_FRAG_SPV)),
|
||||||
convert_float_to_depth_frag(BuildShader(device, CONVERT_FLOAT_TO_DEPTH_FRAG_SPV)),
|
convert_float_to_depth_frag(BuildShader(device, CONVERT_FLOAT_TO_DEPTH_FRAG_SPV)),
|
||||||
convert_abgr8_to_d24s8_frag(BuildShader(device, CONVERT_ABGR8_TO_D24S8_FRAG_SPV)),
|
convert_abgr8_to_d24s8_frag(BuildShader(device, CONVERT_ABGR8_TO_D24S8_FRAG_SPV)),
|
||||||
|
convert_d32f_to_abgr8_frag(BuildShader(device, CONVERT_D32F_TO_ABGR8_FRAG_SPV)),
|
||||||
convert_d24s8_to_abgr8_frag(BuildShader(device, CONVERT_D24S8_TO_ABGR8_FRAG_SPV)),
|
convert_d24s8_to_abgr8_frag(BuildShader(device, CONVERT_D24S8_TO_ABGR8_FRAG_SPV)),
|
||||||
convert_s8d24_to_abgr8_frag(BuildShader(device, CONVERT_S8D24_TO_ABGR8_FRAG_SPV)),
|
convert_s8d24_to_abgr8_frag(BuildShader(device, CONVERT_S8D24_TO_ABGR8_FRAG_SPV)),
|
||||||
linear_sampler(device.GetLogical().CreateSampler(SAMPLER_CREATE_INFO<VK_FILTER_LINEAR>)),
|
linear_sampler(device.GetLogical().CreateSampler(SAMPLER_CREATE_INFO<VK_FILTER_LINEAR>)),
|
||||||
@@ -557,6 +559,13 @@ void BlitImageHelper::ConvertABGR8ToD24S8(const Framebuffer* dst_framebuffer,
|
|||||||
Convert(*convert_abgr8_to_d24s8_pipeline, dst_framebuffer, src_image_view);
|
Convert(*convert_abgr8_to_d24s8_pipeline, dst_framebuffer, src_image_view);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void BlitImageHelper::ConvertD32FToABGR8(const Framebuffer* dst_framebuffer,
|
||||||
|
ImageView& src_image_view) {
|
||||||
|
ConvertPipelineColorTargetEx(convert_d32f_to_abgr8_pipeline, dst_framebuffer->RenderPass(),
|
||||||
|
convert_d32f_to_abgr8_frag);
|
||||||
|
ConvertDepthStencil(*convert_d32f_to_abgr8_pipeline, dst_framebuffer, src_image_view);
|
||||||
|
}
|
||||||
|
|
||||||
void BlitImageHelper::ConvertD24S8ToABGR8(const Framebuffer* dst_framebuffer,
|
void BlitImageHelper::ConvertD24S8ToABGR8(const Framebuffer* dst_framebuffer,
|
||||||
ImageView& src_image_view) {
|
ImageView& src_image_view) {
|
||||||
ConvertPipelineColorTargetEx(convert_d24s8_to_abgr8_pipeline, dst_framebuffer->RenderPass(),
|
ConvertPipelineColorTargetEx(convert_d24s8_to_abgr8_pipeline, dst_framebuffer->RenderPass(),
|
||||||
@@ -609,6 +618,8 @@ void BlitImageHelper::ClearDepthStencil(const Framebuffer* dst_framebuffer, bool
|
|||||||
const VkPipelineLayout layout = *clear_color_pipeline_layout;
|
const VkPipelineLayout layout = *clear_color_pipeline_layout;
|
||||||
scheduler.RequestRenderpass(dst_framebuffer);
|
scheduler.RequestRenderpass(dst_framebuffer);
|
||||||
scheduler.Record([pipeline, layout, clear_depth, dst_region](vk::CommandBuffer cmdbuf) {
|
scheduler.Record([pipeline, layout, clear_depth, dst_region](vk::CommandBuffer cmdbuf) {
|
||||||
|
constexpr std::array blend_constants{0.0f, 0.0f, 0.0f, 0.0f};
|
||||||
|
cmdbuf.SetBlendConstants(blend_constants.data());
|
||||||
cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
|
cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
|
||||||
BindBlitState(cmdbuf, dst_region);
|
BindBlitState(cmdbuf, dst_region);
|
||||||
cmdbuf.PushConstants(layout, VK_SHADER_STAGE_FRAGMENT_BIT, clear_depth);
|
cmdbuf.PushConstants(layout, VK_SHADER_STAGE_FRAGMENT_BIT, clear_depth);
|
||||||
@@ -865,7 +876,7 @@ VkPipeline BlitImageHelper::FindOrEmplaceClearStencilPipeline(
|
|||||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO,
|
.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO,
|
||||||
.pNext = nullptr,
|
.pNext = nullptr,
|
||||||
.flags = 0,
|
.flags = 0,
|
||||||
.depthTestEnable = VK_FALSE,
|
.depthTestEnable = key.depth_clear,
|
||||||
.depthWriteEnable = key.depth_clear,
|
.depthWriteEnable = key.depth_clear,
|
||||||
.depthCompareOp = VK_COMPARE_OP_ALWAYS,
|
.depthCompareOp = VK_COMPARE_OP_ALWAYS,
|
||||||
.depthBoundsTestEnable = VK_FALSE,
|
.depthBoundsTestEnable = VK_FALSE,
|
||||||
|
|||||||
@@ -67,6 +67,8 @@ public:
|
|||||||
|
|
||||||
void ConvertABGR8ToD24S8(const Framebuffer* dst_framebuffer, const ImageView& src_image_view);
|
void ConvertABGR8ToD24S8(const Framebuffer* dst_framebuffer, const ImageView& src_image_view);
|
||||||
|
|
||||||
|
void ConvertD32FToABGR8(const Framebuffer* dst_framebuffer, ImageView& src_image_view);
|
||||||
|
|
||||||
void ConvertD24S8ToABGR8(const Framebuffer* dst_framebuffer, ImageView& src_image_view);
|
void ConvertD24S8ToABGR8(const Framebuffer* dst_framebuffer, ImageView& src_image_view);
|
||||||
|
|
||||||
void ConvertS8D24ToABGR8(const Framebuffer* dst_framebuffer, ImageView& src_image_view);
|
void ConvertS8D24ToABGR8(const Framebuffer* dst_framebuffer, ImageView& src_image_view);
|
||||||
@@ -128,6 +130,7 @@ private:
|
|||||||
vk::ShaderModule convert_depth_to_float_frag;
|
vk::ShaderModule convert_depth_to_float_frag;
|
||||||
vk::ShaderModule convert_float_to_depth_frag;
|
vk::ShaderModule convert_float_to_depth_frag;
|
||||||
vk::ShaderModule convert_abgr8_to_d24s8_frag;
|
vk::ShaderModule convert_abgr8_to_d24s8_frag;
|
||||||
|
vk::ShaderModule convert_d32f_to_abgr8_frag;
|
||||||
vk::ShaderModule convert_d24s8_to_abgr8_frag;
|
vk::ShaderModule convert_d24s8_to_abgr8_frag;
|
||||||
vk::ShaderModule convert_s8d24_to_abgr8_frag;
|
vk::ShaderModule convert_s8d24_to_abgr8_frag;
|
||||||
vk::Sampler linear_sampler;
|
vk::Sampler linear_sampler;
|
||||||
@@ -146,6 +149,7 @@ private:
|
|||||||
vk::Pipeline convert_d16_to_r16_pipeline;
|
vk::Pipeline convert_d16_to_r16_pipeline;
|
||||||
vk::Pipeline convert_r16_to_d16_pipeline;
|
vk::Pipeline convert_r16_to_d16_pipeline;
|
||||||
vk::Pipeline convert_abgr8_to_d24s8_pipeline;
|
vk::Pipeline convert_abgr8_to_d24s8_pipeline;
|
||||||
|
vk::Pipeline convert_d32f_to_abgr8_pipeline;
|
||||||
vk::Pipeline convert_d24s8_to_abgr8_pipeline;
|
vk::Pipeline convert_d24s8_to_abgr8_pipeline;
|
||||||
vk::Pipeline convert_s8d24_to_abgr8_pipeline;
|
vk::Pipeline convert_s8d24_to_abgr8_pipeline;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -214,8 +214,9 @@ struct FormatTuple {
|
|||||||
{VK_FORMAT_E5B9G9R9_UFLOAT_PACK32}, // E5B9G9R9_FLOAT
|
{VK_FORMAT_E5B9G9R9_UFLOAT_PACK32}, // E5B9G9R9_FLOAT
|
||||||
|
|
||||||
// Depth formats
|
// Depth formats
|
||||||
{VK_FORMAT_D32_SFLOAT, Attachable}, // D32_FLOAT
|
{VK_FORMAT_D32_SFLOAT, Attachable}, // D32_FLOAT
|
||||||
{VK_FORMAT_D16_UNORM, Attachable}, // D16_UNORM
|
{VK_FORMAT_D16_UNORM, Attachable}, // D16_UNORM
|
||||||
|
{VK_FORMAT_X8_D24_UNORM_PACK32, Attachable}, // X8_D24_UNORM
|
||||||
|
|
||||||
// Stencil formats
|
// Stencil formats
|
||||||
{VK_FORMAT_S8_UINT, Attachable}, // S8_UINT
|
{VK_FORMAT_S8_UINT, Attachable}, // S8_UINT
|
||||||
|
|||||||
@@ -56,10 +56,6 @@ public:
|
|||||||
return device.GetDriverName();
|
return device.GetDriverName();
|
||||||
}
|
}
|
||||||
|
|
||||||
void NotifySurfaceChanged() override {
|
|
||||||
present_manager.NotifySurfaceChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void Report() const;
|
void Report() const;
|
||||||
|
|
||||||
|
|||||||
@@ -96,6 +96,7 @@ std::size_t GetSizeInBytes(const Tegra::FramebufferConfig& framebuffer) {
|
|||||||
VkFormat GetFormat(const Tegra::FramebufferConfig& framebuffer) {
|
VkFormat GetFormat(const Tegra::FramebufferConfig& framebuffer) {
|
||||||
switch (framebuffer.pixel_format) {
|
switch (framebuffer.pixel_format) {
|
||||||
case Service::android::PixelFormat::Rgba8888:
|
case Service::android::PixelFormat::Rgba8888:
|
||||||
|
case Service::android::PixelFormat::Rgbx8888:
|
||||||
return VK_FORMAT_A8B8G8R8_UNORM_PACK32;
|
return VK_FORMAT_A8B8G8R8_UNORM_PACK32;
|
||||||
case Service::android::PixelFormat::Rgb565:
|
case Service::android::PixelFormat::Rgb565:
|
||||||
return VK_FORMAT_R5G6B5_UNORM_PACK16;
|
return VK_FORMAT_R5G6B5_UNORM_PACK16;
|
||||||
|
|||||||
@@ -103,8 +103,7 @@ PresentManager::PresentManager(const vk::Instance& instance_,
|
|||||||
surface{surface_}, blit_supported{CanBlitToSwapchain(device.GetPhysical(),
|
surface{surface_}, blit_supported{CanBlitToSwapchain(device.GetPhysical(),
|
||||||
swapchain.GetImageViewFormat())},
|
swapchain.GetImageViewFormat())},
|
||||||
use_present_thread{Settings::values.async_presentation.GetValue()},
|
use_present_thread{Settings::values.async_presentation.GetValue()},
|
||||||
image_count{swapchain.GetImageCount()}, last_render_surface{
|
image_count{swapchain.GetImageCount()} {
|
||||||
render_window_.GetWindowInfo().render_surface} {
|
|
||||||
|
|
||||||
auto& dld = device.GetLogical();
|
auto& dld = device.GetLogical();
|
||||||
cmdpool = dld.CreateCommandPool({
|
cmdpool = dld.CreateCommandPool({
|
||||||
@@ -289,44 +288,36 @@ void PresentManager::PresentThread(std::stop_token token) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void PresentManager::NotifySurfaceChanged() {
|
void PresentManager::RecreateSwapchain(Frame* frame) {
|
||||||
#ifdef ANDROID
|
swapchain.Create(*surface, frame->width, frame->height, frame->is_srgb);
|
||||||
std::scoped_lock lock{recreate_surface_mutex};
|
image_count = swapchain.GetImageCount();
|
||||||
recreate_surface_cv.notify_one();
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void PresentManager::CopyToSwapchain(Frame* frame) {
|
void PresentManager::CopyToSwapchain(Frame* frame) {
|
||||||
MICROPROFILE_SCOPE(Vulkan_CopyToSwapchain);
|
bool requires_recreation = false;
|
||||||
|
|
||||||
const auto recreate_swapchain = [&] {
|
while (true) {
|
||||||
swapchain.Create(*surface, frame->width, frame->height, frame->is_srgb);
|
try {
|
||||||
image_count = swapchain.GetImageCount();
|
// Recreate surface and swapchain if needed.
|
||||||
};
|
if (requires_recreation) {
|
||||||
|
surface = CreateSurface(instance, render_window.GetWindowInfo());
|
||||||
|
RecreateSwapchain(frame);
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef ANDROID
|
// Draw to swapchain.
|
||||||
std::unique_lock lock{recreate_surface_mutex};
|
return CopyToSwapchainImpl(frame);
|
||||||
|
} catch (const vk::Exception& except) {
|
||||||
|
if (except.GetResult() != VK_ERROR_SURFACE_LOST_KHR) {
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
const auto needs_recreation = [&] {
|
requires_recreation = true;
|
||||||
if (last_render_surface != render_window.GetWindowInfo().render_surface) {
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
if (swapchain.NeedsRecreation(frame->is_srgb)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
recreate_surface_cv.wait_for(lock, std::chrono::milliseconds(400),
|
|
||||||
[&]() { return !needs_recreation(); });
|
|
||||||
|
|
||||||
// If the frontend recreated the surface, recreate the renderer surface and swapchain.
|
|
||||||
if (last_render_surface != render_window.GetWindowInfo().render_surface) {
|
|
||||||
last_render_surface = render_window.GetWindowInfo().render_surface;
|
|
||||||
surface = CreateSurface(instance, render_window.GetWindowInfo());
|
|
||||||
recreate_swapchain();
|
|
||||||
}
|
}
|
||||||
#endif
|
}
|
||||||
|
|
||||||
|
void PresentManager::CopyToSwapchainImpl(Frame* frame) {
|
||||||
|
MICROPROFILE_SCOPE(Vulkan_CopyToSwapchain);
|
||||||
|
|
||||||
// If the size or colorspace of the incoming frames has changed, recreate the swapchain
|
// If the size or colorspace of the incoming frames has changed, recreate the swapchain
|
||||||
// to account for that.
|
// to account for that.
|
||||||
@@ -334,11 +325,11 @@ void PresentManager::CopyToSwapchain(Frame* frame) {
|
|||||||
const bool size_changed =
|
const bool size_changed =
|
||||||
swapchain.GetWidth() != frame->width || swapchain.GetHeight() != frame->height;
|
swapchain.GetWidth() != frame->width || swapchain.GetHeight() != frame->height;
|
||||||
if (srgb_changed || size_changed) {
|
if (srgb_changed || size_changed) {
|
||||||
recreate_swapchain();
|
RecreateSwapchain(frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
while (swapchain.AcquireNextImage()) {
|
while (swapchain.AcquireNextImage()) {
|
||||||
recreate_swapchain();
|
RecreateSwapchain(frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
const vk::CommandBuffer cmdbuf{frame->cmdbuf};
|
const vk::CommandBuffer cmdbuf{frame->cmdbuf};
|
||||||
@@ -488,4 +479,4 @@ void PresentManager::CopyToSwapchain(Frame* frame) {
|
|||||||
swapchain.Present(render_semaphore);
|
swapchain.Present(render_semaphore);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Vulkan
|
} // namespace Vulkan
|
||||||
|
|||||||
@@ -54,14 +54,15 @@ public:
|
|||||||
/// Waits for the present thread to finish presenting all queued frames.
|
/// Waits for the present thread to finish presenting all queued frames.
|
||||||
void WaitPresent();
|
void WaitPresent();
|
||||||
|
|
||||||
/// This is called to notify the rendering backend of a surface change
|
|
||||||
void NotifySurfaceChanged();
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void PresentThread(std::stop_token token);
|
void PresentThread(std::stop_token token);
|
||||||
|
|
||||||
void CopyToSwapchain(Frame* frame);
|
void CopyToSwapchain(Frame* frame);
|
||||||
|
|
||||||
|
void CopyToSwapchainImpl(Frame* frame);
|
||||||
|
|
||||||
|
void RecreateSwapchain(Frame* frame);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
const vk::Instance& instance;
|
const vk::Instance& instance;
|
||||||
Core::Frontend::EmuWindow& render_window;
|
Core::Frontend::EmuWindow& render_window;
|
||||||
@@ -76,16 +77,13 @@ private:
|
|||||||
std::queue<Frame*> free_queue;
|
std::queue<Frame*> free_queue;
|
||||||
std::condition_variable_any frame_cv;
|
std::condition_variable_any frame_cv;
|
||||||
std::condition_variable free_cv;
|
std::condition_variable free_cv;
|
||||||
std::condition_variable recreate_surface_cv;
|
|
||||||
std::mutex swapchain_mutex;
|
std::mutex swapchain_mutex;
|
||||||
std::mutex recreate_surface_mutex;
|
|
||||||
std::mutex queue_mutex;
|
std::mutex queue_mutex;
|
||||||
std::mutex free_mutex;
|
std::mutex free_mutex;
|
||||||
std::jthread present_thread;
|
std::jthread present_thread;
|
||||||
bool blit_supported;
|
bool blit_supported;
|
||||||
bool use_present_thread;
|
bool use_present_thread;
|
||||||
std::size_t image_count{};
|
std::size_t image_count{};
|
||||||
void* last_render_surface{};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Vulkan
|
} // namespace Vulkan
|
||||||
|
|||||||
@@ -422,7 +422,8 @@ void RasterizerVulkan::Clear(u32 layer_count) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (use_stencil && regs.stencil_front_mask != 0xFF && regs.stencil_front_mask != 0) {
|
if (use_stencil && framebuffer->HasAspectStencilBit() && regs.stencil_front_mask != 0xFF &&
|
||||||
|
regs.stencil_front_mask != 0) {
|
||||||
Region2D dst_region = {
|
Region2D dst_region = {
|
||||||
Offset2D{.x = clear_rect.rect.offset.x, .y = clear_rect.rect.offset.y},
|
Offset2D{.x = clear_rect.rect.offset.x, .y = clear_rect.rect.offset.y},
|
||||||
Offset2D{.x = clear_rect.rect.offset.x + static_cast<s32>(clear_rect.rect.extent.width),
|
Offset2D{.x = clear_rect.rect.offset.x + static_cast<s32>(clear_rect.rect.extent.width),
|
||||||
|
|||||||
@@ -24,25 +24,38 @@ using namespace Common::Literals;
|
|||||||
|
|
||||||
// Maximum potential alignment of a Vulkan buffer
|
// Maximum potential alignment of a Vulkan buffer
|
||||||
constexpr VkDeviceSize MAX_ALIGNMENT = 256;
|
constexpr VkDeviceSize MAX_ALIGNMENT = 256;
|
||||||
// Maximum size to put elements in the stream buffer
|
|
||||||
constexpr VkDeviceSize MAX_STREAM_BUFFER_REQUEST_SIZE = 8_MiB;
|
|
||||||
// Stream buffer size in bytes
|
// Stream buffer size in bytes
|
||||||
constexpr VkDeviceSize STREAM_BUFFER_SIZE = 128_MiB;
|
constexpr VkDeviceSize MAX_STREAM_BUFFER_SIZE = 128_MiB;
|
||||||
constexpr VkDeviceSize REGION_SIZE = STREAM_BUFFER_SIZE / StagingBufferPool::NUM_SYNCS;
|
|
||||||
|
|
||||||
size_t Region(size_t iterator) noexcept {
|
size_t GetStreamBufferSize(const Device& device) {
|
||||||
return iterator / REGION_SIZE;
|
VkDeviceSize size{0};
|
||||||
|
if (device.HasDebuggingToolAttached()) {
|
||||||
|
ForEachDeviceLocalHostVisibleHeap(device, [&size](size_t index, VkMemoryHeap& heap) {
|
||||||
|
size = std::max(size, heap.size);
|
||||||
|
});
|
||||||
|
// If rebar is not supported, cut the max heap size to 40%. This will allow 2 captures to be
|
||||||
|
// loaded at the same time in RenderDoc. If rebar is supported, this shouldn't be an issue
|
||||||
|
// as the heap will be much larger.
|
||||||
|
if (size <= 256_MiB) {
|
||||||
|
size = size * 40 / 100;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
size = MAX_STREAM_BUFFER_SIZE;
|
||||||
|
}
|
||||||
|
return std::min(Common::AlignUp(size, MAX_ALIGNMENT), MAX_STREAM_BUFFER_SIZE);
|
||||||
}
|
}
|
||||||
} // Anonymous namespace
|
} // Anonymous namespace
|
||||||
|
|
||||||
StagingBufferPool::StagingBufferPool(const Device& device_, MemoryAllocator& memory_allocator_,
|
StagingBufferPool::StagingBufferPool(const Device& device_, MemoryAllocator& memory_allocator_,
|
||||||
Scheduler& scheduler_)
|
Scheduler& scheduler_)
|
||||||
: device{device_}, memory_allocator{memory_allocator_}, scheduler{scheduler_} {
|
: device{device_}, memory_allocator{memory_allocator_}, scheduler{scheduler_},
|
||||||
|
stream_buffer_size{GetStreamBufferSize(device)}, region_size{stream_buffer_size /
|
||||||
|
StagingBufferPool::NUM_SYNCS} {
|
||||||
VkBufferCreateInfo stream_ci = {
|
VkBufferCreateInfo stream_ci = {
|
||||||
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
|
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
|
||||||
.pNext = nullptr,
|
.pNext = nullptr,
|
||||||
.flags = 0,
|
.flags = 0,
|
||||||
.size = STREAM_BUFFER_SIZE,
|
.size = stream_buffer_size,
|
||||||
.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT |
|
.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT |
|
||||||
VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT,
|
VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT,
|
||||||
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
|
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
|
||||||
@@ -63,7 +76,7 @@ StagingBufferPool::StagingBufferPool(const Device& device_, MemoryAllocator& mem
|
|||||||
StagingBufferPool::~StagingBufferPool() = default;
|
StagingBufferPool::~StagingBufferPool() = default;
|
||||||
|
|
||||||
StagingBufferRef StagingBufferPool::Request(size_t size, MemoryUsage usage, bool deferred) {
|
StagingBufferRef StagingBufferPool::Request(size_t size, MemoryUsage usage, bool deferred) {
|
||||||
if (!deferred && usage == MemoryUsage::Upload && size <= MAX_STREAM_BUFFER_REQUEST_SIZE) {
|
if (!deferred && usage == MemoryUsage::Upload && size <= region_size) {
|
||||||
return GetStreamBuffer(size);
|
return GetStreamBuffer(size);
|
||||||
}
|
}
|
||||||
return GetStagingBuffer(size, usage, deferred);
|
return GetStagingBuffer(size, usage, deferred);
|
||||||
@@ -101,7 +114,7 @@ StagingBufferRef StagingBufferPool::GetStreamBuffer(size_t size) {
|
|||||||
used_iterator = iterator;
|
used_iterator = iterator;
|
||||||
free_iterator = std::max(free_iterator, iterator + size);
|
free_iterator = std::max(free_iterator, iterator + size);
|
||||||
|
|
||||||
if (iterator + size >= STREAM_BUFFER_SIZE) {
|
if (iterator + size >= stream_buffer_size) {
|
||||||
std::fill(sync_ticks.begin() + Region(used_iterator), sync_ticks.begin() + NUM_SYNCS,
|
std::fill(sync_ticks.begin() + Region(used_iterator), sync_ticks.begin() + NUM_SYNCS,
|
||||||
current_tick);
|
current_tick);
|
||||||
used_iterator = 0;
|
used_iterator = 0;
|
||||||
|
|||||||
@@ -90,6 +90,9 @@ private:
|
|||||||
void ReleaseCache(MemoryUsage usage);
|
void ReleaseCache(MemoryUsage usage);
|
||||||
|
|
||||||
void ReleaseLevel(StagingBuffersCache& cache, size_t log2);
|
void ReleaseLevel(StagingBuffersCache& cache, size_t log2);
|
||||||
|
size_t Region(size_t iter) const noexcept {
|
||||||
|
return iter / region_size;
|
||||||
|
}
|
||||||
|
|
||||||
const Device& device;
|
const Device& device;
|
||||||
MemoryAllocator& memory_allocator;
|
MemoryAllocator& memory_allocator;
|
||||||
@@ -97,6 +100,8 @@ private:
|
|||||||
|
|
||||||
vk::Buffer stream_buffer;
|
vk::Buffer stream_buffer;
|
||||||
std::span<u8> stream_pointer;
|
std::span<u8> stream_pointer;
|
||||||
|
VkDeviceSize stream_buffer_size;
|
||||||
|
VkDeviceSize region_size;
|
||||||
|
|
||||||
size_t iterator = 0;
|
size_t iterator = 0;
|
||||||
size_t used_iterator = 0;
|
size_t used_iterator = 0;
|
||||||
|
|||||||
@@ -238,6 +238,7 @@ constexpr VkBorderColor ConvertBorderColor(const std::array<float, 4>& color) {
|
|||||||
return any_r ? VK_IMAGE_ASPECT_STENCIL_BIT : VK_IMAGE_ASPECT_DEPTH_BIT;
|
return any_r ? VK_IMAGE_ASPECT_STENCIL_BIT : VK_IMAGE_ASPECT_DEPTH_BIT;
|
||||||
case PixelFormat::D16_UNORM:
|
case PixelFormat::D16_UNORM:
|
||||||
case PixelFormat::D32_FLOAT:
|
case PixelFormat::D32_FLOAT:
|
||||||
|
case PixelFormat::X8_D24_UNORM:
|
||||||
return VK_IMAGE_ASPECT_DEPTH_BIT;
|
return VK_IMAGE_ASPECT_DEPTH_BIT;
|
||||||
case PixelFormat::S8_UINT:
|
case PixelFormat::S8_UINT:
|
||||||
return VK_IMAGE_ASPECT_STENCIL_BIT;
|
return VK_IMAGE_ASPECT_STENCIL_BIT;
|
||||||
@@ -1200,6 +1201,9 @@ void TextureCacheRuntime::ConvertImage(Framebuffer* dst, ImageView& dst_view, Im
|
|||||||
if (src_view.format == PixelFormat::D24_UNORM_S8_UINT) {
|
if (src_view.format == PixelFormat::D24_UNORM_S8_UINT) {
|
||||||
return blit_image_helper.ConvertS8D24ToABGR8(dst, src_view);
|
return blit_image_helper.ConvertS8D24ToABGR8(dst, src_view);
|
||||||
}
|
}
|
||||||
|
if (src_view.format == PixelFormat::D32_FLOAT) {
|
||||||
|
return blit_image_helper.ConvertD32FToABGR8(dst, src_view);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case PixelFormat::R32_FLOAT:
|
case PixelFormat::R32_FLOAT:
|
||||||
if (src_view.format == PixelFormat::D32_FLOAT) {
|
if (src_view.format == PixelFormat::D32_FLOAT) {
|
||||||
|
|||||||
@@ -85,6 +85,8 @@ PixelFormat PixelFormatFromDepthFormat(Tegra::DepthFormat format) {
|
|||||||
return PixelFormat::S8_UINT;
|
return PixelFormat::S8_UINT;
|
||||||
case Tegra::DepthFormat::Z32_FLOAT_X24S8_UINT:
|
case Tegra::DepthFormat::Z32_FLOAT_X24S8_UINT:
|
||||||
return PixelFormat::D32_FLOAT_S8_UINT;
|
return PixelFormat::D32_FLOAT_S8_UINT;
|
||||||
|
case Tegra::DepthFormat::X8Z24_UNORM:
|
||||||
|
return PixelFormat::X8_D24_UNORM;
|
||||||
default:
|
default:
|
||||||
UNIMPLEMENTED_MSG("Unimplemented format={}", format);
|
UNIMPLEMENTED_MSG("Unimplemented format={}", format);
|
||||||
return PixelFormat::S8_UINT_D24_UNORM;
|
return PixelFormat::S8_UINT_D24_UNORM;
|
||||||
@@ -202,6 +204,7 @@ PixelFormat PixelFormatFromRenderTargetFormat(Tegra::RenderTargetFormat format)
|
|||||||
PixelFormat PixelFormatFromGPUPixelFormat(Service::android::PixelFormat format) {
|
PixelFormat PixelFormatFromGPUPixelFormat(Service::android::PixelFormat format) {
|
||||||
switch (format) {
|
switch (format) {
|
||||||
case Service::android::PixelFormat::Rgba8888:
|
case Service::android::PixelFormat::Rgba8888:
|
||||||
|
case Service::android::PixelFormat::Rgbx8888:
|
||||||
return PixelFormat::A8B8G8R8_UNORM;
|
return PixelFormat::A8B8G8R8_UNORM;
|
||||||
case Service::android::PixelFormat::Rgb565:
|
case Service::android::PixelFormat::Rgb565:
|
||||||
return PixelFormat::R5G6B5_UNORM;
|
return PixelFormat::R5G6B5_UNORM;
|
||||||
|
|||||||
@@ -115,6 +115,7 @@ enum class PixelFormat {
|
|||||||
// Depth formats
|
// Depth formats
|
||||||
D32_FLOAT = MaxColorFormat,
|
D32_FLOAT = MaxColorFormat,
|
||||||
D16_UNORM,
|
D16_UNORM,
|
||||||
|
X8_D24_UNORM,
|
||||||
|
|
||||||
MaxDepthFormat,
|
MaxDepthFormat,
|
||||||
|
|
||||||
@@ -251,6 +252,7 @@ constexpr std::array<u8, MaxPixelFormat> BLOCK_WIDTH_TABLE = {{
|
|||||||
1, // E5B9G9R9_FLOAT
|
1, // E5B9G9R9_FLOAT
|
||||||
1, // D32_FLOAT
|
1, // D32_FLOAT
|
||||||
1, // D16_UNORM
|
1, // D16_UNORM
|
||||||
|
1, // X8_D24_UNORM
|
||||||
1, // S8_UINT
|
1, // S8_UINT
|
||||||
1, // D24_UNORM_S8_UINT
|
1, // D24_UNORM_S8_UINT
|
||||||
1, // S8_UINT_D24_UNORM
|
1, // S8_UINT_D24_UNORM
|
||||||
@@ -360,6 +362,7 @@ constexpr std::array<u8, MaxPixelFormat> BLOCK_HEIGHT_TABLE = {{
|
|||||||
1, // E5B9G9R9_FLOAT
|
1, // E5B9G9R9_FLOAT
|
||||||
1, // D32_FLOAT
|
1, // D32_FLOAT
|
||||||
1, // D16_UNORM
|
1, // D16_UNORM
|
||||||
|
1, // X8_D24_UNORM
|
||||||
1, // S8_UINT
|
1, // S8_UINT
|
||||||
1, // D24_UNORM_S8_UINT
|
1, // D24_UNORM_S8_UINT
|
||||||
1, // S8_UINT_D24_UNORM
|
1, // S8_UINT_D24_UNORM
|
||||||
@@ -469,6 +472,7 @@ constexpr std::array<u8, MaxPixelFormat> BITS_PER_BLOCK_TABLE = {{
|
|||||||
32, // E5B9G9R9_FLOAT
|
32, // E5B9G9R9_FLOAT
|
||||||
32, // D32_FLOAT
|
32, // D32_FLOAT
|
||||||
16, // D16_UNORM
|
16, // D16_UNORM
|
||||||
|
32, // X8_D24_UNORM
|
||||||
8, // S8_UINT
|
8, // S8_UINT
|
||||||
32, // D24_UNORM_S8_UINT
|
32, // D24_UNORM_S8_UINT
|
||||||
32, // S8_UINT_D24_UNORM
|
32, // S8_UINT_D24_UNORM
|
||||||
|
|||||||
@@ -142,6 +142,10 @@ PixelFormat PixelFormatFromTextureInfo(TextureFormat format, ComponentType red,
|
|||||||
return PixelFormat::D16_UNORM;
|
return PixelFormat::D16_UNORM;
|
||||||
case Hash(TextureFormat::Z16, UNORM, UINT, UINT, UINT, LINEAR):
|
case Hash(TextureFormat::Z16, UNORM, UINT, UINT, UINT, LINEAR):
|
||||||
return PixelFormat::D16_UNORM;
|
return PixelFormat::D16_UNORM;
|
||||||
|
case Hash(TextureFormat::X8Z24, UNORM):
|
||||||
|
return PixelFormat::X8_D24_UNORM;
|
||||||
|
case Hash(TextureFormat::X8Z24, UNORM, UINT, UINT, UINT, LINEAR):
|
||||||
|
return PixelFormat::X8_D24_UNORM;
|
||||||
case Hash(TextureFormat::Z24S8, UINT, UNORM, UNORM, UNORM, LINEAR):
|
case Hash(TextureFormat::Z24S8, UINT, UNORM, UNORM, UNORM, LINEAR):
|
||||||
return PixelFormat::S8_UINT_D24_UNORM;
|
return PixelFormat::S8_UINT_D24_UNORM;
|
||||||
case Hash(TextureFormat::Z24S8, UINT, UNORM, UINT, UINT, LINEAR):
|
case Hash(TextureFormat::Z24S8, UINT, UNORM, UINT, UINT, LINEAR):
|
||||||
|
|||||||
@@ -211,6 +211,8 @@ struct fmt::formatter<VideoCore::Surface::PixelFormat> : fmt::formatter<fmt::str
|
|||||||
return "D32_FLOAT";
|
return "D32_FLOAT";
|
||||||
case PixelFormat::D16_UNORM:
|
case PixelFormat::D16_UNORM:
|
||||||
return "D16_UNORM";
|
return "D16_UNORM";
|
||||||
|
case PixelFormat::X8_D24_UNORM:
|
||||||
|
return "X8_D24_UNORM";
|
||||||
case PixelFormat::S8_UINT:
|
case PixelFormat::S8_UINT:
|
||||||
return "S8_UINT";
|
return "S8_UINT";
|
||||||
case PixelFormat::D24_UNORM_S8_UINT:
|
case PixelFormat::D24_UNORM_S8_UINT:
|
||||||
|
|||||||
@@ -85,6 +85,7 @@ bool ImageViewBase::SupportsAnisotropy() const noexcept {
|
|||||||
// Depth formats
|
// Depth formats
|
||||||
case PixelFormat::D32_FLOAT:
|
case PixelFormat::D32_FLOAT:
|
||||||
case PixelFormat::D16_UNORM:
|
case PixelFormat::D16_UNORM:
|
||||||
|
case PixelFormat::X8_D24_UNORM:
|
||||||
// Stencil formats
|
// Stencil formats
|
||||||
case PixelFormat::S8_UINT:
|
case PixelFormat::S8_UINT:
|
||||||
// DepthStencil formats
|
// DepthStencil formats
|
||||||
|
|||||||
@@ -84,9 +84,12 @@ constexpr std::array VK_FORMAT_A4B4G4R4_UNORM_PACK16{
|
|||||||
} // namespace Alternatives
|
} // namespace Alternatives
|
||||||
|
|
||||||
enum class NvidiaArchitecture {
|
enum class NvidiaArchitecture {
|
||||||
AmpereOrNewer,
|
KeplerOrOlder,
|
||||||
|
Maxwell,
|
||||||
|
Pascal,
|
||||||
|
Volta,
|
||||||
Turing,
|
Turing,
|
||||||
VoltaOrOlder,
|
AmpereOrNewer,
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
@@ -200,6 +203,7 @@ std::unordered_map<VkFormat, VkFormatProperties> GetFormatProperties(vk::Physica
|
|||||||
VK_FORMAT_BC7_UNORM_BLOCK,
|
VK_FORMAT_BC7_UNORM_BLOCK,
|
||||||
VK_FORMAT_D16_UNORM,
|
VK_FORMAT_D16_UNORM,
|
||||||
VK_FORMAT_D16_UNORM_S8_UINT,
|
VK_FORMAT_D16_UNORM_S8_UINT,
|
||||||
|
VK_FORMAT_X8_D24_UNORM_PACK32,
|
||||||
VK_FORMAT_D24_UNORM_S8_UINT,
|
VK_FORMAT_D24_UNORM_S8_UINT,
|
||||||
VK_FORMAT_D32_SFLOAT,
|
VK_FORMAT_D32_SFLOAT,
|
||||||
VK_FORMAT_D32_SFLOAT_S8_UINT,
|
VK_FORMAT_D32_SFLOAT_S8_UINT,
|
||||||
@@ -321,13 +325,38 @@ NvidiaArchitecture GetNvidiaArchitecture(vk::PhysicalDevice physical,
|
|||||||
physical.GetProperties2(physical_properties);
|
physical.GetProperties2(physical_properties);
|
||||||
if (shading_rate_props.primitiveFragmentShadingRateWithMultipleViewports) {
|
if (shading_rate_props.primitiveFragmentShadingRateWithMultipleViewports) {
|
||||||
// Only Ampere and newer support this feature
|
// Only Ampere and newer support this feature
|
||||||
|
// TODO: Find a way to differentiate Ampere and Ada
|
||||||
return NvidiaArchitecture::AmpereOrNewer;
|
return NvidiaArchitecture::AmpereOrNewer;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if (exts.contains(VK_NV_SHADING_RATE_IMAGE_EXTENSION_NAME)) {
|
|
||||||
return NvidiaArchitecture::Turing;
|
return NvidiaArchitecture::Turing;
|
||||||
}
|
}
|
||||||
return NvidiaArchitecture::VoltaOrOlder;
|
|
||||||
|
if (exts.contains(VK_EXT_BLEND_OPERATION_ADVANCED_EXTENSION_NAME)) {
|
||||||
|
VkPhysicalDeviceBlendOperationAdvancedPropertiesEXT advanced_blending_props{};
|
||||||
|
advanced_blending_props.sType =
|
||||||
|
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_BLEND_OPERATION_ADVANCED_PROPERTIES_EXT;
|
||||||
|
VkPhysicalDeviceProperties2 physical_properties{};
|
||||||
|
physical_properties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2;
|
||||||
|
physical_properties.pNext = &advanced_blending_props;
|
||||||
|
physical.GetProperties2(physical_properties);
|
||||||
|
if (advanced_blending_props.advancedBlendMaxColorAttachments == 1) {
|
||||||
|
return NvidiaArchitecture::Maxwell;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (exts.contains(VK_EXT_CONSERVATIVE_RASTERIZATION_EXTENSION_NAME)) {
|
||||||
|
VkPhysicalDeviceConservativeRasterizationPropertiesEXT conservative_raster_props{};
|
||||||
|
conservative_raster_props.sType =
|
||||||
|
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_CONSERVATIVE_RASTERIZATION_PROPERTIES_EXT;
|
||||||
|
physical_properties.pNext = &conservative_raster_props;
|
||||||
|
physical.GetProperties2(physical_properties);
|
||||||
|
if (conservative_raster_props.degenerateLinesRasterized) {
|
||||||
|
return NvidiaArchitecture::Volta;
|
||||||
|
}
|
||||||
|
return NvidiaArchitecture::Pascal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return NvidiaArchitecture::KeplerOrOlder;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<const char*> ExtensionListForVulkan(
|
std::vector<const char*> ExtensionListForVulkan(
|
||||||
@@ -504,19 +533,14 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
|
|||||||
if (is_nvidia) {
|
if (is_nvidia) {
|
||||||
const u32 nv_major_version = (properties.properties.driverVersion >> 22) & 0x3ff;
|
const u32 nv_major_version = (properties.properties.driverVersion >> 22) & 0x3ff;
|
||||||
const auto arch = GetNvidiaArchitecture(physical, supported_extensions);
|
const auto arch = GetNvidiaArchitecture(physical, supported_extensions);
|
||||||
switch (arch) {
|
if (arch >= NvidiaArchitecture::AmpereOrNewer) {
|
||||||
case NvidiaArchitecture::AmpereOrNewer:
|
|
||||||
LOG_WARNING(Render_Vulkan, "Ampere and newer have broken float16 math");
|
LOG_WARNING(Render_Vulkan, "Ampere and newer have broken float16 math");
|
||||||
features.shader_float16_int8.shaderFloat16 = false;
|
features.shader_float16_int8.shaderFloat16 = false;
|
||||||
break;
|
} else if (arch <= NvidiaArchitecture::Volta) {
|
||||||
case NvidiaArchitecture::Turing:
|
|
||||||
break;
|
|
||||||
case NvidiaArchitecture::VoltaOrOlder:
|
|
||||||
if (nv_major_version < 527) {
|
if (nv_major_version < 527) {
|
||||||
LOG_WARNING(Render_Vulkan, "Volta and older have broken VK_KHR_push_descriptor");
|
LOG_WARNING(Render_Vulkan, "Volta and older have broken VK_KHR_push_descriptor");
|
||||||
RemoveExtension(extensions.push_descriptor, VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME);
|
RemoveExtension(extensions.push_descriptor, VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME);
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
if (nv_major_version >= 510) {
|
if (nv_major_version >= 510) {
|
||||||
LOG_WARNING(Render_Vulkan, "NVIDIA Drivers >= 510 do not support MSAA image blits");
|
LOG_WARNING(Render_Vulkan, "NVIDIA Drivers >= 510 do not support MSAA image blits");
|
||||||
@@ -661,7 +685,15 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
|
|||||||
"ANV drivers 22.3.0 to 23.1.0 have broken VK_KHR_push_descriptor");
|
"ANV drivers 22.3.0 to 23.1.0 have broken VK_KHR_push_descriptor");
|
||||||
RemoveExtension(extensions.push_descriptor, VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME);
|
RemoveExtension(extensions.push_descriptor, VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME);
|
||||||
}
|
}
|
||||||
|
} else if (extensions.push_descriptor && is_nvidia) {
|
||||||
|
const auto arch = GetNvidiaArchitecture(physical, supported_extensions);
|
||||||
|
if (arch <= NvidiaArchitecture::Pascal) {
|
||||||
|
LOG_WARNING(Render_Vulkan,
|
||||||
|
"Pascal and older architectures have broken VK_KHR_push_descriptor");
|
||||||
|
RemoveExtension(extensions.push_descriptor, VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (is_mvk) {
|
if (is_mvk) {
|
||||||
LOG_WARNING(Render_Vulkan,
|
LOG_WARNING(Render_Vulkan,
|
||||||
"MVK driver breaks when using more than 16 vertex attributes/bindings");
|
"MVK driver breaks when using more than 16 vertex attributes/bindings");
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
#include "common/alignment.h"
|
#include "common/alignment.h"
|
||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
|
#include "common/literals.h"
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
#include "common/polyfill_ranges.h"
|
#include "common/polyfill_ranges.h"
|
||||||
#include "video_core/vulkan_common/vma.h"
|
#include "video_core/vulkan_common/vma.h"
|
||||||
@@ -69,8 +70,7 @@ struct Range {
|
|||||||
case MemoryUsage::Download:
|
case MemoryUsage::Download:
|
||||||
return VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT;
|
return VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT;
|
||||||
case MemoryUsage::DeviceLocal:
|
case MemoryUsage::DeviceLocal:
|
||||||
return VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT |
|
return {};
|
||||||
VMA_ALLOCATION_CREATE_HOST_ACCESS_ALLOW_TRANSFER_INSTEAD_BIT;
|
|
||||||
}
|
}
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
@@ -212,7 +212,20 @@ MemoryAllocator::MemoryAllocator(const Device& device_)
|
|||||||
: device{device_}, allocator{device.GetAllocator()},
|
: device{device_}, allocator{device.GetAllocator()},
|
||||||
properties{device_.GetPhysical().GetMemoryProperties().memoryProperties},
|
properties{device_.GetPhysical().GetMemoryProperties().memoryProperties},
|
||||||
buffer_image_granularity{
|
buffer_image_granularity{
|
||||||
device_.GetPhysical().GetProperties().limits.bufferImageGranularity} {}
|
device_.GetPhysical().GetProperties().limits.bufferImageGranularity} {
|
||||||
|
// GPUs not supporting rebar may only have a region with less than 256MB host visible/device
|
||||||
|
// local memory. In that case, opening 2 RenderDoc captures side-by-side is not possible due to
|
||||||
|
// the heap running out of memory. With RenderDoc attached and only a small host/device region,
|
||||||
|
// only allow the stream buffer in this memory heap.
|
||||||
|
if (device.HasDebuggingToolAttached()) {
|
||||||
|
using namespace Common::Literals;
|
||||||
|
ForEachDeviceLocalHostVisibleHeap(device, [this](size_t index, VkMemoryHeap& heap) {
|
||||||
|
if (heap.size <= 256_MiB) {
|
||||||
|
valid_memory_types &= ~(1u << index);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
MemoryAllocator::~MemoryAllocator() = default;
|
MemoryAllocator::~MemoryAllocator() = default;
|
||||||
|
|
||||||
@@ -244,7 +257,7 @@ vk::Buffer MemoryAllocator::CreateBuffer(const VkBufferCreateInfo& ci, MemoryUsa
|
|||||||
.usage = MemoryUsageVma(usage),
|
.usage = MemoryUsageVma(usage),
|
||||||
.requiredFlags = 0,
|
.requiredFlags = 0,
|
||||||
.preferredFlags = MemoryUsagePreferedVmaFlags(usage),
|
.preferredFlags = MemoryUsagePreferedVmaFlags(usage),
|
||||||
.memoryTypeBits = 0,
|
.memoryTypeBits = usage == MemoryUsage::Stream ? 0u : valid_memory_types,
|
||||||
.pool = VK_NULL_HANDLE,
|
.pool = VK_NULL_HANDLE,
|
||||||
.pUserData = nullptr,
|
.pUserData = nullptr,
|
||||||
.priority = 0.f,
|
.priority = 0.f,
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
#include <span>
|
#include <span>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
|
#include "video_core/vulkan_common/vulkan_device.h"
|
||||||
#include "video_core/vulkan_common/vulkan_wrapper.h"
|
#include "video_core/vulkan_common/vulkan_wrapper.h"
|
||||||
|
|
||||||
VK_DEFINE_HANDLE(VmaAllocator)
|
VK_DEFINE_HANDLE(VmaAllocator)
|
||||||
@@ -26,6 +27,18 @@ enum class MemoryUsage {
|
|||||||
Stream, ///< Requests device local host visible buffer, falling back host memory.
|
Stream, ///< Requests device local host visible buffer, falling back host memory.
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template <typename F>
|
||||||
|
void ForEachDeviceLocalHostVisibleHeap(const Device& device, F&& f) {
|
||||||
|
auto memory_props = device.GetPhysical().GetMemoryProperties().memoryProperties;
|
||||||
|
for (size_t i = 0; i < memory_props.memoryTypeCount; i++) {
|
||||||
|
auto& memory_type = memory_props.memoryTypes[i];
|
||||||
|
if ((memory_type.propertyFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) &&
|
||||||
|
(memory_type.propertyFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT)) {
|
||||||
|
f(memory_type.heapIndex, memory_props.memoryHeaps[memory_type.heapIndex]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Ownership handle of a memory commitment.
|
/// Ownership handle of a memory commitment.
|
||||||
/// Points to a subregion of a memory allocation.
|
/// Points to a subregion of a memory allocation.
|
||||||
class MemoryCommit {
|
class MemoryCommit {
|
||||||
@@ -124,6 +137,7 @@ private:
|
|||||||
std::vector<std::unique_ptr<MemoryAllocation>> allocations; ///< Current allocations.
|
std::vector<std::unique_ptr<MemoryAllocation>> allocations; ///< Current allocations.
|
||||||
VkDeviceSize buffer_image_granularity; // The granularity for adjacent offsets between buffers
|
VkDeviceSize buffer_image_granularity; // The granularity for adjacent offsets between buffers
|
||||||
// and optimal images
|
// and optimal images
|
||||||
|
u32 valid_memory_types{~0u};
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Vulkan
|
} // namespace Vulkan
|
||||||
|
|||||||
@@ -117,6 +117,9 @@ public:
|
|||||||
virtual ~Exception() = default;
|
virtual ~Exception() = default;
|
||||||
|
|
||||||
const char* what() const noexcept override;
|
const char* what() const noexcept override;
|
||||||
|
VkResult GetResult() const noexcept {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
VkResult result;
|
VkResult result;
|
||||||
|
|||||||
@@ -42,6 +42,9 @@ void ConfigureAudio::Setup(const ConfigurationShared::Builder& builder) {
|
|||||||
for (auto* setting : Settings::values.linkage.by_category[category]) {
|
for (auto* setting : Settings::values.linkage.by_category[category]) {
|
||||||
settings.push_back(setting);
|
settings.push_back(setting);
|
||||||
}
|
}
|
||||||
|
for (auto* setting : UISettings::values.linkage.by_category[category]) {
|
||||||
|
settings.push_back(setting);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
push(Settings::Category::Audio);
|
push(Settings::Category::Audio);
|
||||||
|
|||||||
@@ -29,9 +29,10 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QWidget* parent) {
|
|||||||
INSERT(Settings, sink_id, "Output Engine:", "");
|
INSERT(Settings, sink_id, "Output Engine:", "");
|
||||||
INSERT(Settings, audio_output_device_id, "Output Device:", "");
|
INSERT(Settings, audio_output_device_id, "Output Device:", "");
|
||||||
INSERT(Settings, audio_input_device_id, "Input Device:", "");
|
INSERT(Settings, audio_input_device_id, "Input Device:", "");
|
||||||
INSERT(Settings, audio_muted, "Mute audio when in background", "");
|
INSERT(Settings, audio_muted, "Mute audio", "");
|
||||||
INSERT(Settings, volume, "Volume:", "");
|
INSERT(Settings, volume, "Volume:", "");
|
||||||
INSERT(Settings, dump_audio_commands, "", "");
|
INSERT(Settings, dump_audio_commands, "", "");
|
||||||
|
INSERT(UISettings, mute_when_in_background, "Mute audio when in background", "");
|
||||||
|
|
||||||
// Core
|
// Core
|
||||||
INSERT(Settings, use_multi_core, "Multicore CPU Emulation", "");
|
INSERT(Settings, use_multi_core, "Multicore CPU Emulation", "");
|
||||||
|
|||||||
@@ -1447,6 +1447,7 @@ void GMainWindow::OnAppFocusStateChanged(Qt::ApplicationState state) {
|
|||||||
Settings::values.audio_muted = false;
|
Settings::values.audio_muted = false;
|
||||||
auto_muted = false;
|
auto_muted = false;
|
||||||
}
|
}
|
||||||
|
UpdateVolumeUI();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ struct Values {
|
|||||||
true,
|
true,
|
||||||
true};
|
true};
|
||||||
Setting<bool> mute_when_in_background{
|
Setting<bool> mute_when_in_background{
|
||||||
linkage, false, "muteWhenInBackground", Category::Ui, Settings::Specialization::Default,
|
linkage, false, "muteWhenInBackground", Category::Audio, Settings::Specialization::Default,
|
||||||
true, true};
|
true, true};
|
||||||
Setting<bool> hide_mouse{
|
Setting<bool> hide_mouse{
|
||||||
linkage, true, "hideInactiveMouse", Category::UiGeneral, Settings::Specialization::Default,
|
linkage, true, "hideInactiveMouse", Category::UiGeneral, Settings::Specialization::Default,
|
||||||
|
|||||||
Reference in New Issue
Block a user