diff options
-rw-r--r-- | src/core/hle/kernel/svc.cpp | 5 | ||||
-rw-r--r-- | src/video_core/CMakeLists.txt | 2 | ||||
-rw-r--r-- | src/video_core/engines/shader_bytecode.h | 27 | ||||
-rw-r--r-- | src/video_core/renderer_opengl/gl_rasterizer.cpp | 2 | ||||
-rw-r--r-- | src/video_core/renderer_opengl/gl_rasterizer_cache.cpp | 62 | ||||
-rw-r--r-- | src/video_core/renderer_opengl/gl_rasterizer_cache.h | 16 | ||||
-rw-r--r-- | src/video_core/renderer_opengl/gl_shader_decompiler.cpp | 203 | ||||
-rw-r--r-- | src/video_core/renderer_opengl/gl_shader_gen.cpp | 4 | ||||
-rw-r--r-- | src/video_core/textures/astc.cpp | 1646 | ||||
-rw-r--r-- | src/video_core/textures/astc.h | 15 | ||||
-rw-r--r-- | src/video_core/textures/decoders.cpp | 3 | ||||
-rw-r--r-- | src/yuzu/game_list.cpp | 30 | ||||
-rw-r--r-- | src/yuzu/main.cpp | 16 | ||||
-rw-r--r-- | src/yuzu/main.h | 1 | ||||
-rw-r--r-- | src/yuzu/main.ui | 6 |
15 files changed, 1922 insertions, 116 deletions
diff --git a/src/core/hle/kernel/svc.cpp b/src/core/hle/kernel/svc.cpp index ec3601e8b..097416e9e 100644 --- a/src/core/hle/kernel/svc.cpp +++ b/src/core/hle/kernel/svc.cpp @@ -316,6 +316,11 @@ static ResultCode GetInfo(u64* result, u64 info_id, u64 handle, u64 info_sub_id) "(STUBBED) Attempted to query privileged process id bounds, returned 0"); *result = 0; break; + case GetInfoType::UserExceptionContextAddr: + NGLOG_WARNING(Kernel_SVC, + "(STUBBED) Attempted to query user exception context address, returned 0"); + *result = 0; + break; default: UNIMPLEMENTED(); } diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt index 6e193e7e1..c6431e722 100644 --- a/src/video_core/CMakeLists.txt +++ b/src/video_core/CMakeLists.txt @@ -41,6 +41,8 @@ add_library(video_core STATIC renderer_opengl/maxwell_to_gl.h renderer_opengl/renderer_opengl.cpp renderer_opengl/renderer_opengl.h + textures/astc.cpp + textures/astc.h textures/decoders.cpp textures/decoders.h textures/texture.h diff --git a/src/video_core/engines/shader_bytecode.h b/src/video_core/engines/shader_bytecode.h index 29d88192e..5ff861b04 100644 --- a/src/video_core/engines/shader_bytecode.h +++ b/src/video_core/engines/shader_bytecode.h @@ -230,10 +230,18 @@ union Instruction { } fmnmx; union { + BitField<39, 1, u64> invert_a; + BitField<40, 1, u64> invert_b; + BitField<41, 2, LogicOperation> operation; + BitField<44, 2, u64> unk44; + BitField<48, 3, Pred> pred48; + } lop; + + union { BitField<53, 2, LogicOperation> operation; BitField<55, 1, u64> invert_a; BitField<56, 1, u64> invert_b; - } lop; + } lop32i; float GetImm20_19() const { float result{}; @@ -343,7 +351,8 @@ union Instruction { } iset; union { - BitField<10, 2, Register::Size> size; + BitField<8, 2, Register::Size> dest_size; + BitField<10, 2, Register::Size> src_size; BitField<12, 1, u64> is_output_signed; BitField<13, 1, u64> is_input_signed; BitField<41, 2, u64> selector; @@ -475,6 +484,9 @@ public: I2I_C, I2I_R, I2I_IMM, + LOP_C, + LOP_R, + LOP_IMM, LOP32I, MOV_C, MOV_R, @@ -514,10 +526,10 @@ public: enum class Type { Trivial, Arithmetic, + ArithmeticImmediate, ArithmeticInteger, ArithmeticIntegerImmediate, Bfe, - Logic, Shift, Ffma, Flow, @@ -644,7 +656,7 @@ private: INST("0100110001101---", Id::FMUL_C, Type::Arithmetic, "FMUL_C"), INST("0101110001101---", Id::FMUL_R, Type::Arithmetic, "FMUL_R"), INST("0011100-01101---", Id::FMUL_IMM, Type::Arithmetic, "FMUL_IMM"), - INST("00011110--------", Id::FMUL32_IMM, Type::Arithmetic, "FMUL32_IMM"), + INST("00011110--------", Id::FMUL32_IMM, Type::ArithmeticImmediate, "FMUL32_IMM"), INST("0100110000010---", Id::IADD_C, Type::ArithmeticInteger, "IADD_C"), INST("0101110000010---", Id::IADD_R, Type::ArithmeticInteger, "IADD_R"), INST("0011100-00010---", Id::IADD_IMM, Type::ArithmeticInteger, "IADD_IMM"), @@ -665,7 +677,7 @@ private: INST("0100110010011---", Id::MOV_C, Type::Arithmetic, "MOV_C"), INST("0101110010011---", Id::MOV_R, Type::Arithmetic, "MOV_R"), INST("0011100-10011---", Id::MOV_IMM, Type::Arithmetic, "MOV_IMM"), - INST("000000010000----", Id::MOV32_IMM, Type::Arithmetic, "MOV32_IMM"), + INST("000000010000----", Id::MOV32_IMM, Type::ArithmeticImmediate, "MOV32_IMM"), INST("0100110001100---", Id::FMNMX_C, Type::Arithmetic, "FMNMX_C"), INST("0101110001100---", Id::FMNMX_R, Type::Arithmetic, "FMNMX_R"), INST("0011100-01100---", Id::FMNMX_IMM, Type::Arithmetic, "FMNMX_IMM"), @@ -675,7 +687,10 @@ private: INST("0100110000000---", Id::BFE_C, Type::Bfe, "BFE_C"), INST("0101110000000---", Id::BFE_R, Type::Bfe, "BFE_R"), INST("0011100-00000---", Id::BFE_IMM, Type::Bfe, "BFE_IMM"), - INST("000001----------", Id::LOP32I, Type::Logic, "LOP32I"), + INST("0100110001000---", Id::LOP_C, Type::ArithmeticInteger, "LOP_C"), + INST("0101110001000---", Id::LOP_R, Type::ArithmeticInteger, "LOP_R"), + INST("0011100001000---", Id::LOP_IMM, Type::ArithmeticInteger, "LOP_IMM"), + INST("000001----------", Id::LOP32I, Type::ArithmeticIntegerImmediate, "LOP32I"), INST("0100110001001---", Id::SHL_C, Type::Shift, "SHL_C"), INST("0101110001001---", Id::SHL_R, Type::Shift, "SHL_R"), INST("0011100-01001---", Id::SHL_IMM, Type::Shift, "SHL_IMM"), diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp index 6f05f24a0..833ff8273 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp @@ -740,7 +740,6 @@ void RasterizerOpenGL::SyncDepthOffset() { void RasterizerOpenGL::SyncBlendState() { const auto& regs = Core::System().GetInstance().GPU().Maxwell3D().regs; - ASSERT_MSG(regs.independent_blend_enable == 1, "Only independent blending is implemented"); // TODO(Subv): Support more than just render target 0. state.blend.enabled = regs.blend.enable[0] != 0; @@ -748,6 +747,7 @@ void RasterizerOpenGL::SyncBlendState() { if (!state.blend.enabled) return; + ASSERT_MSG(regs.independent_blend_enable == 1, "Only independent blending is implemented"); ASSERT_MSG(!regs.independent_blend[0].separate_alpha, "Unimplemented"); state.blend.rgb_equation = MaxwellToGL::BlendEquation(regs.independent_blend[0].equation_rgb); state.blend.src_rgb_func = MaxwellToGL::BlendFunc(regs.independent_blend[0].factor_source_rgb); diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp index ff48a2669..b2a61024e 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp @@ -28,6 +28,7 @@ #include "video_core/engines/maxwell_3d.h" #include "video_core/renderer_opengl/gl_rasterizer_cache.h" #include "video_core/renderer_opengl/gl_state.h" +#include "video_core/textures/astc.h" #include "video_core/textures/decoders.h" #include "video_core/utils.h" #include "video_core/video_core.h" @@ -55,6 +56,7 @@ static constexpr std::array<FormatTuple, SurfaceParams::MaxPixelFormat> tex_form {GL_COMPRESSED_RGBA_S3TC_DXT3_EXT, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, true}, // DXT23 {GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, true}, // DXT45 {GL_COMPRESSED_RED_RGTC1, GL_RED, GL_UNSIGNED_INT_8_8_8_8, true}, // DXN1 + {GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, false}, // ASTC_2D_4X4 }}; static const FormatTuple& GetFormatTuple(PixelFormat pixel_format, ComponentType component_type) { @@ -86,6 +88,23 @@ static u16 GetResolutionScaleFactor() { : Settings::values.resolution_factor); } +static void ConvertASTCToRGBA8(std::vector<u8>& data, PixelFormat format, u32 width, u32 height) { + u32 block_width{}; + u32 block_height{}; + + switch (format) { + case PixelFormat::ASTC_2D_4X4: + block_width = 4; + block_height = 4; + break; + default: + NGLOG_CRITICAL(HW_GPU, "Unhandled format: {}", static_cast<u32>(format)); + UNREACHABLE(); + } + + data = Tegra::Texture::ASTC::Decompress(data, width, height, block_width, block_height); +} + template <bool morton_to_gl, PixelFormat format> void MortonCopy(u32 stride, u32 block_height, u32 height, u8* gl_buffer, Tegra::GPUVAddr base, Tegra::GPUVAddr start, Tegra::GPUVAddr end) { @@ -97,6 +116,12 @@ void MortonCopy(u32 stride, u32 block_height, u32 height, u8* gl_buffer, Tegra:: auto data = Tegra::Texture::UnswizzleTexture( *gpu.memory_manager->GpuToCpuAddress(base), SurfaceParams::TextureFormatFromPixelFormat(format), stride, height, block_height); + + if (SurfaceParams::IsFormatASTC(format)) { + // ASTC formats are converted to RGBA8 in software, as most PC GPUs do not support this + ConvertASTCToRGBA8(data, format, stride, height); + } + std::memcpy(gl_buffer, data.data(), data.size()); } else { // TODO(bunnei): Assumes the default rendering GOB size of 16 (128 lines). We should check @@ -118,7 +143,7 @@ static constexpr std::array<void (*)(u32, u32, u32, u8*, Tegra::GPUVAddr, Tegra: MortonCopy<true, PixelFormat::R8>, MortonCopy<true, PixelFormat::RGBA16F>, MortonCopy<true, PixelFormat::R11FG11FB10F>, MortonCopy<true, PixelFormat::DXT1>, MortonCopy<true, PixelFormat::DXT23>, MortonCopy<true, PixelFormat::DXT45>, - MortonCopy<true, PixelFormat::DXN1>, + MortonCopy<true, PixelFormat::DXN1>, MortonCopy<true, PixelFormat::ASTC_2D_4X4>, }; static constexpr std::array<void (*)(u32, u32, u32, u8*, Tegra::GPUVAddr, Tegra::GPUVAddr, @@ -137,6 +162,7 @@ static constexpr std::array<void (*)(u32, u32, u32, u8*, Tegra::GPUVAddr, Tegra: nullptr, nullptr, nullptr, + MortonCopy<false, PixelFormat::ABGR8>, }; // Allocate an uninitialized texture of appropriate size and format for the surface @@ -888,9 +914,6 @@ Surface RasterizerCacheOpenGL::GetSurface(const SurfaceParams& params, ScaleMatc // Use GetSurfaceSubRect instead ASSERT(params.width == params.stride); - ASSERT(!params.is_tiled || - (params.GetActualWidth() % 8 == 0 && params.GetActualHeight() % 8 == 0)); - // Check for an exact match in existing surfaces Surface surface = FindMatch<MatchFlags::Exact | MatchFlags::Invalid>(surface_cache, params, match_res_scale); @@ -1048,8 +1071,13 @@ Surface RasterizerCacheOpenGL::GetTextureSurface(const Tegra::Texture::FullTextu if (config.tic.IsTiled()) { params.block_height = config.tic.BlockHeight(); - params.width = Common::AlignUp(params.width, params.block_height); - params.height = Common::AlignUp(params.height, params.block_height); + + // TODO(bunnei): The below align up is a hack. This is here because some compressed textures + // are not a multiple of their own compression factor, and so this accounts for that. This + // could potentially result in an extra row of 4px being decoded if a texture is not a + // multiple of 4. + params.width = Common::AlignUp(params.width, 4); + params.height = Common::AlignUp(params.height, 4); } else { // Use the texture-provided stride value if the texture isn't tiled. params.stride = static_cast<u32>(params.PixelsInBytes(config.tic.Pitch())); @@ -1057,26 +1085,6 @@ Surface RasterizerCacheOpenGL::GetTextureSurface(const Tegra::Texture::FullTextu params.UpdateParams(); - if (params.GetActualWidth() % 8 != 0 || params.GetActualHeight() % 8 != 0 || - params.stride != params.width) { - Surface src_surface; - MathUtil::Rectangle<u32> rect; - std::tie(src_surface, rect) = GetSurfaceSubRect(params, ScaleMatch::Ignore, true); - - rect = rect.Scale(params.GetCompresssionFactor()); - - params.res_scale = src_surface->res_scale; - Surface tmp_surface = CreateSurface(params); - - auto dst_rect = tmp_surface->GetScaledRect().Scale(params.GetCompresssionFactor()); - BlitTextures(src_surface->texture.handle, rect, tmp_surface->texture.handle, dst_rect, - SurfaceParams::GetFormatType(params.pixel_format), read_framebuffer.handle, - draw_framebuffer.handle); - - remove_surfaces.emplace(tmp_surface); - return tmp_surface; - } - return GetSurface(params, ScaleMatch::Ignore, true); } @@ -1251,7 +1259,7 @@ void RasterizerCacheOpenGL::ValidateSurface(const Surface& surface, Tegra::GPUVA const auto interval = *it & validate_interval; // Look for a valid surface to copy from - SurfaceParams params = surface->FromInterval(interval); + SurfaceParams params = *surface; Surface copy_surface = FindMatch<MatchFlags::Copy>(surface_cache, params, ScaleMatch::Ignore, interval); diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.h b/src/video_core/renderer_opengl/gl_rasterizer_cache.h index 0f43e863d..9da945e19 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer_cache.h +++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.h @@ -65,6 +65,7 @@ struct SurfaceParams { DXT23 = 8, DXT45 = 9, DXN1 = 10, // This is also known as BC4 + ASTC_2D_4X4 = 11, Max, Invalid = 255, @@ -111,6 +112,7 @@ struct SurfaceParams { 4, // DXT23 4, // DXT45 4, // DXN1 + 1, // ASTC_2D_4X4 }}; ASSERT(static_cast<size_t>(format) < compression_factor_table.size()); @@ -136,6 +138,7 @@ struct SurfaceParams { 128, // DXT23 128, // DXT45 64, // DXN1 + 32, // ASTC_2D_4X4 }}; ASSERT(static_cast<size_t>(format) < bpp_table.size()); @@ -162,6 +165,15 @@ struct SurfaceParams { } } + static bool IsFormatASTC(PixelFormat format) { + switch (format) { + case PixelFormat::ASTC_2D_4X4: + return true; + default: + return false; + } + } + static PixelFormat PixelFormatFromGPUPixelFormat(Tegra::FramebufferConfig::PixelFormat format) { switch (format) { case Tegra::FramebufferConfig::PixelFormat::ABGR8: @@ -197,6 +209,8 @@ struct SurfaceParams { return PixelFormat::DXT45; case Tegra::Texture::TextureFormat::DXN1: return PixelFormat::DXN1; + case Tegra::Texture::TextureFormat::ASTC_2D_4X4: + return PixelFormat::ASTC_2D_4X4; default: NGLOG_CRITICAL(HW_GPU, "Unimplemented format={}", static_cast<u32>(format)); UNREACHABLE(); @@ -228,6 +242,8 @@ struct SurfaceParams { return Tegra::Texture::TextureFormat::DXT45; case PixelFormat::DXN1: return Tegra::Texture::TextureFormat::DXN1; + case PixelFormat::ASTC_2D_4X4: + return Tegra::Texture::TextureFormat::ASTC_2D_4X4; default: UNREACHABLE(); } diff --git a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp index 7ce150fda..6dc695def 100644 --- a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp +++ b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp @@ -16,6 +16,7 @@ namespace Decompiler { using Tegra::Shader::Attribute; using Tegra::Shader::Instruction; +using Tegra::Shader::LogicOperation; using Tegra::Shader::OpCode; using Tegra::Shader::Register; using Tegra::Shader::Sampler; @@ -266,6 +267,27 @@ public: } /** + * Returns code that does an integer size conversion for the specified size. + * @param value Value to perform integer size conversion on. + * @param size Register size to use for conversion instructions. + * @returns GLSL string corresponding to the value converted to the specified size. + */ + static std::string ConvertIntegerSize(const std::string& value, Register::Size size) { + switch (size) { + case Register::Size::Byte: + return "((" + value + " << 24) >> 24)"; + case Register::Size::Short: + return "((" + value + " << 16) >> 16)"; + case Register::Size::Word: + // Default - do nothing + return value; + default: + NGLOG_CRITICAL(HW_GPU, "Unimplemented conversion size {}", static_cast<u32>(size)); + UNREACHABLE(); + } + } + + /** * Gets a register as an float. * @param reg The register to get. * @param elem The element to use for the operation. @@ -281,15 +303,18 @@ public: * @param reg The register to get. * @param elem The element to use for the operation. * @param is_signed Whether to get the register as a signed (or unsigned) integer. + * @param size Register size to use for conversion instructions. * @returns GLSL string corresponding to the register as an integer. */ - std::string GetRegisterAsInteger(const Register& reg, unsigned elem = 0, - bool is_signed = true) { + std::string GetRegisterAsInteger(const Register& reg, unsigned elem = 0, bool is_signed = true, + Register::Size size = Register::Size::Word) { const std::string func = GetGLSLConversionFunc( GLSLRegister::Type::Float, is_signed ? GLSLRegister::Type::Integer : GLSLRegister::Type::UnsignedInteger); - return func + '(' + GetRegister(reg, elem) + ')'; + std::string value = func + '(' + GetRegister(reg, elem) + ')'; + + return ConvertIntegerSize(value, size); } /** @@ -319,19 +344,20 @@ public: * @param value_num_components Number of components in the value. * @param is_saturated Optional, when True, saturates the provided value. * @param dest_elem Optional, the destination element to use for the operation. + * @param size Register size to use for conversion instructions. */ void SetRegisterToInteger(const Register& reg, bool is_signed, u64 elem, const std::string& value, u64 dest_num_components, u64 value_num_components, bool is_saturated = false, - u64 dest_elem = 0) { + u64 dest_elem = 0, Register::Size size = Register::Size::Word) { ASSERT_MSG(!is_saturated, "Unimplemented"); const std::string func = GetGLSLConversionFunc( is_signed ? GLSLRegister::Type::Integer : GLSLRegister::Type::UnsignedInteger, GLSLRegister::Type::Float); - SetRegister(reg, elem, func + '(' + value + ')', dest_num_components, value_num_components, - dest_elem); + SetRegister(reg, elem, func + '(' + ConvertIntegerSize(value, size) + ')', + dest_num_components, value_num_components, dest_elem); } /** @@ -734,6 +760,31 @@ private: return (absolute_offset % SchedPeriod) == 0; } + void WriteLogicOperation(Register dest, LogicOperation logic_op, const std::string& op_a, + const std::string& op_b) { + switch (logic_op) { + case LogicOperation::And: { + regs.SetRegisterToInteger(dest, true, 0, '(' + op_a + " & " + op_b + ')', 1, 1); + break; + } + case LogicOperation::Or: { + regs.SetRegisterToInteger(dest, true, 0, '(' + op_a + " | " + op_b + ')', 1, 1); + break; + } + case LogicOperation::Xor: { + regs.SetRegisterToInteger(dest, true, 0, '(' + op_a + " ^ " + op_b + ')', 1, 1); + break; + } + case LogicOperation::PassB: { + regs.SetRegisterToInteger(dest, true, 0, op_b, 1, 1); + break; + } + default: + NGLOG_CRITICAL(HW_GPU, "Unimplemented logic operation: {}", static_cast<u32>(logic_op)); + UNREACHABLE(); + } + } + /** * Compiles a single instruction from Tegra to GLSL. * @param offset the offset of the Tegra shader instruction. @@ -801,11 +852,6 @@ private: break; } - case OpCode::Id::MOV32_IMM: { - // mov32i doesn't have abs or neg bits. - regs.SetRegisterToFloat(instr.gpr0, 0, GetImmediate32(instr), 1, 1); - break; - } case OpCode::Id::FMUL_C: case OpCode::Id::FMUL_R: case OpCode::Id::FMUL_IMM: { @@ -813,13 +859,6 @@ private: instr.alu.saturate_d); break; } - case OpCode::Id::FMUL32_IMM: { - // fmul32i doesn't have abs or neg bits. - regs.SetRegisterToFloat( - instr.gpr0, 0, - regs.GetRegisterAsFloat(instr.gpr8) + " * " + GetImmediate32(instr), 1, 1); - break; - } case OpCode::Id::FADD_C: case OpCode::Id::FADD_R: case OpCode::Id::FADD_IMM: { @@ -892,6 +931,21 @@ private: } break; } + case OpCode::Type::ArithmeticImmediate: { + switch (opcode->GetId()) { + case OpCode::Id::MOV32_IMM: { + regs.SetRegisterToFloat(instr.gpr0, 0, GetImmediate32(instr), 1, 1); + break; + } + case OpCode::Id::FMUL32_IMM: { + regs.SetRegisterToFloat( + instr.gpr0, 0, + regs.GetRegisterAsFloat(instr.gpr8) + " * " + GetImmediate32(instr), 1, 1); + break; + } + } + break; + } case OpCode::Type::Bfe: { ASSERT_MSG(!instr.bfe.negate_b, "Unimplemented"); @@ -917,49 +971,6 @@ private: break; } - case OpCode::Type::Logic: { - std::string op_a = regs.GetRegisterAsInteger(instr.gpr8, 0, true); - - if (instr.alu.lop.invert_a) - op_a = "~(" + op_a + ')'; - - switch (opcode->GetId()) { - case OpCode::Id::LOP32I: { - u32 imm = static_cast<u32>(instr.alu.imm20_32.Value()); - - if (instr.alu.lop.invert_b) - imm = ~imm; - - switch (instr.alu.lop.operation) { - case Tegra::Shader::LogicOperation::And: { - regs.SetRegisterToInteger(instr.gpr0, true, 0, - '(' + op_a + " & " + std::to_string(imm) + ')', 1, 1); - break; - } - case Tegra::Shader::LogicOperation::Or: { - regs.SetRegisterToInteger(instr.gpr0, true, 0, - '(' + op_a + " | " + std::to_string(imm) + ')', 1, 1); - break; - } - case Tegra::Shader::LogicOperation::Xor: { - regs.SetRegisterToInteger(instr.gpr0, true, 0, - '(' + op_a + " ^ " + std::to_string(imm) + ')', 1, 1); - break; - } - default: - NGLOG_CRITICAL(HW_GPU, "Unimplemented lop32i operation: {}", - static_cast<u32>(instr.alu.lop.operation.Value())); - UNREACHABLE(); - } - break; - } - default: { - NGLOG_CRITICAL(HW_GPU, "Unhandled logic instruction: {}", opcode->GetName()); - UNREACHABLE(); - } - } - break; - } case OpCode::Type::Shift: { std::string op_a = regs.GetRegisterAsInteger(instr.gpr8, 0, true); @@ -1005,17 +1016,26 @@ private: case OpCode::Type::ArithmeticIntegerImmediate: { std::string op_a = regs.GetRegisterAsInteger(instr.gpr8); - - if (instr.iadd32i.negate_a) - op_a = '-' + op_a; - - std::string op_b = '(' + std::to_string(instr.alu.imm20_32.Value()) + ')'; + std::string op_b = std::to_string(instr.alu.imm20_32.Value()); switch (opcode->GetId()) { case OpCode::Id::IADD32I: + if (instr.iadd32i.negate_a) + op_a = "-(" + op_a + ')'; + regs.SetRegisterToInteger(instr.gpr0, true, 0, op_a + " + " + op_b, 1, 1, instr.iadd32i.saturate != 0); break; + case OpCode::Id::LOP32I: { + if (instr.alu.lop32i.invert_a) + op_a = "~(" + op_a + ')'; + + if (instr.alu.lop32i.invert_b) + op_b = "~(" + op_b + ')'; + + WriteLogicOperation(instr.gpr0, instr.alu.lop32i.operation, op_a, op_b); + break; + } default: { NGLOG_CRITICAL(HW_GPU, "Unhandled ArithmeticIntegerImmediate instruction: {}", opcode->GetName()); @@ -1026,12 +1046,7 @@ private: } case OpCode::Type::ArithmeticInteger: { std::string op_a = regs.GetRegisterAsInteger(instr.gpr8); - - if (instr.alu_integer.negate_a) - op_a = '-' + op_a; - - std::string op_b = instr.alu_integer.negate_b ? "-" : ""; - + std::string op_b; if (instr.is_b_imm) { op_b += '(' + std::to_string(instr.alu.GetSignedImm20_20()) + ')'; } else { @@ -1047,6 +1062,12 @@ private: case OpCode::Id::IADD_C: case OpCode::Id::IADD_R: case OpCode::Id::IADD_IMM: { + if (instr.alu_integer.negate_a) + op_a = "-(" + op_a + ')'; + + if (instr.alu_integer.negate_b) + op_b = "-(" + op_b + ')'; + regs.SetRegisterToInteger(instr.gpr0, true, 0, op_a + " + " + op_b, 1, 1, instr.alu.saturate_d); break; @@ -1054,12 +1075,33 @@ private: case OpCode::Id::ISCADD_C: case OpCode::Id::ISCADD_R: case OpCode::Id::ISCADD_IMM: { + if (instr.alu_integer.negate_a) + op_a = "-(" + op_a + ')'; + + if (instr.alu_integer.negate_b) + op_b = "-(" + op_b + ')'; + std::string shift = std::to_string(instr.alu_integer.shift_amount.Value()); regs.SetRegisterToInteger(instr.gpr0, true, 0, "((" + op_a + " << " + shift + ") + " + op_b + ')', 1, 1); break; } + case OpCode::Id::LOP_C: + case OpCode::Id::LOP_R: + case OpCode::Id::LOP_IMM: { + ASSERT_MSG(!instr.alu.lop.unk44, "Unimplemented"); + ASSERT_MSG(instr.alu.lop.pred48 == Pred::UnusedIndex, "Unimplemented"); + + if (instr.alu.lop.invert_a) + op_a = "~(" + op_a + ')'; + + if (instr.alu.lop.invert_b) + op_b = "~(" + op_b + ')'; + + WriteLogicOperation(instr.gpr0, instr.alu.lop.operation, op_a, op_b); + break; + } default: { NGLOG_CRITICAL(HW_GPU, "Unhandled ArithmeticInteger instruction: {}", opcode->GetName()); @@ -1108,28 +1150,28 @@ private: break; } case OpCode::Type::Conversion: { - ASSERT_MSG(instr.conversion.size == Register::Size::Word, "Unimplemented"); ASSERT_MSG(!instr.conversion.negate_a, "Unimplemented"); switch (opcode->GetId()) { case OpCode::Id::I2I_R: { ASSERT_MSG(!instr.conversion.selector, "Unimplemented"); - std::string op_a = - regs.GetRegisterAsInteger(instr.gpr20, 0, instr.conversion.is_input_signed); + std::string op_a = regs.GetRegisterAsInteger( + instr.gpr20, 0, instr.conversion.is_input_signed, instr.conversion.src_size); if (instr.conversion.abs_a) { op_a = "abs(" + op_a + ')'; } regs.SetRegisterToInteger(instr.gpr0, instr.conversion.is_output_signed, 0, op_a, 1, - 1, instr.alu.saturate_d); + 1, instr.alu.saturate_d, 0, instr.conversion.dest_size); break; } case OpCode::Id::I2F_R: { + ASSERT_MSG(instr.conversion.dest_size == Register::Size::Word, "Unimplemented"); ASSERT_MSG(!instr.conversion.selector, "Unimplemented"); - std::string op_a = - regs.GetRegisterAsInteger(instr.gpr20, 0, instr.conversion.is_input_signed); + std::string op_a = regs.GetRegisterAsInteger( + instr.gpr20, 0, instr.conversion.is_input_signed, instr.conversion.src_size); if (instr.conversion.abs_a) { op_a = "abs(" + op_a + ')'; @@ -1139,6 +1181,8 @@ private: break; } case OpCode::Id::F2F_R: { + ASSERT_MSG(instr.conversion.dest_size == Register::Size::Word, "Unimplemented"); + ASSERT_MSG(instr.conversion.src_size == Register::Size::Word, "Unimplemented"); std::string op_a = regs.GetRegisterAsFloat(instr.gpr20); switch (instr.conversion.f2f.rounding) { @@ -1168,6 +1212,7 @@ private: break; } case OpCode::Id::F2I_R: { + ASSERT_MSG(instr.conversion.src_size == Register::Size::Word, "Unimplemented"); std::string op_a = regs.GetRegisterAsFloat(instr.gpr20); if (instr.conversion.abs_a) { @@ -1200,7 +1245,7 @@ private: } regs.SetRegisterToInteger(instr.gpr0, instr.conversion.is_output_signed, 0, op_a, 1, - 1); + 1, false, 0, instr.conversion.dest_size); break; } default: { diff --git a/src/video_core/renderer_opengl/gl_shader_gen.cpp b/src/video_core/renderer_opengl/gl_shader_gen.cpp index b88d592b7..c1e6fac9f 100644 --- a/src/video_core/renderer_opengl/gl_shader_gen.cpp +++ b/src/video_core/renderer_opengl/gl_shader_gen.cpp @@ -39,6 +39,10 @@ void main() { // Viewport can be flipped, which is unsupported by glViewport position.xy *= viewport_flip.xy; gl_Position = position; + + // TODO(bunnei): This is likely a hack, position.w should be interpolated as 1.0 + // For now, this is here to bring order in lieu of proper emulation + position.w = 1.0; } )"; out += program.first; diff --git a/src/video_core/textures/astc.cpp b/src/video_core/textures/astc.cpp new file mode 100644 index 000000000..3c4ad1c9d --- /dev/null +++ b/src/video_core/textures/astc.cpp @@ -0,0 +1,1646 @@ +// Copyright 2016 The University of North Carolina at Chapel Hill +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Please send all BUG REPORTS to <pavel@cs.unc.edu>. +// <http://gamma.cs.unc.edu/FasTC/> + +#include <algorithm> +#include <cassert> +#include <cstdint> +#include <cstring> +#include <vector> + +#include "video_core/textures/astc.h" + +class BitStream { +public: + BitStream(unsigned char* ptr, int nBits = 0, int start_offset = 0) + : m_BitsWritten(0), m_BitsRead(0), m_NumBits(nBits), m_CurByte(ptr), + m_NextBit(start_offset % 8), done(false) {} + + int GetBitsWritten() const { + return m_BitsWritten; + } + + ~BitStream() {} + + void WriteBitsR(unsigned int val, unsigned int nBits) { + for (unsigned int i = 0; i < nBits; i++) { + WriteBit((val >> (nBits - i - 1)) & 1); + } + } + + void WriteBits(unsigned int val, unsigned int nBits) { + for (unsigned int i = 0; i < nBits; i++) { + WriteBit((val >> i) & 1); + } + } + + int GetBitsRead() const { + return m_BitsRead; + } + + int ReadBit() { + + int bit = *m_CurByte >> m_NextBit++; + while (m_NextBit >= 8) { + m_NextBit -= 8; + m_CurByte++; + } + + m_BitsRead++; + return bit & 1; + } + + unsigned int ReadBits(unsigned int nBits) { + unsigned int ret = 0; + for (unsigned int i = 0; i < nBits; i++) { + ret |= (ReadBit() & 1) << i; + } + return ret; + } + +private: + void WriteBit(int b) { + + if (done) + return; + + const unsigned int mask = 1 << m_NextBit++; + + // clear the bit + *m_CurByte &= ~mask; + + // Write the bit, if necessary + if (b) + *m_CurByte |= mask; + + // Next byte? + if (m_NextBit >= 8) { + m_CurByte += 1; + m_NextBit = 0; + } + + done = done || ++m_BitsWritten >= m_NumBits; + } + + int m_BitsWritten; + const int m_NumBits; + unsigned char* m_CurByte; + int m_NextBit; + int m_BitsRead; + + bool done; +}; + +template <typename IntType> +class Bits { +private: + const IntType& m_Bits; + + // Don't copy + Bits() {} + Bits(const Bits&) {} + Bits& operator=(const Bits&) {} + +public: + explicit Bits(IntType& v) : m_Bits(v) {} + + uint8_t operator[](uint32_t bitPos) { + return static_cast<uint8_t>((m_Bits >> bitPos) & 1); + } + + IntType operator()(uint32_t start, uint32_t end) { + if (start == end) { + return (*this)[start]; + } else if (start > end) { + uint32_t t = start; + start = end; + end = t; + } + + uint64_t mask = (1 << (end - start + 1)) - 1; + return (m_Bits >> start) & mask; + } +}; + +enum EIntegerEncoding { eIntegerEncoding_JustBits, eIntegerEncoding_Quint, eIntegerEncoding_Trit }; + +class IntegerEncodedValue { +private: + const EIntegerEncoding m_Encoding; + const uint32_t m_NumBits; + uint32_t m_BitValue; + union { + uint32_t m_QuintValue; + uint32_t m_TritValue; + }; + +public: + // Jank, but we're not doing any heavy lifting in this class, so it's + // probably OK. It allows us to use these in std::vectors... + IntegerEncodedValue& operator=(const IntegerEncodedValue& other) { + new (this) IntegerEncodedValue(other); + return *this; + } + + IntegerEncodedValue(EIntegerEncoding encoding, uint32_t numBits) + : m_Encoding(encoding), m_NumBits(numBits) {} + + EIntegerEncoding GetEncoding() const { + return m_Encoding; + } + uint32_t BaseBitLength() const { + return m_NumBits; + } + + uint32_t GetBitValue() const { + return m_BitValue; + } + void SetBitValue(uint32_t val) { + m_BitValue = val; + } + + uint32_t GetTritValue() const { + return m_TritValue; + } + void SetTritValue(uint32_t val) { + m_TritValue = val; + } + + uint32_t GetQuintValue() const { + return m_QuintValue; + } + void SetQuintValue(uint32_t val) { + m_QuintValue = val; + } + + bool MatchesEncoding(const IntegerEncodedValue& other) { + return m_Encoding == other.m_Encoding && m_NumBits == other.m_NumBits; + } + + // Returns the number of bits required to encode nVals values. + uint32_t GetBitLength(uint32_t nVals) { + uint32_t totalBits = m_NumBits * nVals; + if (m_Encoding == eIntegerEncoding_Trit) { + totalBits += (nVals * 8 + 4) / 5; + } else if (m_Encoding == eIntegerEncoding_Quint) { + totalBits += (nVals * 7 + 2) / 3; + } + return totalBits; + } + + // Count the number of bits set in a number. + static inline uint32_t Popcnt(uint32_t n) { + uint32_t c; + for (c = 0; n; c++) { + n &= n - 1; + } + return c; + } + + // Returns a new instance of this struct that corresponds to the + // can take no more than maxval values + static IntegerEncodedValue CreateEncoding(uint32_t maxVal) { + while (maxVal > 0) { + uint32_t check = maxVal + 1; + + // Is maxVal a power of two? + if (!(check & (check - 1))) { + return IntegerEncodedValue(eIntegerEncoding_JustBits, Popcnt(maxVal)); + } + + // Is maxVal of the type 3*2^n - 1? + if ((check % 3 == 0) && !((check / 3) & ((check / 3) - 1))) { + return IntegerEncodedValue(eIntegerEncoding_Trit, Popcnt(check / 3 - 1)); + } + + // Is maxVal of the type 5*2^n - 1? + if ((check % 5 == 0) && !((check / 5) & ((check / 5) - 1))) { + return IntegerEncodedValue(eIntegerEncoding_Quint, Popcnt(check / 5 - 1)); + } + + // Apparently it can't be represented with a bounded integer sequence... + // just iterate. + maxVal--; + } + return IntegerEncodedValue(eIntegerEncoding_JustBits, 0); + } + + // Fills result with the values that are encoded in the given + // bitstream. We must know beforehand what the maximum possible + // value is, and how many values we're decoding. + static void DecodeIntegerSequence(std::vector<IntegerEncodedValue>& result, BitStream& bits, + uint32_t maxRange, uint32_t nValues) { + // Determine encoding parameters + IntegerEncodedValue val = IntegerEncodedValue::CreateEncoding(maxRange); + + // Start decoding + uint32_t nValsDecoded = 0; + while (nValsDecoded < nValues) { + switch (val.GetEncoding()) { + case eIntegerEncoding_Quint: + DecodeQuintBlock(bits, result, val.BaseBitLength()); + nValsDecoded += 3; + break; + + case eIntegerEncoding_Trit: + DecodeTritBlock(bits, result, val.BaseBitLength()); + nValsDecoded += 5; + break; + + case eIntegerEncoding_JustBits: + val.SetBitValue(bits.ReadBits(val.BaseBitLength())); + result.push_back(val); + nValsDecoded++; + break; + } + } + } + +private: + static void DecodeTritBlock(BitStream& bits, std::vector<IntegerEncodedValue>& result, + uint32_t nBitsPerValue) { + // Implement the algorithm in section C.2.12 + uint32_t m[5]; + uint32_t t[5]; + uint32_t T; + + // Read the trit encoded block according to + // table C.2.14 + m[0] = bits.ReadBits(nBitsPerValue); + T = bits.ReadBits(2); + m[1] = bits.ReadBits(nBitsPerValue); + T |= bits.ReadBits(2) << 2; + m[2] = bits.ReadBits(nBitsPerValue); + T |= bits.ReadBit() << 4; + m[3] = bits.ReadBits(nBitsPerValue); + T |= bits.ReadBits(2) << 5; + m[4] = bits.ReadBits(nBitsPerValue); + T |= bits.ReadBit() << 7; + + uint32_t C = 0; + + Bits<uint32_t> Tb(T); + if (Tb(2, 4) == 7) { + C = (Tb(5, 7) << 2) | Tb(0, 1); + t[4] = t[3] = 2; + } else { + C = Tb(0, 4); + if (Tb(5, 6) == 3) { + t[4] = 2; + t[3] = Tb[7]; + } else { + t[4] = Tb[7]; + t[3] = Tb(5, 6); + } + } + + Bits<uint32_t> Cb(C); + if (Cb(0, 1) == 3) { + t[2] = 2; + t[1] = Cb[4]; + t[0] = (Cb[3] << 1) | (Cb[2] & ~Cb[3]); + } else if (Cb(2, 3) == 3) { + t[2] = 2; + t[1] = 2; + t[0] = Cb(0, 1); + } else { + t[2] = Cb[4]; + t[1] = Cb(2, 3); + t[0] = (Cb[1] << 1) | (Cb[0] & ~Cb[1]); + } + + for (uint32_t i = 0; i < 5; i++) { + IntegerEncodedValue val(eIntegerEncoding_Trit, nBitsPerValue); + val.SetBitValue(m[i]); + val.SetTritValue(t[i]); + result.push_back(val); + } + } + + static void DecodeQuintBlock(BitStream& bits, std::vector<IntegerEncodedValue>& result, + uint32_t nBitsPerValue) { + // Implement the algorithm in section C.2.12 + uint32_t m[3]; + uint32_t q[3]; + uint32_t Q; + + // Read the trit encoded block according to + // table C.2.15 + m[0] = bits.ReadBits(nBitsPerValue); + Q = bits.ReadBits(3); + m[1] = bits.ReadBits(nBitsPerValue); + Q |= bits.ReadBits(2) << 3; + m[2] = bits.ReadBits(nBitsPerValue); + Q |= bits.ReadBits(2) << 5; + + Bits<uint32_t> Qb(Q); + if (Qb(1, 2) == 3 && Qb(5, 6) == 0) { + q[0] = q[1] = 4; + q[2] = (Qb[0] << 2) | ((Qb[4] & ~Qb[0]) << 1) | (Qb[3] & ~Qb[0]); + } else { + uint32_t C = 0; + if (Qb(1, 2) == 3) { + q[2] = 4; + C = (Qb(3, 4) << 3) | ((~Qb(5, 6) & 3) << 1) | Qb[0]; + } else { + q[2] = Qb(5, 6); + C = Qb(0, 4); + } + + Bits<uint32_t> Cb(C); + if (Cb(0, 2) == 5) { + q[1] = 4; + q[0] = Cb(3, 4); + } else { + q[1] = Cb(3, 4); + q[0] = Cb(0, 2); + } + } + + for (uint32_t i = 0; i < 3; i++) { + IntegerEncodedValue val(eIntegerEncoding_Quint, nBitsPerValue); + val.m_BitValue = m[i]; + val.m_QuintValue = q[i]; + result.push_back(val); + } + } +}; + +namespace ASTCC { + +struct TexelWeightParams { + uint32_t m_Width; + uint32_t m_Height; + bool m_bDualPlane; + uint32_t m_MaxWeight; + bool m_bError; + bool m_bVoidExtentLDR; + bool m_bVoidExtentHDR; + + TexelWeightParams() { + memset(this, 0, sizeof(*this)); + } + + uint32_t GetPackedBitSize() { + // How many indices do we have? + uint32_t nIdxs = m_Height * m_Width; + if (m_bDualPlane) { + nIdxs *= 2; + } + + return IntegerEncodedValue::CreateEncoding(m_MaxWeight).GetBitLength(nIdxs); + } + + uint32_t GetNumWeightValues() const { + uint32_t ret = m_Width * m_Height; + if (m_bDualPlane) { + ret *= 2; + } + return ret; + } +}; + +TexelWeightParams DecodeBlockInfo(BitStream& strm) { + TexelWeightParams params; + + // Read the entire block mode all at once + uint16_t modeBits = strm.ReadBits(11); + + // Does this match the void extent block mode? + if ((modeBits & 0x01FF) == 0x1FC) { + if (modeBits & 0x200) { + params.m_bVoidExtentHDR = true; + } else { + params.m_bVoidExtentLDR = true; + } + + // Next two bits must be one. + if (!(modeBits & 0x400) || !strm.ReadBit()) { + params.m_bError = true; + } + + return params; + } + + // First check if the last four bits are zero + if ((modeBits & 0xF) == 0) { + params.m_bError = true; + return params; + } + + // If the last two bits are zero, then if bits + // [6-8] are all ones, this is also reserved. + if ((modeBits & 0x3) == 0 && (modeBits & 0x1C0) == 0x1C0) { + params.m_bError = true; + return params; + } + + // Otherwise, there is no error... Figure out the layout + // of the block mode. Layout is determined by a number + // between 0 and 9 corresponding to table C.2.8 of the + // ASTC spec. + uint32_t layout = 0; + + if ((modeBits & 0x1) || (modeBits & 0x2)) { + // layout is in [0-4] + if (modeBits & 0x8) { + // layout is in [2-4] + if (modeBits & 0x4) { + // layout is in [3-4] + if (modeBits & 0x100) { + layout = 4; + } else { + layout = 3; + } + } else { + layout = 2; + } + } else { + // layout is in [0-1] + if (modeBits & 0x4) { + layout = 1; + } else { + layout = 0; + } + } + } else { + // layout is in [5-9] + if (modeBits & 0x100) { + // layout is in [7-9] + if (modeBits & 0x80) { + // layout is in [7-8] + assert((modeBits & 0x40) == 0U); + if (modeBits & 0x20) { + layout = 8; + } else { + layout = 7; + } + } else { + layout = 9; + } + } else { + // layout is in [5-6] + if (modeBits & 0x80) { + layout = 6; + } else { + layout = 5; + } + } + } + + assert(layout < 10); + + // Determine R + uint32_t R = !!(modeBits & 0x10); + if (layout < 5) { + R |= (modeBits & 0x3) << 1; + } else { + R |= (modeBits & 0xC) >> 1; + } + assert(2 <= R && R <= 7); + + // Determine width & height + switch (layout) { + case 0: { + uint32_t A = (modeBits >> 5) & 0x3; + uint32_t B = (modeBits >> 7) & 0x3; + params.m_Width = B + 4; + params.m_Height = A + 2; + break; + } + + case 1: { + uint32_t A = (modeBits >> 5) & 0x3; + uint32_t B = (modeBits >> 7) & 0x3; + params.m_Width = B + 8; + params.m_Height = A + 2; + break; + } + + case 2: { + uint32_t A = (modeBits >> 5) & 0x3; + uint32_t B = (modeBits >> 7) & 0x3; + params.m_Width = A + 2; + params.m_Height = B + 8; + break; + } + + case 3: { + uint32_t A = (modeBits >> 5) & 0x3; + uint32_t B = (modeBits >> 7) & 0x1; + params.m_Width = A + 2; + params.m_Height = B + 6; + break; + } + + case 4: { + uint32_t A = (modeBits >> 5) & 0x3; + uint32_t B = (modeBits >> 7) & 0x1; + params.m_Width = B + 2; + params.m_Height = A + 2; + break; + } + + case 5: { + uint32_t A = (modeBits >> 5) & 0x3; + params.m_Width = 12; + params.m_Height = A + 2; + break; + } + + case 6: { + uint32_t A = (modeBits >> 5) & 0x3; + params.m_Width = A + 2; + params.m_Height = 12; + break; + } + + case 7: { + params.m_Width = 6; + params.m_Height = 10; + break; + } + + case 8: { + params.m_Width = 10; + params.m_Height = 6; + break; + } + + case 9: { + uint32_t A = (modeBits >> 5) & 0x3; + uint32_t B = (modeBits >> 9) & 0x3; + params.m_Width = A + 6; + params.m_Height = B + 6; + break; + } + + default: + assert(!"Don't know this layout..."); + params.m_bError = true; + break; + } + + // Determine whether or not we're using dual planes + // and/or high precision layouts. + bool D = (layout != 9) && (modeBits & 0x400); + bool H = (layout != 9) && (modeBits & 0x200); + + if (H) { + const uint32_t maxWeights[6] = {9, 11, 15, 19, 23, 31}; + params.m_MaxWeight = maxWeights[R - 2]; + } else { + const uint32_t maxWeights[6] = {1, 2, 3, 4, 5, 7}; + params.m_MaxWeight = maxWeights[R - 2]; + } + + params.m_bDualPlane = D; + + return params; +} + +void FillVoidExtentLDR(BitStream& strm, uint32_t* const outBuf, uint32_t blockWidth, + uint32_t blockHeight) { + // Don't actually care about the void extent, just read the bits... + for (int i = 0; i < 4; ++i) { + strm.ReadBits(13); + } + + // Decode the RGBA components and renormalize them to the range [0, 255] + uint16_t r = strm.ReadBits(16); + uint16_t g = strm.ReadBits(16); + uint16_t b = strm.ReadBits(16); + uint16_t a = strm.ReadBits(16); + + uint32_t rgba = (r >> 8) | (g & 0xFF00) | (static_cast<uint32_t>(b) & 0xFF00) << 8 | + (static_cast<uint32_t>(a) & 0xFF00) << 16; + + for (uint32_t j = 0; j < blockHeight; j++) + for (uint32_t i = 0; i < blockWidth; i++) { + outBuf[j * blockWidth + i] = rgba; + } +} + +void FillError(uint32_t* outBuf, uint32_t blockWidth, uint32_t blockHeight) { + for (uint32_t j = 0; j < blockHeight; j++) + for (uint32_t i = 0; i < blockWidth; i++) { + outBuf[j * blockWidth + i] = 0xFFFF00FF; + } +} + +// Replicates low numBits such that [(toBit - 1):(toBit - 1 - fromBit)] +// is the same as [(numBits - 1):0] and repeats all the way down. +template <typename IntType> +IntType Replicate(const IntType& val, uint32_t numBits, uint32_t toBit) { + if (numBits == 0) + return 0; + if (toBit == 0) + return 0; + IntType v = val & ((1 << numBits) - 1); + IntType res = v; + uint32_t reslen = numBits; + while (reslen < toBit) { + uint32_t comp = 0; + if (numBits > toBit - reslen) { + uint32_t newshift = toBit - reslen; + comp = numBits - newshift; + numBits = newshift; + } + res <<= numBits; + res |= v >> comp; + reslen += numBits; + } + return res; +} + +class Pixel { +protected: + typedef int16_t ChannelType; + uint8_t m_BitDepth[4]; + int16_t color[4]; + +public: + Pixel() { + for (int i = 0; i < 4; i++) { + m_BitDepth[i] = 8; + color[i] = 0; + } + } + + Pixel(ChannelType a, ChannelType r, ChannelType g, ChannelType b, unsigned bitDepth = 8) { + for (int i = 0; i < 4; i++) + m_BitDepth[i] = bitDepth; + + color[0] = a; + color[1] = r; + color[2] = g; + color[3] = b; + } + + // Changes the depth of each pixel. This scales the values to + // the appropriate bit depth by either truncating the least + // significant bits when going from larger to smaller bit depth + // or by repeating the most significant bits when going from + // smaller to larger bit depths. + void ChangeBitDepth(const uint8_t (&depth)[4]) { + for (uint32_t i = 0; i < 4; i++) { + Component(i) = ChangeBitDepth(Component(i), m_BitDepth[i], depth[i]); + m_BitDepth[i] = depth[i]; + } + } + + template <typename IntType> + static float ConvertChannelToFloat(IntType channel, uint8_t bitDepth) { + float denominator = static_cast<float>((1 << bitDepth) - 1); + return static_cast<float>(channel) / denominator; + } + + // Changes the bit depth of a single component. See the comment + // above for how we do this. + static ChannelType ChangeBitDepth(Pixel::ChannelType val, uint8_t oldDepth, uint8_t newDepth) { + assert(newDepth <= 8); + assert(oldDepth <= 8); + + if (oldDepth == newDepth) { + // Do nothing + return val; + } else if (oldDepth == 0 && newDepth != 0) { + return (1 << newDepth) - 1; + } else if (newDepth > oldDepth) { + return Replicate(val, oldDepth, newDepth); + } else { + // oldDepth > newDepth + if (newDepth == 0) { + return 0xFF; + } else { + uint8_t bitsWasted = oldDepth - newDepth; + uint16_t v = static_cast<uint16_t>(val); + v = (v + (1 << (bitsWasted - 1))) >> bitsWasted; + v = ::std::min<uint16_t>(::std::max<uint16_t>(0, v), (1 << newDepth) - 1); + return static_cast<uint8_t>(v); + } + } + + assert(!"We shouldn't get here."); + return 0; + } + + const ChannelType& A() const { + return color[0]; + } + ChannelType& A() { + return color[0]; + } + const ChannelType& R() const { + return color[1]; + } + ChannelType& R() { + return color[1]; + } + const ChannelType& G() const { + return color[2]; + } + ChannelType& G() { + return color[2]; + } + const ChannelType& B() const { + return color[3]; + } + ChannelType& B() { + return color[3]; + } + const ChannelType& Component(uint32_t idx) const { + return color[idx]; + } + ChannelType& Component(uint32_t idx) { + return color[idx]; + } + + void GetBitDepth(uint8_t (&outDepth)[4]) const { + for (int i = 0; i < 4; i++) { + outDepth[i] = m_BitDepth[i]; + } + } + + // Take all of the components, transform them to their 8-bit variants, + // and then pack each channel into an R8G8B8A8 32-bit integer. We assume + // that the architecture is little-endian, so the alpha channel will end + // up in the most-significant byte. + uint32_t Pack() const { + Pixel eightBit(*this); + const uint8_t eightBitDepth[4] = {8, 8, 8, 8}; + eightBit.ChangeBitDepth(eightBitDepth); + + uint32_t r = 0; + r |= eightBit.A(); + r <<= 8; + r |= eightBit.B(); + r <<= 8; + r |= eightBit.G(); + r <<= 8; + r |= eightBit.R(); + return r; + } + + // Clamps the pixel to the range [0,255] + void ClampByte() { + for (uint32_t i = 0; i < 4; i++) { + color[i] = (color[i] < 0) ? 0 : ((color[i] > 255) ? 255 : color[i]); + } + } + + void MakeOpaque() { + A() = 255; + } +}; + +void DecodeColorValues(uint32_t* out, uint8_t* data, uint32_t* modes, const uint32_t nPartitions, + const uint32_t nBitsForColorData) { + // First figure out how many color values we have + uint32_t nValues = 0; + for (uint32_t i = 0; i < nPartitions; i++) { + nValues += ((modes[i] >> 2) + 1) << 1; + } + + // Then based on the number of values and the remaining number of bits, + // figure out the max value for each of them... + uint32_t range = 256; + while (--range > 0) { + IntegerEncodedValue val = IntegerEncodedValue::CreateEncoding(range); + uint32_t bitLength = val.GetBitLength(nValues); + if (bitLength <= nBitsForColorData) { + // Find the smallest possible range that matches the given encoding + while (--range > 0) { + IntegerEncodedValue newval = IntegerEncodedValue::CreateEncoding(range); + if (!newval.MatchesEncoding(val)) { + break; + } + } + + // Return to last matching range. + range++; + break; + } + } + + // We now have enough to decode our integer sequence. + std::vector<IntegerEncodedValue> decodedColorValues; + BitStream colorStream(data); + IntegerEncodedValue::DecodeIntegerSequence(decodedColorValues, colorStream, range, nValues); + + // Once we have the decoded values, we need to dequantize them to the 0-255 range + // This procedure is outlined in ASTC spec C.2.13 + uint32_t outIdx = 0; + std::vector<IntegerEncodedValue>::const_iterator itr; + for (itr = decodedColorValues.begin(); itr != decodedColorValues.end(); itr++) { + // Have we already decoded all that we need? + if (outIdx >= nValues) { + break; + } + + const IntegerEncodedValue& val = *itr; + uint32_t bitlen = val.BaseBitLength(); + uint32_t bitval = val.GetBitValue(); + + assert(bitlen >= 1); + + uint32_t A = 0, B = 0, C = 0, D = 0; + // A is just the lsb replicated 9 times. + A = Replicate(bitval & 1, 1, 9); + + switch (val.GetEncoding()) { + // Replicate bits + case eIntegerEncoding_JustBits: + out[outIdx++] = Replicate(bitval, bitlen, 8); + break; + + // Use algorithm in C.2.13 + case eIntegerEncoding_Trit: { + + D = val.GetTritValue(); + + switch (bitlen) { + case 1: { + C = 204; + } break; + + case 2: { + C = 93; + // B = b000b0bb0 + uint32_t b = (bitval >> 1) & 1; + B = (b << 8) | (b << 4) | (b << 2) | (b << 1); + } break; + + case 3: { + C = 44; + // B = cb000cbcb + uint32_t cb = (bitval >> 1) & 3; + B = (cb << 7) | (cb << 2) | cb; + } break; + + case 4: { + C = 22; + // B = dcb000dcb + uint32_t dcb = (bitval >> 1) & 7; + B = (dcb << 6) | dcb; + } break; + + case 5: { + C = 11; + // B = edcb000ed + uint32_t edcb = (bitval >> 1) & 0xF; + B = (edcb << 5) | (edcb >> 2); + } break; + + case 6: { + C = 5; + // B = fedcb000f + uint32_t fedcb = (bitval >> 1) & 0x1F; + B = (fedcb << 4) | (fedcb >> 4); + } break; + + default: + assert(!"Unsupported trit encoding for color values!"); + break; + } // switch(bitlen) + } // case eIntegerEncoding_Trit + break; + + case eIntegerEncoding_Quint: { + + D = val.GetQuintValue(); + + switch (bitlen) { + case 1: { + C = 113; + } break; + + case 2: { + C = 54; + // B = b0000bb00 + uint32_t b = (bitval >> 1) & 1; + B = (b << 8) | (b << 3) | (b << 2); + } break; + + case 3: { + C = 26; + // B = cb0000cbc + uint32_t cb = (bitval >> 1) & 3; + B = (cb << 7) | (cb << 1) | (cb >> 1); + } break; + + case 4: { + C = 13; + // B = dcb0000dc + uint32_t dcb = (bitval >> 1) & 7; + B = (dcb << 6) | (dcb >> 1); + } break; + + case 5: { + C = 6; + // B = edcb0000e + uint32_t edcb = (bitval >> 1) & 0xF; + B = (edcb << 5) | (edcb >> 3); + } break; + + default: + assert(!"Unsupported quint encoding for color values!"); + break; + } // switch(bitlen) + } // case eIntegerEncoding_Quint + break; + } // switch(val.GetEncoding()) + + if (val.GetEncoding() != eIntegerEncoding_JustBits) { + uint32_t T = D * C + B; + T ^= A; + T = (A & 0x80) | (T >> 2); + out[outIdx++] = T; + } + } + + // Make sure that each of our values is in the proper range... + for (uint32_t i = 0; i < nValues; i++) { + assert(out[i] <= 255); + } +} + +uint32_t UnquantizeTexelWeight(const IntegerEncodedValue& val) { + uint32_t bitval = val.GetBitValue(); + uint32_t bitlen = val.BaseBitLength(); + + uint32_t A = Replicate(bitval & 1, 1, 7); + uint32_t B = 0, C = 0, D = 0; + + uint32_t result = 0; + switch (val.GetEncoding()) { + case eIntegerEncoding_JustBits: + result = Replicate(bitval, bitlen, 6); + break; + + case eIntegerEncoding_Trit: { + D = val.GetTritValue(); + assert(D < 3); + + switch (bitlen) { + case 0: { + uint32_t results[3] = {0, 32, 63}; + result = results[D]; + } break; + + case 1: { + C = 50; + } break; + + case 2: { + C = 23; + uint32_t b = (bitval >> 1) & 1; + B = (b << 6) | (b << 2) | b; + } break; + + case 3: { + C = 11; + uint32_t cb = (bitval >> 1) & 3; + B = (cb << 5) | cb; + } break; + + default: + assert(!"Invalid trit encoding for texel weight"); + break; + } + } break; + + case eIntegerEncoding_Quint: { + D = val.GetQuintValue(); + assert(D < 5); + + switch (bitlen) { + case 0: { + uint32_t results[5] = {0, 16, 32, 47, 63}; + result = results[D]; + } break; + + case 1: { + C = 28; + } break; + + case 2: { + C = 13; + uint32_t b = (bitval >> 1) & 1; + B = (b << 6) | (b << 1); + } break; + + default: + assert(!"Invalid quint encoding for texel weight"); + break; + } + } break; + } + + if (val.GetEncoding() != eIntegerEncoding_JustBits && bitlen > 0) { + // Decode the value... + result = D * C + B; + result ^= A; + result = (A & 0x20) | (result >> 2); + } + + assert(result < 64); + + // Change from [0,63] to [0,64] + if (result > 32) { + result += 1; + } + + return result; +} + +void UnquantizeTexelWeights(uint32_t out[2][144], std::vector<IntegerEncodedValue>& weights, + const TexelWeightParams& params, const uint32_t blockWidth, + const uint32_t blockHeight) { + uint32_t weightIdx = 0; + uint32_t unquantized[2][144]; + std::vector<IntegerEncodedValue>::const_iterator itr; + for (itr = weights.begin(); itr != weights.end(); itr++) { + unquantized[0][weightIdx] = UnquantizeTexelWeight(*itr); + + if (params.m_bDualPlane) { + itr++; + unquantized[1][weightIdx] = UnquantizeTexelWeight(*itr); + if (itr == weights.end()) { + break; + } + } + + if (++weightIdx >= (params.m_Width * params.m_Height)) + break; + } + + // Do infill if necessary (Section C.2.18) ... + uint32_t Ds = (1024 + (blockWidth / 2)) / (blockWidth - 1); + uint32_t Dt = (1024 + (blockHeight / 2)) / (blockHeight - 1); + + const uint32_t kPlaneScale = params.m_bDualPlane ? 2U : 1U; + for (uint32_t plane = 0; plane < kPlaneScale; plane++) + for (uint32_t t = 0; t < blockHeight; t++) + for (uint32_t s = 0; s < blockWidth; s++) { + uint32_t cs = Ds * s; + uint32_t ct = Dt * t; + + uint32_t gs = (cs * (params.m_Width - 1) + 32) >> 6; + uint32_t gt = (ct * (params.m_Height - 1) + 32) >> 6; + + uint32_t js = gs >> 4; + uint32_t fs = gs & 0xF; + + uint32_t jt = gt >> 4; + uint32_t ft = gt & 0x0F; + + uint32_t w11 = (fs * ft + 8) >> 4; + uint32_t w10 = ft - w11; + uint32_t w01 = fs - w11; + uint32_t w00 = 16 - fs - ft + w11; + + uint32_t v0 = js + jt * params.m_Width; + +#define FIND_TEXEL(tidx, bidx) \ + uint32_t p##bidx = 0; \ + do { \ + if ((tidx) < (params.m_Width * params.m_Height)) { \ + p##bidx = unquantized[plane][(tidx)]; \ + } \ + } while (0) + + FIND_TEXEL(v0, 00); + FIND_TEXEL(v0 + 1, 01); + FIND_TEXEL(v0 + params.m_Width, 10); + FIND_TEXEL(v0 + params.m_Width + 1, 11); + +#undef FIND_TEXEL + + out[plane][t * blockWidth + s] = + (p00 * w00 + p01 * w01 + p10 * w10 + p11 * w11 + 8) >> 4; + } +} + +// Transfers a bit as described in C.2.14 +static inline void BitTransferSigned(int32_t& a, int32_t& b) { + b >>= 1; + b |= a & 0x80; + a >>= 1; + a &= 0x3F; + if (a & 0x20) + a -= 0x40; +} + +// Adds more precision to the blue channel as described +// in C.2.14 +static inline Pixel BlueContract(int32_t a, int32_t r, int32_t g, int32_t b) { + return Pixel(static_cast<int16_t>(a), static_cast<int16_t>((r + b) >> 1), + static_cast<int16_t>((g + b) >> 1), static_cast<int16_t>(b)); +} + +// Partition selection functions as specified in +// C.2.21 +static inline uint32_t hash52(uint32_t p) { + p ^= p >> 15; + p -= p << 17; + p += p << 7; + p += p << 4; + p ^= p >> 5; + p += p << 16; + p ^= p >> 7; + p ^= p >> 3; + p ^= p << 6; + p ^= p >> 17; + return p; +} + +static uint32_t SelectPartition(int32_t seed, int32_t x, int32_t y, int32_t z, + int32_t partitionCount, int32_t smallBlock) { + if (1 == partitionCount) + return 0; + + if (smallBlock) { + x <<= 1; + y <<= 1; + z <<= 1; + } + + seed += (partitionCount - 1) * 1024; + + uint32_t rnum = hash52(static_cast<uint32_t>(seed)); + uint8_t seed1 = static_cast<uint8_t>(rnum & 0xF); + uint8_t seed2 = static_cast<uint8_t>((rnum >> 4) & 0xF); + uint8_t seed3 = static_cast<uint8_t>((rnum >> 8) & 0xF); + uint8_t seed4 = static_cast<uint8_t>((rnum >> 12) & 0xF); + uint8_t seed5 = static_cast<uint8_t>((rnum >> 16) & 0xF); + uint8_t seed6 = static_cast<uint8_t>((rnum >> 20) & 0xF); + uint8_t seed7 = static_cast<uint8_t>((rnum >> 24) & 0xF); + uint8_t seed8 = static_cast<uint8_t>((rnum >> 28) & 0xF); + uint8_t seed9 = static_cast<uint8_t>((rnum >> 18) & 0xF); + uint8_t seed10 = static_cast<uint8_t>((rnum >> 22) & 0xF); + uint8_t seed11 = static_cast<uint8_t>((rnum >> 26) & 0xF); + uint8_t seed12 = static_cast<uint8_t>(((rnum >> 30) | (rnum << 2)) & 0xF); + + seed1 *= seed1; + seed2 *= seed2; + seed3 *= seed3; + seed4 *= seed4; + seed5 *= seed5; + seed6 *= seed6; + seed7 *= seed7; + seed8 *= seed8; + seed9 *= seed9; + seed10 *= seed10; + seed11 *= seed11; + seed12 *= seed12; + + int32_t sh1, sh2, sh3; + if (seed & 1) { + sh1 = (seed & 2) ? 4 : 5; + sh2 = (partitionCount == 3) ? 6 : 5; + } else { + sh1 = (partitionCount == 3) ? 6 : 5; + sh2 = (seed & 2) ? 4 : 5; + } + sh3 = (seed & 0x10) ? sh1 : sh2; + + seed1 >>= sh1; + seed2 >>= sh2; + seed3 >>= sh1; + seed4 >>= sh2; + seed5 >>= sh1; + seed6 >>= sh2; + seed7 >>= sh1; + seed8 >>= sh2; + seed9 >>= sh3; + seed10 >>= sh3; + seed11 >>= sh3; + seed12 >>= sh3; + + int32_t a = seed1 * x + seed2 * y + seed11 * z + (rnum >> 14); + int32_t b = seed3 * x + seed4 * y + seed12 * z + (rnum >> 10); + int32_t c = seed5 * x + seed6 * y + seed9 * z + (rnum >> 6); + int32_t d = seed7 * x + seed8 * y + seed10 * z + (rnum >> 2); + + a &= 0x3F; + b &= 0x3F; + c &= 0x3F; + d &= 0x3F; + + if (partitionCount < 4) + d = 0; + if (partitionCount < 3) + c = 0; + + if (a >= b && a >= c && a >= d) + return 0; + else if (b >= c && b >= d) + return 1; + else if (c >= d) + return 2; + return 3; +} + +static inline uint32_t Select2DPartition(int32_t seed, int32_t x, int32_t y, int32_t partitionCount, + int32_t smallBlock) { + return SelectPartition(seed, x, y, 0, partitionCount, smallBlock); +} + +// Section C.2.14 +void ComputeEndpoints(Pixel& ep1, Pixel& ep2, const uint32_t*& colorValues, + uint32_t colorEndpointMode) { +#define READ_UINT_VALUES(N) \ + uint32_t v[N]; \ + for (uint32_t i = 0; i < N; i++) { \ + v[i] = *(colorValues++); \ + } + +#define READ_INT_VALUES(N) \ + int32_t v[N]; \ + for (uint32_t i = 0; i < N; i++) { \ + v[i] = static_cast<int32_t>(*(colorValues++)); \ + } + + switch (colorEndpointMode) { + case 0: { + READ_UINT_VALUES(2) + ep1 = Pixel(0xFF, v[0], v[0], v[0]); + ep2 = Pixel(0xFF, v[1], v[1], v[1]); + } break; + + case 1: { + READ_UINT_VALUES(2) + uint32_t L0 = (v[0] >> 2) | (v[1] & 0xC0); + uint32_t L1 = std::max(L0 + (v[1] & 0x3F), 0xFFU); + ep1 = Pixel(0xFF, L0, L0, L0); + ep2 = Pixel(0xFF, L1, L1, L1); + } break; + + case 4: { + READ_UINT_VALUES(4) + ep1 = Pixel(v[2], v[0], v[0], v[0]); + ep2 = Pixel(v[3], v[1], v[1], v[1]); + } break; + + case 5: { + READ_INT_VALUES(4) + BitTransferSigned(v[1], v[0]); + BitTransferSigned(v[3], v[2]); + ep1 = Pixel(v[2], v[0], v[0], v[0]); + ep2 = Pixel(v[2] + v[3], v[0] + v[1], v[0] + v[1], v[0] + v[1]); + ep1.ClampByte(); + ep2.ClampByte(); + } break; + + case 6: { + READ_UINT_VALUES(4) + ep1 = Pixel(0xFF, v[0] * v[3] >> 8, v[1] * v[3] >> 8, v[2] * v[3] >> 8); + ep2 = Pixel(0xFF, v[0], v[1], v[2]); + } break; + + case 8: { + READ_UINT_VALUES(6) + if (v[1] + v[3] + v[5] >= v[0] + v[2] + v[4]) { + ep1 = Pixel(0xFF, v[0], v[2], v[4]); + ep2 = Pixel(0xFF, v[1], v[3], v[5]); + } else { + ep1 = BlueContract(0xFF, v[1], v[3], v[5]); + ep2 = BlueContract(0xFF, v[0], v[2], v[4]); + } + } break; + + case 9: { + READ_INT_VALUES(6) + BitTransferSigned(v[1], v[0]); + BitTransferSigned(v[3], v[2]); + BitTransferSigned(v[5], v[4]); + if (v[1] + v[3] + v[5] >= 0) { + ep1 = Pixel(0xFF, v[0], v[2], v[4]); + ep2 = Pixel(0xFF, v[0] + v[1], v[2] + v[3], v[4] + v[5]); + } else { + ep1 = BlueContract(0xFF, v[0] + v[1], v[2] + v[3], v[4] + v[5]); + ep2 = BlueContract(0xFF, v[0], v[2], v[4]); + } + ep1.ClampByte(); + ep2.ClampByte(); + } break; + + case 10: { + READ_UINT_VALUES(6) + ep1 = Pixel(v[4], v[0] * v[3] >> 8, v[1] * v[3] >> 8, v[2] * v[3] >> 8); + ep2 = Pixel(v[5], v[0], v[1], v[2]); + } break; + + case 12: { + READ_UINT_VALUES(8) + if (v[1] + v[3] + v[5] >= v[0] + v[2] + v[4]) { + ep1 = Pixel(v[6], v[0], v[2], v[4]); + ep2 = Pixel(v[7], v[1], v[3], v[5]); + } else { + ep1 = BlueContract(v[7], v[1], v[3], v[5]); + ep2 = BlueContract(v[6], v[0], v[2], v[4]); + } + } break; + + case 13: { + READ_INT_VALUES(8) + BitTransferSigned(v[1], v[0]); + BitTransferSigned(v[3], v[2]); + BitTransferSigned(v[5], v[4]); + BitTransferSigned(v[7], v[6]); + if (v[1] + v[3] + v[5] >= 0) { + ep1 = Pixel(v[6], v[0], v[2], v[4]); + ep2 = Pixel(v[7] + v[6], v[0] + v[1], v[2] + v[3], v[4] + v[5]); + } else { + ep1 = BlueContract(v[6] + v[7], v[0] + v[1], v[2] + v[3], v[4] + v[5]); + ep2 = BlueContract(v[6], v[0], v[2], v[4]); + } + ep1.ClampByte(); + ep2.ClampByte(); + } break; + + default: + assert(!"Unsupported color endpoint mode (is it HDR?)"); + break; + } + +#undef READ_UINT_VALUES +#undef READ_INT_VALUES +} + +void DecompressBlock(uint8_t inBuf[16], const uint32_t blockWidth, const uint32_t blockHeight, + uint32_t* outBuf) { + BitStream strm(inBuf); + TexelWeightParams weightParams = DecodeBlockInfo(strm); + + // Was there an error? + if (weightParams.m_bError) { + assert(!"Invalid block mode"); + FillError(outBuf, blockWidth, blockHeight); + return; + } + + if (weightParams.m_bVoidExtentLDR) { + FillVoidExtentLDR(strm, outBuf, blockWidth, blockHeight); + return; + } + + if (weightParams.m_bVoidExtentHDR) { + assert(!"HDR void extent blocks are unsupported!"); + FillError(outBuf, blockWidth, blockHeight); + return; + } + + if (weightParams.m_Width > blockWidth) { + assert(!"Texel weight grid width should be smaller than block width"); + FillError(outBuf, blockWidth, blockHeight); + return; + } + + if (weightParams.m_Height > blockHeight) { + assert(!"Texel weight grid height should be smaller than block height"); + FillError(outBuf, blockWidth, blockHeight); + return; + } + + // Read num partitions + uint32_t nPartitions = strm.ReadBits(2) + 1; + assert(nPartitions <= 4); + + if (nPartitions == 4 && weightParams.m_bDualPlane) { + assert(!"Dual plane mode is incompatible with four partition blocks"); + FillError(outBuf, blockWidth, blockHeight); + return; + } + + // Based on the number of partitions, read the color endpoint mode for + // each partition. + + // Determine partitions, partition index, and color endpoint modes + int32_t planeIdx = -1; + uint32_t partitionIndex; + uint32_t colorEndpointMode[4] = {0, 0, 0, 0}; + + // Define color data. + uint8_t colorEndpointData[16]; + memset(colorEndpointData, 0, sizeof(colorEndpointData)); + BitStream colorEndpointStream(colorEndpointData, 16 * 8, 0); + + // Read extra config data... + uint32_t baseCEM = 0; + if (nPartitions == 1) { + colorEndpointMode[0] = strm.ReadBits(4); + partitionIndex = 0; + } else { + partitionIndex = strm.ReadBits(10); + baseCEM = strm.ReadBits(6); + } + uint32_t baseMode = (baseCEM & 3); + + // Remaining bits are color endpoint data... + uint32_t nWeightBits = weightParams.GetPackedBitSize(); + int32_t remainingBits = 128 - nWeightBits - strm.GetBitsRead(); + + // Consider extra bits prior to texel data... + uint32_t extraCEMbits = 0; + if (baseMode) { + switch (nPartitions) { + case 2: + extraCEMbits += 2; + break; + case 3: + extraCEMbits += 5; + break; + case 4: + extraCEMbits += 8; + break; + default: + assert(false); + break; + } + } + remainingBits -= extraCEMbits; + + // Do we have a dual plane situation? + uint32_t planeSelectorBits = 0; + if (weightParams.m_bDualPlane) { + planeSelectorBits = 2; + } + remainingBits -= planeSelectorBits; + + // Read color data... + uint32_t colorDataBits = remainingBits; + while (remainingBits > 0) { + uint32_t nb = std::min(remainingBits, 8); + uint32_t b = strm.ReadBits(nb); + colorEndpointStream.WriteBits(b, nb); + remainingBits -= 8; + } + + // Read the plane selection bits + planeIdx = strm.ReadBits(planeSelectorBits); + + // Read the rest of the CEM + if (baseMode) { + uint32_t extraCEM = strm.ReadBits(extraCEMbits); + uint32_t CEM = (extraCEM << 6) | baseCEM; + CEM >>= 2; + + bool C[4] = {0}; + for (uint32_t i = 0; i < nPartitions; i++) { + C[i] = CEM & 1; + CEM >>= 1; + } + + uint8_t M[4] = {0}; + for (uint32_t i = 0; i < nPartitions; i++) { + M[i] = CEM & 3; + CEM >>= 2; + assert(M[i] <= 3); + } + + for (uint32_t i = 0; i < nPartitions; i++) { + colorEndpointMode[i] = baseMode; + if (!(C[i])) + colorEndpointMode[i] -= 1; + colorEndpointMode[i] <<= 2; + colorEndpointMode[i] |= M[i]; + } + } else if (nPartitions > 1) { + uint32_t CEM = baseCEM >> 2; + for (uint32_t i = 0; i < nPartitions; i++) { + colorEndpointMode[i] = CEM; + } + } + + // Make sure everything up till here is sane. + for (uint32_t i = 0; i < nPartitions; i++) { + assert(colorEndpointMode[i] < 16); + } + assert(strm.GetBitsRead() + weightParams.GetPackedBitSize() == 128); + + // Decode both color data and texel weight data + uint32_t colorValues[32]; // Four values, two endpoints, four maximum paritions + DecodeColorValues(colorValues, colorEndpointData, colorEndpointMode, nPartitions, + colorDataBits); + + Pixel endpoints[4][2]; + const uint32_t* colorValuesPtr = colorValues; + for (uint32_t i = 0; i < nPartitions; i++) { + ComputeEndpoints(endpoints[i][0], endpoints[i][1], colorValuesPtr, colorEndpointMode[i]); + } + + // Read the texel weight data.. + uint8_t texelWeightData[16]; + memcpy(texelWeightData, inBuf, sizeof(texelWeightData)); + + // Reverse everything + for (uint32_t i = 0; i < 8; i++) { +// Taken from http://graphics.stanford.edu/~seander/bithacks.html#ReverseByteWith64Bits +#define REVERSE_BYTE(b) (((b)*0x80200802ULL) & 0x0884422110ULL) * 0x0101010101ULL >> 32 + unsigned char a = static_cast<unsigned char>(REVERSE_BYTE(texelWeightData[i])); + unsigned char b = static_cast<unsigned char>(REVERSE_BYTE(texelWeightData[15 - i])); +#undef REVERSE_BYTE + + texelWeightData[i] = b; + texelWeightData[15 - i] = a; + } + + // Make sure that higher non-texel bits are set to zero + const uint32_t clearByteStart = (weightParams.GetPackedBitSize() >> 3) + 1; + texelWeightData[clearByteStart - 1] &= (1 << (weightParams.GetPackedBitSize() % 8)) - 1; + memset(texelWeightData + clearByteStart, 0, 16 - clearByteStart); + + std::vector<IntegerEncodedValue> texelWeightValues; + BitStream weightStream(texelWeightData); + + IntegerEncodedValue::DecodeIntegerSequence(texelWeightValues, weightStream, + weightParams.m_MaxWeight, + weightParams.GetNumWeightValues()); + + // Blocks can be at most 12x12, so we can have as many as 144 weights + uint32_t weights[2][144]; + UnquantizeTexelWeights(weights, texelWeightValues, weightParams, blockWidth, blockHeight); + + // Now that we have endpoints and weights, we can interpolate and generate + // the proper decoding... + for (uint32_t j = 0; j < blockHeight; j++) + for (uint32_t i = 0; i < blockWidth; i++) { + uint32_t partition = Select2DPartition(partitionIndex, i, j, nPartitions, + (blockHeight * blockWidth) < 32); + assert(partition < nPartitions); + + Pixel p; + for (uint32_t c = 0; c < 4; c++) { + uint32_t C0 = endpoints[partition][0].Component(c); + C0 = Replicate(C0, 8, 16); + uint32_t C1 = endpoints[partition][1].Component(c); + C1 = Replicate(C1, 8, 16); + + uint32_t plane = 0; + if (weightParams.m_bDualPlane && (((planeIdx + 1) & 3) == c)) { + plane = 1; + } + + uint32_t weight = weights[plane][j * blockWidth + i]; + uint32_t C = (C0 * (64 - weight) + C1 * weight + 32) / 64; + if (C == 65535) { + p.Component(c) = 255; + } else { + double Cf = static_cast<double>(C); + p.Component(c) = static_cast<uint16_t>(255.0 * (Cf / 65536.0) + 0.5); + } + } + + outBuf[j * blockWidth + i] = p.Pack(); + } +} + +} // namespace ASTCC + +namespace Tegra::Texture::ASTC { + +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 blockIdx = 0; + std::vector<uint8_t> outData; + outData.resize(height * width * 4); + for (uint32_t j = 0; j < height; j += block_height) { + for (uint32_t i = 0; i < width; i += block_width) { + + uint8_t* blockPtr = data.data() + blockIdx * 16; + + // Blocks can be at most 12x12 + uint32_t uncompData[144]; + ASTCC::DecompressBlock(blockPtr, block_width, block_height, uncompData); + + uint32_t decompWidth = std::min(block_width, width - i); + uint32_t decompHeight = std::min(block_height, height - j); + + uint8_t* outRow = outData.data() + (j * width + i) * 4; + for (uint32_t jj = 0; jj < decompHeight; jj++) { + memcpy(outRow + jj * width * 4, uncompData + jj * block_width, decompWidth * 4); + } + + blockIdx++; + } + } + + return outData; +} + +} // namespace Tegra::Texture::ASTC diff --git a/src/video_core/textures/astc.h b/src/video_core/textures/astc.h new file mode 100644 index 000000000..f0d7c0e56 --- /dev/null +++ b/src/video_core/textures/astc.h @@ -0,0 +1,15 @@ +// Copyright 2018 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <cstdint> +#include <vector> + +namespace Tegra::Texture::ASTC { + +std::vector<uint8_t> Decompress(std::vector<uint8_t>& data, uint32_t width, uint32_t height, + uint32_t block_width, uint32_t block_height); + +} // namespace Tegra::Texture::ASTC diff --git a/src/video_core/textures/decoders.cpp b/src/video_core/textures/decoders.cpp index 7bf9c4c4b..0db4367f1 100644 --- a/src/video_core/textures/decoders.cpp +++ b/src/video_core/textures/decoders.cpp @@ -53,6 +53,7 @@ u32 BytesPerPixel(TextureFormat format) { case TextureFormat::DXT45: // In this case a 'pixel' actually refers to a 4x4 tile. return 16; + case TextureFormat::ASTC_2D_4X4: case TextureFormat::A8R8G8B8: case TextureFormat::A2B10G10R10: case TextureFormat::BF10GF11RF11: @@ -94,6 +95,7 @@ std::vector<u8> UnswizzleTexture(VAddr address, TextureFormat format, u32 width, case TextureFormat::R8: case TextureFormat::R16_G16_B16_A16: case TextureFormat::BF10GF11RF11: + case TextureFormat::ASTC_2D_4X4: CopySwizzledData(width, height, bytes_per_pixel, bytes_per_pixel, data, unswizzled_data.data(), true, block_height); break; @@ -115,6 +117,7 @@ std::vector<u8> DecodeTexture(const std::vector<u8>& texture_data, TextureFormat case TextureFormat::DXT23: case TextureFormat::DXT45: case TextureFormat::DXN1: + case TextureFormat::ASTC_2D_4X4: case TextureFormat::A8R8G8B8: case TextureFormat::A2B10G10R10: case TextureFormat::A1B5G5R5: diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp index bbd681eae..9e585b082 100644 --- a/src/yuzu/game_list.cpp +++ b/src/yuzu/game_list.cpp @@ -3,6 +3,7 @@ // Refer to the license.txt file included. #include <QApplication> +#include <QDir> #include <QFileInfo> #include <QHeaderView> #include <QKeyEvent> @@ -264,8 +265,17 @@ void GameList::ValidateEntry(const QModelIndex& item) { if (file_path.isEmpty()) return; std::string std_file_path(file_path.toStdString()); - if (!FileUtil::Exists(std_file_path) || FileUtil::IsDirectory(std_file_path)) + if (!FileUtil::Exists(std_file_path)) return; + if (FileUtil::IsDirectory(std_file_path)) { + QDir dir(std_file_path.c_str()); + QStringList matching_main = dir.entryList(QStringList("main"), QDir::Files); + if (matching_main.size() == 1) { + emit GameChosen(dir.path() + DIR_SEP + matching_main[0]); + } + return; + } + // Users usually want to run a diffrent game after closing one search_field->clear(); emit GameChosen(file_path); @@ -363,6 +373,19 @@ static bool HasSupportedFileExtension(const std::string& file_name) { return GameList::supported_file_extensions.contains(file.suffix(), Qt::CaseInsensitive); } +static bool IsExtractedNCAMain(const std::string& file_name) { + return QFileInfo(file_name.c_str()).fileName() == "main"; +} + +static QString FormatGameName(const std::string& physical_name) { + QFileInfo file_info(physical_name.c_str()); + if (IsExtractedNCAMain(physical_name)) { + return file_info.dir().path(); + } else { + return QString::fromStdString(physical_name); + } +} + void GameList::RefreshGameDirectory() { if (!UISettings::values.gamedir.isEmpty() && current_worker != nullptr) { NGLOG_INFO(Frontend, "Change detected in the games directory. Reloading game list."); @@ -380,7 +403,8 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign return false; // Breaks the callback loop. bool is_dir = FileUtil::IsDirectory(physical_name); - if (!is_dir && HasSupportedFileExtension(physical_name)) { + if (!is_dir && + (HasSupportedFileExtension(physical_name) || IsExtractedNCAMain(physical_name))) { std::unique_ptr<Loader::AppLoader> loader = Loader::GetLoader(physical_name); if (!loader) return true; @@ -392,7 +416,7 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign loader->ReadProgramId(program_id); emit EntryReady({ - new GameListItemPath(QString::fromStdString(physical_name), smdh, program_id), + new GameListItemPath(FormatGameName(physical_name), smdh, program_id), new GameListItem( QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType()))), new GameListItemSize(FileUtil::GetSize(physical_name)), diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index aa9028399..97be548d7 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -13,6 +13,7 @@ #include <QMessageBox> #include <QtGui> #include <QtWidgets> +#include "common/common_paths.h" #include "common/logging/backend.h" #include "common/logging/filter.h" #include "common/logging/log.h" @@ -278,6 +279,7 @@ void GMainWindow::ConnectWidgetEvents() { void GMainWindow::ConnectMenuEvents() { // File connect(ui.action_Load_File, &QAction::triggered, this, &GMainWindow::OnMenuLoadFile); + connect(ui.action_Load_Folder, &QAction::triggered, this, &GMainWindow::OnMenuLoadFolder); connect(ui.action_Select_Game_List_Root, &QAction::triggered, this, &GMainWindow::OnMenuSelectGameListRoot); connect(ui.action_Exit, &QAction::triggered, this, &QMainWindow::close); @@ -550,6 +552,8 @@ void GMainWindow::OnMenuLoadFile() { for (const auto& piece : game_list->supported_file_extensions) extensions += "*." + piece + " "; + extensions += "main "; + QString file_filter = tr("Switch Executable") + " (" + extensions + ")"; file_filter += ";;" + tr("All Files (*.*)"); @@ -562,6 +566,18 @@ void GMainWindow::OnMenuLoadFile() { } } +void GMainWindow::OnMenuLoadFolder() { + QDir dir = QFileDialog::getExistingDirectory(this, tr("Open Extracted ROM Directory")); + + QStringList matching_main = dir.entryList(QStringList("main"), QDir::Files); + if (matching_main.size() == 1) { + BootGame(dir.path() + DIR_SEP + matching_main[0]); + } else { + QMessageBox::warning(this, tr("Invalid Directory Selected"), + tr("The directory you have selected does not contain a 'main' file.")); + } +} + void GMainWindow::OnMenuSelectGameListRoot() { QString dir_path = QFileDialog::getExistingDirectory(this, tr("Select Directory")); if (!dir_path.isEmpty()) { diff --git a/src/yuzu/main.h b/src/yuzu/main.h index 3e29d5fc4..074bba3f9 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h @@ -123,6 +123,7 @@ private slots: void OnGameListLoadFile(QString game_path); void OnGameListOpenSaveFolder(u64 program_id); void OnMenuLoadFile(); + void OnMenuLoadFolder(); /// Called whenever a user selects the "File->Select Game List Root" menu item void OnMenuSelectGameListRoot(); void OnMenuRecentFile(); diff --git a/src/yuzu/main.ui b/src/yuzu/main.ui index 0fcd93cc2..22c4cad08 100644 --- a/src/yuzu/main.ui +++ b/src/yuzu/main.ui @@ -58,6 +58,7 @@ </property> </widget> <addaction name="action_Load_File"/> + <addaction name="action_Load_Folder"/> <addaction name="separator"/> <addaction name="action_Select_Game_List_Root"/> <addaction name="menu_recent_files"/> @@ -106,6 +107,11 @@ <string>Load File...</string> </property> </action> + <action name="action_Load_Folder"> + <property name="text"> + <string>Load Folder...</string> + </property> + </action> <action name="action_Load_Symbol_Map"> <property name="text"> <string>Load Symbol Map...</string> |