diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt index 5e56105fc..535858e8e 100644 --- a/externals/CMakeLists.txt +++ b/externals/CMakeLists.txt @@ -60,6 +60,7 @@ if (ENABLE_TESTS) add_subdirectory(catch2) endif() target_link_libraries(catch2 INTERFACE Catch2::Catch2WithMain) + include(Catch) endif() # Crypto++ diff --git a/src/android/app/src/main/java/org/citra/citra_emu/NativeLibrary.kt b/src/android/app/src/main/java/org/citra/citra_emu/NativeLibrary.kt index 9d2015baa..f8cb55874 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/NativeLibrary.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/NativeLibrary.kt @@ -504,8 +504,9 @@ object NativeLibrary { const val ErrorSystemFiles = 8 const val ErrorSavestate = 9 const val ErrorArticDisconnected = 10 - const val ShutdownRequested = 11 - const val ErrorUnknown = 12 + const val ErrorN3DSApplication = 11 + const val ShutdownRequested = 12 + const val ErrorUnknown = 13 fun newInstance(resultCode: Int): EmulationErrorDialogFragment { val args = Bundle() diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/IntSetting.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/IntSetting.kt index 2c8cbf2a1..eb1a880a5 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/IntSetting.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/IntSetting.kt @@ -17,7 +17,7 @@ enum class IntSetting( CAMERA_INNER_FLIP(SettingKeys.camera_inner_flip(), Settings.SECTION_CAMERA, 0), CAMERA_OUTER_LEFT_FLIP(SettingKeys.camera_outer_left_flip(), Settings.SECTION_CAMERA, 0), CAMERA_OUTER_RIGHT_FLIP(SettingKeys.camera_outer_right_flip(), Settings.SECTION_CAMERA, 0), - GRAPHICS_API(SettingKeys.graphics_api(), Settings.SECTION_RENDERER, 1), + GRAPHICS_API(SettingKeys.graphics_api(), Settings.SECTION_RENDERER, 2), RESOLUTION_FACTOR(SettingKeys.resolution_factor(), Settings.SECTION_RENDERER, 1), STEREOSCOPIC_3D_MODE(SettingKeys.render_3d(), Settings.SECTION_RENDERER, 2), STEREOSCOPIC_3D_DEPTH(SettingKeys.factor_3d(), Settings.SECTION_RENDERER, 0), diff --git a/src/android/app/src/main/jni/default_ini.h b/src/android/app/src/main/jni/default_ini.h index 5e2eab1d1..ce93d0e6f 100644 --- a/src/android/app/src/main/jni/default_ini.h +++ b/src/android/app/src/main/jni/default_ini.h @@ -90,7 +90,7 @@ static const char* android_config_default_file_content = (BOOST_HANA_STRING(R"( [Renderer] # Whether to render using OpenGL -# 1: OpenGL ES (default), 2: Vulkan +# 1: OpenGL ES, 2: Vulkan (default) )") DECLARE_KEY(graphics_api) BOOST_HANA_STRING(R"( # Whether to compile shaders on multiple worker threads (Vulkan only) diff --git a/src/android/app/src/main/res/layout/fragment_system_files.xml b/src/android/app/src/main/res/layout/fragment_system_files.xml index bae1cda1c..650766748 100644 --- a/src/android/app/src/main/res/layout/fragment_system_files.xml +++ b/src/android/app/src/main/res/layout/fragment_system_files.xml @@ -5,13 +5,13 @@ android:id="@+id/coordinator_about" android:layout_width="match_parent" android:layout_height="match_parent" + android:fitsSystemWindows="true" android:background="?attr/colorSurface"> + android:layout_height="wrap_content"> apply_region_free_patch{true, Keys::apply_region_free_patch}; // Renderer + // clang-format off SwitchableSetting graphics_api{ -#if defined(ENABLE_OPENGL) +#if defined(ANDROID) && defined(ENABLE_VULKAN) // Prefer Vulkan on Android, OpenGL on everything else + GraphicsAPI::Vulkan, +#elif defined(ENABLE_OPENGL) GraphicsAPI::OpenGL, #elif defined(ENABLE_VULKAN) GraphicsAPI::Vulkan, @@ -510,6 +513,7 @@ struct Values { #error "At least one renderer must be enabled." #endif GraphicsAPI::Software, GraphicsAPI::Vulkan, Keys::graphics_api}; + // clang-format on SwitchableSetting physical_device{0, Keys::physical_device}; Setting use_gles{false, Keys::use_gles}; Setting renderer_debug{false, Keys::renderer_debug}; diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt index d35e6ba21..027609c26 100644 --- a/src/tests/CMakeLists.txt +++ b/src/tests/CMakeLists.txt @@ -36,6 +36,10 @@ if (ENABLE_LIBRETRO) endif() add_test(NAME tests COMMAND tests) +if(NOT ANDROID) + catch_discover_tests(tests) +endif() + if (CITRA_USE_PRECOMPILED_HEADERS) target_precompile_headers(tests PRIVATE precompiled_headers.h) diff --git a/src/tests/video_core/shader.cpp b/src/tests/video_core/shader.cpp index db868a260..4ea976668 100644 --- a/src/tests/video_core/shader.cpp +++ b/src/tests/video_core/shader.cpp @@ -481,6 +481,39 @@ SHADER_TEST_CASE("RSQ", "[video_core][shader]") { REQUIRE(shader.Run({0.0625f}).x == Catch::Approx(4.0f).margin(0.004f)); } +SHADER_TEST_CASE("SETEMIT", "[video_core][shader]") { + Pica::GeometryEmitter geometry_emitter; + + for (u8 winding = 0; winding <= 1; ++winding) { + for (u8 prim_emit = 0; prim_emit <= 1; ++prim_emit) { + for (u8 vertex_id = 0; vertex_id <= 3; ++vertex_id) { + auto shader_setup = CompileShaderSetup({ + {OpCode::Id::NOP}, // setemit + {OpCode::Id::END}, + }); + + // nihstro does not support the SETEMIT instructions, so the instruction-binary must + // be manually + // inserted here: + nihstro::Instruction SETEMIT = {}; + SETEMIT.opcode = nihstro::OpCode(nihstro::OpCode::Id::SETEMIT); + SETEMIT.setemit.winding.Assign(winding); + SETEMIT.setemit.prim_emit.Assign(prim_emit); + SETEMIT.setemit.vertex_id.Assign(vertex_id); + shader_setup->UpdateProgramCode(0, SETEMIT.hex); + + auto shader = TestType(std::move(shader_setup)); + Pica::ShaderUnit shader_unit(&geometry_emitter); + shader.Run(shader_unit, 1.0f); + + REQUIRE(geometry_emitter.emit_state.winding == winding); + REQUIRE(geometry_emitter.emit_state.prim_emit == prim_emit); + REQUIRE(geometry_emitter.emit_state.vertex_id == vertex_id); + } + } + } +} + SHADER_TEST_CASE("Uniform Read", "[video_core][shader]") { const auto sh_input = SourceRegister::MakeInput(0); const auto sh_c0 = SourceRegister::MakeFloat(0); diff --git a/src/video_core/pica/shader_unit.cpp b/src/video_core/pica/shader_unit.cpp index 5d81f857a..725dd9ebb 100644 --- a/src/video_core/pica/shader_unit.cpp +++ b/src/video_core/pica/shader_unit.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Citra Emulator Project +// Copyright Citra Emulator Project / Azahar Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. @@ -29,15 +29,15 @@ void ShaderUnit::WriteOutput(const ShaderRegs& config, AttributeBuffer& buffer) } void GeometryEmitter::Emit(std::span, 16> output_regs) { - ASSERT(vertex_id < 3); + ASSERT(emit_state.vertex_id < 3); u32 output_index{}; for (u32 reg : Common::BitSet(output_mask)) { - buffer[vertex_id][output_index++] = output_regs[reg]; + buffer[emit_state.vertex_id][output_index++] = output_regs[reg]; } - if (prim_emit) { - if (winding) { + if (emit_state.prim_emit) { + if (emit_state.winding) { handlers->winding_setter(); } for (std::size_t i = 0; i < buffer.size(); ++i) { diff --git a/src/video_core/pica/shader_unit.h b/src/video_core/pica/shader_unit.h index 2f9c1843f..80eea8d23 100644 --- a/src/video_core/pica/shader_unit.h +++ b/src/video_core/pica/shader_unit.h @@ -1,4 +1,4 @@ -// Copyright 2023 Citra Emulator Project +// Copyright Citra Emulator Project / Azahar Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. @@ -75,11 +75,18 @@ struct GeometryEmitter { void Emit(std::span, 16> output_regs); public: - std::array buffer; - u8 vertex_id; - bool prim_emit; - bool winding; + union EmitState { + struct { + bool winding : 1; + bool prim_emit : 1; + u8 vertex_id : 2; + }; + u8 raw; + } emit_state; + static_assert(sizeof(emit_state) == 1); + u32 output_mask; + std::array buffer; Handlers* handlers; private: @@ -87,9 +94,7 @@ private: template void serialize(Archive& ar, const u32 file_version) { ar & buffer; - ar & vertex_id; - ar & prim_emit; - ar & winding; + ar & emit_state.raw; ar & output_mask; } }; diff --git a/src/video_core/shader/shader_interpreter.cpp b/src/video_core/shader/shader_interpreter.cpp index cb06a62bc..6d373e28f 100644 --- a/src/video_core/shader/shader_interpreter.cpp +++ b/src/video_core/shader/shader_interpreter.cpp @@ -671,9 +671,9 @@ static void RunInterpreter(const ShaderSetup& setup, ShaderUnit& state, case OpCode::Id::SETEMIT: { auto* emitter = state.emitter_ptr; ASSERT_MSG(emitter, "Execute SETEMIT on VS"); - emitter->vertex_id = instr.setemit.vertex_id; - emitter->prim_emit = instr.setemit.prim_emit != 0; - emitter->winding = instr.setemit.winding != 0; + emitter->emit_state.vertex_id = instr.setemit.vertex_id; + emitter->emit_state.prim_emit = instr.setemit.prim_emit != 0; + emitter->emit_state.winding = instr.setemit.winding != 0; break; } diff --git a/src/video_core/shader/shader_jit_a64_compiler.cpp b/src/video_core/shader/shader_jit_a64_compiler.cpp index 636d9cb57..90c8425bc 100644 --- a/src/video_core/shader/shader_jit_a64_compiler.cpp +++ b/src/video_core/shader/shader_jit_a64_compiler.cpp @@ -865,12 +865,13 @@ void JitShader::Compile_SETE(Instruction instr) { l(have_emitter); - MOV(XSCRATCH1.toW(), instr.setemit.vertex_id); - STRB(XSCRATCH1.toW(), XSCRATCH0, u32(offsetof(GeometryEmitter, vertex_id))); - MOV(XSCRATCH1.toW(), instr.setemit.prim_emit); - STRB(XSCRATCH1.toW(), XSCRATCH0, u32(offsetof(GeometryEmitter, prim_emit))); - MOV(XSCRATCH1.toW(), instr.setemit.winding); - STRB(XSCRATCH1.toW(), XSCRATCH0, u32(offsetof(GeometryEmitter, winding))); + const GeometryEmitter::EmitState new_state{ + .winding = instr.setemit.winding != 0, + .prim_emit = instr.setemit.prim_emit != 0, + .vertex_id = static_cast(instr.setemit.vertex_id), + }; + MOV(XSCRATCH1.toW(), new_state.raw); + STRB(XSCRATCH1.toW(), XSCRATCH0, u32(offsetof(GeometryEmitter, emit_state))); l(end); } diff --git a/src/video_core/shader/shader_jit_x64_compiler.cpp b/src/video_core/shader/shader_jit_x64_compiler.cpp index 84cdc09bc..1c1ff92b2 100644 --- a/src/video_core/shader/shader_jit_x64_compiler.cpp +++ b/src/video_core/shader/shader_jit_x64_compiler.cpp @@ -905,9 +905,12 @@ void JitShader::Compile_SETE(Instruction instr) { jmp(end); L(have_emitter); - mov(byte[rax + offsetof(GeometryEmitter, vertex_id)], instr.setemit.vertex_id); - mov(byte[rax + offsetof(GeometryEmitter, prim_emit)], instr.setemit.prim_emit); - mov(byte[rax + offsetof(GeometryEmitter, winding)], instr.setemit.winding); + const GeometryEmitter::EmitState new_state{ + .winding = instr.setemit.winding != 0, + .prim_emit = instr.setemit.prim_emit != 0, + .vertex_id = static_cast(instr.setemit.vertex_id), + }; + mov(byte[rax + offsetof(GeometryEmitter, emit_state)], new_state.raw); L(end); }