mirror of
https://git.eden-emu.dev/eden-emu/eden.git
synced 2026-11-01 11:58:19 +00:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a72d30642f | |||
| 05b4d7f4cc | |||
| 5ac896fbd7 | |||
| 53e7a51d2e | |||
| db83070bf2 | |||
| e903d70d9e | |||
| 802531c457 | |||
| bd071e3464 | |||
| a845100bf0 |
@@ -0,0 +1,2 @@
|
|||||||
|
liberapay: crueter
|
||||||
|
custom: 'https://www.paypal.biz/edenemulator'
|
||||||
@@ -1,4 +1,8 @@
|
|||||||
- [ ] I have read and followed the [Contribution Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/CONTRIBUTING.md#code-contributions).
|
- [ ] I have read and followed the [Contribution Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/CONTRIBUTING.md#code-contributions).
|
||||||
- [ ] I have read and followed the [Coding Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/Coding.md) to the best of my ability.
|
- [ ] I have read and followed the [Coding Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/Coding.md) to the best of my ability.
|
||||||
|
- [ ] I have read and followed the [website policy](https://git.eden-emu.dev/eden-emu/site-policy).
|
||||||
|
- [ ] I affirm that the content of this pull request body was written and submitted entirely by me, in my own words, and not by an AI agent or LLM.
|
||||||
|
- [ ] I affirm that I did not read any of this, and understand that my pull request will be closed if I check this box.
|
||||||
|
- [ ] I affirm that, by submitting this pull request, I am responsible for all of the changes contained within these commits.
|
||||||
|
|
||||||
-------------------
|
-------------------
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
diff --git a/cmake/ConfigureOpenSSL.cmake b/cmake/ConfigureOpenSSL.cmake
|
||||||
|
index 3012e05..2ae23ff 100644
|
||||||
|
--- a/cmake/ConfigureOpenSSL.cmake
|
||||||
|
+++ b/cmake/ConfigureOpenSSL.cmake
|
||||||
|
@@ -108,7 +108,8 @@ function(configure_openssl)
|
||||||
|
)
|
||||||
|
|
||||||
|
if(NOT "${CONFIGURE_OPTIONS_OLD}" STREQUAL "")
|
||||||
|
- if(CONFIGURE_OPTIONS STREQUAL CONFIGURE_OPTIONS_OLD)
|
||||||
|
+ # TODO(lizzie): Emscripten has issues with rebuilding due to the wrapper it uses
|
||||||
|
+ if(CMAKE_SYSTEM_NAME MATCHES "Emscripten" OR CONFIGURE_OPTIONS STREQUAL CONFIGURE_OPTIONS_OLD)
|
||||||
|
message(STATUS "Found previous configure results. Don't perform configuration")
|
||||||
|
return()
|
||||||
|
endif()
|
||||||
|
@@ -134,10 +135,24 @@ function(configure_openssl)
|
||||||
|
set(VERBOSE_OPTION OUTPUT_QUIET)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
+ if (CMAKE_SYSTEM_NAME MATCHES "Emscripten")
|
||||||
|
+ set(EMSCRIPTEN_CMAKE_WRAPPER "emcmake")
|
||||||
|
+ find_program(EMCC emcc REQUIRED)
|
||||||
|
+ set(EMSCRIPTEN_LINKER ${EMCC})
|
||||||
|
+ list(APPEND CONFIGURE_COMMAND wasm64)
|
||||||
|
+ else()
|
||||||
|
+ set(EMSCRIPTEN_CMAKE_WRAPPER "")
|
||||||
|
+ set(EMSCRIPTEN_LINKER ${CMAKE_LINKER})
|
||||||
|
+ endif ()
|
||||||
|
+
|
||||||
|
execute_process(
|
||||||
|
- COMMAND ${CMAKE_COMMAND} -E env
|
||||||
|
+ COMMAND ${EMSCRIPTEN_CMAKE_WRAPPER} ${CMAKE_COMMAND} -E env
|
||||||
|
"CFLAGS=${CMAKE_C_FLAGS}"
|
||||||
|
"CXXFLAGS=${CMAKE_CXX_FLAGS}"
|
||||||
|
+ "LDFLAGS=${CMAKE_CXX_LINK_FLAGS}"
|
||||||
|
+ "CC=${CMAKE_C_COMPILER}"
|
||||||
|
+ "CXX=${CMAKE_CXX_COMPILER}"
|
||||||
|
+ "LD=${EMSCRIPTEN_LINKER}"
|
||||||
|
${CONFIGURE_COMMAND}
|
||||||
|
WORKING_DIRECTORY ${CONFIGURE_BUILD_DIR}
|
||||||
|
${VERBOSE_OPTION}
|
||||||
@@ -1,7 +1,10 @@
|
|||||||
# AGENTS
|
# AGENTS
|
||||||
|
|
||||||
Read and follow the AI policy found in `docs/policies/AI.md`, and the coding policies found in `docs/policies/Coding.md`.
|
Read and follow the [contribution guidelines](./CONTRIBUTING.md) and our [site policy](https://git.eden-emu.dev/eden-emu/site-policy/src/branch/master)..
|
||||||
|
|
||||||
Do not, under any circumstances, ever submit PRs or push commits on behalf of the user.
|
Do not, under any circumstances, ever:
|
||||||
|
|
||||||
|
- Submit PRs, push commits, or request any API calls on behalf of the user.
|
||||||
|
- Write commit messages, PR descriptions, or issues on behalf of the user.
|
||||||
|
|
||||||
Keep comments to a reasonable level. Do not overexplain or add/remove comments from unrelated parts of the codebase.
|
Keep comments to a reasonable level. Do not overexplain or add/remove comments from unrelated parts of the codebase.
|
||||||
|
|||||||
+26
-2
@@ -16,8 +16,11 @@ Eden is free, open-source, copyleft software, licensed under the terms of the [G
|
|||||||
|
|
||||||
### Policies
|
### Policies
|
||||||
|
|
||||||
- AI usage is allowed, but we reserve the right to close and rework your PR if you don't fully understand the changes you're making.
|
- AI usage is allowed, but we reserve the right to close and rework your PR if you don't fully understand the changes you're making. Additionally:
|
||||||
- As a common courtesy, please disclose your AI usage under the PR description, as `Assisted-by: <AI model> (<how it helped>)`.
|
- As a common courtesy, please disclose your AI usage under the PR description, as `Assisted-by: <AI model> (<how it helped>)`.
|
||||||
|
- All PR descriptions, issue bodies, and comments **must** be written by you, in your own words.
|
||||||
|
- AI-written comments/descriptions/bodies are not only difficult for us to read, they also indicate to us that you did not put in the effort to actually understand your changes. These will be closed, deleted, or hidden without warning.
|
||||||
|
- Spamming of low-effort AI-generated pull requests or issues is considered a [Human Effort Denial of Service](https://tiangolo.com/open-source/contributing/#human-effort-denial-of-service), and will result in the immediate suspension of your Forgejo account, or you getting blocked from the organization on other platforms.
|
||||||
- Usage of any form of profanity or otherwise unsavory language is discouraged but not strictly disallowed.
|
- Usage of any form of profanity or otherwise unsavory language is discouraged but not strictly disallowed.
|
||||||
- New code must follow the same style as the surrounding codebase. Exceptions may be granted in certain cases.
|
- New code must follow the same style as the surrounding codebase. Exceptions may be granted in certain cases.
|
||||||
- Maintainers reserve the right to change your patches and pull requests at will. We will try to avoid this.
|
- Maintainers reserve the right to change your patches and pull requests at will. We will try to avoid this.
|
||||||
@@ -26,6 +29,25 @@ Eden is free, open-source, copyleft software, licensed under the terms of the [G
|
|||||||
- While we do *not* adhere to the terms of a formal code of conduct, you are expected to treat other developers, contributors, and community members respectfully.
|
- While we do *not* adhere to the terms of a formal code of conduct, you are expected to treat other developers, contributors, and community members respectfully.
|
||||||
- You **must** have basic knowledge of [Git](https://git-scm.com/learn). Knowing how to manage your branches and follow proper fork policies is a necessity.
|
- You **must** have basic knowledge of [Git](https://git-scm.com/learn). Knowing how to manage your branches and follow proper fork policies is a necessity.
|
||||||
|
|
||||||
|
### Pull Requests
|
||||||
|
|
||||||
|
Pull requests are only to be merged by core developers when properly tested and discussions conclude on Discord or other communication channels. Labels are recommended but not required. However, all PRs MUST be namespaced and optionally typed:
|
||||||
|
|
||||||
|
```txt
|
||||||
|
[cmake] refactor: CPM over submodules
|
||||||
|
[desktop] feat: implement firmware install from ZIP
|
||||||
|
[hle] stub fw20 functions
|
||||||
|
[core] test: raise maximum CPU cores to 6
|
||||||
|
[cmake, core] Unbreak FreeBSD Building Process
|
||||||
|
```
|
||||||
|
|
||||||
|
- The level of namespacing is generally left to the committer's choice.
|
||||||
|
- However, we never recommend going more than two levels *except* in `hle`, in which case you may go as many as four levels depending on the specificity of your changes.
|
||||||
|
- Ocassionally, up to two additional namespaces may be provided for more clarity.
|
||||||
|
- Changes that affect the entire project (sans CMake changes) should be namespaced as `meta`.
|
||||||
|
- Maintainers are permitted to change namespaces at will.
|
||||||
|
- Commits within PRs are not required to be namespaced, but it is highly recommended.
|
||||||
|
|
||||||
### Where do I start?
|
### Where do I start?
|
||||||
|
|
||||||
Anywhere you like! If you are facing an issue and want to fix it, go ahead. For new features, you are heavily encouraged to open a feature request on GitHub, Codeberg, or Forgejo first, discussing the motivations, potential implementation, and user flow of your desired feature. Our UI/UX designers will work with you to refine your feature before you actually choose to implement it.
|
Anywhere you like! If you are facing an issue and want to fix it, go ahead. For new features, you are heavily encouraged to open a feature request on GitHub, Codeberg, or Forgejo first, discussing the motivations, potential implementation, and user flow of your desired feature. Our UI/UX designers will work with you to refine your feature before you actually choose to implement it.
|
||||||
@@ -39,6 +61,8 @@ There are five primary ways to contribute.
|
|||||||
- Submit pull requests directly to our source tree
|
- Submit pull requests directly to our source tree
|
||||||
- You must first [create an account](https://git.eden-emu.dev/user/sign_up).
|
- You must first [create an account](https://git.eden-emu.dev/user/sign_up).
|
||||||
- You may use an internal account, or you may use a GitHub/Codeberg account linked to our instance.
|
- You may use an internal account, or you may use a GitHub/Codeberg account linked to our instance.
|
||||||
|
- Please make sure to read our [site policy](https://git.eden-emu.dev/eden-emu/site-policy/src/branch/master).
|
||||||
|
- Both your account and your associated fork must be public, *not* limited/private, otherwise our CI workflow will not work on your pull requests.
|
||||||
- Submit pull requests to our [GitHub mirror](https://github.com/eden-emulator/Issue-Reports)
|
- Submit pull requests to our [GitHub mirror](https://github.com/eden-emulator/Issue-Reports)
|
||||||
- Note that this is subject to being removed by DMCA, so don't rely on this.
|
- Note that this is subject to being removed by DMCA, so don't rely on this.
|
||||||
- Submit pull requests to our [Codeberg mirror](https://codeberg.org/eden-emu/eden)
|
- Submit pull requests to our [Codeberg mirror](https://codeberg.org/eden-emu/eden)
|
||||||
@@ -48,7 +72,7 @@ There are five primary ways to contribute.
|
|||||||
- Alongside the contents of your email, please attach the patch/diff file itself for easy access and use.
|
- Alongside the contents of your email, please attach the patch/diff file itself for easy access and use.
|
||||||
- Email our developers at [`developers@eden-emu.dev`](mailto:developers@eden-emu.dev) with any of your relevant findings or code changes.
|
- Email our developers at [`developers@eden-emu.dev`](mailto:developers@eden-emu.dev) with any of your relevant findings or code changes.
|
||||||
|
|
||||||
To test your changes, ensure to read the [build documentation](./docs/Build.md) for your specific platform(s) to ensure everything compiles and works properly. You should also make sure that your branch is up-to-date with the upstream `master` branch before opening a pull request.
|
To test your changes, ensure to read the [build documentation](./docs/Build.md) for your specific platform(s) to ensure everything compiles and works properly. You should also make sure that your branch is up-to-date with the upstream `master` branch before opening a pull request. **You** are responsible for ensuring that your contribution does what it says and doesn't break everything else horribly.
|
||||||
|
|
||||||
## Non-code Contributions
|
## Non-code Contributions
|
||||||
|
|
||||||
|
|||||||
+2
-1
@@ -202,7 +202,8 @@
|
|||||||
"0001-cpmutil-compat.patch",
|
"0001-cpmutil-compat.patch",
|
||||||
"0002-use-ccache.patch",
|
"0002-use-ccache.patch",
|
||||||
"0003-use-cmake-compiler-flags.patch",
|
"0003-use-cmake-compiler-flags.patch",
|
||||||
"0004-use-shell-wrapper.patch"
|
"0004-use-shell-wrapper.patch",
|
||||||
|
"0005-cross-compile.patch"
|
||||||
],
|
],
|
||||||
"repo": "jimmy-park/openssl-cmake",
|
"repo": "jimmy-park/openssl-cmake",
|
||||||
"version": "3.6.2"
|
"version": "3.6.2"
|
||||||
|
|||||||
+25
-4
@@ -6,10 +6,31 @@ General guide for cross compiling.
|
|||||||
|
|
||||||
A painless guide for cross compilation (or to test NCE) from a x86_64 system without polluting your main.
|
A painless guide for cross compilation (or to test NCE) from a x86_64 system without polluting your main.
|
||||||
|
|
||||||
- Install QEMU: `sudo pkg install qemu`
|
```sh
|
||||||
- Download Debian 13: `wget https://cdimage.debian.org/debian-cd/current/arm64/iso-cd/debian-13.0.0-arm64-netinst.iso`
|
#!/bin/sh
|
||||||
- Create a system disk: `qemu-img create -f qcow2 debian-13-arm64-ci.qcow2 30G`
|
ISO_FILE="debian-13.0.0-arm64-netinst.iso"
|
||||||
- Run the VM: `qemu-system-aarch64 -M virt -m 2G -cpu max -bios /usr/local/share/qemu/edk2-aarch64-code.fd -drive if=none,file=debian-13.0.0-arm64-netinst.iso,format=raw,id=cdrom -device scsi-cd,drive=cdrom -drive if=none,file=debian-13-arm64-ci.qcow2,id=hd0,format=qcow2 -device virtio-blk-device,drive=hd0 -device virtio-gpu-pci -device usb-ehci -device usb-kbd -device intel-hda -device hda-output -nic user,model=virtio-net-pci`
|
DISK_FILE="debian-13-arm64-ci.qcow2"
|
||||||
|
which -- qemu-system-aarch64 || sudo pkg install qemu
|
||||||
|
[ -f "$ISO_FILE" ] || wget https://cdimage.debian.org/debian-cd/current/arm64/iso-cd/$ISO_FILE
|
||||||
|
[ -f "$DISK_FILE" ] || qemu-img create -f qcow2 "$DISK_FILE" 30G
|
||||||
|
QEMU_FLAGS=(
|
||||||
|
-M virt
|
||||||
|
-m 2G
|
||||||
|
-cpu max
|
||||||
|
-bios /usr/local/share/qemu/edk2-aarch64-code.fd
|
||||||
|
-drive if=none,file=$ISO_FILE,format=raw,id=cdrom
|
||||||
|
-drive if=none,file=$DISK_FILE,id=hd0,format=qcow2
|
||||||
|
-device scsi-cd,drive=cdrom
|
||||||
|
-device virtio-blk-device,drive=hd0
|
||||||
|
-device virtio-gpu-pci
|
||||||
|
-device usb-ehci
|
||||||
|
-device usb-kbd
|
||||||
|
-device intel-hda
|
||||||
|
-device hda-output
|
||||||
|
-nic user,model=virtio-net-pci
|
||||||
|
)
|
||||||
|
qemu-system-aarch64 "${QEMU_FLAGS[@]}"
|
||||||
|
```
|
||||||
|
|
||||||
## Gentoo
|
## Gentoo
|
||||||
|
|
||||||
|
|||||||
@@ -24,25 +24,6 @@ If the work is licensed/vendored from other people or projects, you may omit the
|
|||||||
|
|
||||||
For more information on the license header script, run `.ci/license-header.sh -h`.
|
For more information on the license header script, run `.ci/license-header.sh -h`.
|
||||||
|
|
||||||
## Pull Requests
|
|
||||||
|
|
||||||
Pull requests are only to be merged by core developers when properly tested and discussions conclude on Discord or other communication channels. Labels are recommended but not required. However, all PRs MUST be namespaced and optionally typed:
|
|
||||||
|
|
||||||
```txt
|
|
||||||
[cmake] refactor: CPM over submodules
|
|
||||||
[desktop] feat: implement firmware install from ZIP
|
|
||||||
[hle] stub fw20 functions
|
|
||||||
[core] test: raise maximum CPU cores to 6
|
|
||||||
[cmake, core] Unbreak FreeBSD Building Process
|
|
||||||
```
|
|
||||||
|
|
||||||
- The level of namespacing is generally left to the committer's choice.
|
|
||||||
- However, we never recommend going more than two levels *except* in `hle`, in which case you may go as many as four levels depending on the specificity of your changes.
|
|
||||||
- Ocassionally, up to two additional namespaces may be provided for more clarity.
|
|
||||||
- Changes that affect the entire project (sans CMake changes) should be namespaced as `meta`.
|
|
||||||
- Maintainers are permitted to change namespaces at will.
|
|
||||||
- Commits within PRs are not required to be namespaced, but it is highly recommended.
|
|
||||||
|
|
||||||
## Adding new settings
|
## Adding new settings
|
||||||
|
|
||||||
When adding new settings, use `tr("Setting:")` if the setting is meant to be a field, otherwise use `tr("Setting")` if the setting is meant to be a Yes/No or checkmark type of setting, see [this short style guide](https://learn.microsoft.com/en-us/style-guide/punctuation/colons#in-ui).
|
When adding new settings, use `tr("Setting:")` if the setting is meant to be a field, otherwise use `tr("Setting")` if the setting is meant to be a Yes/No or checkmark type of setting, see [this short style guide](https://learn.microsoft.com/en-us/style-guide/punctuation/colons#in-ui).
|
||||||
|
|||||||
+5
-5
@@ -101,6 +101,11 @@ endfunction()
|
|||||||
# ia64 exists
|
# ia64 exists
|
||||||
# the rest exist, but are probably less popular than ia64
|
# the rest exist, but are probably less popular than ia64
|
||||||
|
|
||||||
|
detect_architecture_symbols(
|
||||||
|
ARCH wasm
|
||||||
|
SYMBOLS
|
||||||
|
"__EMSCRIPTEN__")
|
||||||
|
|
||||||
detect_architecture_symbols(
|
detect_architecture_symbols(
|
||||||
ARCH arm64
|
ARCH arm64
|
||||||
SYMBOLS
|
SYMBOLS
|
||||||
@@ -203,11 +208,6 @@ detect_architecture_symbols(
|
|||||||
"__loongarch__"
|
"__loongarch__"
|
||||||
"__loongarch64")
|
"__loongarch64")
|
||||||
|
|
||||||
detect_architecture_symbols(
|
|
||||||
ARCH wasm
|
|
||||||
SYMBOLS
|
|
||||||
"__EMSCRIPTEN__")
|
|
||||||
|
|
||||||
# "generic" target
|
# "generic" target
|
||||||
# If you have reached this point, you're on some as-of-yet unsupported architecture.
|
# If you have reached this point, you're on some as-of-yet unsupported architecture.
|
||||||
# See the docs up above for known unsupported architectures
|
# See the docs up above for known unsupported architectures
|
||||||
|
|||||||
@@ -165,13 +165,10 @@ bool ArmNce::HandleGuestAccessFault(GuestContext* guest_ctx, void* raw_info, voi
|
|||||||
const Common::ProcessAddress addr =
|
const Common::ProcessAddress addr =
|
||||||
(reinterpret_cast<u64>(info->si_addr) & ~Memory::YUZU_PAGEMASK);
|
(reinterpret_cast<u64>(info->si_addr) & ~Memory::YUZU_PAGEMASK);
|
||||||
auto& memory = guest_ctx->parent->m_running_thread->GetOwnerProcess()->GetMemory();
|
auto& memory = guest_ctx->parent->m_running_thread->GetOwnerProcess()->GetMemory();
|
||||||
const auto result = memory.InvalidateNCE(addr, Memory::YUZU_PAGESIZE);
|
if (memory.InvalidateNCE(addr, Memory::YUZU_PAGESIZE)) {
|
||||||
if (result == Memory::NCEAccessFaultAction::Retry) {
|
// We handled the access successfully and are returning to guest code.
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (result == Memory::NCEAccessFaultAction::Instruction) {
|
|
||||||
return HandleGuestAlignmentFault(guest_ctx, raw_info, raw_context);
|
|
||||||
}
|
|
||||||
|
|
||||||
// We couldn't handle the access.
|
// We couldn't handle the access.
|
||||||
return HandleFailedGuestFault(guest_ctx, raw_info, raw_context);
|
return HandleFailedGuestFault(guest_ctx, raw_info, raw_context);
|
||||||
@@ -234,7 +231,7 @@ HaltReason ArmNce::RunThread(Kernel::KThread* thread) {
|
|||||||
hr = ReturnToRunCodeByExceptionLevelChange(m_thread_id, thread_params); // Android: Use "process handle SIGUSR2 -n true -p true -s false" (and SIGURG) in LLDB when debugging
|
hr = ReturnToRunCodeByExceptionLevelChange(m_thread_id, thread_params); // Android: Use "process handle SIGUSR2 -n true -p true -s false" (and SIGURG) in LLDB when debugging
|
||||||
}
|
}
|
||||||
|
|
||||||
// Critical section for thread cleanup.
|
// Critical section for thread cleanup
|
||||||
std::atomic_thread_fence(std::memory_order_acquire);
|
std::atomic_thread_fence(std::memory_order_acquire);
|
||||||
|
|
||||||
// Cache values before releasing thread
|
// Cache values before releasing thread
|
||||||
|
|||||||
+8
-13
@@ -739,7 +739,7 @@ struct Memory::Impl {
|
|||||||
PAddr last_address;
|
PAddr last_address;
|
||||||
};
|
};
|
||||||
|
|
||||||
bool TryInvalidateGPUMemory(const u8* p, size_t size) {
|
void InvalidateGPUMemory(u8* p, size_t size) {
|
||||||
constexpr size_t sys_core = Core::Hardware::NUM_CPU_CORES - 1;
|
constexpr size_t sys_core = Core::Hardware::NUM_CPU_CORES - 1;
|
||||||
const size_t core = (std::min)(system.GetCurrentHostThreadID(),
|
const size_t core = (std::min)(system.GetCurrentHostThreadID(),
|
||||||
sys_core); // any other calls threads go to syscore.
|
sys_core); // any other calls threads go to syscore.
|
||||||
@@ -755,13 +755,9 @@ struct Memory::Impl {
|
|||||||
sys_core_guard.unlock();
|
sys_core_guard.unlock();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
bool has_pending_writes = false;
|
|
||||||
auto& gpu = system.GPU();
|
auto& gpu = system.GPU();
|
||||||
gpu_device_memory->ApplyOpOnPointer(
|
gpu_device_memory->ApplyOpOnPointer(
|
||||||
p, scratch_buffers[core], [&](DAddr address) { has_pending_writes |= gpu.MustFlushRegion(address, size); });
|
p, scratch_buffers[core], [&](DAddr address) { gpu.InvalidateRegion(address, size); });
|
||||||
if (has_pending_writes) return false;
|
|
||||||
gpu_device_memory->ApplyOpOnPointer(p, scratch_buffers[core], [&](DAddr address) { gpu.InvalidateRegion(address, size); });
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Core::System& system;
|
Core::System& system;
|
||||||
@@ -965,7 +961,7 @@ void Memory::MarkRegionDebug(Common::ProcessAddress vaddr, u64 size, bool debug)
|
|||||||
impl->MarkRegionDebug(GetInteger(vaddr), size, debug);
|
impl->MarkRegionDebug(GetInteger(vaddr), size, debug);
|
||||||
}
|
}
|
||||||
|
|
||||||
NCEAccessFaultAction Memory::InvalidateNCE(Common::ProcessAddress vaddr, size_t size) {
|
bool Memory::InvalidateNCE(Common::ProcessAddress vaddr, size_t size) {
|
||||||
[[maybe_unused]] bool mapped = true;
|
[[maybe_unused]] bool mapped = true;
|
||||||
[[maybe_unused]] bool rasterizer = false;
|
[[maybe_unused]] bool rasterizer = false;
|
||||||
|
|
||||||
@@ -977,18 +973,17 @@ NCEAccessFaultAction Memory::InvalidateNCE(Common::ProcessAddress vaddr, size_t
|
|||||||
mapped = false;
|
mapped = false;
|
||||||
},
|
},
|
||||||
[&] { rasterizer = true; });
|
[&] { rasterizer = true; });
|
||||||
|
if (rasterizer) {
|
||||||
|
impl->InvalidateGPUMemory(ptr, size);
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef __ANDROID__
|
#ifdef __ANDROID__
|
||||||
if (!rasterizer && mapped) {
|
if (!rasterizer && mapped) {
|
||||||
impl->host_buffer->DeferredMapSeparateHeap(GetInteger(vaddr));
|
impl->host_buffer->DeferredMapSeparateHeap(GetInteger(vaddr));
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (rasterizer) {
|
return mapped && ptr != nullptr;
|
||||||
if (!impl->TryInvalidateGPUMemory(ptr, size)) {
|
|
||||||
return NCEAccessFaultAction::Instruction;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return mapped && ptr != nullptr ? NCEAccessFaultAction::Retry : NCEAccessFaultAction::Unmapped;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Memory::InvalidateSeparateHeap(void* fault_address) {
|
bool Memory::InvalidateSeparateHeap(void* fault_address) {
|
||||||
|
|||||||
+2
-8
@@ -1,4 +1,4 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: 2014 Citra Emulator Project
|
// SPDX-FileCopyrightText: 2014 Citra Emulator Project
|
||||||
@@ -46,12 +46,6 @@ constexpr std::size_t YUZU_PAGEBITS = 12;
|
|||||||
constexpr u64 YUZU_PAGESIZE = 1ULL << YUZU_PAGEBITS;
|
constexpr u64 YUZU_PAGESIZE = 1ULL << YUZU_PAGEBITS;
|
||||||
constexpr u64 YUZU_PAGEMASK = YUZU_PAGESIZE - 1;
|
constexpr u64 YUZU_PAGEMASK = YUZU_PAGESIZE - 1;
|
||||||
|
|
||||||
enum class NCEAccessFaultAction {
|
|
||||||
Unmapped,
|
|
||||||
Retry,
|
|
||||||
Instruction,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Virtual user-space memory regions
|
/// Virtual user-space memory regions
|
||||||
enum : u64 {
|
enum : u64 {
|
||||||
/// TLS (Thread-Local Storage) related.
|
/// TLS (Thread-Local Storage) related.
|
||||||
@@ -499,7 +493,7 @@ public:
|
|||||||
|
|
||||||
void SetGPUDirtyManagers(std::span<Core::GPUDirtyMemoryManager> managers);
|
void SetGPUDirtyManagers(std::span<Core::GPUDirtyMemoryManager> managers);
|
||||||
|
|
||||||
NCEAccessFaultAction InvalidateNCE(Common::ProcessAddress vaddr, size_t size);
|
bool InvalidateNCE(Common::ProcessAddress vaddr, size_t size);
|
||||||
|
|
||||||
bool InvalidateSeparateHeap(void* fault_address);
|
bool InvalidateSeparateHeap(void* fault_address);
|
||||||
|
|
||||||
|
|||||||
+16
-69
@@ -11,34 +11,15 @@ set(dynarmic_VERSION ${dynarmic_VERSION_MAJOR}.${dynarmic_VERSION_MINOR}.${dynar
|
|||||||
|
|
||||||
project(dynarmic LANGUAGES C CXX ASM VERSION ${dynarmic_VERSION})
|
project(dynarmic LANGUAGES C CXX ASM VERSION ${dynarmic_VERSION})
|
||||||
|
|
||||||
# Determine if we're built as a subproject (using add_subdirectory)
|
|
||||||
# or if this is the master project.
|
|
||||||
set(MASTER_PROJECT OFF)
|
|
||||||
if (CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR)
|
|
||||||
set(MASTER_PROJECT ON)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if (MASTER_PROJECT)
|
|
||||||
include(CTest)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
# Dynarmic project options
|
|
||||||
option(DYNARMIC_ENABLE_CPU_FEATURE_DETECTION "Turning this off causes dynarmic to assume the host CPU doesn't support anything later than SSE3" ON)
|
|
||||||
|
|
||||||
if (OPENBSD OR DRAGONFLY OR NETBSD)
|
if (OPENBSD OR DRAGONFLY OR NETBSD)
|
||||||
set(REQUIRE_WX ON)
|
set(REQUIRE_WX ON)
|
||||||
else()
|
else()
|
||||||
set(REQUIRE_WX OFF)
|
set(REQUIRE_WX OFF)
|
||||||
endif()
|
endif()
|
||||||
option(DYNARMIC_ENABLE_NO_EXECUTE_SUPPORT "Enables support for systems that require W^X" ${REQUIRE_WX})
|
option(DYNARMIC_ENABLE_NO_EXECUTE_SUPPORT "Enables support for systems that require W^X" ${REQUIRE_WX})
|
||||||
|
|
||||||
option(DYNARMIC_IGNORE_ASSERTS "Ignore asserts" ON)
|
|
||||||
option(DYNARMIC_TESTS_USE_UNICORN "Enable fuzzing tests against unicorn" OFF)
|
option(DYNARMIC_TESTS_USE_UNICORN "Enable fuzzing tests against unicorn" OFF)
|
||||||
CMAKE_DEPENDENT_OPTION(DYNARMIC_USE_LLVM "Support disassembly of jitted x86_64 code using LLVM" OFF "NOT YUZU_DISABLE_LLVM" OFF)
|
CMAKE_DEPENDENT_OPTION(DYNARMIC_USE_LLVM "Support disassembly of jitted x86_64 code using LLVM" OFF "NOT YUZU_DISABLE_LLVM" OFF)
|
||||||
|
|
||||||
option(DYNARMIC_INSTALL "Install dynarmic headers and CMake files" OFF)
|
|
||||||
option(DYNARMIC_USE_BUNDLED_EXTERNALS "Use all bundled externals (useful when e.g. cross-compiling)" OFF)
|
|
||||||
|
|
||||||
# Set hard requirements for C++
|
# Set hard requirements for C++
|
||||||
set(CMAKE_CXX_STANDARD 20)
|
set(CMAKE_CXX_STANDARD 20)
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
@@ -88,7 +69,7 @@ if (MSVC)
|
|||||||
-Wno-missing-braces)
|
-Wno-missing-braces)
|
||||||
endif()
|
endif()
|
||||||
else()
|
else()
|
||||||
set(DYNARMIC_CXX_FLAGS
|
list(APPEND DYNARMIC_CXX_FLAGS
|
||||||
-Wall
|
-Wall
|
||||||
-Wextra
|
-Wextra
|
||||||
-Wcast-qual
|
-Wcast-qual
|
||||||
@@ -103,37 +84,35 @@ else()
|
|||||||
# to Address& after first checking isMEM(), and that code is inlined in a situation where
|
# to Address& after first checking isMEM(), and that code is inlined in a situation where
|
||||||
# GCC knows that the variable is actually a Reg64. isMEM() will never return true for a
|
# GCC knows that the variable is actually a Reg64. isMEM() will never return true for a
|
||||||
# Reg64, but GCC doesn't know that.
|
# Reg64, but GCC doesn't know that.
|
||||||
list(APPEND DYNARMIC_CXX_FLAGS -Wno-array-bounds)
|
list(APPEND DYNARMIC_CXX_FLAGS
|
||||||
list(APPEND DYNARMIC_CXX_FLAGS -Wstack-usage=4096)
|
-Wno-array-bounds
|
||||||
|
-Wstack-usage=4096)
|
||||||
endif()
|
endif()
|
||||||
if (CXX_CLANG)
|
if (CXX_CLANG)
|
||||||
# Bracket depth determines maximum size of a fold expression in Clang since 9c9974c3ccb6.
|
list(APPEND DYNARMIC_CXX_FLAGS
|
||||||
# And this in turns limits the size of a std::array.
|
# Bracket depth determines maximum size of a fold expression in Clang since 9c9974c3ccb6.
|
||||||
list(APPEND DYNARMIC_CXX_FLAGS -fbracket-depth=1024)
|
# And this in turns limits the size of a std::array.
|
||||||
# Clang mistakenly blames CMake for using unused arguments during compilation
|
-fbracket-depth=1024
|
||||||
list(APPEND DYNARMIC_CXX_FLAGS -Wno-unused-command-line-argument)
|
# Clang mistakenly blames CMake for using unused arguments during compilation
|
||||||
|
-Wno-unused-command-line-argument)
|
||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (NOT Boost_FOUND)
|
if (NOT Boost_FOUND)
|
||||||
find_package(Boost 1.57 REQUIRED)
|
find_package(Boost 1.57 REQUIRED)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
find_package(fmt 8 CONFIG)
|
find_package(fmt 8 CONFIG)
|
||||||
|
|
||||||
if ("arm64" IN_LIST ARCHITECTURE OR DYNARMIC_TESTS)
|
if (ARCHITECTURE_arm64)
|
||||||
find_package(oaknut 2.0.1 CONFIG)
|
find_package(oaknut 2.0.1 CONFIG)
|
||||||
endif()
|
endif()
|
||||||
|
if (ARCHITECTURE_riscv64)
|
||||||
if ("riscv64" IN_LIST ARCHITECTURE)
|
|
||||||
find_package(biscuit 0.9.1 REQUIRED)
|
find_package(biscuit 0.9.1 REQUIRED)
|
||||||
endif()
|
endif()
|
||||||
|
if (ARCHITECTURE_loongarch64)
|
||||||
if ("loongarch64" IN_LIST ARCHITECTURE)
|
|
||||||
find_package(lagoon REQUIRED)
|
find_package(lagoon REQUIRED)
|
||||||
endif()
|
endif()
|
||||||
|
if (ARCHITECTURE_x86_64)
|
||||||
if ("x86_64" IN_LIST ARCHITECTURE)
|
|
||||||
find_package(xbyak 7 CONFIG)
|
find_package(xbyak 7 CONFIG)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
@@ -142,44 +121,12 @@ if (DYNARMIC_USE_LLVM)
|
|||||||
separate_arguments(LLVM_DEFINITIONS)
|
separate_arguments(LLVM_DEFINITIONS)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
# Dynarmic project files
|
||||||
|
add_subdirectory(src/dynarmic)
|
||||||
if (DYNARMIC_TESTS)
|
if (DYNARMIC_TESTS)
|
||||||
find_package(Catch2 3 CONFIG)
|
find_package(Catch2 3 CONFIG)
|
||||||
if (DYNARMIC_TESTS_USE_UNICORN)
|
if (DYNARMIC_TESTS_USE_UNICORN)
|
||||||
find_package(Unicorn REQUIRED)
|
find_package(Unicorn REQUIRED)
|
||||||
endif()
|
endif()
|
||||||
endif()
|
|
||||||
|
|
||||||
# Dynarmic project files
|
|
||||||
add_subdirectory(src/dynarmic)
|
|
||||||
if (DYNARMIC_TESTS)
|
|
||||||
add_subdirectory(tests)
|
add_subdirectory(tests)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
#
|
|
||||||
# Install
|
|
||||||
#
|
|
||||||
if (DYNARMIC_INSTALL)
|
|
||||||
include(GNUInstallDirs)
|
|
||||||
include(CMakePackageConfigHelpers)
|
|
||||||
|
|
||||||
install(TARGETS dynarmic EXPORT dynarmicTargets)
|
|
||||||
install(EXPORT dynarmicTargets
|
|
||||||
NAMESPACE dynarmic::
|
|
||||||
DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/dynarmic"
|
|
||||||
)
|
|
||||||
|
|
||||||
configure_package_config_file(CMakeModules/dynarmicConfig.cmake.in
|
|
||||||
dynarmicConfig.cmake
|
|
||||||
INSTALL_DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/dynarmic"
|
|
||||||
)
|
|
||||||
write_basic_package_version_file(dynarmicConfigVersion.cmake
|
|
||||||
COMPATIBILITY SameMajorVersion
|
|
||||||
)
|
|
||||||
install(FILES
|
|
||||||
"${CMAKE_CURRENT_BINARY_DIR}/dynarmicConfig.cmake"
|
|
||||||
"${CMAKE_CURRENT_BINARY_DIR}/dynarmicConfigVersion.cmake"
|
|
||||||
DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/dynarmic"
|
|
||||||
)
|
|
||||||
|
|
||||||
install(DIRECTORY src/dynarmic TYPE INCLUDE FILES_MATCHING PATTERN "*.h")
|
|
||||||
endif()
|
|
||||||
|
|||||||
@@ -1,29 +0,0 @@
|
|||||||
# SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
function(target_architecture_specific_sources project arch)
|
|
||||||
if (NOT MULTIARCH_BUILD)
|
|
||||||
target_sources("${project}" PRIVATE ${ARGN})
|
|
||||||
return()
|
|
||||||
endif()
|
|
||||||
|
|
||||||
foreach(input_file IN LISTS ARGN)
|
|
||||||
if(input_file MATCHES ".cpp$")
|
|
||||||
if(NOT IS_ABSOLUTE ${input_file})
|
|
||||||
set(input_file "${CMAKE_CURRENT_SOURCE_DIR}/${input_file}")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
set(output_file "${CMAKE_CURRENT_BINARY_DIR}/arch_gen/${input_file}")
|
|
||||||
add_custom_command(
|
|
||||||
OUTPUT "${output_file}"
|
|
||||||
COMMAND ${CMAKE_COMMAND} "-Darch=${arch}"
|
|
||||||
"-Dinput_file=${input_file}"
|
|
||||||
"-Doutput_file=${output_file}"
|
|
||||||
-P "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/impl/TargetArchitectureSpecificSourcesWrapFile.cmake"
|
|
||||||
DEPENDS "${input_file}"
|
|
||||||
VERBATIM
|
|
||||||
)
|
|
||||||
target_sources(${project} PRIVATE "${output_file}")
|
|
||||||
endif()
|
|
||||||
endforeach()
|
|
||||||
endfunction()
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
@PACKAGE_INIT@
|
|
||||||
|
|
||||||
include(CMakeFindDependencyMacro)
|
|
||||||
|
|
||||||
set(ARCHITECTURE "@ARCHITECTURE@")
|
|
||||||
|
|
||||||
if (NOT @BUILD_SHARED_LIBS@)
|
|
||||||
find_dependency(Boost 1.57)
|
|
||||||
find_dependency(fmt 9)
|
|
||||||
find_dependency(mcl 0.1.12 EXACT)
|
|
||||||
|
|
||||||
if ("arm64" IN_LIST ARCHITECTURE)
|
|
||||||
find_dependency(oaknut 2.0.1)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if ("riscv" IN_LIST ARCHITECTURE)
|
|
||||||
find_dependency(biscuit 0.9.1)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if ("x86_64" IN_LIST ARCHITECTURE)
|
|
||||||
find_dependency(xbyak 7)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if (@DYNARMIC_USE_LLVM@)
|
|
||||||
find_dependency(LLVM)
|
|
||||||
endif()
|
|
||||||
endif()
|
|
||||||
|
|
||||||
include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake")
|
|
||||||
|
|
||||||
check_required_components(@PROJECT_NAME@)
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
# SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
string(TOUPPER "${arch}" arch)
|
|
||||||
file(READ "${input_file}" f_contents)
|
|
||||||
file(WRITE "${output_file}" "#if defined(ARCHITECTURE_${arch})\n${f_contents}\n#endif\n")
|
|
||||||
@@ -1,7 +1,5 @@
|
|||||||
# SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
# SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
include(TargetArchitectureSpecificSources)
|
|
||||||
|
|
||||||
add_library(dynarmic STATIC
|
add_library(dynarmic STATIC
|
||||||
mcl/bit.hpp
|
mcl/bit.hpp
|
||||||
mcl/function_info.hpp
|
mcl/function_info.hpp
|
||||||
@@ -135,7 +133,7 @@ add_library(dynarmic STATIC
|
|||||||
interface/A64/config.h
|
interface/A64/config.h
|
||||||
)
|
)
|
||||||
|
|
||||||
if ("x86_64" IN_LIST ARCHITECTURE)
|
if (ARCHITECTURE_x86_64)
|
||||||
# Newer versions of xbyak (>= 7.25.0) have stricter checks that currently
|
# Newer versions of xbyak (>= 7.25.0) have stricter checks that currently
|
||||||
# fail in dynarmic
|
# fail in dynarmic
|
||||||
target_compile_definitions(dynarmic PRIVATE XBYAK_STRICT_CHECK_MEM_REG_SIZE=0)
|
target_compile_definitions(dynarmic PRIVATE XBYAK_STRICT_CHECK_MEM_REG_SIZE=0)
|
||||||
@@ -143,7 +141,7 @@ if ("x86_64" IN_LIST ARCHITECTURE)
|
|||||||
target_compile_definitions(dynarmic PRIVATE XBYAK_OLD_DISP_CHECK=1)
|
target_compile_definitions(dynarmic PRIVATE XBYAK_OLD_DISP_CHECK=1)
|
||||||
target_link_libraries(dynarmic PRIVATE xbyak::xbyak)
|
target_link_libraries(dynarmic PRIVATE xbyak::xbyak)
|
||||||
|
|
||||||
target_architecture_specific_sources(dynarmic "x86_64"
|
target_sources(dynarmic PRIVATE
|
||||||
backend/x64/abi.cpp
|
backend/x64/abi.cpp
|
||||||
backend/x64/abi.h
|
backend/x64/abi.h
|
||||||
backend/x64/block_of_code.cpp
|
backend/x64/block_of_code.cpp
|
||||||
@@ -201,10 +199,10 @@ if ("x86_64" IN_LIST ARCHITECTURE)
|
|||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if ("arm64" IN_LIST ARCHITECTURE)
|
if (ARCHITECTURE_arm64)
|
||||||
target_link_libraries(dynarmic PRIVATE merry::oaknut)
|
target_link_libraries(dynarmic PRIVATE merry::oaknut)
|
||||||
|
|
||||||
target_architecture_specific_sources(dynarmic "arm64"
|
target_sources(dynarmic PRIVATE
|
||||||
backend/arm64/a32_jitstate.cpp
|
backend/arm64/a32_jitstate.cpp
|
||||||
backend/arm64/a32_jitstate.h
|
backend/arm64/a32_jitstate.h
|
||||||
backend/arm64/a64_jitstate.h
|
backend/arm64/a64_jitstate.h
|
||||||
@@ -255,7 +253,7 @@ if ("arm64" IN_LIST ARCHITECTURE)
|
|||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if ("riscv64" IN_LIST ARCHITECTURE)
|
if (ARCHITECTURE_riscv64)
|
||||||
target_link_libraries(dynarmic PRIVATE biscuit::biscuit)
|
target_link_libraries(dynarmic PRIVATE biscuit::biscuit)
|
||||||
|
|
||||||
target_sources(dynarmic PRIVATE
|
target_sources(dynarmic PRIVATE
|
||||||
@@ -295,7 +293,7 @@ if ("riscv64" IN_LIST ARCHITECTURE)
|
|||||||
message(WARNING "TODO: Incomplete frontend for this host architecture")
|
message(WARNING "TODO: Incomplete frontend for this host architecture")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if ("loongarch64" IN_LIST ARCHITECTURE)
|
if (ARCHITECTURE_loongarch64)
|
||||||
target_link_libraries(dynarmic PRIVATE lagoon::lagoon)
|
target_link_libraries(dynarmic PRIVATE lagoon::lagoon)
|
||||||
|
|
||||||
target_sources(dynarmic PRIVATE
|
target_sources(dynarmic PRIVATE
|
||||||
@@ -400,15 +398,9 @@ if (DYNARMIC_USE_LLVM)
|
|||||||
target_compile_definitions(dynarmic PRIVATE DYNARMIC_USE_LLVM=1 ${LLVM_DEFINITIONS})
|
target_compile_definitions(dynarmic PRIVATE DYNARMIC_USE_LLVM=1 ${LLVM_DEFINITIONS})
|
||||||
llvm_config(dynarmic USE_SHARED armdesc armdisassembler aarch64desc aarch64disassembler x86desc x86disassembler)
|
llvm_config(dynarmic USE_SHARED armdesc armdisassembler aarch64desc aarch64disassembler x86desc x86disassembler)
|
||||||
endif()
|
endif()
|
||||||
if (DYNARMIC_ENABLE_CPU_FEATURE_DETECTION)
|
|
||||||
target_compile_definitions(dynarmic PRIVATE DYNARMIC_ENABLE_CPU_FEATURE_DETECTION=1)
|
|
||||||
endif()
|
|
||||||
if (DYNARMIC_ENABLE_NO_EXECUTE_SUPPORT)
|
if (DYNARMIC_ENABLE_NO_EXECUTE_SUPPORT)
|
||||||
target_compile_definitions(dynarmic PRIVATE DYNARMIC_ENABLE_NO_EXECUTE_SUPPORT=1)
|
target_compile_definitions(dynarmic PRIVATE DYNARMIC_ENABLE_NO_EXECUTE_SUPPORT=1)
|
||||||
endif()
|
endif()
|
||||||
if (DYNARMIC_IGNORE_ASSERTS)
|
|
||||||
target_compile_definitions(dynarmic PRIVATE MCL_IGNORE_ASSERTS=1)
|
|
||||||
endif()
|
|
||||||
if (CMAKE_SYSTEM_NAME STREQUAL "Windows")
|
if (CMAKE_SYSTEM_NAME STREQUAL "Windows")
|
||||||
target_compile_definitions(dynarmic PRIVATE FMT_USE_WINDOWS_H=0)
|
target_compile_definitions(dynarmic PRIVATE FMT_USE_WINDOWS_H=0)
|
||||||
endif()
|
endif()
|
||||||
|
|||||||
@@ -197,15 +197,15 @@ void EmitA32Terminal(biscuit::Assembler& as, EmitContext& ctx, IR::Term::CheckHa
|
|||||||
|
|
||||||
void EmitA32LeafTerminal(biscuit::Assembler& as, EmitContext& ctx, IR::Term::LeafTerminal terminal, IR::LocationDescriptor initial_location, bool is_single_step) {
|
void EmitA32LeafTerminal(biscuit::Assembler& as, EmitContext& ctx, IR::Term::LeafTerminal terminal, IR::LocationDescriptor initial_location, bool is_single_step) {
|
||||||
if (auto const x = std::get_if<IR::Term::ReturnToDispatch>(&terminal))
|
if (auto const x = std::get_if<IR::Term::ReturnToDispatch>(&terminal))
|
||||||
return EmitA32LeafTerminal(as, ctx, *x, initial_location, is_single_step);
|
return EmitA32Terminal(as, ctx, *x, initial_location, is_single_step);
|
||||||
if (auto const x = std::get_if<IR::Term::LinkBlock>(&terminal))
|
if (auto const x = std::get_if<IR::Term::LinkBlock>(&terminal))
|
||||||
return EmitA32LeafTerminal(as, ctx, *x, initial_location, is_single_step);
|
return EmitA32Terminal(as, ctx, *x, initial_location, is_single_step);
|
||||||
if (auto const x = std::get_if<IR::Term::LinkBlockFast>(&terminal))
|
if (auto const x = std::get_if<IR::Term::LinkBlockFast>(&terminal))
|
||||||
return EmitA32LeafTerminal(as, ctx, *x, initial_location, is_single_step);
|
return EmitA32Terminal(as, ctx, *x, initial_location, is_single_step);
|
||||||
if (auto const x = std::get_if<IR::Term::PopRSBHint>(&terminal))
|
if (auto const x = std::get_if<IR::Term::PopRSBHint>(&terminal))
|
||||||
return EmitA32LeafTerminal(as, ctx, *x, initial_location, is_single_step);
|
return EmitA32Terminal(as, ctx, *x, initial_location, is_single_step);
|
||||||
if (auto const x = std::get_if<IR::Term::FastDispatchHint>(&terminal))
|
if (auto const x = std::get_if<IR::Term::FastDispatchHint>(&terminal))
|
||||||
return EmitA32LeafTerminal(as, ctx, *x, initial_location, is_single_step);
|
return EmitA32Terminal(as, ctx, *x, initial_location, is_single_step);
|
||||||
UNREACHABLE();
|
UNREACHABLE();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -78,7 +78,6 @@ void ProtectMemory(const void* base, size_t size, bool is_executable) {
|
|||||||
|
|
||||||
static const HostFeature features = []() {
|
static const HostFeature features = []() {
|
||||||
HostFeature f{};
|
HostFeature f{};
|
||||||
#ifdef DYNARMIC_ENABLE_CPU_FEATURE_DETECTION
|
|
||||||
using Cpu = Xbyak::util::Cpu;
|
using Cpu = Xbyak::util::Cpu;
|
||||||
Xbyak::util::Cpu cpu_info{};
|
Xbyak::util::Cpu cpu_info{};
|
||||||
if (cpu_info.has(Cpu::tSSSE3)) f |= HostFeature::SSSE3;
|
if (cpu_info.has(Cpu::tSSSE3)) f |= HostFeature::SSSE3;
|
||||||
@@ -121,7 +120,6 @@ static const HostFeature features = []() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return f;
|
return f;
|
||||||
#endif
|
|
||||||
}();
|
}();
|
||||||
HostFeature GetHostFeatures() {
|
HostFeature GetHostFeatures() {
|
||||||
return features;
|
return features;
|
||||||
|
|||||||
@@ -275,6 +275,7 @@ const void* EmitReadMemoryMov(BlockOfCode& code, int value_idx, const Xbyak::Reg
|
|||||||
code.xadd(qword[addr], Xbyak::Reg64(value_idx));
|
code.xadd(qword[addr], Xbyak::Reg64(value_idx));
|
||||||
break;
|
break;
|
||||||
case 128:
|
case 128:
|
||||||
|
ASSERT(Xbyak::Xmm(value_idx) != xmm0);
|
||||||
code.lock();
|
code.lock();
|
||||||
code.cmpxchg16b(xword[addr]);
|
code.cmpxchg16b(xword[addr]);
|
||||||
if (code.HasHostFeature(HostFeature::SSE41)) {
|
if (code.HasHostFeature(HostFeature::SSE41)) {
|
||||||
|
|||||||
@@ -6226,9 +6226,7 @@ void EmitX64::EmitVectorZeroExtend64(EmitContext& ctx, IR::Inst* inst) {
|
|||||||
void EmitX64::EmitVectorZeroUpper(EmitContext& ctx, IR::Inst* inst) {
|
void EmitX64::EmitVectorZeroUpper(EmitContext& ctx, IR::Inst* inst) {
|
||||||
auto args = ctx.reg_alloc.GetArgumentInfo(inst);
|
auto args = ctx.reg_alloc.GetArgumentInfo(inst);
|
||||||
auto const a = ctx.reg_alloc.UseScratchXmm(code, args[0]);
|
auto const a = ctx.reg_alloc.UseScratchXmm(code, args[0]);
|
||||||
|
|
||||||
code.movq(a, a); // TODO: !IsLastUse
|
code.movq(a, a); // TODO: !IsLastUse
|
||||||
|
|
||||||
ctx.reg_alloc.DefineValue(code, inst, a);
|
ctx.reg_alloc.DefineValue(code, inst, a);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,12 +14,6 @@
|
|||||||
#include "dynarmic/backend/x64/hostloc.h"
|
#include "dynarmic/backend/x64/hostloc.h"
|
||||||
#include "dynarmic/common/spin_lock.h"
|
#include "dynarmic/common/spin_lock.h"
|
||||||
|
|
||||||
#ifdef DYNARMIC_ENABLE_NO_EXECUTE_SUPPORT
|
|
||||||
static const auto default_cg_mode = Xbyak::DontSetProtectRWE;
|
|
||||||
#else
|
|
||||||
static const auto default_cg_mode = nullptr; //Allow RWE
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace Dynarmic {
|
namespace Dynarmic {
|
||||||
|
|
||||||
void EmitSpinLockLock(Xbyak::CodeGenerator& code, Xbyak::Reg64 ptr, Xbyak::Reg32 tmp, bool waitpkg) {
|
void EmitSpinLockLock(Xbyak::CodeGenerator& code, Xbyak::Reg64 ptr, Xbyak::Reg32 tmp, bool waitpkg) {
|
||||||
@@ -78,7 +72,13 @@ namespace {
|
|||||||
struct SpinLockImpl {
|
struct SpinLockImpl {
|
||||||
void Initialize() noexcept;
|
void Initialize() noexcept;
|
||||||
static void GlobalInitialize() noexcept;
|
static void GlobalInitialize() noexcept;
|
||||||
Xbyak::CodeGenerator code = Xbyak::CodeGenerator(4096, default_cg_mode);
|
Xbyak::CodeGenerator code = Xbyak::CodeGenerator(4096
|
||||||
|
#ifdef DYNARMIC_ENABLE_NO_EXECUTE_SUPPORT
|
||||||
|
, Xbyak::DontSetProtectRWE
|
||||||
|
#else
|
||||||
|
, nullptr //Allow RWE
|
||||||
|
#endif
|
||||||
|
);
|
||||||
void (*lock)(volatile int*) = nullptr;
|
void (*lock)(volatile int*) = nullptr;
|
||||||
void (*unlock)(volatile int*) = nullptr;
|
void (*unlock)(volatile int*) = nullptr;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ struct detail {
|
|||||||
shifts[arg_index] = bit_position;
|
shifts[arg_index] = bit_position;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#if !defined(DYNARMIC_IGNORE_ASSERTS) && !defined(__ANDROID__)
|
#if !defined(__ANDROID__)
|
||||||
// Avoids a MSVC ICE, and avoids Android NDK issue.
|
// Avoids a MSVC ICE, and avoids Android NDK issue.
|
||||||
DEBUG_ASSERT(std::all_of(masks.begin(), masks.end(), [](auto m) { return m != 0; }));
|
DEBUG_ASSERT(std::all_of(masks.begin(), masks.end(), [](auto m) { return m != 0; }));
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -2632,3 +2632,66 @@ TEST_CASE("A64: Rounding", "[a64]") {
|
|||||||
CHECK(jit.GetVector(5) == Vector{0x4000000040000000, 0x4000000040000000});
|
CHECK(jit.GetVector(5) == Vector{0x4000000040000000, 0x4000000040000000});
|
||||||
CHECK(jit.GetVector(6) == Vector{0x4000000040000000, 0x4000000040000000});
|
CHECK(jit.GetVector(6) == Vector{0x4000000040000000, 0x4000000040000000});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("A64: SM4", "[a64]") {
|
||||||
|
A64TestEnv env;
|
||||||
|
A64::UserConfig jit_user_config{};
|
||||||
|
jit_user_config.callbacks = &env;
|
||||||
|
A64::Jit jit{jit_user_config};
|
||||||
|
|
||||||
|
oaknut::VectorCodeGenerator code{env.code_mem, nullptr};
|
||||||
|
|
||||||
|
code.SM4EKEY(V4.S4(), V0.S4(), V1.S4());
|
||||||
|
code.SM4E(V5.S4(), V0.S4(), V1.S4());
|
||||||
|
|
||||||
|
jit.SetPC(0);
|
||||||
|
jit.SetVector(0, {0xefcdab8967452301, 0x1032547698badcfe});
|
||||||
|
jit.SetVector(1, {0xefcdab8967452301, 0x1032547698badcfe});
|
||||||
|
env.ticks_left = env.code_mem.size();
|
||||||
|
CheckedRun([&]() { jit.Run(); });
|
||||||
|
|
||||||
|
CHECK(jit.GetVector(4) == Vector{0x486a0c2e00224466, 0x5c7e183a62402604});
|
||||||
|
CHECK(jit.GetVector(5) == Vector{0xc2e086a43c1e785a, 0xd7f593b104264062});
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("A64: SM3", "[a64]") {
|
||||||
|
A64TestEnv env;
|
||||||
|
A64::UserConfig jit_user_config{};
|
||||||
|
jit_user_config.callbacks = &env;
|
||||||
|
SECTION("SM3SS1") {
|
||||||
|
A64::Jit jit{jit_user_config};
|
||||||
|
oaknut::VectorCodeGenerator code{env.code_mem, nullptr};
|
||||||
|
code.SM3SS1(V0.S4(), V0.S4(), V1.S4(), V2.S4());
|
||||||
|
jit.SetPC(0);
|
||||||
|
jit.SetVector(0, {0xec4aff517369c667, 0x46e3fbf2abbacd29});
|
||||||
|
jit.SetVector(1, {0x8de7e81bf854c27c, 0x9ac99f33632e5a76});
|
||||||
|
jit.SetVector(2, {0x5aa35831b70d3266, 0xd45ee95817055d25});
|
||||||
|
env.ticks_left = env.code_mem.size();
|
||||||
|
CheckedRun([&]() { jit.Run(); });
|
||||||
|
CHECK(jit.GetVector(0) == Vector{0x0000000000000000, 0x73d67cd700000000});
|
||||||
|
}
|
||||||
|
SECTION("SM3PARTW1") {
|
||||||
|
A64::Jit jit{jit_user_config};
|
||||||
|
oaknut::VectorCodeGenerator code{env.code_mem, nullptr};
|
||||||
|
code.SM3PARTW1(V0.S4(), V0.S4(), V1.S4(), V2.S4());
|
||||||
|
jit.SetPC(0);
|
||||||
|
jit.SetVector(0, {0xec4aff517369c667, 0x46e3fbf2abbacd29});
|
||||||
|
jit.SetVector(1, {0x8de7e81bf854c27c, 0x9ac99f33632e5a76});
|
||||||
|
jit.SetVector(2, {0x5aa35831b70d3266, 0xd45ee95817055d25});
|
||||||
|
env.ticks_left = env.code_mem.size();
|
||||||
|
CheckedRun([&]() { jit.Run(); });
|
||||||
|
CHECK(jit.GetVector(0) == Vector{0x653c64995693280c, 0xabcc7db97adebf12});
|
||||||
|
}
|
||||||
|
SECTION("SM3PARTW2") {
|
||||||
|
A64::Jit jit{jit_user_config};
|
||||||
|
oaknut::VectorCodeGenerator code{env.code_mem, nullptr};
|
||||||
|
code.SM3PARTW2(V0.S4(), V0.S4(), V1.S4(), V2.S4());
|
||||||
|
jit.SetPC(0);
|
||||||
|
jit.SetVector(0, {0xec4aff517369c667, 0x46e3fbf2abbacd29});
|
||||||
|
jit.SetVector(1, {0x8de7e81bf854c27c, 0x9ac99f33632e5a76});
|
||||||
|
jit.SetVector(2, {0x5aa35831b70d3266, 0xd45ee95817055d25});
|
||||||
|
env.ticks_left = env.code_mem.size();
|
||||||
|
CheckedRun([&]() { jit.Run(); });
|
||||||
|
CHECK(jit.GetVector(0) == Vector{0x30010fe70da43740, 0x6702425b4a3a05d4});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
# SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
# SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
include(TargetArchitectureSpecificSources)
|
|
||||||
|
|
||||||
add_executable(dynarmic_tests
|
add_executable(dynarmic_tests
|
||||||
fp/FPToFixed.cpp
|
fp/FPToFixed.cpp
|
||||||
fp/FPValue.cpp
|
fp/FPValue.cpp
|
||||||
@@ -44,13 +42,13 @@ if (DYNARMIC_TESTS_USE_UNICORN)
|
|||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if ("riscv" IN_LIST ARCHITECTURE)
|
if (ARCHITECTURE_riscv64)
|
||||||
target_link_libraries(dynarmic_tests PRIVATE biscuit::biscuit)
|
target_link_libraries(dynarmic_tests PRIVATE biscuit::biscuit)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if ("x86_64" IN_LIST ARCHITECTURE)
|
if (ARCHITECTURE_x86_64)
|
||||||
target_link_libraries(dynarmic_tests PRIVATE xbyak::xbyak)
|
target_link_libraries(dynarmic_tests PRIVATE xbyak::xbyak)
|
||||||
target_architecture_specific_sources(dynarmic_tests "x86_64"
|
target_sources(dynarmic_tests PRIVATE
|
||||||
x64_cpu_info.cpp
|
x64_cpu_info.cpp
|
||||||
native/preserve_xmm.cpp
|
native/preserve_xmm.cpp
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -40,9 +40,10 @@ struct HandleKey {
|
|||||||
u32 sec_index, sec_offset, sec_shift_left;
|
u32 sec_index, sec_offset, sec_shift_left;
|
||||||
u32 count;
|
u32 count;
|
||||||
bool has_secondary;
|
bool has_secondary;
|
||||||
|
u32 mask, sec_mask;
|
||||||
constexpr bool operator==(const HandleKey& o) const noexcept {
|
constexpr bool operator==(const HandleKey& o) const noexcept {
|
||||||
return std::tie(index, offset, shift_left, sec_index, sec_offset, sec_shift_left, count, has_secondary)
|
return std::tie(index, offset, shift_left, sec_index, sec_offset, sec_shift_left, count, has_secondary, mask, sec_mask)
|
||||||
== std::tie(o.index, o.offset, o.shift_left, o.sec_index, o.sec_offset, o.sec_shift_left, o.count, o.has_secondary);
|
== std::tie(o.index, o.offset, o.shift_left, o.sec_index, o.sec_offset, o.sec_shift_left, o.count, o.has_secondary, o.mask, o.sec_mask);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
struct HandleKeyHash {
|
struct HandleKeyHash {
|
||||||
@@ -52,6 +53,7 @@ struct HandleKeyHash {
|
|||||||
h ^= (size_t(k.sec_index) << 33) ^ (size_t(k.sec_offset) << 2);
|
h ^= (size_t(k.sec_index) << 33) ^ (size_t(k.sec_offset) << 2);
|
||||||
h ^= (size_t(k.sec_shift_left) << 3);
|
h ^= (size_t(k.sec_shift_left) << 3);
|
||||||
h ^= (size_t(k.count) << 7);
|
h ^= (size_t(k.count) << 7);
|
||||||
|
h ^= (size_t(k.mask) << 11) ^ (size_t(k.sec_mask) << 13);
|
||||||
return h ^ (k.has_secondary ? 0x9e3779b97f4a7c15ULL : 0ULL);
|
return h ^ (k.has_secondary ? 0x9e3779b97f4a7c15ULL : 0ULL);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -66,6 +68,14 @@ struct ConstBufferAddr {
|
|||||||
IR::U32 dynamic_offset;
|
IR::U32 dynamic_offset;
|
||||||
u32 count;
|
u32 count;
|
||||||
bool has_secondary;
|
bool has_secondary;
|
||||||
|
/// The dynamic offset is a constant buffer value shifted left, the same for the whole draw
|
||||||
|
bool has_index_cbuf;
|
||||||
|
u32 index_cbuf_index;
|
||||||
|
u32 index_cbuf_offset;
|
||||||
|
u32 index_shift;
|
||||||
|
/// Applied after the shifts
|
||||||
|
u32 mask = 0xffffffff;
|
||||||
|
u32 secondary_mask = 0xffffffff;
|
||||||
};
|
};
|
||||||
|
|
||||||
class Environment {
|
class Environment {
|
||||||
|
|||||||
+3
-3
@@ -1,4 +1,4 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||||
@@ -41,9 +41,9 @@ union F2I {
|
|||||||
BitField<39, 2, Rounding> rounding;
|
BitField<39, 2, Rounding> rounding;
|
||||||
BitField<41, 1, u64> half;
|
BitField<41, 1, u64> half;
|
||||||
BitField<44, 1, u64> ftz;
|
BitField<44, 1, u64> ftz;
|
||||||
BitField<45, 1, u64> abs;
|
BitField<45, 1, u64> neg;
|
||||||
BitField<47, 1, u64> cc;
|
BitField<47, 1, u64> cc;
|
||||||
BitField<49, 1, u64> neg;
|
BitField<49, 1, u64> abs;
|
||||||
};
|
};
|
||||||
|
|
||||||
size_t BitSize(DestFormat dest_format) {
|
size_t BitSize(DestFormat dest_format) {
|
||||||
|
|||||||
@@ -97,7 +97,8 @@ u32 DynamicDescriptorSizeShift(const IR::U32& dynamic_offset) {
|
|||||||
if (!shift.IsImmediate())
|
if (!shift.IsImmediate())
|
||||||
return DESCRIPTOR_SIZE_SHIFT;
|
return DESCRIPTOR_SIZE_SHIFT;
|
||||||
const u32 size_shift = shift.U32();
|
const u32 size_shift = shift.U32();
|
||||||
return size_shift >= DESCRIPTOR_SIZE_SHIFT && size_shift < 31 ? size_shift : DESCRIPTOR_SIZE_SHIFT;
|
// Separate texture and sampler handles are 4 bytes apart
|
||||||
|
return size_shift >= 2 && size_shift < 31 ? size_shift : DESCRIPTOR_SIZE_SHIFT;
|
||||||
}
|
}
|
||||||
|
|
||||||
u32 DynamicDescriptorCount(u32 base_offset, u32 size_shift, u32 max_descriptors) {
|
u32 DynamicDescriptorCount(u32 base_offset, u32 size_shift, u32 max_descriptors) {
|
||||||
@@ -113,6 +114,37 @@ u32 DynamicDescriptorCount(u32 base_offset, u32 size_shift, u32 max_descriptors)
|
|||||||
return std::min(descriptor_limit, available_count);
|
return std::min(descriptor_limit, available_count);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct UniformIndex {
|
||||||
|
u32 cbuf_index;
|
||||||
|
u32 cbuf_offset;
|
||||||
|
u32 shift;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Matches a dynamic handle offset that is a constant buffer value, optionally shifted left
|
||||||
|
std::optional<UniformIndex> TrackUniformIndex(const IR::U32& dynamic_offset) {
|
||||||
|
if (dynamic_offset.IsImmediate()) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
const IR::Inst* inst = dynamic_offset.InstRecursive();
|
||||||
|
u32 shift = 0;
|
||||||
|
if (inst->GetOpcode() == IR::Opcode::ShiftLeftLogical32) {
|
||||||
|
if (!inst->Arg(1).IsImmediate() || inst->Arg(0).IsImmediate()) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
shift = inst->Arg(1).U32();
|
||||||
|
inst = inst->Arg(0).InstRecursive();
|
||||||
|
}
|
||||||
|
if (inst->GetOpcode() != IR::Opcode::GetCbufU32 || !inst->Arg(0).IsImmediate() ||
|
||||||
|
!inst->Arg(1).IsImmediate() || shift >= 32) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
return UniformIndex{
|
||||||
|
.cbuf_index = inst->Arg(0).U32(),
|
||||||
|
.cbuf_offset = inst->Arg(1).U32(),
|
||||||
|
.shift = shift,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
u32 SaturatingSub(u32 lhs, u32 rhs) {
|
u32 SaturatingSub(u32 lhs, u32 rhs) {
|
||||||
return lhs > rhs ? lhs - rhs : 0;
|
return lhs > rhs ? lhs - rhs : 0;
|
||||||
}
|
}
|
||||||
@@ -327,14 +359,15 @@ static inline u32 GetTextureHandleCached(Environment& env, const ConstBufferAddr
|
|||||||
// If has secondary, then it will be cbuf.secondary_{index|offset}, else its 0.
|
// If has secondary, then it will be cbuf.secondary_{index|offset}, else its 0.
|
||||||
// So we can just hand it out the raw variable without using sec_idx or sec_off
|
// So we can just hand it out the raw variable without using sec_idx or sec_off
|
||||||
// because comparing 0 against 0 will yield true.
|
// because comparing 0 against 0 will yield true.
|
||||||
const HandleKey hk{cbuf.index, cbuf.offset, cbuf.shift_left, cbuf.secondary_index, cbuf.secondary_offset, cbuf.secondary_shift_left, cbuf.count, cbuf.has_secondary};
|
const HandleKey hk{cbuf.index, cbuf.offset, cbuf.shift_left, cbuf.secondary_index, cbuf.secondary_offset, cbuf.secondary_shift_left, cbuf.count, cbuf.has_secondary, cbuf.mask, cbuf.secondary_mask};
|
||||||
if (auto it = env.handle_cache.find(hk); it != env.handle_cache.end())
|
if (auto it = env.handle_cache.find(hk); it != env.handle_cache.end())
|
||||||
return it->second;
|
return it->second;
|
||||||
const u32 sec_idx = cbuf.has_secondary ? cbuf.secondary_index : cbuf.index;
|
u32 handle = (ReadCbufCached(env, cbuf.index, cbuf.offset) << cbuf.shift_left) & cbuf.mask;
|
||||||
const u32 sec_off = cbuf.has_secondary ? cbuf.secondary_offset : cbuf.offset;
|
if (cbuf.has_secondary) {
|
||||||
const u32 lhs = ReadCbufCached(env, cbuf.index, cbuf.offset) << cbuf.shift_left;
|
handle |= (ReadCbufCached(env, cbuf.secondary_index, cbuf.secondary_offset)
|
||||||
const u32 rhs = ReadCbufCached(env, sec_idx, sec_off) << cbuf.secondary_shift_left;
|
<< cbuf.secondary_shift_left) &
|
||||||
const u32 handle = lhs | rhs;
|
cbuf.secondary_mask;
|
||||||
|
}
|
||||||
env.handle_cache.emplace(hk, handle);
|
env.handle_cache.emplace(hk, handle);
|
||||||
return handle;
|
return handle;
|
||||||
}
|
}
|
||||||
@@ -406,10 +439,22 @@ std::optional<ConstBufferAddr> TryGetConstBuffer(const IR::Inst* inst, Environme
|
|||||||
if (lhs->has_secondary || rhs->has_secondary) {
|
if (lhs->has_secondary || rhs->has_secondary) {
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
if (lhs->count > 1 || rhs->count > 1) {
|
if (lhs->count > 1 && rhs->count > 1) {
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
if (lhs->shift_left > 0 || lhs->index > rhs->index || lhs->offset > rhs->offset) {
|
if (rhs->has_index_cbuf || lhs->has_index_cbuf) {
|
||||||
|
if (rhs->has_index_cbuf && lhs->has_index_cbuf) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
if (rhs->has_index_cbuf) {
|
||||||
|
std::swap(lhs, rhs);
|
||||||
|
}
|
||||||
|
} else if (rhs->count > 1) {
|
||||||
|
// An indexed handle array combined with a single handle (e.g. texture indices with
|
||||||
|
// a shared sampler), only the primary handle is indexed
|
||||||
|
std::swap(lhs, rhs);
|
||||||
|
} else if (lhs->count == 1 &&
|
||||||
|
(lhs->shift_left > 0 || lhs->index > rhs->index || lhs->offset > rhs->offset)) {
|
||||||
std::swap(lhs, rhs);
|
std::swap(lhs, rhs);
|
||||||
}
|
}
|
||||||
return ConstBufferAddr{
|
return ConstBufferAddr{
|
||||||
@@ -419,9 +464,15 @@ std::optional<ConstBufferAddr> TryGetConstBuffer(const IR::Inst* inst, Environme
|
|||||||
.secondary_index = rhs->index,
|
.secondary_index = rhs->index,
|
||||||
.secondary_offset = rhs->offset,
|
.secondary_offset = rhs->offset,
|
||||||
.secondary_shift_left = rhs->shift_left,
|
.secondary_shift_left = rhs->shift_left,
|
||||||
.dynamic_offset = {},
|
.dynamic_offset = lhs->dynamic_offset,
|
||||||
.count = 1,
|
.count = lhs->count,
|
||||||
.has_secondary = true,
|
.has_secondary = true,
|
||||||
|
.has_index_cbuf = lhs->has_index_cbuf,
|
||||||
|
.index_cbuf_index = lhs->index_cbuf_index,
|
||||||
|
.index_cbuf_offset = lhs->index_cbuf_offset,
|
||||||
|
.index_shift = lhs->index_shift,
|
||||||
|
.mask = lhs->mask,
|
||||||
|
.secondary_mask = rhs->mask,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
case IR::Opcode::ShiftLeftLogical32: {
|
case IR::Opcode::ShiftLeftLogical32: {
|
||||||
@@ -432,6 +483,8 @@ std::optional<ConstBufferAddr> TryGetConstBuffer(const IR::Inst* inst, Environme
|
|||||||
std::optional lhs{TrackCached(inst->Arg(0), env, host_info, block)};
|
std::optional lhs{TrackCached(inst->Arg(0), env, host_info, block)};
|
||||||
if (lhs) {
|
if (lhs) {
|
||||||
lhs->shift_left = shift.U32();
|
lhs->shift_left = shift.U32();
|
||||||
|
// A mask applied before the shift moves with the value
|
||||||
|
lhs->mask <<= shift.U32();
|
||||||
}
|
}
|
||||||
return lhs;
|
return lhs;
|
||||||
}
|
}
|
||||||
@@ -459,7 +512,7 @@ std::optional<ConstBufferAddr> TryGetConstBuffer(const IR::Inst* inst, Environme
|
|||||||
}
|
}
|
||||||
std::optional lhs{TrackCached(op1, env, host_info, block)};
|
std::optional lhs{TrackCached(op1, env, host_info, block)};
|
||||||
if (lhs) {
|
if (lhs) {
|
||||||
lhs->shift_left = static_cast<u32>(std::countr_zero(op2.U32()));
|
lhs->mask &= op2.U32();
|
||||||
}
|
}
|
||||||
return lhs;
|
return lhs;
|
||||||
break;
|
break;
|
||||||
@@ -489,12 +542,12 @@ std::optional<ConstBufferAddr> TryGetConstBuffer(const IR::Inst* inst, Environme
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
IR::Inst* const offset_inst{offset.InstRecursive()};
|
IR::Inst* const offset_inst{offset.InstRecursive()};
|
||||||
if (offset_inst->GetOpcode() != IR::Opcode::IAdd32) {
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
u32 base_offset{};
|
u32 base_offset{};
|
||||||
IR::U32 dynamic_offset;
|
IR::U32 dynamic_offset;
|
||||||
if (offset_inst->Arg(0).IsImmediate()) {
|
if (offset_inst->GetOpcode() != IR::Opcode::IAdd32) {
|
||||||
|
// The handle array starts at the beginning of the constant buffer
|
||||||
|
dynamic_offset = IR::U32{offset};
|
||||||
|
} else if (offset_inst->Arg(0).IsImmediate()) {
|
||||||
base_offset = offset_inst->Arg(0).U32();
|
base_offset = offset_inst->Arg(0).U32();
|
||||||
dynamic_offset = IR::U32{offset_inst->Arg(1)};
|
dynamic_offset = IR::U32{offset_inst->Arg(1)};
|
||||||
} else if (offset_inst->Arg(1).IsImmediate()) {
|
} else if (offset_inst->Arg(1).IsImmediate()) {
|
||||||
@@ -504,6 +557,7 @@ std::optional<ConstBufferAddr> TryGetConstBuffer(const IR::Inst* inst, Environme
|
|||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
auto const size_shift = DynamicDescriptorSizeShift(dynamic_offset);
|
auto const size_shift = DynamicDescriptorSizeShift(dynamic_offset);
|
||||||
|
const std::optional<UniformIndex> uniform_index{TrackUniformIndex(dynamic_offset)};
|
||||||
auto const sampled_limit = (std::max)(1U, (std::min)(host_info.max_per_stage_descriptor_sampled_images,
|
auto const sampled_limit = (std::max)(1U, (std::min)(host_info.max_per_stage_descriptor_sampled_images,
|
||||||
host_info.max_descriptor_set_sampled_images));
|
host_info.max_descriptor_set_sampled_images));
|
||||||
auto const resource_limit = (std::max)(1U, host_info.max_per_stage_resources);
|
auto const resource_limit = (std::max)(1U, host_info.max_per_stage_resources);
|
||||||
@@ -517,6 +571,10 @@ std::optional<ConstBufferAddr> TryGetConstBuffer(const IR::Inst* inst, Environme
|
|||||||
.dynamic_offset = dynamic_offset,
|
.dynamic_offset = dynamic_offset,
|
||||||
.count = DynamicDescriptorCount(base_offset, size_shift, (std::min)({DESCRIPTOR_MAX_COUNT, sampled_limit, resource_limit})),
|
.count = DynamicDescriptorCount(base_offset, size_shift, (std::min)({DESCRIPTOR_MAX_COUNT, sampled_limit, resource_limit})),
|
||||||
.has_secondary = false,
|
.has_secondary = false,
|
||||||
|
.has_index_cbuf = uniform_index.has_value(),
|
||||||
|
.index_cbuf_index = uniform_index ? uniform_index->cbuf_index : 0,
|
||||||
|
.index_cbuf_offset = uniform_index ? uniform_index->cbuf_offset : 0,
|
||||||
|
.index_shift = uniform_index ? uniform_index->shift : 0,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -551,12 +609,13 @@ TextureInst MakeInst(Environment& env, IR::Block* block, IR::Inst& inst, const H
|
|||||||
}
|
}
|
||||||
|
|
||||||
u32 GetTextureHandle(Environment& env, const ConstBufferAddr& cbuf) {
|
u32 GetTextureHandle(Environment& env, const ConstBufferAddr& cbuf) {
|
||||||
const u32 secondary_index{cbuf.has_secondary ? cbuf.secondary_index : cbuf.index};
|
u32 handle{(env.ReadCbufValue(cbuf.index, cbuf.offset) << cbuf.shift_left) & cbuf.mask};
|
||||||
const u32 secondary_offset{cbuf.has_secondary ? cbuf.secondary_offset : cbuf.offset};
|
if (cbuf.has_secondary) {
|
||||||
const u32 lhs_raw{env.ReadCbufValue(cbuf.index, cbuf.offset) << cbuf.shift_left};
|
handle |= (env.ReadCbufValue(cbuf.secondary_index, cbuf.secondary_offset)
|
||||||
const u32 rhs_raw{env.ReadCbufValue(secondary_index, secondary_offset)
|
<< cbuf.secondary_shift_left) &
|
||||||
<< cbuf.secondary_shift_left};
|
cbuf.secondary_mask;
|
||||||
return lhs_raw | rhs_raw;
|
}
|
||||||
|
return handle;
|
||||||
}
|
}
|
||||||
|
|
||||||
[[maybe_unused]] TextureType ReadTextureType(Environment& env, const ConstBufferAddr& cbuf) {
|
[[maybe_unused]] TextureType ReadTextureType(Environment& env, const ConstBufferAddr& cbuf) {
|
||||||
@@ -590,7 +649,8 @@ public:
|
|||||||
desc.secondary_cbuf_offset == existing.secondary_cbuf_offset &&
|
desc.secondary_cbuf_offset == existing.secondary_cbuf_offset &&
|
||||||
desc.secondary_shift_left == existing.secondary_shift_left &&
|
desc.secondary_shift_left == existing.secondary_shift_left &&
|
||||||
desc.count == existing.count && desc.size_shift == existing.size_shift &&
|
desc.count == existing.count && desc.size_shift == existing.size_shift &&
|
||||||
desc.has_secondary == existing.has_secondary;
|
desc.has_secondary == existing.has_secondary && desc.mask == existing.mask &&
|
||||||
|
desc.secondary_mask == existing.secondary_mask;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -616,7 +676,12 @@ public:
|
|||||||
desc.secondary_cbuf_index == existing.secondary_cbuf_index &&
|
desc.secondary_cbuf_index == existing.secondary_cbuf_index &&
|
||||||
desc.secondary_cbuf_offset == existing.secondary_cbuf_offset &&
|
desc.secondary_cbuf_offset == existing.secondary_cbuf_offset &&
|
||||||
desc.secondary_shift_left == existing.secondary_shift_left &&
|
desc.secondary_shift_left == existing.secondary_shift_left &&
|
||||||
desc.count == existing.count && desc.size_shift == existing.size_shift;
|
desc.count == existing.count && desc.size_shift == existing.size_shift &&
|
||||||
|
desc.has_index_cbuf == existing.has_index_cbuf &&
|
||||||
|
desc.index_cbuf_index == existing.index_cbuf_index &&
|
||||||
|
desc.index_cbuf_offset == existing.index_cbuf_offset &&
|
||||||
|
desc.index_shift == existing.index_shift && desc.mask == existing.mask &&
|
||||||
|
desc.secondary_mask == existing.secondary_mask;
|
||||||
})};
|
})};
|
||||||
// TODO: Read this from TIC
|
// TODO: Read this from TIC
|
||||||
texture_descriptors[index].is_multisample |= desc.is_multisample;
|
texture_descriptors[index].is_multisample |= desc.is_multisample;
|
||||||
@@ -863,9 +928,12 @@ void TexturePass(Environment& env, IR::Program& program, const HostTranslateInfo
|
|||||||
.secondary_shift_left = cbuf.secondary_shift_left,
|
.secondary_shift_left = cbuf.secondary_shift_left,
|
||||||
.count = count,
|
.count = count,
|
||||||
.size_shift = size_shift,
|
.size_shift = size_shift,
|
||||||
|
.mask = cbuf.mask,
|
||||||
|
.secondary_mask = cbuf.secondary_mask,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
count = std::min(count, sampled_dynamic_cap);
|
// A uniform index is resolved when drawing, so a single descriptor is enough
|
||||||
|
count = cbuf.has_index_cbuf ? 1 : std::min(count, sampled_dynamic_cap);
|
||||||
const bool is_integer{is_texture_pixel_format_integer(cbuf)};
|
const bool is_integer{is_texture_pixel_format_integer(cbuf)};
|
||||||
index = descriptors.Add(TextureDescriptor{
|
index = descriptors.Add(TextureDescriptor{
|
||||||
.type = flags.type,
|
.type = flags.type,
|
||||||
@@ -881,6 +949,12 @@ void TexturePass(Environment& env, IR::Program& program, const HostTranslateInfo
|
|||||||
.secondary_shift_left = cbuf.secondary_shift_left,
|
.secondary_shift_left = cbuf.secondary_shift_left,
|
||||||
.count = count,
|
.count = count,
|
||||||
.size_shift = size_shift,
|
.size_shift = size_shift,
|
||||||
|
.has_index_cbuf = cbuf.has_index_cbuf,
|
||||||
|
.index_cbuf_index = cbuf.index_cbuf_index,
|
||||||
|
.index_cbuf_offset = cbuf.index_cbuf_offset,
|
||||||
|
.index_shift = cbuf.index_shift,
|
||||||
|
.mask = cbuf.mask,
|
||||||
|
.secondary_mask = cbuf.secondary_mask,
|
||||||
});
|
});
|
||||||
flags.is_integer.Assign(is_integer ? 1 : 0);
|
flags.is_integer.Assign(is_integer ? 1 : 0);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -186,6 +186,8 @@ struct TextureBufferDescriptor {
|
|||||||
u32 secondary_shift_left;
|
u32 secondary_shift_left;
|
||||||
u32 count;
|
u32 count;
|
||||||
u32 size_shift;
|
u32 size_shift;
|
||||||
|
u32 mask = 0xffffffff;
|
||||||
|
u32 secondary_mask = 0xffffffff;
|
||||||
|
|
||||||
auto operator<=>(const TextureBufferDescriptor&) const = default;
|
auto operator<=>(const TextureBufferDescriptor&) const = default;
|
||||||
};
|
};
|
||||||
@@ -219,6 +221,13 @@ struct TextureDescriptor {
|
|||||||
u32 secondary_shift_left;
|
u32 secondary_shift_left;
|
||||||
u32 count;
|
u32 count;
|
||||||
u32 size_shift;
|
u32 size_shift;
|
||||||
|
/// The handle offset is moved by a constant buffer value read when drawing
|
||||||
|
bool has_index_cbuf;
|
||||||
|
u32 index_cbuf_index;
|
||||||
|
u32 index_cbuf_offset;
|
||||||
|
u32 index_shift;
|
||||||
|
u32 mask = 0xffffffff;
|
||||||
|
u32 secondary_mask = 0xffffffff;
|
||||||
|
|
||||||
auto operator<=>(const TextureDescriptor&) const = default;
|
auto operator<=>(const TextureDescriptor&) const = default;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -125,8 +125,7 @@ void BufferCache<P>::WriteMemory(DAddr device_addr, u64 size) {
|
|||||||
ClearDownload(device_addr, size);
|
ClearDownload(device_addr, size);
|
||||||
gpu_modified_ranges.Subtract(device_addr, size);
|
gpu_modified_ranges.Subtract(device_addr, size);
|
||||||
}
|
}
|
||||||
const DAddr page_start = Common::AlignDown(device_addr, DEVICE_PAGESIZE);
|
memory_tracker.MarkRegionAsCpuModified(device_addr, size);
|
||||||
memory_tracker.MarkRegionAsCpuModified(device_addr, size,IsRegionGpuModified(page_start, Common::AlignUp(device_addr + size, DEVICE_PAGESIZE) - page_start));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class P>
|
template <class P>
|
||||||
@@ -154,7 +153,7 @@ bool BufferCache<P>::OnCPUWrite(DAddr device_addr, u64 size) {
|
|||||||
if (!is_dirty) {
|
if (!is_dirty) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (IsRegionGpuModified(device_addr, size)) {
|
if (memory_tracker.IsRegionGpuModified(device_addr, size)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
WriteMemory(device_addr, size);
|
WriteMemory(device_addr, size);
|
||||||
@@ -949,9 +948,14 @@ void BufferCache<P>::BindHostGraphicsUniformBuffer(size_t stage, u32 index, u32
|
|||||||
return alignment > 1 && (offset % alignment) != 0;
|
return alignment > 1 && (offset % alignment) != 0;
|
||||||
}
|
}
|
||||||
}();
|
}();
|
||||||
|
// Uniform data rewritten by the CPU between draws would need a transfer copy into the cached
|
||||||
|
// buffer, which ends the current render pass. Read it from the stream buffer instead.
|
||||||
|
static constexpr u32 MAX_STREAMED_UNIFORM_BYTES = static_cast<u32>(16_KiB);
|
||||||
const bool use_fast_buffer = needs_alignment_stream
|
const bool use_fast_buffer = needs_alignment_stream
|
||||||
|| (has_host_buffer && size <= channel_state->uniform_buffer_skip_cache_size
|
|| (has_host_buffer && !memory_tracker.IsRegionGpuModified(device_addr, size)
|
||||||
&& !memory_tracker.IsRegionGpuModified(device_addr, size));
|
&& (size <= channel_state->uniform_buffer_skip_cache_size
|
||||||
|
|| (size <= MAX_STREAMED_UNIFORM_BYTES
|
||||||
|
&& memory_tracker.IsRegionCpuModified(device_addr, size))));
|
||||||
if (use_fast_buffer) {
|
if (use_fast_buffer) {
|
||||||
if constexpr (IS_OPENGL) {
|
if constexpr (IS_OPENGL) {
|
||||||
if (runtime.HasFastBufferSubData()) {
|
if (runtime.HasFastBufferSubData()) {
|
||||||
@@ -1782,23 +1786,13 @@ bool BufferCache<P>::SynchronizeBuffer(Buffer& buffer, DAddr device_addr, u32 si
|
|||||||
u64 largest_copy = 0;
|
u64 largest_copy = 0;
|
||||||
DAddr buffer_start = buffer.CpuAddr();
|
DAddr buffer_start = buffer.CpuAddr();
|
||||||
memory_tracker.ForEachUploadRange(device_addr, size, [&](u64 device_addr_out, u64 range_size) {
|
memory_tracker.ForEachUploadRange(device_addr, size, [&](u64 device_addr_out, u64 range_size) {
|
||||||
const auto add_upload = [&](DAddr start, DAddr end) {
|
copies.push_back(BufferCopy{
|
||||||
if (start == end) return;
|
.src_offset = total_size_bytes,
|
||||||
const u64 upload_size = end - start;
|
.dst_offset = device_addr_out - buffer_start,
|
||||||
copies.push_back(BufferCopy{
|
.size = range_size,
|
||||||
.src_offset = total_size_bytes,
|
|
||||||
.dst_offset = start - buffer_start,
|
|
||||||
.size = upload_size,
|
|
||||||
});
|
|
||||||
total_size_bytes += upload_size;
|
|
||||||
largest_copy = (std::max)(largest_copy, upload_size);
|
|
||||||
};
|
|
||||||
DAddr upload_start = device_addr_out;
|
|
||||||
gpu_modified_ranges.ForEachInRange(device_addr_out, range_size, [&](DAddr gpu_start, DAddr gpu_end) {
|
|
||||||
add_upload(upload_start, gpu_start);
|
|
||||||
upload_start = gpu_end;
|
|
||||||
});
|
});
|
||||||
add_upload(upload_start, device_addr_out + range_size);
|
total_size_bytes += range_size;
|
||||||
|
largest_copy = (std::max)(largest_copy, range_size);
|
||||||
});
|
});
|
||||||
if (total_size_bytes == 0) {
|
if (total_size_bytes == 0) {
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -259,6 +259,23 @@ public:
|
|||||||
|
|
||||||
void BindHostComputeBuffers();
|
void BindHostComputeBuffers();
|
||||||
|
|
||||||
|
/// Calls func(buffer, offset, device_addr, size) for each storage buffer the current compute
|
||||||
|
/// dispatch writes through a single host buffer
|
||||||
|
template <typename Func>
|
||||||
|
void ForEachWrittenComputeStorageBuffer(Func&& func) {
|
||||||
|
const u32 written = channel_state->enabled_compute_storage_buffers &
|
||||||
|
channel_state->written_compute_storage_buffers;
|
||||||
|
ForEachEnabledBit(written, [&](u32 index) {
|
||||||
|
const Binding& binding = channel_state->compute_storage_buffers[index];
|
||||||
|
if (binding.buffer_id == NULL_BUFFER_ID || binding.segment_count != 0 ||
|
||||||
|
binding.size == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Buffer& buffer = slot_buffers[binding.buffer_id];
|
||||||
|
func(buffer, buffer.Offset(binding.device_addr), binding.device_addr, binding.size);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
void SetUniformBuffersState(const std::array<u32, NUM_STAGES>& mask,
|
void SetUniformBuffersState(const std::array<u32, NUM_STAGES>& mask,
|
||||||
const UniformBufferSizes* sizes);
|
const UniformBufferSizes* sizes);
|
||||||
|
|
||||||
|
|||||||
@@ -71,9 +71,9 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Mark region as CPU modified, notifying the device_tracker about this change
|
/// Mark region as CPU modified, notifying the device_tracker about this change
|
||||||
void MarkRegionAsCpuModified(VAddr dirty_cpu_addr, u64 query_size, bool keep_tracking = false) {
|
void MarkRegionAsCpuModified(VAddr dirty_cpu_addr, u64 query_size) {
|
||||||
IteratePages<true>(dirty_cpu_addr, query_size, [keep_tracking](Manager* manager, u64 offset, size_t size) {
|
IteratePages<true>(dirty_cpu_addr, query_size, [](Manager* manager, u64 offset, size_t size) {
|
||||||
manager->ChangeRegionState(Type::CPU, true, manager->cpu_addr + offset, size, keep_tracking);
|
manager->ChangeRegionState(Type::CPU, true, manager->cpu_addr + offset, size);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -115,20 +115,19 @@ struct WordManager {
|
|||||||
/// @param enable If enabling or disabling
|
/// @param enable If enabling or disabling
|
||||||
/// @param dirty_addr Base address to mark or unmark as modified
|
/// @param dirty_addr Base address to mark or unmark as modified
|
||||||
/// @param size Size in bytes to mark or unmark as modified
|
/// @param size Size in bytes to mark or unmark as modified
|
||||||
/// @param keep_tracking Keep CPU-modified pages protected for subsequent native writes.
|
void ChangeRegionState(Type type, bool enable, u64 dirty_addr, u64 size) noexcept {
|
||||||
void ChangeRegionState(Type type, bool enable, u64 dirty_addr, u64 size, bool keep_tracking = false) noexcept {
|
|
||||||
std::span<u64> state_words = Span(type);
|
std::span<u64> state_words = Span(type);
|
||||||
[[maybe_unused]] std::span<u64> untracked_words = Span(Type::Untracked);
|
[[maybe_unused]] std::span<u64> untracked_words = Span(Type::Untracked);
|
||||||
[[maybe_unused]] std::span<u64> cached_words = Span(Type::CachedCPU);
|
[[maybe_unused]] std::span<u64> cached_words = Span(Type::CachedCPU);
|
||||||
std::vector<std::pair<VAddr, u64>> ranges;
|
std::vector<std::pair<VAddr, u64>> ranges;
|
||||||
IterateWords(dirty_addr - cpu_addr, size, [&](size_t index, u64 mask) {
|
IterateWords(dirty_addr - cpu_addr, size, [&](size_t index, u64 mask) {
|
||||||
if (type == Type::CPU || type == Type::CachedCPU) {
|
if (type == Type::CPU || type == Type::CachedCPU) {
|
||||||
CollectChangedRanges(!enable || keep_tracking, index, untracked_words[index], mask, ranges);
|
CollectChangedRanges(!enable, index, untracked_words[index], mask, ranges);
|
||||||
}
|
}
|
||||||
if (enable) {
|
if (enable) {
|
||||||
state_words[index] |= mask;
|
state_words[index] |= mask;
|
||||||
if (type == Type::CPU || type == Type::CachedCPU)
|
if (type == Type::CPU || type == Type::CachedCPU)
|
||||||
untracked_words[index] = (untracked_words[index] & ~mask) | (keep_tracking ? u64{0} : mask);
|
untracked_words[index] |= mask;
|
||||||
if (type == Type::CPU)
|
if (type == Type::CPU)
|
||||||
cached_words[index] &= ~mask;
|
cached_words[index] &= ~mask;
|
||||||
} else {
|
} else {
|
||||||
@@ -140,7 +139,7 @@ struct WordManager {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (!ranges.empty()) {
|
if (!ranges.empty()) {
|
||||||
ApplyCollectedRanges(ranges, (!enable || keep_tracking) ? 1 : -1);
|
ApplyCollectedRanges(ranges, (!enable) ? 1 : -1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -234,10 +234,6 @@ struct GPU::Impl {
|
|||||||
return renderer->ReadRasterizer()->OnCPUWrite(addr, size);
|
return renderer->ReadRasterizer()->OnCPUWrite(addr, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MustFlushRegion(DAddr addr, u64 size) {
|
|
||||||
return renderer->ReadRasterizer()->MustFlushRegion(addr, size);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Notify rasterizer that any caches of the specified region should be flushed and invalidated
|
/// Notify rasterizer that any caches of the specified region should be flushed and invalidated
|
||||||
void FlushAndInvalidateRegion(DAddr addr, u64 size) {
|
void FlushAndInvalidateRegion(DAddr addr, u64 size) {
|
||||||
gpu_thread.FlushAndInvalidateRegion(addr, size, is_async);
|
gpu_thread.FlushAndInvalidateRegion(addr, size, is_async);
|
||||||
@@ -530,10 +526,6 @@ bool GPU::OnCPUWrite(DAddr addr, u64 size) {
|
|||||||
return impl->OnCPUWrite(addr, size);
|
return impl->OnCPUWrite(addr, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GPU::MustFlushRegion(DAddr addr, u64 size) {
|
|
||||||
return impl->MustFlushRegion(addr, size);
|
|
||||||
}
|
|
||||||
|
|
||||||
void GPU::FlushAndInvalidateRegion(DAddr addr, u64 size) {
|
void GPU::FlushAndInvalidateRegion(DAddr addr, u64 size) {
|
||||||
impl->FlushAndInvalidateRegion(addr, size);
|
impl->FlushAndInvalidateRegion(addr, size);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -254,9 +254,6 @@ public:
|
|||||||
/// sensible, false otherwise, addr and size must be a valid combination
|
/// sensible, false otherwise, addr and size must be a valid combination
|
||||||
bool OnCPUWrite(DAddr addr, u64 size);
|
bool OnCPUWrite(DAddr addr, u64 size);
|
||||||
|
|
||||||
/// Check whether this range still has GPU writes that must be flushed to CPU memory.
|
|
||||||
bool MustFlushRegion(DAddr addr, u64 size);
|
|
||||||
|
|
||||||
/// Notify rasterizer that any caches of the specified region should be flushed and invalidated
|
/// Notify rasterizer that any caches of the specified region should be flushed and invalidated
|
||||||
void FlushAndInvalidateRegion(DAddr addr, u64 size);
|
void FlushAndInvalidateRegion(DAddr addr, u64 size);
|
||||||
|
|
||||||
|
|||||||
@@ -83,6 +83,7 @@ set(SHADER_FILES
|
|||||||
${CMAKE_CURRENT_SOURCE_DIR}/vulkan_present.vert
|
${CMAKE_CURRENT_SOURCE_DIR}/vulkan_present.vert
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/vulkan_present_scaleforce_fp16.frag
|
${CMAKE_CURRENT_SOURCE_DIR}/vulkan_present_scaleforce_fp16.frag
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/vulkan_present_scaleforce_fp32.frag
|
${CMAKE_CURRENT_SOURCE_DIR}/vulkan_present_scaleforce_fp32.frag
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/vulkan_block_linear_unswizzle_2d_buffer.comp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/vulkan_quad_indexed.comp
|
${CMAKE_CURRENT_SOURCE_DIR}/vulkan_quad_indexed.comp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/vulkan_turbo_mode.comp
|
${CMAKE_CURRENT_SOURCE_DIR}/vulkan_turbo_mode.comp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/vulkan_uint8.comp
|
${CMAKE_CURRENT_SOURCE_DIR}/vulkan_uint8.comp
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
#version 460 core
|
||||||
|
|
||||||
|
// Copies a block linear 2D image from a buffer into a tightly packed linear buffer, 4 bytes at a
|
||||||
|
// time. 16 byte chunks of a row are contiguous in a GOB, so any texel size works as long as the
|
||||||
|
// row size is a multiple of 4 bytes.
|
||||||
|
|
||||||
|
layout(local_size_x = 64, local_size_y = 1, local_size_z = 1) in;
|
||||||
|
|
||||||
|
layout(std430, set = 0, binding = 0) readonly buffer InputBuffer {
|
||||||
|
uint swizzled[];
|
||||||
|
};
|
||||||
|
|
||||||
|
layout(std430, set = 0, binding = 1) writeonly buffer OutputBuffer {
|
||||||
|
uint linear[];
|
||||||
|
};
|
||||||
|
|
||||||
|
layout(push_constant) uniform PushConstants {
|
||||||
|
uint src_word_offset;
|
||||||
|
uint row_words;
|
||||||
|
uint height;
|
||||||
|
uint layer_stride;
|
||||||
|
uint block_size;
|
||||||
|
uint x_shift;
|
||||||
|
uint block_height;
|
||||||
|
uint block_height_mask;
|
||||||
|
};
|
||||||
|
|
||||||
|
const uint GOB_SIZE_X_SHIFT = 6;
|
||||||
|
const uint GOB_SIZE_Y_SHIFT = 3;
|
||||||
|
const uint GOB_SIZE_SHIFT = GOB_SIZE_X_SHIFT + GOB_SIZE_Y_SHIFT;
|
||||||
|
|
||||||
|
uint SwizzleTable(uint pos) {
|
||||||
|
const uint t[8] = uint[](
|
||||||
|
0x12100200, 0x13110301, 0x16140604, 0x17150705,
|
||||||
|
0x1a180a08, 0x1b190b09, 0x1e1c0e0c, 0x1f1d0f0d
|
||||||
|
);
|
||||||
|
const uint i = pos >> 4;
|
||||||
|
const uint h = (t[i / 4] >> ((i % 4) * 8)) & 0xff;
|
||||||
|
return (h << 4) | (pos & 0xf);
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
const uint word = gl_GlobalInvocationID.x;
|
||||||
|
const uint y = gl_GlobalInvocationID.y;
|
||||||
|
const uint layer = gl_GlobalInvocationID.z;
|
||||||
|
if (word >= row_words || y >= height) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const uint x = word * 4;
|
||||||
|
const uint block_y = y >> GOB_SIZE_Y_SHIFT;
|
||||||
|
|
||||||
|
uint offset = layer * layer_stride;
|
||||||
|
offset += (block_y >> block_height) * block_size;
|
||||||
|
offset += (block_y & block_height_mask) << GOB_SIZE_SHIFT;
|
||||||
|
offset += (x >> GOB_SIZE_X_SHIFT) << x_shift;
|
||||||
|
offset += SwizzleTable((y & 7) * 64 + (x & 63));
|
||||||
|
|
||||||
|
linear[(layer * height + y) * row_words + word] = swizzled[src_word_offset + offset / 4];
|
||||||
|
}
|
||||||
@@ -107,22 +107,36 @@ void ComputePipeline::Configure() {
|
|||||||
const auto read_handle{[&](const auto& desc, u32 index) {
|
const auto read_handle{[&](const auto& desc, u32 index) {
|
||||||
ASSERT(((qmd.const_buffer_enable_mask >> desc.cbuf_index) & 1) != 0);
|
ASSERT(((qmd.const_buffer_enable_mask >> desc.cbuf_index) & 1) != 0);
|
||||||
const u32 index_offset{index << desc.size_shift};
|
const u32 index_offset{index << desc.size_shift};
|
||||||
const u32 offset{desc.cbuf_offset + index_offset};
|
u32 offset{desc.cbuf_offset + index_offset};
|
||||||
|
if constexpr (std::is_same_v<decltype(desc), const Shader::TextureDescriptor&>) {
|
||||||
|
if (desc.has_index_cbuf) {
|
||||||
|
ASSERT(((qmd.const_buffer_enable_mask >> desc.index_cbuf_index) & 1) != 0);
|
||||||
|
offset += gpu_memory->Read<u32>(cbufs[desc.index_cbuf_index].Address() +
|
||||||
|
desc.index_cbuf_offset)
|
||||||
|
<< desc.index_shift;
|
||||||
|
}
|
||||||
|
}
|
||||||
const GPUVAddr addr{cbufs[desc.cbuf_index].Address() + offset};
|
const GPUVAddr addr{cbufs[desc.cbuf_index].Address() + offset};
|
||||||
if constexpr (std::is_same_v<decltype(desc), const Shader::TextureDescriptor&> ||
|
if constexpr (std::is_same_v<decltype(desc), const Shader::TextureDescriptor&> ||
|
||||||
std::is_same_v<decltype(desc), const Shader::TextureBufferDescriptor&>) {
|
std::is_same_v<decltype(desc), const Shader::TextureBufferDescriptor&>) {
|
||||||
if (desc.has_secondary) {
|
if (desc.has_secondary) {
|
||||||
ASSERT(((qmd.const_buffer_enable_mask >> desc.secondary_cbuf_index) & 1) != 0);
|
ASSERT(((qmd.const_buffer_enable_mask >> desc.secondary_cbuf_index) & 1) != 0);
|
||||||
const u32 secondary_offset{desc.secondary_cbuf_offset + index_offset};
|
const u32 secondary_offset{desc.secondary_cbuf_offset};
|
||||||
const GPUVAddr separate_addr{cbufs[desc.secondary_cbuf_index].Address() +
|
const GPUVAddr separate_addr{cbufs[desc.secondary_cbuf_index].Address() +
|
||||||
secondary_offset};
|
secondary_offset};
|
||||||
const u32 lhs_raw{gpu_memory->Read<u32>(addr) << desc.shift_left};
|
const u32 lhs_raw{(gpu_memory->Read<u32>(addr) << desc.shift_left) & desc.mask};
|
||||||
const u32 rhs_raw{gpu_memory->Read<u32>(separate_addr)
|
const u32 rhs_raw{(gpu_memory->Read<u32>(separate_addr)
|
||||||
<< desc.secondary_shift_left};
|
<< desc.secondary_shift_left) &
|
||||||
|
desc.secondary_mask};
|
||||||
return TexturePair(lhs_raw | rhs_raw, via_header_index);
|
return TexturePair(lhs_raw | rhs_raw, via_header_index);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return TexturePair(gpu_memory->Read<u32>(addr), via_header_index);
|
u32 raw{gpu_memory->Read<u32>(addr)};
|
||||||
|
if constexpr (std::is_same_v<decltype(desc), const Shader::TextureDescriptor&> ||
|
||||||
|
std::is_same_v<decltype(desc), const Shader::TextureBufferDescriptor&>) {
|
||||||
|
raw &= desc.mask;
|
||||||
|
}
|
||||||
|
return TexturePair(raw, via_header_index);
|
||||||
}};
|
}};
|
||||||
const auto add_image{[&](const auto& desc, bool blacklist) {
|
const auto add_image{[&](const auto& desc, bool blacklist) {
|
||||||
for (u32 index = 0; index < desc.count; ++index) {
|
for (u32 index = 0; index < desc.count; ++index) {
|
||||||
|
|||||||
@@ -308,23 +308,37 @@ bool GraphicsPipeline::ConfigureImpl(bool is_indexed) {
|
|||||||
const auto read_handle{[&](const auto& desc, u32 index) {
|
const auto read_handle{[&](const auto& desc, u32 index) {
|
||||||
ASSERT(cbufs[desc.cbuf_index].enabled);
|
ASSERT(cbufs[desc.cbuf_index].enabled);
|
||||||
const u32 index_offset{index << desc.size_shift};
|
const u32 index_offset{index << desc.size_shift};
|
||||||
const u32 offset{desc.cbuf_offset + index_offset};
|
u32 offset{desc.cbuf_offset + index_offset};
|
||||||
|
if constexpr (std::is_same_v<decltype(desc), const TextureDescriptor&>) {
|
||||||
|
if (desc.has_index_cbuf) {
|
||||||
|
ASSERT(cbufs[desc.index_cbuf_index].enabled);
|
||||||
|
offset += gpu_memory->Read<u32>(cbufs[desc.index_cbuf_index].address +
|
||||||
|
desc.index_cbuf_offset)
|
||||||
|
<< desc.index_shift;
|
||||||
|
}
|
||||||
|
}
|
||||||
const GPUVAddr addr{cbufs[desc.cbuf_index].address + offset};
|
const GPUVAddr addr{cbufs[desc.cbuf_index].address + offset};
|
||||||
if constexpr (std::is_same_v<decltype(desc), const TextureDescriptor&> ||
|
if constexpr (std::is_same_v<decltype(desc), const TextureDescriptor&> ||
|
||||||
std::is_same_v<decltype(desc), const TextureBufferDescriptor&>) {
|
std::is_same_v<decltype(desc), const TextureBufferDescriptor&>) {
|
||||||
if (desc.has_secondary) {
|
if (desc.has_secondary) {
|
||||||
ASSERT(cbufs[desc.secondary_cbuf_index].enabled);
|
ASSERT(cbufs[desc.secondary_cbuf_index].enabled);
|
||||||
const u32 second_offset{desc.secondary_cbuf_offset + index_offset};
|
const u32 second_offset{desc.secondary_cbuf_offset};
|
||||||
const GPUVAddr separate_addr{cbufs[desc.secondary_cbuf_index].address +
|
const GPUVAddr separate_addr{cbufs[desc.secondary_cbuf_index].address +
|
||||||
second_offset};
|
second_offset};
|
||||||
const u32 lhs_raw{gpu_memory->Read<u32>(addr) << desc.shift_left};
|
const u32 lhs_raw{(gpu_memory->Read<u32>(addr) << desc.shift_left) & desc.mask};
|
||||||
const u32 rhs_raw{gpu_memory->Read<u32>(separate_addr)
|
const u32 rhs_raw{(gpu_memory->Read<u32>(separate_addr)
|
||||||
<< desc.secondary_shift_left};
|
<< desc.secondary_shift_left) &
|
||||||
|
desc.secondary_mask};
|
||||||
const u32 raw{lhs_raw | rhs_raw};
|
const u32 raw{lhs_raw | rhs_raw};
|
||||||
return TexturePair(raw, via_header_index);
|
return TexturePair(raw, via_header_index);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return TexturePair(gpu_memory->Read<u32>(addr), via_header_index);
|
u32 raw{gpu_memory->Read<u32>(addr)};
|
||||||
|
if constexpr (std::is_same_v<decltype(desc), const TextureDescriptor&> ||
|
||||||
|
std::is_same_v<decltype(desc), const TextureBufferDescriptor&>) {
|
||||||
|
raw &= desc.mask;
|
||||||
|
}
|
||||||
|
return TexturePair(raw, via_header_index);
|
||||||
}};
|
}};
|
||||||
const auto add_image{[&](const auto& desc, bool blacklist) LAMBDA_FORCEINLINE {
|
const auto add_image{[&](const auto& desc, bool blacklist) LAMBDA_FORCEINLINE {
|
||||||
for (u32 index = 0; index < desc.count; ++index) {
|
for (u32 index = 0; index < desc.count; ++index) {
|
||||||
|
|||||||
@@ -129,7 +129,13 @@ void FixedPipelineState::Refresh(Tegra::Engines::Maxwell3D& maxwell3d, DynamicFe
|
|||||||
topology.Assign(can_collapse_topology_class
|
topology.Assign(can_collapse_topology_class
|
||||||
? TOPOLOGY_CLASS_REPRESENTATIVE_LUT[static_cast<size_t>(topology_)]
|
? TOPOLOGY_CLASS_REPRESENTATIVE_LUT[static_cast<size_t>(topology_)]
|
||||||
: topology_);
|
: topology_);
|
||||||
msaa_mode.Assign(regs.anti_alias_samples_mode);
|
// Target-independent rasterization rasterizes (and depth tests) at the raster sample count
|
||||||
|
if (regs.tir_mode == Maxwell::TIRMode::RasterNTargetM &&
|
||||||
|
regs.anti_alias_raster != Maxwell::AntiAliasRaster::Mode1x1) {
|
||||||
|
msaa_mode.Assign(static_cast<Tegra::Texture::MsaaMode>(regs.anti_alias_raster));
|
||||||
|
} else {
|
||||||
|
msaa_mode.Assign(regs.anti_alias_samples_mode);
|
||||||
|
}
|
||||||
attachment0_dual_source_blend.Assign(ComputeAttachment0DualSourceBlend(regs) ? 1 : 0);
|
attachment0_dual_source_blend.Assign(ComputeAttachment0DualSourceBlend(regs) ? 1 : 0);
|
||||||
|
|
||||||
raw2 = 0;
|
raw2 = 0;
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
#include "video_core/host_shaders/queries_prefix_scan_sum_comp_spv.h"
|
#include "video_core/host_shaders/queries_prefix_scan_sum_comp_spv.h"
|
||||||
#include "video_core/host_shaders/queries_prefix_scan_sum_nosubgroups_comp_spv.h"
|
#include "video_core/host_shaders/queries_prefix_scan_sum_nosubgroups_comp_spv.h"
|
||||||
#include "video_core/host_shaders/resolve_conditional_render_comp_spv.h"
|
#include "video_core/host_shaders/resolve_conditional_render_comp_spv.h"
|
||||||
|
#include "video_core/host_shaders/vulkan_block_linear_unswizzle_2d_buffer_comp_spv.h"
|
||||||
#include "video_core/host_shaders/vulkan_quad_indexed_comp_spv.h"
|
#include "video_core/host_shaders/vulkan_quad_indexed_comp_spv.h"
|
||||||
#include "video_core/host_shaders/vulkan_uint8_comp_spv.h"
|
#include "video_core/host_shaders/vulkan_uint8_comp_spv.h"
|
||||||
#include "video_core/host_shaders/block_linear_unswizzle_3d_bcn_comp_spv.h"
|
#include "video_core/host_shaders/block_linear_unswizzle_3d_bcn_comp_spv.h"
|
||||||
@@ -31,6 +32,7 @@
|
|||||||
#include "video_core/renderer_vulkan/vk_update_descriptor.h"
|
#include "video_core/renderer_vulkan/vk_update_descriptor.h"
|
||||||
#include "video_core/texture_cache/accelerated_swizzle.h"
|
#include "video_core/texture_cache/accelerated_swizzle.h"
|
||||||
#include "video_core/texture_cache/types.h"
|
#include "video_core/texture_cache/types.h"
|
||||||
|
#include "video_core/texture_cache/util.h"
|
||||||
#include "video_core/textures/decoders.h"
|
#include "video_core/textures/decoders.h"
|
||||||
#include "video_core/vulkan_common/vulkan_device.h"
|
#include "video_core/vulkan_common/vulkan_device.h"
|
||||||
#include "video_core/vulkan_common/vulkan_wrapper.h"
|
#include "video_core/vulkan_common/vulkan_wrapper.h"
|
||||||
@@ -640,6 +642,143 @@ struct alignas(16) BlockLinearUnswizzle3DPushConstants {
|
|||||||
};
|
};
|
||||||
static_assert(sizeof(BlockLinearUnswizzle3DPushConstants) <= 128);
|
static_assert(sizeof(BlockLinearUnswizzle3DPushConstants) <= 128);
|
||||||
|
|
||||||
|
struct BlockLinearUnswizzle2DBufferPushConstants {
|
||||||
|
u32 src_word_offset;
|
||||||
|
u32 row_words;
|
||||||
|
u32 height;
|
||||||
|
u32 layer_stride;
|
||||||
|
u32 block_size;
|
||||||
|
u32 x_shift;
|
||||||
|
u32 block_height;
|
||||||
|
u32 block_height_mask;
|
||||||
|
};
|
||||||
|
|
||||||
|
BlockLinearUnswizzle2DBufferPass::BlockLinearUnswizzle2DBufferPass(
|
||||||
|
const Device& device_, Scheduler& scheduler_, DescriptorPool& descriptor_pool_,
|
||||||
|
StagingBufferPool& staging_buffer_pool_,
|
||||||
|
ComputePassDescriptorQueue& compute_pass_descriptor_queue_)
|
||||||
|
: ComputePass(device_, scheduler_, descriptor_pool_, INPUT_OUTPUT_DESCRIPTOR_SET_BINDINGS,
|
||||||
|
INPUT_OUTPUT_DESCRIPTOR_UPDATE_TEMPLATE, INPUT_OUTPUT_BANK_INFO,
|
||||||
|
COMPUTE_PUSH_CONSTANT_RANGE<sizeof(BlockLinearUnswizzle2DBufferPushConstants)>,
|
||||||
|
VULKAN_BLOCK_LINEAR_UNSWIZZLE_2D_BUFFER_COMP_SPV),
|
||||||
|
scheduler{scheduler_}, staging_buffer_pool{staging_buffer_pool_},
|
||||||
|
compute_pass_descriptor_queue{compute_pass_descriptor_queue_} {}
|
||||||
|
|
||||||
|
BlockLinearUnswizzle2DBufferPass::~BlockLinearUnswizzle2DBufferPass() = default;
|
||||||
|
|
||||||
|
bool BlockLinearUnswizzle2DBufferPass::IsSupported(const Image& image) {
|
||||||
|
const VideoCommon::ImageInfo& info = image.info;
|
||||||
|
if (info.type != VideoCommon::ImageType::e2D || info.num_samples != 1 ||
|
||||||
|
VideoCore::Surface::GetFormatType(info.format) != VideoCore::Surface::SurfaceType::ColorTexture ||
|
||||||
|
VideoCore::Surface::DefaultBlockWidth(info.format) != 1 ||
|
||||||
|
VideoCore::Surface::DefaultBlockHeight(info.format) != 1 ||
|
||||||
|
True(image.flags & VideoCommon::ImageFlagBits::Converted) ||
|
||||||
|
True(image.flags & VideoCommon::ImageFlagBits::Rescaled)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const u32 bytes_per_block = VideoCore::Surface::BytesPerBlock(info.format);
|
||||||
|
for (const VideoCommon::SwizzleParameters& swizzle : VideoCommon::FullUploadSwizzles(info)) {
|
||||||
|
if ((swizzle.num_tiles.width * bytes_per_block) % 4 != 0 || swizzle.num_tiles.depth != 1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::pair<VkBuffer, boost::container::small_vector<VkBufferImageCopy, 16>>
|
||||||
|
BlockLinearUnswizzle2DBufferPass::Unswizzle(Image& image, VkBuffer src_buffer,
|
||||||
|
VkDeviceSize src_offset) {
|
||||||
|
const VideoCommon::ImageInfo& info = image.info;
|
||||||
|
const auto swizzles = VideoCommon::FullUploadSwizzles(info);
|
||||||
|
const u32 bytes_per_block = VideoCore::Surface::BytesPerBlock(info.format);
|
||||||
|
const u32 layers = static_cast<u32>(info.resources.layers);
|
||||||
|
const VkDeviceSize alignment = (std::max<VkDeviceSize>)(device.GetStorageBufferAlignment(), 4);
|
||||||
|
|
||||||
|
VkDeviceSize total_size = 0;
|
||||||
|
for (const VideoCommon::SwizzleParameters& swizzle : swizzles) {
|
||||||
|
total_size = Common::AlignUp(total_size, alignment);
|
||||||
|
total_size += VkDeviceSize{swizzle.num_tiles.width} * bytes_per_block *
|
||||||
|
swizzle.num_tiles.height * layers;
|
||||||
|
}
|
||||||
|
const StagingBufferRef linear = staging_buffer_pool.Request(total_size, MemoryUsage::DeviceLocal);
|
||||||
|
|
||||||
|
scheduler.RequestOutsideRenderPassOperationContext();
|
||||||
|
scheduler.Record([](vk::CommandBuffer cmdbuf) {
|
||||||
|
static constexpr VkMemoryBarrier READ_BARRIER{
|
||||||
|
.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
|
||||||
|
.pNext = nullptr,
|
||||||
|
.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT,
|
||||||
|
.dstAccessMask = VK_ACCESS_SHADER_READ_BIT,
|
||||||
|
};
|
||||||
|
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
|
||||||
|
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, READ_BARRIER);
|
||||||
|
});
|
||||||
|
|
||||||
|
boost::container::small_vector<VkBufferImageCopy, 16> copies;
|
||||||
|
VkDeviceSize linear_offset = 0;
|
||||||
|
for (const VideoCommon::SwizzleParameters& swizzle : swizzles) {
|
||||||
|
linear_offset = Common::AlignUp(linear_offset, alignment);
|
||||||
|
const auto params = VideoCommon::Accelerated::MakeBlockLinearSwizzle2DParams(swizzle, info);
|
||||||
|
const u32 row_words = swizzle.num_tiles.width * bytes_per_block / 4;
|
||||||
|
const u32 height = swizzle.num_tiles.height;
|
||||||
|
const VkDeviceSize level_size = VkDeviceSize{row_words} * 4 * height * layers;
|
||||||
|
const VkDeviceSize level_src = src_offset + swizzle.buffer_offset;
|
||||||
|
const VkDeviceSize aligned_src = level_src & ~(alignment - 1);
|
||||||
|
const VkDeviceSize src_size = image.guest_size_bytes - swizzle.buffer_offset +
|
||||||
|
(level_src - aligned_src);
|
||||||
|
|
||||||
|
compute_pass_descriptor_queue.Acquire(scheduler, 2);
|
||||||
|
compute_pass_descriptor_queue.AddBuffer(src_buffer, aligned_src, src_size);
|
||||||
|
compute_pass_descriptor_queue.AddBuffer(linear.buffer, linear.offset + linear_offset,
|
||||||
|
level_size);
|
||||||
|
const void* const descriptor_data{compute_pass_descriptor_queue.UpdateData()};
|
||||||
|
const BlockLinearUnswizzle2DBufferPushConstants push{
|
||||||
|
.src_word_offset = static_cast<u32>((level_src - aligned_src) / 4),
|
||||||
|
.row_words = row_words,
|
||||||
|
.height = height,
|
||||||
|
.layer_stride = params.layer_stride,
|
||||||
|
.block_size = params.block_size,
|
||||||
|
.x_shift = params.x_shift,
|
||||||
|
.block_height = params.block_height,
|
||||||
|
.block_height_mask = params.block_height_mask,
|
||||||
|
};
|
||||||
|
scheduler.Record([this, descriptor_data, push, layers](vk::CommandBuffer cmdbuf) {
|
||||||
|
static constexpr u32 WORKGROUP_SIZE_X = 64;
|
||||||
|
const VkDescriptorSet set = descriptor_allocator.Commit();
|
||||||
|
device.GetLogical().UpdateDescriptorSet(set, *descriptor_template, descriptor_data);
|
||||||
|
cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_COMPUTE, *pipeline);
|
||||||
|
cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_COMPUTE, *layout, 0, set, {});
|
||||||
|
cmdbuf.PushConstants(*layout, VK_SHADER_STAGE_COMPUTE_BIT, push);
|
||||||
|
cmdbuf.Dispatch(Common::DivCeil(push.row_words, WORKGROUP_SIZE_X), push.height, layers);
|
||||||
|
});
|
||||||
|
copies.push_back(VkBufferImageCopy{
|
||||||
|
.bufferOffset = linear.offset + linear_offset,
|
||||||
|
.bufferRowLength = 0,
|
||||||
|
.bufferImageHeight = 0,
|
||||||
|
.imageSubresource{
|
||||||
|
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
|
||||||
|
.mipLevel = static_cast<u32>(swizzle.level),
|
||||||
|
.baseArrayLayer = 0,
|
||||||
|
.layerCount = layers,
|
||||||
|
},
|
||||||
|
.imageOffset{0, 0, 0},
|
||||||
|
.imageExtent{swizzle.num_tiles.width, height, 1},
|
||||||
|
});
|
||||||
|
linear_offset += level_size;
|
||||||
|
}
|
||||||
|
scheduler.Record([](vk::CommandBuffer cmdbuf) {
|
||||||
|
static constexpr VkMemoryBarrier WRITE_BARRIER{
|
||||||
|
.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
|
||||||
|
.pNext = nullptr,
|
||||||
|
.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT,
|
||||||
|
.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT,
|
||||||
|
};
|
||||||
|
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
|
||||||
|
VK_PIPELINE_STAGE_TRANSFER_BIT, 0, WRITE_BARRIER);
|
||||||
|
});
|
||||||
|
return {linear.buffer, std::move(copies)};
|
||||||
|
}
|
||||||
|
|
||||||
BlockLinearUnswizzle3DPass::BlockLinearUnswizzle3DPass(
|
BlockLinearUnswizzle3DPass::BlockLinearUnswizzle3DPass(
|
||||||
const Device& device_, Scheduler& scheduler_,
|
const Device& device_, Scheduler& scheduler_,
|
||||||
DescriptorPool& descriptor_pool_,
|
DescriptorPool& descriptor_pool_,
|
||||||
|
|||||||
@@ -10,6 +10,8 @@
|
|||||||
#include <span>
|
#include <span>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
|
#include <boost/container/small_vector.hpp>
|
||||||
|
|
||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
#include "video_core/engines/maxwell_3d.h"
|
#include "video_core/engines/maxwell_3d.h"
|
||||||
#include "video_core/renderer_vulkan/vk_descriptor_pool.h"
|
#include "video_core/renderer_vulkan/vk_descriptor_pool.h"
|
||||||
@@ -137,6 +139,28 @@ private:
|
|||||||
MemoryAllocator& memory_allocator;
|
MemoryAllocator& memory_allocator;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class BlockLinearUnswizzle2DBufferPass final : public ComputePass {
|
||||||
|
public:
|
||||||
|
explicit BlockLinearUnswizzle2DBufferPass(
|
||||||
|
const Device& device_, Scheduler& scheduler_, DescriptorPool& descriptor_pool_,
|
||||||
|
StagingBufferPool& staging_buffer_pool_,
|
||||||
|
ComputePassDescriptorQueue& compute_pass_descriptor_queue_);
|
||||||
|
~BlockLinearUnswizzle2DBufferPass();
|
||||||
|
|
||||||
|
/// Returns true when the guest layout of the image can be unswizzled by this pass
|
||||||
|
[[nodiscard]] static bool IsSupported(const Image& image);
|
||||||
|
|
||||||
|
/// Unswizzles the block linear guest data of the image, read from a GPU buffer, into a linear
|
||||||
|
/// buffer. Returns the linear buffer and the copies that upload it into the image.
|
||||||
|
std::pair<VkBuffer, boost::container::small_vector<VkBufferImageCopy, 16>> Unswizzle(
|
||||||
|
Image& image, VkBuffer src_buffer, VkDeviceSize src_offset);
|
||||||
|
|
||||||
|
private:
|
||||||
|
Scheduler& scheduler;
|
||||||
|
StagingBufferPool& staging_buffer_pool;
|
||||||
|
ComputePassDescriptorQueue& compute_pass_descriptor_queue;
|
||||||
|
};
|
||||||
|
|
||||||
class BlockLinearUnswizzle3DPass final : public ComputePass {
|
class BlockLinearUnswizzle3DPass final : public ComputePass {
|
||||||
public:
|
public:
|
||||||
explicit BlockLinearUnswizzle3DPass(const Device& device_, Scheduler& scheduler_,
|
explicit BlockLinearUnswizzle3DPass(const Device& device_, Scheduler& scheduler_,
|
||||||
|
|||||||
@@ -174,21 +174,36 @@ bool ComputePipeline::Configure(Tegra::Engines::KeplerCompute& kepler_compute,
|
|||||||
const auto read_handle{[&](const auto& desc, u32 index) {
|
const auto read_handle{[&](const auto& desc, u32 index) {
|
||||||
ASSERT(((qmd.const_buffer_enable_mask >> desc.cbuf_index) & 1) != 0);
|
ASSERT(((qmd.const_buffer_enable_mask >> desc.cbuf_index) & 1) != 0);
|
||||||
const u32 index_offset{index << desc.size_shift};
|
const u32 index_offset{index << desc.size_shift};
|
||||||
const u32 offset{desc.cbuf_offset + index_offset};
|
u32 offset{desc.cbuf_offset + index_offset};
|
||||||
|
if constexpr (std::is_same_v<decltype(desc), const Shader::TextureDescriptor&>) {
|
||||||
|
if (desc.has_index_cbuf) {
|
||||||
|
ASSERT(((qmd.const_buffer_enable_mask >> desc.index_cbuf_index) & 1) != 0);
|
||||||
|
offset += gpu_memory.Read<u32>(cbufs[desc.index_cbuf_index].Address() +
|
||||||
|
desc.index_cbuf_offset)
|
||||||
|
<< desc.index_shift;
|
||||||
|
}
|
||||||
|
}
|
||||||
const GPUVAddr addr{cbufs[desc.cbuf_index].Address() + offset};
|
const GPUVAddr addr{cbufs[desc.cbuf_index].Address() + offset};
|
||||||
if constexpr (std::is_same_v<decltype(desc), const Shader::TextureDescriptor&> ||
|
if constexpr (std::is_same_v<decltype(desc), const Shader::TextureDescriptor&> ||
|
||||||
std::is_same_v<decltype(desc), const Shader::TextureBufferDescriptor&>) {
|
std::is_same_v<decltype(desc), const Shader::TextureBufferDescriptor&>) {
|
||||||
if (desc.has_secondary) {
|
if (desc.has_secondary) {
|
||||||
ASSERT(((qmd.const_buffer_enable_mask >> desc.secondary_cbuf_index) & 1) != 0);
|
ASSERT(((qmd.const_buffer_enable_mask >> desc.secondary_cbuf_index) & 1) != 0);
|
||||||
const u32 secondary_offset{desc.secondary_cbuf_offset + index_offset};
|
const u32 secondary_offset{desc.secondary_cbuf_offset};
|
||||||
const GPUVAddr separate_addr{cbufs[desc.secondary_cbuf_index].Address() +
|
const GPUVAddr separate_addr{cbufs[desc.secondary_cbuf_index].Address() +
|
||||||
secondary_offset};
|
secondary_offset};
|
||||||
const u32 lhs_raw{gpu_memory.Read<u32>(addr) << desc.shift_left};
|
const u32 lhs_raw{(gpu_memory.Read<u32>(addr) << desc.shift_left) & desc.mask};
|
||||||
const u32 rhs_raw{gpu_memory.Read<u32>(separate_addr) << desc.secondary_shift_left};
|
const u32 rhs_raw{(gpu_memory.Read<u32>(separate_addr)
|
||||||
|
<< desc.secondary_shift_left) &
|
||||||
|
desc.secondary_mask};
|
||||||
return TexturePair(lhs_raw | rhs_raw, via_header_index);
|
return TexturePair(lhs_raw | rhs_raw, via_header_index);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return TexturePair(gpu_memory.Read<u32>(addr), via_header_index);
|
u32 raw{gpu_memory.Read<u32>(addr)};
|
||||||
|
if constexpr (std::is_same_v<decltype(desc), const Shader::TextureDescriptor&> ||
|
||||||
|
std::is_same_v<decltype(desc), const Shader::TextureBufferDescriptor&>) {
|
||||||
|
raw &= desc.mask;
|
||||||
|
}
|
||||||
|
return TexturePair(raw, via_header_index);
|
||||||
}};
|
}};
|
||||||
const auto add_image{[&](const auto& desc, bool blacklist) {
|
const auto add_image{[&](const auto& desc, bool blacklist) {
|
||||||
for (u32 index = 0; index < desc.count; ++index) {
|
for (u32 index = 0; index < desc.count; ++index) {
|
||||||
|
|||||||
@@ -377,23 +377,37 @@ bool GraphicsPipeline::ConfigureImpl(bool is_indexed) {
|
|||||||
const auto read_handle{[&](const auto& desc, u32 index) {
|
const auto read_handle{[&](const auto& desc, u32 index) {
|
||||||
ASSERT(cbufs[desc.cbuf_index].enabled);
|
ASSERT(cbufs[desc.cbuf_index].enabled);
|
||||||
const u32 index_offset{index << desc.size_shift};
|
const u32 index_offset{index << desc.size_shift};
|
||||||
const u32 offset{desc.cbuf_offset + index_offset};
|
u32 offset{desc.cbuf_offset + index_offset};
|
||||||
|
if constexpr (std::is_same_v<decltype(desc), const Shader::TextureDescriptor&>) {
|
||||||
|
if (desc.has_index_cbuf) {
|
||||||
|
ASSERT(cbufs[desc.index_cbuf_index].enabled);
|
||||||
|
offset += gpu_memory->Read<u32>(cbufs[desc.index_cbuf_index].address +
|
||||||
|
desc.index_cbuf_offset)
|
||||||
|
<< desc.index_shift;
|
||||||
|
}
|
||||||
|
}
|
||||||
const GPUVAddr addr{cbufs[desc.cbuf_index].address + offset};
|
const GPUVAddr addr{cbufs[desc.cbuf_index].address + offset};
|
||||||
if constexpr (std::is_same_v<decltype(desc), const Shader::TextureDescriptor&> ||
|
if constexpr (std::is_same_v<decltype(desc), const Shader::TextureDescriptor&> ||
|
||||||
std::is_same_v<decltype(desc), const Shader::TextureBufferDescriptor&>) {
|
std::is_same_v<decltype(desc), const Shader::TextureBufferDescriptor&>) {
|
||||||
if (desc.has_secondary) {
|
if (desc.has_secondary) {
|
||||||
ASSERT(cbufs[desc.secondary_cbuf_index].enabled);
|
ASSERT(cbufs[desc.secondary_cbuf_index].enabled);
|
||||||
const u32 second_offset{desc.secondary_cbuf_offset + index_offset};
|
const u32 second_offset{desc.secondary_cbuf_offset};
|
||||||
const GPUVAddr separate_addr{cbufs[desc.secondary_cbuf_index].address +
|
const GPUVAddr separate_addr{cbufs[desc.secondary_cbuf_index].address +
|
||||||
second_offset};
|
second_offset};
|
||||||
const u32 lhs_raw{gpu_memory->Read<u32>(addr) << desc.shift_left};
|
const u32 lhs_raw{(gpu_memory->Read<u32>(addr) << desc.shift_left) & desc.mask};
|
||||||
const u32 rhs_raw{gpu_memory->Read<u32>(separate_addr)
|
const u32 rhs_raw{(gpu_memory->Read<u32>(separate_addr)
|
||||||
<< desc.secondary_shift_left};
|
<< desc.secondary_shift_left) &
|
||||||
|
desc.secondary_mask};
|
||||||
const u32 raw{lhs_raw | rhs_raw};
|
const u32 raw{lhs_raw | rhs_raw};
|
||||||
return TexturePair(raw, via_header_index);
|
return TexturePair(raw, via_header_index);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return TexturePair(gpu_memory->Read<u32>(addr), via_header_index);
|
u32 raw{gpu_memory->Read<u32>(addr)};
|
||||||
|
if constexpr (std::is_same_v<decltype(desc), const Shader::TextureDescriptor&> ||
|
||||||
|
std::is_same_v<decltype(desc), const Shader::TextureBufferDescriptor&>) {
|
||||||
|
raw &= desc.mask;
|
||||||
|
}
|
||||||
|
return TexturePair(raw, via_header_index);
|
||||||
}};
|
}};
|
||||||
const auto add_image{[&](const auto& desc, bool blacklist) LAMBDA_FORCEINLINE {
|
const auto add_image{[&](const auto& desc, bool blacklist) LAMBDA_FORCEINLINE {
|
||||||
for (u32 index = 0; index < desc.count; ++index) {
|
for (u32 index = 0; index < desc.count; ++index) {
|
||||||
|
|||||||
@@ -601,6 +601,12 @@ void RasterizerVulkan::DispatchCompute() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const auto update_images_written_by_buffers = [this] {
|
||||||
|
buffer_cache.ForEachWrittenComputeStorageBuffer(
|
||||||
|
[this](auto& buffer, u32 offset, DAddr device_addr, u32 size) {
|
||||||
|
texture_cache.UpdateImagesFromBuffer(device_addr, size, buffer.Handle(), offset);
|
||||||
|
});
|
||||||
|
};
|
||||||
const auto& qmd{kepler_compute->launch_description};
|
const auto& qmd{kepler_compute->launch_description};
|
||||||
auto indirect_address = kepler_compute->GetIndirectComputeAddress();
|
auto indirect_address = kepler_compute->GetIndirectComputeAddress();
|
||||||
if (indirect_address) {
|
if (indirect_address) {
|
||||||
@@ -617,6 +623,7 @@ void RasterizerVulkan::DispatchCompute() {
|
|||||||
}
|
}
|
||||||
cmdbuf.DispatchIndirect(indirect_buffer, indirect_offset);
|
cmdbuf.DispatchIndirect(indirect_buffer, indirect_offset);
|
||||||
});
|
});
|
||||||
|
update_images_written_by_buffers();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const std::array<u32, 3> dim{qmd.grid_dim_x, qmd.grid_dim_y, qmd.grid_dim_z};
|
const std::array<u32, 3> dim{qmd.grid_dim_x, qmd.grid_dim_y, qmd.grid_dim_z};
|
||||||
@@ -639,6 +646,7 @@ void RasterizerVulkan::DispatchCompute() {
|
|||||||
}
|
}
|
||||||
cmdbuf.Dispatch(dim[0], dim[1], dim[2]);
|
cmdbuf.Dispatch(dim[0], dim[1], dim[2]);
|
||||||
});
|
});
|
||||||
|
update_images_written_by_buffers();
|
||||||
|
|
||||||
// Log compute dispatch
|
// Log compute dispatch
|
||||||
if (GPU::Logging::IsActive() &&
|
if (GPU::Logging::IsActive() &&
|
||||||
|
|||||||
@@ -946,6 +946,8 @@ TextureCacheRuntime::TextureCacheRuntime(const Device& device_, Scheduler& sched
|
|||||||
astc_decoder_pass.emplace(device, scheduler, descriptor_pool, staging_buffer_pool,
|
astc_decoder_pass.emplace(device, scheduler, descriptor_pool, staging_buffer_pool,
|
||||||
compute_pass_descriptor_queue, memory_allocator);
|
compute_pass_descriptor_queue, memory_allocator);
|
||||||
}
|
}
|
||||||
|
bl2d_buffer_unswizzle_pass.emplace(device, scheduler, descriptor_pool, staging_buffer_pool,
|
||||||
|
compute_pass_descriptor_queue);
|
||||||
if (!device.IsKhrImageFormatListSupported()) {
|
if (!device.IsKhrImageFormatListSupported()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1089,6 +1091,58 @@ VkImageView TextureCacheRuntime::GetOrCreateResolveShadow(VkImage msaa_image, Vk
|
|||||||
return *shadow.view;
|
return *shadow.view;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
VkImageView TextureCacheRuntime::GetOrCreateTirColorScratch(VkImage color_image, VkFormat format,
|
||||||
|
VkExtent2D extent, u32 layers,
|
||||||
|
VkSampleCountFlagBits samples) {
|
||||||
|
TirColorScratch& scratch = tir_color_scratch[color_image];
|
||||||
|
if (scratch.image && scratch.format == format && scratch.extent.width == extent.width &&
|
||||||
|
scratch.extent.height == extent.height && scratch.layers == layers &&
|
||||||
|
scratch.samples == samples) {
|
||||||
|
return *scratch.view;
|
||||||
|
}
|
||||||
|
if (scratch.image) {
|
||||||
|
scheduler.Finish();
|
||||||
|
}
|
||||||
|
scratch.image = memory_allocator.CreateImage(VkImageCreateInfo{
|
||||||
|
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
|
||||||
|
.pNext = nullptr,
|
||||||
|
.flags = 0,
|
||||||
|
.imageType = VK_IMAGE_TYPE_2D,
|
||||||
|
.format = format,
|
||||||
|
.extent = {extent.width, extent.height, 1},
|
||||||
|
.mipLevels = 1,
|
||||||
|
.arrayLayers = layers,
|
||||||
|
.samples = samples,
|
||||||
|
.tiling = VK_IMAGE_TILING_OPTIMAL,
|
||||||
|
.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT,
|
||||||
|
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
|
||||||
|
.queueFamilyIndexCount = 0,
|
||||||
|
.pQueueFamilyIndices = nullptr,
|
||||||
|
.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
|
||||||
|
});
|
||||||
|
scratch.view = device.GetLogical().CreateImageView(VkImageViewCreateInfo{
|
||||||
|
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
|
||||||
|
.pNext = nullptr,
|
||||||
|
.flags = 0,
|
||||||
|
.image = *scratch.image,
|
||||||
|
.viewType = layers > 1 ? VK_IMAGE_VIEW_TYPE_2D_ARRAY : VK_IMAGE_VIEW_TYPE_2D,
|
||||||
|
.format = format,
|
||||||
|
.components{},
|
||||||
|
.subresourceRange{
|
||||||
|
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
|
||||||
|
.baseMipLevel = 0,
|
||||||
|
.levelCount = 1,
|
||||||
|
.baseArrayLayer = 0,
|
||||||
|
.layerCount = layers,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
scratch.format = format;
|
||||||
|
scratch.extent = extent;
|
||||||
|
scratch.layers = layers;
|
||||||
|
scratch.samples = samples;
|
||||||
|
return *scratch.view;
|
||||||
|
}
|
||||||
|
|
||||||
const TextureCacheRuntime::ResolveShadow* TextureCacheRuntime::GetValidResolveShadow(
|
const TextureCacheRuntime::ResolveShadow* TextureCacheRuntime::GetValidResolveShadow(
|
||||||
VkImage msaa_image) const {
|
VkImage msaa_image) const {
|
||||||
const auto it = resolve_shadows.find(msaa_image);
|
const auto it = resolve_shadows.find(msaa_image);
|
||||||
@@ -2068,6 +2122,27 @@ void Image::UploadMemory(VkBuffer buffer, VkDeviceSize offset,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Image::UploadSwizzledFromBuffer(VkBuffer buffer, VkDeviceSize offset) {
|
||||||
|
if (runtime == nullptr || !runtime->bl2d_buffer_unswizzle_pass || offset % 4 != 0 ||
|
||||||
|
guest_size_bytes > runtime->device.GetMaxStorageBufferRange() ||
|
||||||
|
!BlockLinearUnswizzle2DBufferPass::IsSupported(*this)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (ENABLE_MSAA_RESOLVE_CONSUME) {
|
||||||
|
runtime->InvalidateResolveShadow(Handle());
|
||||||
|
}
|
||||||
|
auto [linear_buffer, copies] =
|
||||||
|
runtime->bl2d_buffer_unswizzle_pass->Unswizzle(*this, buffer, offset);
|
||||||
|
const VkImage vk_image = *original_image;
|
||||||
|
const VkImageAspectFlags vk_aspect_mask = aspect_mask;
|
||||||
|
const bool was_initialized = std::exchange(initialized, true);
|
||||||
|
scheduler->Record([linear_buffer, vk_image, vk_aspect_mask, was_initialized,
|
||||||
|
copies = std::move(copies)](vk::CommandBuffer cmdbuf) {
|
||||||
|
CopyBufferToImage(cmdbuf, linear_buffer, vk_image, vk_aspect_mask, was_initialized, copies);
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
void Image::UploadMemory(const StagingBufferRef& map, std::span<const BufferImageCopy> copies) {
|
void Image::UploadMemory(const StagingBufferRef& map, std::span<const BufferImageCopy> copies) {
|
||||||
UploadMemory(map.buffer, map.offset, copies);
|
UploadMemory(map.buffer, map.offset, copies);
|
||||||
}
|
}
|
||||||
@@ -2970,6 +3045,26 @@ Framebuffer::Framebuffer(TextureCacheRuntime& runtime, ImageView* color_buffer,
|
|||||||
|
|
||||||
Framebuffer::~Framebuffer() = default;
|
Framebuffer::~Framebuffer() = default;
|
||||||
|
|
||||||
|
/// Single-sampled color targets with a multisampled depth buffer, as used by target-independent
|
||||||
|
/// rasterization. Vulkan requires matching sample counts for these attachments.
|
||||||
|
static bool IsMixedSamplesTarget(std::span<ImageView*, NUM_RT> color_buffers,
|
||||||
|
const ImageView* depth_buffer) {
|
||||||
|
if (depth_buffer == nullptr || depth_buffer->Samples() == VK_SAMPLE_COUNT_1_BIT) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
bool has_color = false;
|
||||||
|
for (const ImageView* const color_buffer : color_buffers) {
|
||||||
|
if (color_buffer == nullptr) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (color_buffer->Samples() != VK_SAMPLE_COUNT_1_BIT) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
has_color = true;
|
||||||
|
}
|
||||||
|
return has_color;
|
||||||
|
}
|
||||||
|
|
||||||
void Framebuffer::CreateFramebuffer(TextureCacheRuntime& runtime,
|
void Framebuffer::CreateFramebuffer(TextureCacheRuntime& runtime,
|
||||||
std::span<ImageView*, NUM_RT> color_buffers,
|
std::span<ImageView*, NUM_RT> color_buffers,
|
||||||
ImageView* depth_buffer, bool is_rescaled_) {
|
ImageView* depth_buffer, bool is_rescaled_) {
|
||||||
@@ -3024,6 +3119,13 @@ void Framebuffer::CreateFramebuffer(TextureCacheRuntime& runtime,
|
|||||||
} else {
|
} else {
|
||||||
renderpass_key.depth_format = PixelFormat::Invalid;
|
renderpass_key.depth_format = PixelFormat::Invalid;
|
||||||
}
|
}
|
||||||
|
if (IsMixedSamplesTarget(color_buffers, depth_buffer)) {
|
||||||
|
const VkExtent2D extent{(std::min)(render_area.width, width),
|
||||||
|
(std::min)(render_area.height, height)};
|
||||||
|
CreateMixedSamplesFramebuffer(runtime, color_buffers, attachments, renderpass_key,
|
||||||
|
static_cast<u32>((std::max)(num_layers, 1)), extent);
|
||||||
|
return;
|
||||||
|
}
|
||||||
renderpass_key.samples = samples;
|
renderpass_key.samples = samples;
|
||||||
const bool do_resolve_color = ENABLE_MSAA_TILER_RESOLVE &&
|
const bool do_resolve_color = ENABLE_MSAA_TILER_RESOLVE &&
|
||||||
samples != VK_SAMPLE_COUNT_1_BIT && num_colors > 0 && runtime.device.IsTiler();
|
samples != VK_SAMPLE_COUNT_1_BIT && num_colors > 0 && runtime.device.IsTiler();
|
||||||
@@ -3040,10 +3142,7 @@ void Framebuffer::CreateFramebuffer(TextureCacheRuntime& runtime,
|
|||||||
discard_msaa_depth_stencil = ENABLE_MSAA_RESOLVE_CONSUME &&
|
discard_msaa_depth_stencil = ENABLE_MSAA_RESOLVE_CONSUME &&
|
||||||
ENABLE_MSAA_DEPTH_STENCIL_DISCARD && do_resolve_depth_stencil;
|
ENABLE_MSAA_DEPTH_STENCIL_DISCARD && do_resolve_depth_stencil;
|
||||||
|
|
||||||
renderpass = runtime.render_pass_cache.Get(renderpass_key);
|
UseRenderPass(runtime, renderpass_key);
|
||||||
render_pass_key = renderpass_key;
|
|
||||||
render_pass_cache = &runtime.render_pass_cache;
|
|
||||||
runtime_ptr = &runtime;
|
|
||||||
render_area.width = (std::min)(render_area.width, width);
|
render_area.width = (std::min)(render_area.width, width);
|
||||||
render_area.height = (std::min)(render_area.height, height);
|
render_area.height = (std::min)(render_area.height, height);
|
||||||
|
|
||||||
@@ -3075,6 +3174,53 @@ void Framebuffer::CreateFramebuffer(TextureCacheRuntime& runtime,
|
|||||||
}
|
}
|
||||||
|
|
||||||
num_color_buffers = static_cast<u32>(num_colors);
|
num_color_buffers = static_cast<u32>(num_colors);
|
||||||
|
CreateVkFramebuffer(runtime, attachments, static_cast<u32>((std::max)(num_layers, 1)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Framebuffer::CreateMixedSamplesFramebuffer(TextureCacheRuntime& runtime,
|
||||||
|
std::span<ImageView*, NUM_RT> color_buffers,
|
||||||
|
std::span<const VkImageView> attachments,
|
||||||
|
RenderPassKey renderpass_key, u32 layers,
|
||||||
|
VkExtent2D extent) {
|
||||||
|
// attachments holds the real color targets followed by the depth buffer
|
||||||
|
const size_t num_colors = attachments.size() - 1;
|
||||||
|
boost::container::small_vector<VkImageView, NUM_RT * 2 + 2> mixed_attachments;
|
||||||
|
for (const ImageView* const color_buffer : color_buffers) {
|
||||||
|
if (!color_buffer) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const VkFormat vk_format = MaxwellToVK::SurfaceFormat(runtime.device, FormatType::Optimal,
|
||||||
|
true, color_buffer->format)
|
||||||
|
.format;
|
||||||
|
mixed_attachments.push_back(runtime.GetOrCreateTirColorScratch(
|
||||||
|
color_buffer->ImageHandle(), vk_format, extent, layers, samples));
|
||||||
|
}
|
||||||
|
mixed_attachments.push_back(attachments[num_colors]);
|
||||||
|
for (size_t index = 0; index < num_colors; ++index) {
|
||||||
|
mixed_attachments.push_back(attachments[index]);
|
||||||
|
}
|
||||||
|
// Multisampled scratch color + depth, resolving color into the real single-sampled targets
|
||||||
|
renderpass_key.samples = samples;
|
||||||
|
renderpass_key.resolve_color = true;
|
||||||
|
renderpass_key.resolve_depth_stencil = false;
|
||||||
|
discard_msaa_color = false;
|
||||||
|
discard_msaa_depth_stencil = false;
|
||||||
|
UseRenderPass(runtime, renderpass_key);
|
||||||
|
render_area = extent;
|
||||||
|
num_color_buffers = static_cast<u32>(num_colors);
|
||||||
|
CreateVkFramebuffer(runtime, mixed_attachments, layers);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Framebuffer::UseRenderPass(TextureCacheRuntime& runtime,
|
||||||
|
const RenderPassKey& renderpass_key) {
|
||||||
|
renderpass = runtime.render_pass_cache.Get(renderpass_key);
|
||||||
|
render_pass_key = renderpass_key;
|
||||||
|
render_pass_cache = &runtime.render_pass_cache;
|
||||||
|
runtime_ptr = &runtime;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Framebuffer::CreateVkFramebuffer(TextureCacheRuntime& runtime,
|
||||||
|
std::span<const VkImageView> attachments, u32 layers) {
|
||||||
framebuffer = runtime.device.GetLogical().CreateFramebuffer({
|
framebuffer = runtime.device.GetLogical().CreateFramebuffer({
|
||||||
.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO,
|
.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO,
|
||||||
.pNext = nullptr,
|
.pNext = nullptr,
|
||||||
@@ -3084,7 +3230,7 @@ void Framebuffer::CreateFramebuffer(TextureCacheRuntime& runtime,
|
|||||||
.pAttachments = attachments.data(),
|
.pAttachments = attachments.data(),
|
||||||
.width = render_area.width,
|
.width = render_area.width,
|
||||||
.height = render_area.height,
|
.height = render_area.height,
|
||||||
.layers = static_cast<u32>((std::max)(num_layers, 1)),
|
.layers = layers,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -133,6 +133,11 @@ public:
|
|||||||
|
|
||||||
[[nodiscard]] const ResolveShadow* GetValidResolveShadow(VkImage msaa_image) const;
|
[[nodiscard]] const ResolveShadow* GetValidResolveShadow(VkImage msaa_image) const;
|
||||||
|
|
||||||
|
/// Multisampled stand-in for a single-sampled color target rendered with a TIR depth buffer
|
||||||
|
[[nodiscard]] VkImageView GetOrCreateTirColorScratch(VkImage color_image, VkFormat format,
|
||||||
|
VkExtent2D extent, u32 layers,
|
||||||
|
VkSampleCountFlagBits samples);
|
||||||
|
|
||||||
void InvalidateResolveShadow(VkImage msaa_image);
|
void InvalidateResolveShadow(VkImage msaa_image);
|
||||||
|
|
||||||
void MarkResolveShadowUpToDate(VkImage msaa_image);
|
void MarkResolveShadowUpToDate(VkImage msaa_image);
|
||||||
@@ -159,6 +164,7 @@ public:
|
|||||||
std::optional<ASTCDecoderPass> astc_decoder_pass;
|
std::optional<ASTCDecoderPass> astc_decoder_pass;
|
||||||
|
|
||||||
std::optional<BlockLinearUnswizzle3DPass> bl3d_unswizzle_pass;
|
std::optional<BlockLinearUnswizzle3DPass> bl3d_unswizzle_pass;
|
||||||
|
std::optional<BlockLinearUnswizzle2DBufferPass> bl2d_buffer_unswizzle_pass;
|
||||||
const Settings::ResolutionScalingInfo& resolution;
|
const Settings::ResolutionScalingInfo& resolution;
|
||||||
std::array<std::vector<VkFormat>, VideoCore::Surface::MaxPixelFormat> view_formats;
|
std::array<std::vector<VkFormat>, VideoCore::Surface::MaxPixelFormat> view_formats;
|
||||||
|
|
||||||
@@ -187,6 +193,15 @@ public:
|
|||||||
|
|
||||||
std::vector<MsaaScratchImage> msaa_scratch_images;
|
std::vector<MsaaScratchImage> msaa_scratch_images;
|
||||||
::Common::unordered_map<VkImage, ResolveShadow> resolve_shadows;
|
::Common::unordered_map<VkImage, ResolveShadow> resolve_shadows;
|
||||||
|
struct TirColorScratch {
|
||||||
|
vk::Image image;
|
||||||
|
vk::ImageView view;
|
||||||
|
VkFormat format = VK_FORMAT_UNDEFINED;
|
||||||
|
VkExtent2D extent{};
|
||||||
|
u32 layers = 0;
|
||||||
|
VkSampleCountFlagBits samples = VK_SAMPLE_COUNT_1_BIT;
|
||||||
|
};
|
||||||
|
::Common::unordered_map<VkImage, TirColorScratch> tir_color_scratch;
|
||||||
std::vector<std::pair<u64, ResolveShadow>> pending_resolve_shadows;
|
std::vector<std::pair<u64, ResolveShadow>> pending_resolve_shadows;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -279,6 +294,22 @@ public:
|
|||||||
void MarkResolveShadowsUpToDate() const;
|
void MarkResolveShadowsUpToDate() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
/// Builds a framebuffer for single-sampled color targets drawn with a multisampled depth
|
||||||
|
/// buffer (target-independent rasterization). Color is rendered into multisampled scratch
|
||||||
|
/// images that resolve into the real targets at the end of the pass.
|
||||||
|
void CreateMixedSamplesFramebuffer(TextureCacheRuntime& runtime,
|
||||||
|
std::span<ImageView*, NUM_RT> color_buffers,
|
||||||
|
std::span<const VkImageView> attachments,
|
||||||
|
RenderPassKey renderpass_key, u32 layers,
|
||||||
|
VkExtent2D extent);
|
||||||
|
|
||||||
|
/// Fetches the render pass for the key from the cache and remembers it for variants.
|
||||||
|
void UseRenderPass(TextureCacheRuntime& runtime, const RenderPassKey& renderpass_key);
|
||||||
|
|
||||||
|
/// Creates the Vulkan framebuffer for the current render pass and render area.
|
||||||
|
void CreateVkFramebuffer(TextureCacheRuntime& runtime,
|
||||||
|
std::span<const VkImageView> attachments, u32 layers);
|
||||||
|
|
||||||
static constexpr size_t NUM_MEMOIZED_RENDER_PASS_VARIANTS = 8;
|
static constexpr size_t NUM_MEMOIZED_RENDER_PASS_VARIANTS = 8;
|
||||||
|
|
||||||
vk::Framebuffer framebuffer;
|
vk::Framebuffer framebuffer;
|
||||||
@@ -325,6 +356,10 @@ public:
|
|||||||
void UploadMemory(const StagingBufferRef& map,
|
void UploadMemory(const StagingBufferRef& map,
|
||||||
std::span<const VideoCommon::BufferImageCopy> copies);
|
std::span<const VideoCommon::BufferImageCopy> copies);
|
||||||
|
|
||||||
|
/// Replaces the image contents with its block linear guest data held in a GPU buffer.
|
||||||
|
/// Returns false when the image layout is not supported.
|
||||||
|
bool UploadSwizzledFromBuffer(VkBuffer buffer, VkDeviceSize offset);
|
||||||
|
|
||||||
void DownloadMemory(VkBuffer buffer, size_t offset,
|
void DownloadMemory(VkBuffer buffer, size_t offset,
|
||||||
std::span<const VideoCommon::BufferImageCopy> copies);
|
std::span<const VideoCommon::BufferImageCopy> copies);
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
@@ -6,4 +9,6 @@
|
|||||||
|
|
||||||
namespace VideoCommon {
|
namespace VideoCommon {
|
||||||
template class VideoCommon::TextureCache<Vulkan::TextureCacheParams>;
|
template class VideoCommon::TextureCache<Vulkan::TextureCacheParams>;
|
||||||
|
template void VideoCommon::TextureCache<Vulkan::TextureCacheParams>::UpdateImagesFromBuffer<VkBuffer>(
|
||||||
|
DAddr, size_t, VkBuffer, u64);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -595,6 +595,36 @@ void TextureCache<P>::WriteMemory(DAddr cpu_addr, size_t size) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <class P>
|
||||||
|
template <typename BufferHandle>
|
||||||
|
void TextureCache<P>::UpdateImagesFromBuffer(DAddr cpu_addr, size_t size, BufferHandle buffer,
|
||||||
|
u64 buffer_offset) {
|
||||||
|
if constexpr (requires(Image& image) { image.UploadSwizzledFromBuffer(buffer, buffer_offset); }) {
|
||||||
|
// The guest can write a render target through a storage buffer bound over its memory
|
||||||
|
// (e.g. a compute clear). Only images the binding matches exactly are replaced, a larger
|
||||||
|
// binding may hold unrelated live images in the parts the shader does not write.
|
||||||
|
static constexpr size_t MAX_BINDING_PADDING = 64 * 1024;
|
||||||
|
ForEachImageInRegion(cpu_addr, size, [&](ImageId image_id, Image& image) {
|
||||||
|
if (image.cpu_addr != cpu_addr || size < image.guest_size_bytes ||
|
||||||
|
size - image.guest_size_bytes > MAX_BINDING_PADDING ||
|
||||||
|
True(image.flags & ImageFlagBits::Remapped) ||
|
||||||
|
True(image.flags & ImageFlagBits::Sparse)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!image.UploadSwizzledFromBuffer(buffer, buffer_offset)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (True(image.flags & ImageFlagBits::CpuModified)) {
|
||||||
|
image.flags &= ~ImageFlagBits::CpuModified;
|
||||||
|
if (False(image.flags & ImageFlagBits::Tracked)) {
|
||||||
|
TrackImage(image, image_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MarkModification(image);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
template <class P>
|
template <class P>
|
||||||
bool TextureCache<P>::IsDownloadable(const ImageBase& image) const noexcept {
|
bool TextureCache<P>::IsDownloadable(const ImageBase& image) const noexcept {
|
||||||
if (!image.IsSafeGpuCopy()) {
|
if (!image.IsSafeGpuCopy()) {
|
||||||
@@ -1928,7 +1958,26 @@ ImageViewId TextureCache<P>::FindDepthBuffer() {
|
|||||||
if (gpu_addr == 0) {
|
if (gpu_addr == 0) {
|
||||||
return ImageViewId{};
|
return ImageViewId{};
|
||||||
}
|
}
|
||||||
const ImageInfo info(regs.zeta, regs.zeta_size, regs.anti_alias_samples_mode);
|
// Target-independent rasterization: the depth buffer keeps the raster sample count while
|
||||||
|
// the color targets use anti_alias_samples_mode.
|
||||||
|
Tegra::Texture::MsaaMode zeta_msaa_mode = regs.anti_alias_samples_mode;
|
||||||
|
if (regs.anti_alias_raster != Tegra::Engines::Maxwell3D::Regs::AntiAliasRaster::Mode1x1) {
|
||||||
|
const auto raster_mode = static_cast<Tegra::Texture::MsaaMode>(regs.anti_alias_raster);
|
||||||
|
bool use_raster = regs.tir_mode == Tegra::Engines::Maxwell3D::Regs::TIRMode::RasterNTargetM;
|
||||||
|
if (!use_raster && regs.anti_alias_samples_mode == Tegra::Texture::MsaaMode::Msaa1x1) {
|
||||||
|
// Clears can be issued before tir_mode is enabled. Only a depth buffer sized for the
|
||||||
|
// raster samples relative to the color target is treated as multisampled, otherwise
|
||||||
|
// it ping-pongs between a 1x and a multisampled image every frame.
|
||||||
|
const auto& rt0 = regs.rt[0];
|
||||||
|
use_raster = rt0.Address() != 0 && rt0.format != Tegra::RenderTargetFormat::NONE &&
|
||||||
|
regs.zeta_size.width == rt0.width * NumSamplesX(raster_mode) &&
|
||||||
|
regs.zeta_size.height == rt0.height * NumSamplesY(raster_mode);
|
||||||
|
}
|
||||||
|
if (use_raster) {
|
||||||
|
zeta_msaa_mode = raster_mode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const ImageInfo info(regs.zeta, regs.zeta_size, zeta_msaa_mode);
|
||||||
return FindRenderTargetView(info, gpu_addr);
|
return FindRenderTargetView(info, gpu_addr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -203,6 +203,12 @@ public:
|
|||||||
/// Mark images in a range as modified from the CPU
|
/// Mark images in a range as modified from the CPU
|
||||||
void WriteMemory(DAddr cpu_addr, size_t size);
|
void WriteMemory(DAddr cpu_addr, size_t size);
|
||||||
|
|
||||||
|
/// Replaces the contents of the images fully inside a range that a GPU buffer wrote, with the
|
||||||
|
/// data of that buffer
|
||||||
|
template <typename BufferHandle>
|
||||||
|
void UpdateImagesFromBuffer(DAddr cpu_addr, size_t size, BufferHandle buffer,
|
||||||
|
u64 buffer_offset);
|
||||||
|
|
||||||
/// Download contents of host images to guest memory in a region
|
/// Download contents of host images to guest memory in a region
|
||||||
void DownloadMemory(DAddr cpu_addr, size_t size);
|
void DownloadMemory(DAddr cpu_addr, size_t size);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user