mirror of
https://github.com/yuzu-emu/yuzu.git
synced 2024-07-04 23:31:19 +01:00
Merge branch 'master' into Texture2DArray
This commit is contained in:
commit
d3b9599b2d
23 changed files with 392 additions and 195 deletions
|
@ -13,6 +13,7 @@
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
#include <share.h> // For _SH_DENYWR
|
#include <share.h> // For _SH_DENYWR
|
||||||
|
#include <windows.h> // For OutputDebugStringA
|
||||||
#else
|
#else
|
||||||
#define _SH_DENYWR 0
|
#define _SH_DENYWR 0
|
||||||
#endif
|
#endif
|
||||||
|
@ -139,12 +140,18 @@ void FileBackend::Write(const Entry& entry) {
|
||||||
if (!file.IsOpen() || bytes_written > MAX_BYTES_WRITTEN) {
|
if (!file.IsOpen() || bytes_written > MAX_BYTES_WRITTEN) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
bytes_written += file.WriteString(FormatLogMessage(entry) + '\n');
|
bytes_written += file.WriteString(FormatLogMessage(entry).append(1, '\n'));
|
||||||
if (entry.log_level >= Level::Error) {
|
if (entry.log_level >= Level::Error) {
|
||||||
file.Flush();
|
file.Flush();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DebuggerBackend::Write(const Entry& entry) {
|
||||||
|
#ifdef _WIN32
|
||||||
|
::OutputDebugStringA(FormatLogMessage(entry).append(1, '\n').c_str());
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
/// Macro listing all log classes. Code should define CLS and SUB as desired before invoking this.
|
/// Macro listing all log classes. Code should define CLS and SUB as desired before invoking this.
|
||||||
#define ALL_LOG_CLASSES() \
|
#define ALL_LOG_CLASSES() \
|
||||||
CLS(Log) \
|
CLS(Log) \
|
||||||
|
|
|
@ -103,6 +103,20 @@ private:
|
||||||
std::size_t bytes_written;
|
std::size_t bytes_written;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Backend that writes to Visual Studio's output window
|
||||||
|
*/
|
||||||
|
class DebuggerBackend : public Backend {
|
||||||
|
public:
|
||||||
|
static const char* Name() {
|
||||||
|
return "debugger";
|
||||||
|
}
|
||||||
|
const char* GetName() const override {
|
||||||
|
return Name();
|
||||||
|
}
|
||||||
|
void Write(const Entry& entry) override;
|
||||||
|
};
|
||||||
|
|
||||||
void AddBackend(std::unique_ptr<Backend> backend);
|
void AddBackend(std::unique_ptr<Backend> backend);
|
||||||
|
|
||||||
void RemoveBackend(std::string_view backend_name);
|
void RemoveBackend(std::string_view backend_name);
|
||||||
|
|
|
@ -161,7 +161,7 @@ void HwOpus::OpenOpusDecoder(Kernel::HLERequestContext& ctx) {
|
||||||
ASSERT_MSG(channel_count == 1 || channel_count == 2, "Invalid channel count");
|
ASSERT_MSG(channel_count == 1 || channel_count == 2, "Invalid channel count");
|
||||||
|
|
||||||
std::size_t worker_sz = WorkerBufferSize(channel_count);
|
std::size_t worker_sz = WorkerBufferSize(channel_count);
|
||||||
ASSERT_MSG(buffer_sz < worker_sz, "Worker buffer too large");
|
ASSERT_MSG(buffer_sz >= worker_sz, "Worker buffer too large");
|
||||||
std::unique_ptr<OpusDecoder, OpusDeleter> decoder{
|
std::unique_ptr<OpusDecoder, OpusDeleter> decoder{
|
||||||
static_cast<OpusDecoder*>(operator new(worker_sz))};
|
static_cast<OpusDecoder*>(operator new(worker_sz))};
|
||||||
if (opus_decoder_init(decoder.get(), sample_rate, channel_count)) {
|
if (opus_decoder_init(decoder.get(), sample_rate, channel_count)) {
|
||||||
|
|
|
@ -427,6 +427,9 @@ void Controller_NPad::VibrateController(const std::vector<u32>& controller_ids,
|
||||||
}
|
}
|
||||||
|
|
||||||
Kernel::SharedPtr<Kernel::Event> Controller_NPad::GetStyleSetChangedEvent() const {
|
Kernel::SharedPtr<Kernel::Event> Controller_NPad::GetStyleSetChangedEvent() const {
|
||||||
|
// TODO(ogniK): Figure out the best time to signal this event. This event seems that it should
|
||||||
|
// be signalled at least once, and signaled after a new controller is connected?
|
||||||
|
styleset_changed_event->Signal();
|
||||||
return styleset_changed_event;
|
return styleset_changed_event;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -96,6 +96,8 @@ public:
|
||||||
// TODO(shinyquagsire23): Other update callbacks? (accel, gyro?)
|
// TODO(shinyquagsire23): Other update callbacks? (accel, gyro?)
|
||||||
|
|
||||||
CoreTiming::ScheduleEvent(pad_update_ticks, pad_update_event);
|
CoreTiming::ScheduleEvent(pad_update_ticks, pad_update_event);
|
||||||
|
|
||||||
|
ReloadInputDevices();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ActivateController(HidController controller) {
|
void ActivateController(HidController controller) {
|
||||||
|
|
|
@ -58,9 +58,9 @@ public:
|
||||||
/// Rotate source image 90 degrees clockwise
|
/// Rotate source image 90 degrees clockwise
|
||||||
Rotate90 = 0x04,
|
Rotate90 = 0x04,
|
||||||
/// Rotate source image 180 degrees
|
/// Rotate source image 180 degrees
|
||||||
Roate180 = 0x03,
|
Rotate180 = 0x03,
|
||||||
/// Rotate source image 270 degrees clockwise
|
/// Rotate source image 270 degrees clockwise
|
||||||
Roate270 = 0x07,
|
Rotate270 = 0x07,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Buffer {
|
struct Buffer {
|
||||||
|
|
|
@ -33,6 +33,7 @@ add_library(video_core STATIC
|
||||||
renderer_opengl/gl_rasterizer.h
|
renderer_opengl/gl_rasterizer.h
|
||||||
renderer_opengl/gl_rasterizer_cache.cpp
|
renderer_opengl/gl_rasterizer_cache.cpp
|
||||||
renderer_opengl/gl_rasterizer_cache.h
|
renderer_opengl/gl_rasterizer_cache.h
|
||||||
|
renderer_opengl/gl_resource_manager.cpp
|
||||||
renderer_opengl/gl_resource_manager.h
|
renderer_opengl/gl_resource_manager.h
|
||||||
renderer_opengl/gl_shader_cache.cpp
|
renderer_opengl/gl_shader_cache.cpp
|
||||||
renderer_opengl/gl_shader_cache.h
|
renderer_opengl/gl_shader_cache.h
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
#include "core/settings.h"
|
#include "core/settings.h"
|
||||||
#include "video_core/engines/maxwell_3d.h"
|
#include "video_core/engines/maxwell_3d.h"
|
||||||
#include "video_core/renderer_opengl/gl_rasterizer_cache.h"
|
#include "video_core/renderer_opengl/gl_rasterizer_cache.h"
|
||||||
|
#include "video_core/renderer_opengl/gl_state.h"
|
||||||
#include "video_core/renderer_opengl/utils.h"
|
#include "video_core/renderer_opengl/utils.h"
|
||||||
#include "video_core/surface.h"
|
#include "video_core/surface.h"
|
||||||
#include "video_core/textures/astc.h"
|
#include "video_core/textures/astc.h"
|
||||||
|
@ -58,16 +59,14 @@ void SurfaceParams::InitCacheParameters(Tegra::GPUVAddr gpu_addr_) {
|
||||||
|
|
||||||
std::size_t SurfaceParams::InnerMipmapMemorySize(u32 mip_level, bool force_gl, bool layer_only,
|
std::size_t SurfaceParams::InnerMipmapMemorySize(u32 mip_level, bool force_gl, bool layer_only,
|
||||||
bool uncompressed) const {
|
bool uncompressed) const {
|
||||||
const u32 compression_factor{GetCompressionFactor(pixel_format)};
|
const u32 tile_x{GetDefaultBlockWidth(pixel_format)};
|
||||||
|
const u32 tile_y{GetDefaultBlockHeight(pixel_format)};
|
||||||
const u32 bytes_per_pixel{GetBytesPerPixel(pixel_format)};
|
const u32 bytes_per_pixel{GetBytesPerPixel(pixel_format)};
|
||||||
u32 m_depth = (layer_only ? 1U : depth);
|
u32 m_depth = (layer_only ? 1U : depth);
|
||||||
u32 m_width = MipWidth(mip_level);
|
u32 m_width = MipWidth(mip_level);
|
||||||
u32 m_height = MipHeight(mip_level);
|
u32 m_height = MipHeight(mip_level);
|
||||||
m_width = uncompressed ? m_width
|
m_width = uncompressed ? m_width : std::max(1U, (m_width + tile_x - 1) / tile_x);
|
||||||
: std::max(1U, (m_width + compression_factor - 1) / compression_factor);
|
m_height = uncompressed ? m_height : std::max(1U, (m_height + tile_y - 1) / tile_y);
|
||||||
m_height = uncompressed
|
|
||||||
? m_height
|
|
||||||
: std::max(1U, (m_height + compression_factor - 1) / compression_factor);
|
|
||||||
m_depth = std::max(1U, m_depth >> mip_level);
|
m_depth = std::max(1U, m_depth >> mip_level);
|
||||||
u32 m_block_height = MipBlockHeight(mip_level);
|
u32 m_block_height = MipBlockHeight(mip_level);
|
||||||
u32 m_block_depth = MipBlockDepth(mip_level);
|
u32 m_block_depth = MipBlockDepth(mip_level);
|
||||||
|
@ -128,6 +127,13 @@ std::size_t SurfaceParams::InnerMemorySize(bool force_gl, bool layer_only,
|
||||||
params.target = SurfaceTarget::Texture2D;
|
params.target = SurfaceTarget::Texture2D;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case SurfaceTarget::TextureCubeArray:
|
||||||
|
params.depth = config.tic.Depth() * 6;
|
||||||
|
if (!entry.IsArray()) {
|
||||||
|
ASSERT(params.depth == 6);
|
||||||
|
params.target = SurfaceTarget::TextureCubemap;
|
||||||
|
}
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
LOG_CRITICAL(HW_GPU, "Unknown depth for target={}", static_cast<u32>(params.target));
|
LOG_CRITICAL(HW_GPU, "Unknown depth for target={}", static_cast<u32>(params.target));
|
||||||
UNREACHABLE();
|
UNREACHABLE();
|
||||||
|
@ -305,6 +311,8 @@ static constexpr std::array<FormatTuple, VideoCore::Surface::MaxPixelFormat> tex
|
||||||
{GL_SRGB8_ALPHA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_8X8_SRGB
|
{GL_SRGB8_ALPHA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_8X8_SRGB
|
||||||
{GL_SRGB8_ALPHA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_8X5_SRGB
|
{GL_SRGB8_ALPHA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_8X5_SRGB
|
||||||
{GL_SRGB8_ALPHA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_5X4_SRGB
|
{GL_SRGB8_ALPHA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_5X4_SRGB
|
||||||
|
{GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_5X5
|
||||||
|
{GL_SRGB8_ALPHA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_5X5_SRGB
|
||||||
|
|
||||||
// Depth formats
|
// Depth formats
|
||||||
{GL_DEPTH_COMPONENT32F, GL_DEPTH_COMPONENT, GL_FLOAT, ComponentType::Float, false}, // Z32F
|
{GL_DEPTH_COMPONENT32F, GL_DEPTH_COMPONENT, GL_FLOAT, ComponentType::Float, false}, // Z32F
|
||||||
|
@ -334,6 +342,8 @@ static GLenum SurfaceTargetToGL(SurfaceTarget target) {
|
||||||
return GL_TEXTURE_2D_ARRAY;
|
return GL_TEXTURE_2D_ARRAY;
|
||||||
case SurfaceTarget::TextureCubemap:
|
case SurfaceTarget::TextureCubemap:
|
||||||
return GL_TEXTURE_CUBE_MAP;
|
return GL_TEXTURE_CUBE_MAP;
|
||||||
|
case SurfaceTarget::TextureCubeArray:
|
||||||
|
return GL_TEXTURE_CUBE_MAP_ARRAY_ARB;
|
||||||
}
|
}
|
||||||
LOG_CRITICAL(Render_OpenGL, "Unimplemented texture target={}", static_cast<u32>(target));
|
LOG_CRITICAL(Render_OpenGL, "Unimplemented texture target={}", static_cast<u32>(target));
|
||||||
UNREACHABLE();
|
UNREACHABLE();
|
||||||
|
@ -364,15 +374,18 @@ void MortonCopy(u32 stride, u32 block_height, u32 height, u32 block_depth, u32 d
|
||||||
|
|
||||||
// With the BCn formats (DXT and DXN), each 4x4 tile is swizzled instead of just individual
|
// With the BCn formats (DXT and DXN), each 4x4 tile is swizzled instead of just individual
|
||||||
// pixel values.
|
// pixel values.
|
||||||
const u32 tile_size{IsFormatBCn(format) ? 4U : 1U};
|
const u32 tile_size_x{GetDefaultBlockWidth(format)};
|
||||||
|
const u32 tile_size_y{GetDefaultBlockHeight(format)};
|
||||||
|
|
||||||
if (morton_to_gl) {
|
if (morton_to_gl) {
|
||||||
const std::vector<u8> data = Tegra::Texture::UnswizzleTexture(
|
const std::vector<u8> data =
|
||||||
addr, tile_size, bytes_per_pixel, stride, height, depth, block_height, block_depth);
|
Tegra::Texture::UnswizzleTexture(addr, tile_size_x, tile_size_y, bytes_per_pixel,
|
||||||
|
stride, height, depth, block_height, block_depth);
|
||||||
const std::size_t size_to_copy{std::min(gl_buffer_size, data.size())};
|
const std::size_t size_to_copy{std::min(gl_buffer_size, data.size())};
|
||||||
memcpy(gl_buffer, data.data(), size_to_copy);
|
memcpy(gl_buffer, data.data(), size_to_copy);
|
||||||
} else {
|
} else {
|
||||||
Tegra::Texture::CopySwizzledData(stride / tile_size, height / tile_size, depth,
|
Tegra::Texture::CopySwizzledData((stride + tile_size_x - 1) / tile_size_x,
|
||||||
|
(height + tile_size_y - 1) / tile_size_y, depth,
|
||||||
bytes_per_pixel, bytes_per_pixel, Memory::GetPointer(addr),
|
bytes_per_pixel, bytes_per_pixel, Memory::GetPointer(addr),
|
||||||
gl_buffer, false, block_height, block_depth);
|
gl_buffer, false, block_height, block_depth);
|
||||||
}
|
}
|
||||||
|
@ -440,6 +453,8 @@ static constexpr GLConversionArray morton_to_gl_fns = {
|
||||||
MortonCopy<true, PixelFormat::ASTC_2D_8X8_SRGB>,
|
MortonCopy<true, PixelFormat::ASTC_2D_8X8_SRGB>,
|
||||||
MortonCopy<true, PixelFormat::ASTC_2D_8X5_SRGB>,
|
MortonCopy<true, PixelFormat::ASTC_2D_8X5_SRGB>,
|
||||||
MortonCopy<true, PixelFormat::ASTC_2D_5X4_SRGB>,
|
MortonCopy<true, PixelFormat::ASTC_2D_5X4_SRGB>,
|
||||||
|
MortonCopy<true, PixelFormat::ASTC_2D_5X5>,
|
||||||
|
MortonCopy<true, PixelFormat::ASTC_2D_5X5_SRGB>,
|
||||||
MortonCopy<true, PixelFormat::Z32F>,
|
MortonCopy<true, PixelFormat::Z32F>,
|
||||||
MortonCopy<true, PixelFormat::Z16>,
|
MortonCopy<true, PixelFormat::Z16>,
|
||||||
MortonCopy<true, PixelFormat::Z24S8>,
|
MortonCopy<true, PixelFormat::Z24S8>,
|
||||||
|
@ -508,6 +523,8 @@ static constexpr GLConversionArray gl_to_morton_fns = {
|
||||||
nullptr,
|
nullptr,
|
||||||
nullptr,
|
nullptr,
|
||||||
nullptr,
|
nullptr,
|
||||||
|
nullptr,
|
||||||
|
nullptr,
|
||||||
MortonCopy<false, PixelFormat::Z32F>,
|
MortonCopy<false, PixelFormat::Z32F>,
|
||||||
MortonCopy<false, PixelFormat::Z16>,
|
MortonCopy<false, PixelFormat::Z16>,
|
||||||
MortonCopy<false, PixelFormat::Z24S8>,
|
MortonCopy<false, PixelFormat::Z24S8>,
|
||||||
|
@ -754,6 +771,7 @@ static void CopySurface(const Surface& src_surface, const Surface& dst_surface,
|
||||||
break;
|
break;
|
||||||
case SurfaceTarget::Texture3D:
|
case SurfaceTarget::Texture3D:
|
||||||
case SurfaceTarget::Texture2DArray:
|
case SurfaceTarget::Texture2DArray:
|
||||||
|
case SurfaceTarget::TextureCubeArray:
|
||||||
glTextureSubImage3D(dst_surface->Texture().handle, 0, 0, 0, 0, width, height,
|
glTextureSubImage3D(dst_surface->Texture().handle, 0, 0, 0, 0, width, height,
|
||||||
static_cast<GLsizei>(dst_params.depth), dest_format.format,
|
static_cast<GLsizei>(dst_params.depth), dest_format.format,
|
||||||
dest_format.type, nullptr);
|
dest_format.type, nullptr);
|
||||||
|
@ -806,6 +824,7 @@ CachedSurface::CachedSurface(const SurfaceParams& params)
|
||||||
break;
|
break;
|
||||||
case SurfaceTarget::Texture3D:
|
case SurfaceTarget::Texture3D:
|
||||||
case SurfaceTarget::Texture2DArray:
|
case SurfaceTarget::Texture2DArray:
|
||||||
|
case SurfaceTarget::TextureCubeArray:
|
||||||
glTexStorage3D(SurfaceTargetToGL(params.target), params.max_mip_level,
|
glTexStorage3D(SurfaceTargetToGL(params.target), params.max_mip_level,
|
||||||
format_tuple.internal_format, rect.GetWidth(), rect.GetHeight(),
|
format_tuple.internal_format, rect.GetWidth(), rect.GetHeight(),
|
||||||
params.depth);
|
params.depth);
|
||||||
|
@ -897,21 +916,24 @@ static void ConvertG8R8ToR8G8(std::vector<u8>& data, u32 width, u32 height) {
|
||||||
* typical desktop GPUs.
|
* typical desktop GPUs.
|
||||||
*/
|
*/
|
||||||
static void ConvertFormatAsNeeded_LoadGLBuffer(std::vector<u8>& data, PixelFormat pixel_format,
|
static void ConvertFormatAsNeeded_LoadGLBuffer(std::vector<u8>& data, PixelFormat pixel_format,
|
||||||
u32 width, u32 height) {
|
u32 width, u32 height, u32 depth) {
|
||||||
switch (pixel_format) {
|
switch (pixel_format) {
|
||||||
case PixelFormat::ASTC_2D_4X4:
|
case PixelFormat::ASTC_2D_4X4:
|
||||||
case PixelFormat::ASTC_2D_8X8:
|
case PixelFormat::ASTC_2D_8X8:
|
||||||
case PixelFormat::ASTC_2D_8X5:
|
case PixelFormat::ASTC_2D_8X5:
|
||||||
case PixelFormat::ASTC_2D_5X4:
|
case PixelFormat::ASTC_2D_5X4:
|
||||||
|
case PixelFormat::ASTC_2D_5X5:
|
||||||
case PixelFormat::ASTC_2D_4X4_SRGB:
|
case PixelFormat::ASTC_2D_4X4_SRGB:
|
||||||
case PixelFormat::ASTC_2D_8X8_SRGB:
|
case PixelFormat::ASTC_2D_8X8_SRGB:
|
||||||
case PixelFormat::ASTC_2D_8X5_SRGB:
|
case PixelFormat::ASTC_2D_8X5_SRGB:
|
||||||
case PixelFormat::ASTC_2D_5X4_SRGB: {
|
case PixelFormat::ASTC_2D_5X4_SRGB:
|
||||||
|
case PixelFormat::ASTC_2D_5X5_SRGB: {
|
||||||
// Convert ASTC pixel formats to RGBA8, as most desktop GPUs do not support ASTC.
|
// Convert ASTC pixel formats to RGBA8, as most desktop GPUs do not support ASTC.
|
||||||
u32 block_width{};
|
u32 block_width{};
|
||||||
u32 block_height{};
|
u32 block_height{};
|
||||||
std::tie(block_width, block_height) = GetASTCBlockSize(pixel_format);
|
std::tie(block_width, block_height) = GetASTCBlockSize(pixel_format);
|
||||||
data = Tegra::Texture::ASTC::Decompress(data, width, height, block_width, block_height);
|
data =
|
||||||
|
Tegra::Texture::ASTC::Decompress(data, width, height, depth, block_width, block_height);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case PixelFormat::S8Z24:
|
case PixelFormat::S8Z24:
|
||||||
|
@ -971,7 +993,7 @@ void CachedSurface::LoadGLBuffer() {
|
||||||
}
|
}
|
||||||
for (u32 i = 0; i < params.max_mip_level; i++)
|
for (u32 i = 0; i < params.max_mip_level; i++)
|
||||||
ConvertFormatAsNeeded_LoadGLBuffer(gl_buffer[i], params.pixel_format, params.MipWidth(i),
|
ConvertFormatAsNeeded_LoadGLBuffer(gl_buffer[i], params.pixel_format, params.MipWidth(i),
|
||||||
params.MipHeight(i));
|
params.MipHeight(i), params.MipDepth(i));
|
||||||
}
|
}
|
||||||
|
|
||||||
MICROPROFILE_DEFINE(OpenGL_SurfaceFlush, "OpenGL", "Surface Flush", MP_RGB(128, 192, 64));
|
MICROPROFILE_DEFINE(OpenGL_SurfaceFlush, "OpenGL", "Surface Flush", MP_RGB(128, 192, 64));
|
||||||
|
@ -1055,6 +1077,7 @@ void CachedSurface::UploadGLMipmapTexture(u32 mip_map, GLuint read_fb_handle,
|
||||||
&gl_buffer[mip_map][buffer_offset]);
|
&gl_buffer[mip_map][buffer_offset]);
|
||||||
break;
|
break;
|
||||||
case SurfaceTarget::Texture2DArray:
|
case SurfaceTarget::Texture2DArray:
|
||||||
|
case SurfaceTarget::TextureCubeArray:
|
||||||
glCompressedTexImage3D(SurfaceTargetToGL(params.target), mip_map, tuple.internal_format,
|
glCompressedTexImage3D(SurfaceTargetToGL(params.target), mip_map, tuple.internal_format,
|
||||||
static_cast<GLsizei>(params.MipWidth(mip_map)),
|
static_cast<GLsizei>(params.MipWidth(mip_map)),
|
||||||
static_cast<GLsizei>(params.MipHeight(mip_map)),
|
static_cast<GLsizei>(params.MipHeight(mip_map)),
|
||||||
|
@ -1104,6 +1127,7 @@ void CachedSurface::UploadGLMipmapTexture(u32 mip_map, GLuint read_fb_handle,
|
||||||
tuple.format, tuple.type, &gl_buffer[mip_map][buffer_offset]);
|
tuple.format, tuple.type, &gl_buffer[mip_map][buffer_offset]);
|
||||||
break;
|
break;
|
||||||
case SurfaceTarget::Texture2DArray:
|
case SurfaceTarget::Texture2DArray:
|
||||||
|
case SurfaceTarget::TextureCubeArray:
|
||||||
glTexSubImage3D(SurfaceTargetToGL(params.target), mip_map, x0, y0, 0,
|
glTexSubImage3D(SurfaceTargetToGL(params.target), mip_map, x0, y0, 0,
|
||||||
static_cast<GLsizei>(rect.GetWidth()),
|
static_cast<GLsizei>(rect.GetWidth()),
|
||||||
static_cast<GLsizei>(rect.GetHeight()), params.depth, tuple.format,
|
static_cast<GLsizei>(rect.GetHeight()), params.depth, tuple.format,
|
||||||
|
@ -1307,6 +1331,7 @@ Surface RasterizerCacheOpenGL::RecreateSurface(const Surface& old_surface,
|
||||||
case SurfaceTarget::TextureCubemap:
|
case SurfaceTarget::TextureCubemap:
|
||||||
case SurfaceTarget::Texture3D:
|
case SurfaceTarget::Texture3D:
|
||||||
case SurfaceTarget::Texture2DArray:
|
case SurfaceTarget::Texture2DArray:
|
||||||
|
case SurfaceTarget::TextureCubeArray:
|
||||||
AccurateCopySurface(old_surface, new_surface);
|
AccurateCopySurface(old_surface, new_surface);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -49,6 +49,8 @@ struct SurfaceParams {
|
||||||
return "Texture2DArray";
|
return "Texture2DArray";
|
||||||
case SurfaceTarget::TextureCubemap:
|
case SurfaceTarget::TextureCubemap:
|
||||||
return "TextureCubemap";
|
return "TextureCubemap";
|
||||||
|
case SurfaceTarget::TextureCubeArray:
|
||||||
|
return "TextureCubeArray";
|
||||||
default:
|
default:
|
||||||
LOG_CRITICAL(HW_GPU, "Unimplemented surface_target={}", static_cast<u32>(target));
|
LOG_CRITICAL(HW_GPU, "Unimplemented surface_target={}", static_cast<u32>(target));
|
||||||
UNREACHABLE();
|
UNREACHABLE();
|
||||||
|
@ -139,7 +141,7 @@ struct SurfaceParams {
|
||||||
}
|
}
|
||||||
|
|
||||||
u32 MipDepth(u32 mip_level) const {
|
u32 MipDepth(u32 mip_level) const {
|
||||||
return std::max(1U, depth >> mip_level);
|
return is_layered ? depth : std::max(1U, depth >> mip_level);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Auto block resizing algorithm from:
|
// Auto block resizing algorithm from:
|
||||||
|
|
146
src/video_core/renderer_opengl/gl_resource_manager.cpp
Normal file
146
src/video_core/renderer_opengl/gl_resource_manager.cpp
Normal file
|
@ -0,0 +1,146 @@
|
||||||
|
// Copyright 2015 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include <utility>
|
||||||
|
#include <glad/glad.h>
|
||||||
|
#include "common/common_types.h"
|
||||||
|
#include "video_core/renderer_opengl/gl_resource_manager.h"
|
||||||
|
#include "video_core/renderer_opengl/gl_shader_util.h"
|
||||||
|
#include "video_core/renderer_opengl/gl_state.h"
|
||||||
|
|
||||||
|
namespace OpenGL {
|
||||||
|
|
||||||
|
void OGLTexture::Create() {
|
||||||
|
if (handle != 0)
|
||||||
|
return;
|
||||||
|
glGenTextures(1, &handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OGLTexture::Release() {
|
||||||
|
if (handle == 0)
|
||||||
|
return;
|
||||||
|
glDeleteTextures(1, &handle);
|
||||||
|
OpenGLState::GetCurState().UnbindTexture(handle).Apply();
|
||||||
|
handle = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OGLSampler::Create() {
|
||||||
|
if (handle != 0)
|
||||||
|
return;
|
||||||
|
glGenSamplers(1, &handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OGLSampler::Release() {
|
||||||
|
if (handle == 0)
|
||||||
|
return;
|
||||||
|
glDeleteSamplers(1, &handle);
|
||||||
|
OpenGLState::GetCurState().ResetSampler(handle).Apply();
|
||||||
|
handle = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OGLShader::Create(const char* source, GLenum type) {
|
||||||
|
if (handle != 0)
|
||||||
|
return;
|
||||||
|
if (source == nullptr)
|
||||||
|
return;
|
||||||
|
handle = GLShader::LoadShader(source, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OGLShader::Release() {
|
||||||
|
if (handle == 0)
|
||||||
|
return;
|
||||||
|
glDeleteShader(handle);
|
||||||
|
handle = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OGLProgram::CreateFromSource(const char* vert_shader, const char* geo_shader,
|
||||||
|
const char* frag_shader, bool separable_program) {
|
||||||
|
OGLShader vert, geo, frag;
|
||||||
|
if (vert_shader)
|
||||||
|
vert.Create(vert_shader, GL_VERTEX_SHADER);
|
||||||
|
if (geo_shader)
|
||||||
|
geo.Create(geo_shader, GL_GEOMETRY_SHADER);
|
||||||
|
if (frag_shader)
|
||||||
|
frag.Create(frag_shader, GL_FRAGMENT_SHADER);
|
||||||
|
Create(separable_program, vert.handle, geo.handle, frag.handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OGLProgram::Release() {
|
||||||
|
if (handle == 0)
|
||||||
|
return;
|
||||||
|
glDeleteProgram(handle);
|
||||||
|
OpenGLState::GetCurState().ResetProgram(handle).Apply();
|
||||||
|
handle = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OGLPipeline::Create() {
|
||||||
|
if (handle != 0)
|
||||||
|
return;
|
||||||
|
glGenProgramPipelines(1, &handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OGLPipeline::Release() {
|
||||||
|
if (handle == 0)
|
||||||
|
return;
|
||||||
|
glDeleteProgramPipelines(1, &handle);
|
||||||
|
OpenGLState::GetCurState().ResetPipeline(handle).Apply();
|
||||||
|
handle = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OGLBuffer::Create() {
|
||||||
|
if (handle != 0)
|
||||||
|
return;
|
||||||
|
glGenBuffers(1, &handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OGLBuffer::Release() {
|
||||||
|
if (handle == 0)
|
||||||
|
return;
|
||||||
|
glDeleteBuffers(1, &handle);
|
||||||
|
OpenGLState::GetCurState().ResetBuffer(handle).Apply();
|
||||||
|
handle = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OGLSync::Create() {
|
||||||
|
if (handle != 0)
|
||||||
|
return;
|
||||||
|
handle = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OGLSync::Release() {
|
||||||
|
if (handle == 0)
|
||||||
|
return;
|
||||||
|
glDeleteSync(handle);
|
||||||
|
handle = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OGLVertexArray::Create() {
|
||||||
|
if (handle != 0)
|
||||||
|
return;
|
||||||
|
glGenVertexArrays(1, &handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OGLVertexArray::Release() {
|
||||||
|
if (handle == 0)
|
||||||
|
return;
|
||||||
|
glDeleteVertexArrays(1, &handle);
|
||||||
|
OpenGLState::GetCurState().ResetVertexArray(handle).Apply();
|
||||||
|
handle = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OGLFramebuffer::Create() {
|
||||||
|
if (handle != 0)
|
||||||
|
return;
|
||||||
|
glGenFramebuffers(1, &handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OGLFramebuffer::Release() {
|
||||||
|
if (handle == 0)
|
||||||
|
return;
|
||||||
|
glDeleteFramebuffers(1, &handle);
|
||||||
|
OpenGLState::GetCurState().ResetFramebuffer(handle).Apply();
|
||||||
|
handle = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace OpenGL
|
|
@ -8,7 +8,6 @@
|
||||||
#include <glad/glad.h>
|
#include <glad/glad.h>
|
||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
#include "video_core/renderer_opengl/gl_shader_util.h"
|
#include "video_core/renderer_opengl/gl_shader_util.h"
|
||||||
#include "video_core/renderer_opengl/gl_state.h"
|
|
||||||
|
|
||||||
namespace OpenGL {
|
namespace OpenGL {
|
||||||
|
|
||||||
|
@ -29,20 +28,10 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a new internal OpenGL resource and stores the handle
|
/// Creates a new internal OpenGL resource and stores the handle
|
||||||
void Create() {
|
void Create();
|
||||||
if (handle != 0)
|
|
||||||
return;
|
|
||||||
glGenTextures(1, &handle);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Deletes the internal OpenGL resource
|
/// Deletes the internal OpenGL resource
|
||||||
void Release() {
|
void Release();
|
||||||
if (handle == 0)
|
|
||||||
return;
|
|
||||||
glDeleteTextures(1, &handle);
|
|
||||||
OpenGLState::GetCurState().UnbindTexture(handle).Apply();
|
|
||||||
handle = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
GLuint handle = 0;
|
GLuint handle = 0;
|
||||||
};
|
};
|
||||||
|
@ -64,20 +53,10 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a new internal OpenGL resource and stores the handle
|
/// Creates a new internal OpenGL resource and stores the handle
|
||||||
void Create() {
|
void Create();
|
||||||
if (handle != 0)
|
|
||||||
return;
|
|
||||||
glGenSamplers(1, &handle);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Deletes the internal OpenGL resource
|
/// Deletes the internal OpenGL resource
|
||||||
void Release() {
|
void Release();
|
||||||
if (handle == 0)
|
|
||||||
return;
|
|
||||||
glDeleteSamplers(1, &handle);
|
|
||||||
OpenGLState::GetCurState().ResetSampler(handle).Apply();
|
|
||||||
handle = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
GLuint handle = 0;
|
GLuint handle = 0;
|
||||||
};
|
};
|
||||||
|
@ -98,20 +77,9 @@ public:
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Create(const char* source, GLenum type) {
|
void Create(const char* source, GLenum type);
|
||||||
if (handle != 0)
|
|
||||||
return;
|
|
||||||
if (source == nullptr)
|
|
||||||
return;
|
|
||||||
handle = GLShader::LoadShader(source, type);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Release() {
|
void Release();
|
||||||
if (handle == 0)
|
|
||||||
return;
|
|
||||||
glDeleteShader(handle);
|
|
||||||
handle = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
GLuint handle = 0;
|
GLuint handle = 0;
|
||||||
};
|
};
|
||||||
|
@ -141,25 +109,10 @@ public:
|
||||||
|
|
||||||
/// Creates a new internal OpenGL resource and stores the handle
|
/// Creates a new internal OpenGL resource and stores the handle
|
||||||
void CreateFromSource(const char* vert_shader, const char* geo_shader, const char* frag_shader,
|
void CreateFromSource(const char* vert_shader, const char* geo_shader, const char* frag_shader,
|
||||||
bool separable_program = false) {
|
bool separable_program = false);
|
||||||
OGLShader vert, geo, frag;
|
|
||||||
if (vert_shader)
|
|
||||||
vert.Create(vert_shader, GL_VERTEX_SHADER);
|
|
||||||
if (geo_shader)
|
|
||||||
geo.Create(geo_shader, GL_GEOMETRY_SHADER);
|
|
||||||
if (frag_shader)
|
|
||||||
frag.Create(frag_shader, GL_FRAGMENT_SHADER);
|
|
||||||
Create(separable_program, vert.handle, geo.handle, frag.handle);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Deletes the internal OpenGL resource
|
/// Deletes the internal OpenGL resource
|
||||||
void Release() {
|
void Release();
|
||||||
if (handle == 0)
|
|
||||||
return;
|
|
||||||
glDeleteProgram(handle);
|
|
||||||
OpenGLState::GetCurState().ResetProgram(handle).Apply();
|
|
||||||
handle = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
GLuint handle = 0;
|
GLuint handle = 0;
|
||||||
};
|
};
|
||||||
|
@ -178,20 +131,10 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a new internal OpenGL resource and stores the handle
|
/// Creates a new internal OpenGL resource and stores the handle
|
||||||
void Create() {
|
void Create();
|
||||||
if (handle != 0)
|
|
||||||
return;
|
|
||||||
glGenProgramPipelines(1, &handle);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Deletes the internal OpenGL resource
|
/// Deletes the internal OpenGL resource
|
||||||
void Release() {
|
void Release();
|
||||||
if (handle == 0)
|
|
||||||
return;
|
|
||||||
glDeleteProgramPipelines(1, &handle);
|
|
||||||
OpenGLState::GetCurState().ResetPipeline(handle).Apply();
|
|
||||||
handle = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
GLuint handle = 0;
|
GLuint handle = 0;
|
||||||
};
|
};
|
||||||
|
@ -213,20 +156,10 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a new internal OpenGL resource and stores the handle
|
/// Creates a new internal OpenGL resource and stores the handle
|
||||||
void Create() {
|
void Create();
|
||||||
if (handle != 0)
|
|
||||||
return;
|
|
||||||
glGenBuffers(1, &handle);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Deletes the internal OpenGL resource
|
/// Deletes the internal OpenGL resource
|
||||||
void Release() {
|
void Release();
|
||||||
if (handle == 0)
|
|
||||||
return;
|
|
||||||
glDeleteBuffers(1, &handle);
|
|
||||||
OpenGLState::GetCurState().ResetBuffer(handle).Apply();
|
|
||||||
handle = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
GLuint handle = 0;
|
GLuint handle = 0;
|
||||||
};
|
};
|
||||||
|
@ -247,19 +180,10 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a new internal OpenGL resource and stores the handle
|
/// Creates a new internal OpenGL resource and stores the handle
|
||||||
void Create() {
|
void Create();
|
||||||
if (handle != 0)
|
|
||||||
return;
|
|
||||||
handle = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Deletes the internal OpenGL resource
|
/// Deletes the internal OpenGL resource
|
||||||
void Release() {
|
void Release();
|
||||||
if (handle == 0)
|
|
||||||
return;
|
|
||||||
glDeleteSync(handle);
|
|
||||||
handle = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
GLsync handle = 0;
|
GLsync handle = 0;
|
||||||
};
|
};
|
||||||
|
@ -281,20 +205,10 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a new internal OpenGL resource and stores the handle
|
/// Creates a new internal OpenGL resource and stores the handle
|
||||||
void Create() {
|
void Create();
|
||||||
if (handle != 0)
|
|
||||||
return;
|
|
||||||
glGenVertexArrays(1, &handle);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Deletes the internal OpenGL resource
|
/// Deletes the internal OpenGL resource
|
||||||
void Release() {
|
void Release();
|
||||||
if (handle == 0)
|
|
||||||
return;
|
|
||||||
glDeleteVertexArrays(1, &handle);
|
|
||||||
OpenGLState::GetCurState().ResetVertexArray(handle).Apply();
|
|
||||||
handle = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
GLuint handle = 0;
|
GLuint handle = 0;
|
||||||
};
|
};
|
||||||
|
@ -316,20 +230,10 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a new internal OpenGL resource and stores the handle
|
/// Creates a new internal OpenGL resource and stores the handle
|
||||||
void Create() {
|
void Create();
|
||||||
if (handle != 0)
|
|
||||||
return;
|
|
||||||
glGenFramebuffers(1, &handle);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Deletes the internal OpenGL resource
|
/// Deletes the internal OpenGL resource
|
||||||
void Release() {
|
void Release();
|
||||||
if (handle == 0)
|
|
||||||
return;
|
|
||||||
glDeleteFramebuffers(1, &handle);
|
|
||||||
OpenGLState::GetCurState().ResetFramebuffer(handle).Apply();
|
|
||||||
handle = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
GLuint handle = 0;
|
GLuint handle = 0;
|
||||||
};
|
};
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
#include <glad/glad.h>
|
#include <glad/glad.h>
|
||||||
|
|
||||||
#include "video_core/renderer_opengl/gl_resource_manager.h"
|
#include "video_core/renderer_opengl/gl_resource_manager.h"
|
||||||
|
#include "video_core/renderer_opengl/gl_state.h"
|
||||||
#include "video_core/renderer_opengl/maxwell_to_gl.h"
|
#include "video_core/renderer_opengl/maxwell_to_gl.h"
|
||||||
|
|
||||||
namespace OpenGL::GLShader {
|
namespace OpenGL::GLShader {
|
||||||
|
|
|
@ -19,6 +19,8 @@ SurfaceTarget SurfaceTargetFromTextureType(Tegra::Texture::TextureType texture_t
|
||||||
return SurfaceTarget::Texture3D;
|
return SurfaceTarget::Texture3D;
|
||||||
case Tegra::Texture::TextureType::TextureCubemap:
|
case Tegra::Texture::TextureType::TextureCubemap:
|
||||||
return SurfaceTarget::TextureCubemap;
|
return SurfaceTarget::TextureCubemap;
|
||||||
|
case Tegra::Texture::TextureType::TextureCubeArray:
|
||||||
|
return SurfaceTarget::TextureCubeArray;
|
||||||
case Tegra::Texture::TextureType::Texture1DArray:
|
case Tegra::Texture::TextureType::Texture1DArray:
|
||||||
return SurfaceTarget::Texture1DArray;
|
return SurfaceTarget::Texture1DArray;
|
||||||
case Tegra::Texture::TextureType::Texture2DArray:
|
case Tegra::Texture::TextureType::Texture2DArray:
|
||||||
|
@ -39,6 +41,7 @@ bool SurfaceTargetIsLayered(SurfaceTarget target) {
|
||||||
case SurfaceTarget::Texture1DArray:
|
case SurfaceTarget::Texture1DArray:
|
||||||
case SurfaceTarget::Texture2DArray:
|
case SurfaceTarget::Texture2DArray:
|
||||||
case SurfaceTarget::TextureCubemap:
|
case SurfaceTarget::TextureCubemap:
|
||||||
|
case SurfaceTarget::TextureCubeArray:
|
||||||
return true;
|
return true;
|
||||||
default:
|
default:
|
||||||
LOG_CRITICAL(HW_GPU, "Unimplemented surface_target={}", static_cast<u32>(target));
|
LOG_CRITICAL(HW_GPU, "Unimplemented surface_target={}", static_cast<u32>(target));
|
||||||
|
@ -297,6 +300,8 @@ PixelFormat PixelFormatFromTextureFormat(Tegra::Texture::TextureFormat format,
|
||||||
return is_srgb ? PixelFormat::ASTC_2D_4X4_SRGB : PixelFormat::ASTC_2D_4X4;
|
return is_srgb ? PixelFormat::ASTC_2D_4X4_SRGB : PixelFormat::ASTC_2D_4X4;
|
||||||
case Tegra::Texture::TextureFormat::ASTC_2D_5X4:
|
case Tegra::Texture::TextureFormat::ASTC_2D_5X4:
|
||||||
return is_srgb ? PixelFormat::ASTC_2D_5X4_SRGB : PixelFormat::ASTC_2D_5X4;
|
return is_srgb ? PixelFormat::ASTC_2D_5X4_SRGB : PixelFormat::ASTC_2D_5X4;
|
||||||
|
case Tegra::Texture::TextureFormat::ASTC_2D_5X5:
|
||||||
|
return is_srgb ? PixelFormat::ASTC_2D_5X5_SRGB : PixelFormat::ASTC_2D_5X5;
|
||||||
case Tegra::Texture::TextureFormat::ASTC_2D_8X8:
|
case Tegra::Texture::TextureFormat::ASTC_2D_8X8:
|
||||||
return is_srgb ? PixelFormat::ASTC_2D_8X8_SRGB : PixelFormat::ASTC_2D_8X8;
|
return is_srgb ? PixelFormat::ASTC_2D_8X8_SRGB : PixelFormat::ASTC_2D_8X8;
|
||||||
case Tegra::Texture::TextureFormat::ASTC_2D_8X5:
|
case Tegra::Texture::TextureFormat::ASTC_2D_8X5:
|
||||||
|
@ -440,10 +445,12 @@ bool IsPixelFormatASTC(PixelFormat format) {
|
||||||
switch (format) {
|
switch (format) {
|
||||||
case PixelFormat::ASTC_2D_4X4:
|
case PixelFormat::ASTC_2D_4X4:
|
||||||
case PixelFormat::ASTC_2D_5X4:
|
case PixelFormat::ASTC_2D_5X4:
|
||||||
|
case PixelFormat::ASTC_2D_5X5:
|
||||||
case PixelFormat::ASTC_2D_8X8:
|
case PixelFormat::ASTC_2D_8X8:
|
||||||
case PixelFormat::ASTC_2D_8X5:
|
case PixelFormat::ASTC_2D_8X5:
|
||||||
case PixelFormat::ASTC_2D_4X4_SRGB:
|
case PixelFormat::ASTC_2D_4X4_SRGB:
|
||||||
case PixelFormat::ASTC_2D_5X4_SRGB:
|
case PixelFormat::ASTC_2D_5X4_SRGB:
|
||||||
|
case PixelFormat::ASTC_2D_5X5_SRGB:
|
||||||
case PixelFormat::ASTC_2D_8X8_SRGB:
|
case PixelFormat::ASTC_2D_8X8_SRGB:
|
||||||
case PixelFormat::ASTC_2D_8X5_SRGB:
|
case PixelFormat::ASTC_2D_8X5_SRGB:
|
||||||
return true;
|
return true;
|
||||||
|
@ -453,27 +460,7 @@ bool IsPixelFormatASTC(PixelFormat format) {
|
||||||
}
|
}
|
||||||
|
|
||||||
std::pair<u32, u32> GetASTCBlockSize(PixelFormat format) {
|
std::pair<u32, u32> GetASTCBlockSize(PixelFormat format) {
|
||||||
switch (format) {
|
return {GetDefaultBlockWidth(format), GetDefaultBlockHeight(format)};
|
||||||
case PixelFormat::ASTC_2D_4X4:
|
|
||||||
return {4, 4};
|
|
||||||
case PixelFormat::ASTC_2D_5X4:
|
|
||||||
return {5, 4};
|
|
||||||
case PixelFormat::ASTC_2D_8X8:
|
|
||||||
return {8, 8};
|
|
||||||
case PixelFormat::ASTC_2D_8X5:
|
|
||||||
return {8, 5};
|
|
||||||
case PixelFormat::ASTC_2D_4X4_SRGB:
|
|
||||||
return {4, 4};
|
|
||||||
case PixelFormat::ASTC_2D_5X4_SRGB:
|
|
||||||
return {5, 4};
|
|
||||||
case PixelFormat::ASTC_2D_8X8_SRGB:
|
|
||||||
return {8, 8};
|
|
||||||
case PixelFormat::ASTC_2D_8X5_SRGB:
|
|
||||||
return {8, 5};
|
|
||||||
default:
|
|
||||||
LOG_CRITICAL(HW_GPU, "Unhandled format: {}", static_cast<u32>(format));
|
|
||||||
UNREACHABLE();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IsFormatBCn(PixelFormat format) {
|
bool IsFormatBCn(PixelFormat format) {
|
||||||
|
|
|
@ -72,19 +72,21 @@ enum class PixelFormat {
|
||||||
ASTC_2D_8X8_SRGB = 54,
|
ASTC_2D_8X8_SRGB = 54,
|
||||||
ASTC_2D_8X5_SRGB = 55,
|
ASTC_2D_8X5_SRGB = 55,
|
||||||
ASTC_2D_5X4_SRGB = 56,
|
ASTC_2D_5X4_SRGB = 56,
|
||||||
|
ASTC_2D_5X5 = 57,
|
||||||
|
ASTC_2D_5X5_SRGB = 58,
|
||||||
|
|
||||||
MaxColorFormat,
|
MaxColorFormat,
|
||||||
|
|
||||||
// Depth formats
|
// Depth formats
|
||||||
Z32F = 57,
|
Z32F = 59,
|
||||||
Z16 = 58,
|
Z16 = 60,
|
||||||
|
|
||||||
MaxDepthFormat,
|
MaxDepthFormat,
|
||||||
|
|
||||||
// DepthStencil formats
|
// DepthStencil formats
|
||||||
Z24S8 = 59,
|
Z24S8 = 61,
|
||||||
S8Z24 = 60,
|
S8Z24 = 62,
|
||||||
Z32FS8 = 61,
|
Z32FS8 = 63,
|
||||||
|
|
||||||
MaxDepthStencilFormat,
|
MaxDepthStencilFormat,
|
||||||
|
|
||||||
|
@ -118,6 +120,7 @@ enum class SurfaceTarget {
|
||||||
Texture1DArray,
|
Texture1DArray,
|
||||||
Texture2DArray,
|
Texture2DArray,
|
||||||
TextureCubemap,
|
TextureCubemap,
|
||||||
|
TextureCubeArray,
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -188,6 +191,8 @@ static constexpr u32 GetCompressionFactor(PixelFormat format) {
|
||||||
4, // ASTC_2D_8X8_SRGB
|
4, // ASTC_2D_8X8_SRGB
|
||||||
4, // ASTC_2D_8X5_SRGB
|
4, // ASTC_2D_8X5_SRGB
|
||||||
4, // ASTC_2D_5X4_SRGB
|
4, // ASTC_2D_5X4_SRGB
|
||||||
|
4, // ASTC_2D_5X5
|
||||||
|
4, // ASTC_2D_5X5_SRGB
|
||||||
1, // Z32F
|
1, // Z32F
|
||||||
1, // Z16
|
1, // Z16
|
||||||
1, // Z24S8
|
1, // Z24S8
|
||||||
|
@ -199,6 +204,79 @@ static constexpr u32 GetCompressionFactor(PixelFormat format) {
|
||||||
return compression_factor_table[static_cast<std::size_t>(format)];
|
return compression_factor_table[static_cast<std::size_t>(format)];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static constexpr u32 GetDefaultBlockWidth(PixelFormat format) {
|
||||||
|
if (format == PixelFormat::Invalid)
|
||||||
|
return 0;
|
||||||
|
constexpr std::array<u32, MaxPixelFormat> block_width_table = {{
|
||||||
|
1, // ABGR8U
|
||||||
|
1, // ABGR8S
|
||||||
|
1, // ABGR8UI
|
||||||
|
1, // B5G6R5U
|
||||||
|
1, // A2B10G10R10U
|
||||||
|
1, // A1B5G5R5U
|
||||||
|
1, // R8U
|
||||||
|
1, // R8UI
|
||||||
|
1, // RGBA16F
|
||||||
|
1, // RGBA16U
|
||||||
|
1, // RGBA16UI
|
||||||
|
1, // R11FG11FB10F
|
||||||
|
1, // RGBA32UI
|
||||||
|
4, // DXT1
|
||||||
|
4, // DXT23
|
||||||
|
4, // DXT45
|
||||||
|
4, // DXN1
|
||||||
|
4, // DXN2UNORM
|
||||||
|
4, // DXN2SNORM
|
||||||
|
4, // BC7U
|
||||||
|
4, // BC6H_UF16
|
||||||
|
4, // BC6H_SF16
|
||||||
|
4, // ASTC_2D_4X4
|
||||||
|
1, // G8R8U
|
||||||
|
1, // G8R8S
|
||||||
|
1, // BGRA8
|
||||||
|
1, // RGBA32F
|
||||||
|
1, // RG32F
|
||||||
|
1, // R32F
|
||||||
|
1, // R16F
|
||||||
|
1, // R16U
|
||||||
|
1, // R16S
|
||||||
|
1, // R16UI
|
||||||
|
1, // R16I
|
||||||
|
1, // RG16
|
||||||
|
1, // RG16F
|
||||||
|
1, // RG16UI
|
||||||
|
1, // RG16I
|
||||||
|
1, // RG16S
|
||||||
|
1, // RGB32F
|
||||||
|
1, // RGBA8_SRGB
|
||||||
|
1, // RG8U
|
||||||
|
1, // RG8S
|
||||||
|
1, // RG32UI
|
||||||
|
1, // R32UI
|
||||||
|
8, // ASTC_2D_8X8
|
||||||
|
8, // ASTC_2D_8X5
|
||||||
|
5, // ASTC_2D_5X4
|
||||||
|
1, // BGRA8_SRGB
|
||||||
|
4, // DXT1_SRGB
|
||||||
|
4, // DXT23_SRGB
|
||||||
|
4, // DXT45_SRGB
|
||||||
|
4, // BC7U_SRGB
|
||||||
|
4, // ASTC_2D_4X4_SRGB
|
||||||
|
8, // ASTC_2D_8X8_SRGB
|
||||||
|
8, // ASTC_2D_8X5_SRGB
|
||||||
|
5, // ASTC_2D_5X4_SRGB
|
||||||
|
5, // ASTC_2D_5X5
|
||||||
|
5, // ASTC_2D_5X5_SRGB
|
||||||
|
1, // Z32F
|
||||||
|
1, // Z16
|
||||||
|
1, // Z24S8
|
||||||
|
1, // S8Z24
|
||||||
|
1, // Z32FS8
|
||||||
|
}};
|
||||||
|
ASSERT(static_cast<std::size_t>(format) < block_width_table.size());
|
||||||
|
return block_width_table[static_cast<std::size_t>(format)];
|
||||||
|
}
|
||||||
|
|
||||||
static constexpr u32 GetDefaultBlockHeight(PixelFormat format) {
|
static constexpr u32 GetDefaultBlockHeight(PixelFormat format) {
|
||||||
if (format == PixelFormat::Invalid)
|
if (format == PixelFormat::Invalid)
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -261,6 +339,8 @@ static constexpr u32 GetDefaultBlockHeight(PixelFormat format) {
|
||||||
8, // ASTC_2D_8X8_SRGB
|
8, // ASTC_2D_8X8_SRGB
|
||||||
5, // ASTC_2D_8X5_SRGB
|
5, // ASTC_2D_8X5_SRGB
|
||||||
4, // ASTC_2D_5X4_SRGB
|
4, // ASTC_2D_5X4_SRGB
|
||||||
|
5, // ASTC_2D_5X5
|
||||||
|
5, // ASTC_2D_5X5_SRGB
|
||||||
1, // Z32F
|
1, // Z32F
|
||||||
1, // Z16
|
1, // Z16
|
||||||
1, // Z24S8
|
1, // Z24S8
|
||||||
|
@ -299,7 +379,7 @@ static constexpr u32 GetFormatBpp(PixelFormat format) {
|
||||||
128, // BC7U
|
128, // BC7U
|
||||||
128, // BC6H_UF16
|
128, // BC6H_UF16
|
||||||
128, // BC6H_SF16
|
128, // BC6H_SF16
|
||||||
32, // ASTC_2D_4X4
|
128, // ASTC_2D_4X4
|
||||||
16, // G8R8U
|
16, // G8R8U
|
||||||
16, // G8R8S
|
16, // G8R8S
|
||||||
32, // BGRA8
|
32, // BGRA8
|
||||||
|
@ -322,18 +402,20 @@ static constexpr u32 GetFormatBpp(PixelFormat format) {
|
||||||
16, // RG8S
|
16, // RG8S
|
||||||
64, // RG32UI
|
64, // RG32UI
|
||||||
32, // R32UI
|
32, // R32UI
|
||||||
16, // ASTC_2D_8X8
|
128, // ASTC_2D_8X8
|
||||||
16, // ASTC_2D_8X5
|
128, // ASTC_2D_8X5
|
||||||
32, // ASTC_2D_5X4
|
128, // ASTC_2D_5X4
|
||||||
32, // BGRA8_SRGB
|
32, // BGRA8_SRGB
|
||||||
64, // DXT1_SRGB
|
64, // DXT1_SRGB
|
||||||
128, // DXT23_SRGB
|
128, // DXT23_SRGB
|
||||||
128, // DXT45_SRGB
|
128, // DXT45_SRGB
|
||||||
128, // BC7U
|
128, // BC7U
|
||||||
32, // ASTC_2D_4X4_SRGB
|
128, // ASTC_2D_4X4_SRGB
|
||||||
16, // ASTC_2D_8X8_SRGB
|
128, // ASTC_2D_8X8_SRGB
|
||||||
16, // ASTC_2D_8X5_SRGB
|
128, // ASTC_2D_8X5_SRGB
|
||||||
32, // ASTC_2D_5X4_SRGB
|
128, // ASTC_2D_5X4_SRGB
|
||||||
|
128, // ASTC_2D_5X5
|
||||||
|
128, // ASTC_2D_5X5_SRGB
|
||||||
32, // Z32F
|
32, // Z32F
|
||||||
16, // Z16
|
16, // Z16
|
||||||
32, // Z24S8
|
32, // Z24S8
|
||||||
|
|
|
@ -1598,9 +1598,10 @@ static void DecompressBlock(uint8_t inBuf[16], const uint32_t blockWidth,
|
||||||
namespace Tegra::Texture::ASTC {
|
namespace Tegra::Texture::ASTC {
|
||||||
|
|
||||||
std::vector<uint8_t> Decompress(std::vector<uint8_t>& data, uint32_t width, uint32_t height,
|
std::vector<uint8_t> Decompress(std::vector<uint8_t>& data, uint32_t width, uint32_t height,
|
||||||
uint32_t block_width, uint32_t block_height) {
|
uint32_t depth, uint32_t block_width, uint32_t block_height) {
|
||||||
uint32_t blockIdx = 0;
|
uint32_t blockIdx = 0;
|
||||||
std::vector<uint8_t> outData(height * width * 4);
|
std::vector<uint8_t> outData(height * width * depth * 4);
|
||||||
|
for (uint32_t k = 0; k < depth; k++) {
|
||||||
for (uint32_t j = 0; j < height; j += block_height) {
|
for (uint32_t j = 0; j < height; j += block_height) {
|
||||||
for (uint32_t i = 0; i < width; i += block_width) {
|
for (uint32_t i = 0; i < width; i += block_width) {
|
||||||
|
|
||||||
|
@ -1621,6 +1622,7 @@ std::vector<uint8_t> Decompress(std::vector<uint8_t>& data, uint32_t width, uint
|
||||||
blockIdx++;
|
blockIdx++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return outData;
|
return outData;
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,6 @@
|
||||||
namespace Tegra::Texture::ASTC {
|
namespace Tegra::Texture::ASTC {
|
||||||
|
|
||||||
std::vector<uint8_t> Decompress(std::vector<uint8_t>& data, uint32_t width, uint32_t height,
|
std::vector<uint8_t> Decompress(std::vector<uint8_t>& data, uint32_t width, uint32_t height,
|
||||||
uint32_t block_width, uint32_t block_height);
|
uint32_t depth, uint32_t block_width, uint32_t block_height);
|
||||||
|
|
||||||
} // namespace Tegra::Texture::ASTC
|
} // namespace Tegra::Texture::ASTC
|
||||||
|
|
|
@ -227,12 +227,14 @@ u32 BytesPerPixel(TextureFormat format) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<u8> UnswizzleTexture(VAddr address, u32 tile_size, u32 bytes_per_pixel, u32 width,
|
std::vector<u8> UnswizzleTexture(VAddr address, u32 tile_size_x, u32 tile_size_y,
|
||||||
u32 height, u32 depth, u32 block_height, u32 block_depth) {
|
u32 bytes_per_pixel, u32 width, u32 height, u32 depth,
|
||||||
|
u32 block_height, u32 block_depth) {
|
||||||
std::vector<u8> unswizzled_data(width * height * depth * bytes_per_pixel);
|
std::vector<u8> unswizzled_data(width * height * depth * bytes_per_pixel);
|
||||||
CopySwizzledData(width / tile_size, height / tile_size, depth, bytes_per_pixel, bytes_per_pixel,
|
CopySwizzledData((width + tile_size_x - 1) / tile_size_x,
|
||||||
Memory::GetPointer(address), unswizzled_data.data(), true, block_height,
|
(height + tile_size_y - 1) / tile_size_y, depth, bytes_per_pixel,
|
||||||
block_depth);
|
bytes_per_pixel, Memory::GetPointer(address), unswizzled_data.data(), true,
|
||||||
|
block_height, block_depth);
|
||||||
return unswizzled_data;
|
return unswizzled_data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,8 +19,8 @@ inline std::size_t GetGOBSize() {
|
||||||
/**
|
/**
|
||||||
* Unswizzles a swizzled texture without changing its format.
|
* Unswizzles a swizzled texture without changing its format.
|
||||||
*/
|
*/
|
||||||
std::vector<u8> UnswizzleTexture(VAddr address, u32 tile_size, u32 bytes_per_pixel, u32 width,
|
std::vector<u8> UnswizzleTexture(VAddr address, u32 tile_size_x, u32 tile_size_y,
|
||||||
u32 height, u32 depth,
|
u32 bytes_per_pixel, u32 width, u32 height, u32 depth,
|
||||||
u32 block_height = TICEntry::DefaultBlockHeight,
|
u32 block_height = TICEntry::DefaultBlockHeight,
|
||||||
u32 block_depth = TICEntry::DefaultBlockHeight);
|
u32 block_depth = TICEntry::DefaultBlockHeight);
|
||||||
|
|
||||||
|
|
|
@ -386,9 +386,9 @@ void GraphicsSurfaceWidget::OnUpdate() {
|
||||||
|
|
||||||
// TODO(bunnei): Will not work with BCn formats that swizzle 4x4 tiles.
|
// TODO(bunnei): Will not work with BCn formats that swizzle 4x4 tiles.
|
||||||
// Needs to be fixed if we plan to use this feature more, otherwise we may remove it.
|
// Needs to be fixed if we plan to use this feature more, otherwise we may remove it.
|
||||||
auto unswizzled_data =
|
auto unswizzled_data = Tegra::Texture::UnswizzleTexture(
|
||||||
Tegra::Texture::UnswizzleTexture(*address, 1, Tegra::Texture::BytesPerPixel(surface_format),
|
*address, 1, 1, Tegra::Texture::BytesPerPixel(surface_format), surface_width,
|
||||||
surface_width, surface_height, 1U);
|
surface_height, 1U);
|
||||||
|
|
||||||
auto texture_data = Tegra::Texture::DecodeTexture(unswizzled_data, surface_format,
|
auto texture_data = Tegra::Texture::DecodeTexture(unswizzled_data, surface_format,
|
||||||
surface_width, surface_height);
|
surface_width, surface_height);
|
||||||
|
|
|
@ -142,6 +142,9 @@ static void InitializeLogging() {
|
||||||
const std::string& log_dir = FileUtil::GetUserPath(FileUtil::UserPath::LogDir);
|
const std::string& log_dir = FileUtil::GetUserPath(FileUtil::UserPath::LogDir);
|
||||||
FileUtil::CreateFullPath(log_dir);
|
FileUtil::CreateFullPath(log_dir);
|
||||||
Log::AddBackend(std::make_unique<Log::FileBackend>(log_dir + LOG_FILE));
|
Log::AddBackend(std::make_unique<Log::FileBackend>(log_dir + LOG_FILE));
|
||||||
|
#ifdef _WIN32
|
||||||
|
Log::AddBackend(std::make_unique<Log::DebuggerBackend>());
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
GMainWindow::GMainWindow()
|
GMainWindow::GMainWindow()
|
||||||
|
@ -454,6 +457,7 @@ void GMainWindow::ConnectMenuEvents() {
|
||||||
connect(ui.action_Fullscreen, &QAction::triggered, this, &GMainWindow::ToggleFullscreen);
|
connect(ui.action_Fullscreen, &QAction::triggered, this, &GMainWindow::ToggleFullscreen);
|
||||||
|
|
||||||
// Help
|
// Help
|
||||||
|
connect(ui.action_Open_yuzu_Folder, &QAction::triggered, this, &GMainWindow::OnOpenYuzuFolder);
|
||||||
connect(ui.action_Rederive, &QAction::triggered, this,
|
connect(ui.action_Rederive, &QAction::triggered, this,
|
||||||
std::bind(&GMainWindow::OnReinitializeKeys, this, ReinitializeKeyBehavior::Warning));
|
std::bind(&GMainWindow::OnReinitializeKeys, this, ReinitializeKeyBehavior::Warning));
|
||||||
connect(ui.action_About, &QAction::triggered, this, &GMainWindow::OnAbout);
|
connect(ui.action_About, &QAction::triggered, this, &GMainWindow::OnAbout);
|
||||||
|
@ -1374,6 +1378,11 @@ void GMainWindow::OnLoadAmiibo() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GMainWindow::OnOpenYuzuFolder() {
|
||||||
|
QDesktopServices::openUrl(QUrl::fromLocalFile(
|
||||||
|
QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::UserDir))));
|
||||||
|
}
|
||||||
|
|
||||||
void GMainWindow::OnAbout() {
|
void GMainWindow::OnAbout() {
|
||||||
AboutDialog aboutDialog(this);
|
AboutDialog aboutDialog(this);
|
||||||
aboutDialog.exec();
|
aboutDialog.exec();
|
||||||
|
@ -1532,7 +1541,7 @@ void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) {
|
||||||
"derivation. It will be attempted but may not complete.<br><br>") +
|
"derivation. It will be attempted but may not complete.<br><br>") +
|
||||||
errors +
|
errors +
|
||||||
tr("<br><br>You can get all of these and dump all of your games easily by "
|
tr("<br><br>You can get all of these and dump all of your games easily by "
|
||||||
"following <a href='https://yuzu-emu.org/help/quickstart/quickstart/'>the "
|
"following <a href='https://yuzu-emu.org/help/quickstart/'>the "
|
||||||
"quickstart guide</a>. Alternatively, you can use another method of dumping "
|
"quickstart guide</a>. Alternatively, you can use another method of dumping "
|
||||||
"to obtain all of your keys."));
|
"to obtain all of your keys."));
|
||||||
}
|
}
|
||||||
|
|
|
@ -167,6 +167,7 @@ private slots:
|
||||||
void OnMenuRecentFile();
|
void OnMenuRecentFile();
|
||||||
void OnConfigure();
|
void OnConfigure();
|
||||||
void OnLoadAmiibo();
|
void OnLoadAmiibo();
|
||||||
|
void OnOpenYuzuFolder();
|
||||||
void OnAbout();
|
void OnAbout();
|
||||||
void OnToggleFilterBar();
|
void OnToggleFilterBar();
|
||||||
void OnDisplayTitleBars(bool);
|
void OnDisplayTitleBars(bool);
|
||||||
|
|
|
@ -110,6 +110,7 @@
|
||||||
<string>&Help</string>
|
<string>&Help</string>
|
||||||
</property>
|
</property>
|
||||||
<addaction name="action_Report_Compatibility"/>
|
<addaction name="action_Report_Compatibility"/>
|
||||||
|
<addaction name="action_Open_yuzu_Folder" />
|
||||||
<addaction name="separator"/>
|
<addaction name="separator"/>
|
||||||
<addaction name="action_About"/>
|
<addaction name="action_About"/>
|
||||||
</widget>
|
</widget>
|
||||||
|
@ -277,6 +278,11 @@
|
||||||
<bool>false</bool>
|
<bool>false</bool>
|
||||||
</property>
|
</property>
|
||||||
</action>
|
</action>
|
||||||
|
<action name="action_Open_yuzu_Folder">
|
||||||
|
<property name="text">
|
||||||
|
<string>Open yuzu Folder</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
</widget>
|
</widget>
|
||||||
<resources/>
|
<resources/>
|
||||||
<connections/>
|
<connections/>
|
||||||
|
|
|
@ -76,6 +76,9 @@ static void InitializeLogging() {
|
||||||
const std::string& log_dir = FileUtil::GetUserPath(FileUtil::UserPath::LogDir);
|
const std::string& log_dir = FileUtil::GetUserPath(FileUtil::UserPath::LogDir);
|
||||||
FileUtil::CreateFullPath(log_dir);
|
FileUtil::CreateFullPath(log_dir);
|
||||||
Log::AddBackend(std::make_unique<Log::FileBackend>(log_dir + LOG_FILE));
|
Log::AddBackend(std::make_unique<Log::FileBackend>(log_dir + LOG_FILE));
|
||||||
|
#ifdef _WIN32
|
||||||
|
Log::AddBackend(std::make_unique<Log::DebuggerBackend>());
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Application entry point
|
/// Application entry point
|
||||||
|
|
Loading…
Reference in a new issue