early-access version 3914
This commit is contained in:
parent
b5cdabcc51
commit
9238ae5660
17 changed files with 409 additions and 69 deletions
|
@ -1,7 +1,7 @@
|
||||||
yuzu emulator early access
|
yuzu emulator early access
|
||||||
=============
|
=============
|
||||||
|
|
||||||
This is the source code for early-access 3913.
|
This is the source code for early-access 3914.
|
||||||
|
|
||||||
## Legal Notice
|
## Legal Notice
|
||||||
|
|
||||||
|
|
|
@ -23,6 +23,7 @@
|
||||||
#define SDMC_DIR "sdmc"
|
#define SDMC_DIR "sdmc"
|
||||||
#define SHADER_DIR "shader"
|
#define SHADER_DIR "shader"
|
||||||
#define TAS_DIR "tas"
|
#define TAS_DIR "tas"
|
||||||
|
#define ICONS_DIR "icons"
|
||||||
|
|
||||||
// yuzu-specific files
|
// yuzu-specific files
|
||||||
|
|
||||||
|
|
|
@ -129,6 +129,7 @@ public:
|
||||||
GenerateYuzuPath(YuzuPath::SDMCDir, yuzu_path / SDMC_DIR);
|
GenerateYuzuPath(YuzuPath::SDMCDir, yuzu_path / SDMC_DIR);
|
||||||
GenerateYuzuPath(YuzuPath::ShaderDir, yuzu_path / SHADER_DIR);
|
GenerateYuzuPath(YuzuPath::ShaderDir, yuzu_path / SHADER_DIR);
|
||||||
GenerateYuzuPath(YuzuPath::TASDir, yuzu_path / TAS_DIR);
|
GenerateYuzuPath(YuzuPath::TASDir, yuzu_path / TAS_DIR);
|
||||||
|
GenerateYuzuPath(YuzuPath::IconsDir, yuzu_path / ICONS_DIR);
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
|
@ -25,6 +25,7 @@ enum class YuzuPath {
|
||||||
SDMCDir, // Where the emulated SDMC is stored.
|
SDMCDir, // Where the emulated SDMC is stored.
|
||||||
ShaderDir, // Where shaders are stored.
|
ShaderDir, // Where shaders are stored.
|
||||||
TASDir, // Where TAS scripts are stored.
|
TASDir, // Where TAS scripts are stored.
|
||||||
|
IconsDir, // Where Icons for Windows shortcuts are stored.
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -2949,6 +2949,23 @@ Result KPageTable::UnlockForIpcUserBuffer(KProcessAddress address, size_t size)
|
||||||
KMemoryAttribute::Locked, nullptr));
|
KMemoryAttribute::Locked, nullptr));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Result KPageTable::LockForTransferMemory(KPageGroup* out, KProcessAddress address, size_t size,
|
||||||
|
KMemoryPermission perm) {
|
||||||
|
R_RETURN(this->LockMemoryAndOpen(out, nullptr, address, size, KMemoryState::FlagCanTransfer,
|
||||||
|
KMemoryState::FlagCanTransfer, KMemoryPermission::All,
|
||||||
|
KMemoryPermission::UserReadWrite, KMemoryAttribute::All,
|
||||||
|
KMemoryAttribute::None, perm, KMemoryAttribute::Locked));
|
||||||
|
}
|
||||||
|
|
||||||
|
Result KPageTable::UnlockForTransferMemory(KProcessAddress address, size_t size,
|
||||||
|
const KPageGroup& pg) {
|
||||||
|
R_RETURN(this->UnlockMemory(address, size, KMemoryState::FlagCanTransfer,
|
||||||
|
KMemoryState::FlagCanTransfer, KMemoryPermission::None,
|
||||||
|
KMemoryPermission::None, KMemoryAttribute::All,
|
||||||
|
KMemoryAttribute::Locked, KMemoryPermission::UserReadWrite,
|
||||||
|
KMemoryAttribute::Locked, std::addressof(pg)));
|
||||||
|
}
|
||||||
|
|
||||||
Result KPageTable::LockForCodeMemory(KPageGroup* out, KProcessAddress addr, size_t size) {
|
Result KPageTable::LockForCodeMemory(KPageGroup* out, KProcessAddress addr, size_t size) {
|
||||||
R_RETURN(this->LockMemoryAndOpen(
|
R_RETURN(this->LockMemoryAndOpen(
|
||||||
out, nullptr, addr, size, KMemoryState::FlagCanCodeMemory, KMemoryState::FlagCanCodeMemory,
|
out, nullptr, addr, size, KMemoryState::FlagCanCodeMemory, KMemoryState::FlagCanCodeMemory,
|
||||||
|
|
|
@ -104,6 +104,9 @@ public:
|
||||||
Result CleanupForIpcServer(KProcessAddress address, size_t size, KMemoryState dst_state);
|
Result CleanupForIpcServer(KProcessAddress address, size_t size, KMemoryState dst_state);
|
||||||
Result CleanupForIpcClient(KProcessAddress address, size_t size, KMemoryState dst_state);
|
Result CleanupForIpcClient(KProcessAddress address, size_t size, KMemoryState dst_state);
|
||||||
|
|
||||||
|
Result LockForTransferMemory(KPageGroup* out, KProcessAddress address, size_t size,
|
||||||
|
KMemoryPermission perm);
|
||||||
|
Result UnlockForTransferMemory(KProcessAddress address, size_t size, const KPageGroup& pg);
|
||||||
Result LockForCodeMemory(KPageGroup* out, KProcessAddress addr, size_t size);
|
Result LockForCodeMemory(KPageGroup* out, KProcessAddress addr, size_t size);
|
||||||
Result UnlockForCodeMemory(KProcessAddress addr, size_t size, const KPageGroup& pg);
|
Result UnlockForCodeMemory(KProcessAddress addr, size_t size, const KPageGroup& pg);
|
||||||
Result MakeAndOpenPageGroup(KPageGroup* out, KProcessAddress address, size_t num_pages,
|
Result MakeAndOpenPageGroup(KPageGroup* out, KProcessAddress address, size_t num_pages,
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
// 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
|
||||||
|
|
||||||
|
#include "common/scope_exit.h"
|
||||||
#include "core/hle/kernel/k_process.h"
|
#include "core/hle/kernel/k_process.h"
|
||||||
#include "core/hle/kernel/k_resource_limit.h"
|
#include "core/hle/kernel/k_resource_limit.h"
|
||||||
#include "core/hle/kernel/k_transfer_memory.h"
|
#include "core/hle/kernel/k_transfer_memory.h"
|
||||||
|
@ -9,28 +10,50 @@
|
||||||
namespace Kernel {
|
namespace Kernel {
|
||||||
|
|
||||||
KTransferMemory::KTransferMemory(KernelCore& kernel)
|
KTransferMemory::KTransferMemory(KernelCore& kernel)
|
||||||
: KAutoObjectWithSlabHeapAndContainer{kernel} {}
|
: KAutoObjectWithSlabHeapAndContainer{kernel}, m_lock{kernel} {}
|
||||||
|
|
||||||
KTransferMemory::~KTransferMemory() = default;
|
KTransferMemory::~KTransferMemory() = default;
|
||||||
|
|
||||||
Result KTransferMemory::Initialize(KProcessAddress address, std::size_t size,
|
Result KTransferMemory::Initialize(KProcessAddress addr, std::size_t size,
|
||||||
Svc::MemoryPermission owner_perm) {
|
Svc::MemoryPermission own_perm) {
|
||||||
// Set members.
|
// Set members.
|
||||||
m_owner = GetCurrentProcessPointer(m_kernel);
|
m_owner = GetCurrentProcessPointer(m_kernel);
|
||||||
|
|
||||||
// TODO(bunnei): Lock for transfer memory
|
// Get the owner page table.
|
||||||
|
auto& page_table = m_owner->GetPageTable();
|
||||||
|
|
||||||
|
// Construct the page group, guarding to make sure our state is valid on exit.
|
||||||
|
m_page_group.emplace(m_kernel, page_table.GetBlockInfoManager());
|
||||||
|
auto pg_guard = SCOPE_GUARD({ m_page_group.reset(); });
|
||||||
|
|
||||||
|
// Lock the memory.
|
||||||
|
R_TRY(page_table.LockForTransferMemory(std::addressof(*m_page_group), addr, size,
|
||||||
|
ConvertToKMemoryPermission(own_perm)));
|
||||||
|
|
||||||
// Set remaining tracking members.
|
// Set remaining tracking members.
|
||||||
m_owner->Open();
|
m_owner->Open();
|
||||||
m_owner_perm = owner_perm;
|
m_owner_perm = own_perm;
|
||||||
m_address = address;
|
m_address = addr;
|
||||||
m_size = size;
|
|
||||||
m_is_initialized = true;
|
m_is_initialized = true;
|
||||||
|
m_is_mapped = false;
|
||||||
|
|
||||||
|
// We succeeded.
|
||||||
|
pg_guard.Cancel();
|
||||||
R_SUCCEED();
|
R_SUCCEED();
|
||||||
}
|
}
|
||||||
|
|
||||||
void KTransferMemory::Finalize() {}
|
void KTransferMemory::Finalize() {
|
||||||
|
// Unlock.
|
||||||
|
if (!m_is_mapped) {
|
||||||
|
const size_t size = m_page_group->GetNumPages() * PageSize;
|
||||||
|
ASSERT(R_SUCCEEDED(
|
||||||
|
m_owner->GetPageTable().UnlockForTransferMemory(m_address, size, *m_page_group)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close the page group.
|
||||||
|
m_page_group->Close();
|
||||||
|
m_page_group->Finalize();
|
||||||
|
}
|
||||||
|
|
||||||
void KTransferMemory::PostDestroy(uintptr_t arg) {
|
void KTransferMemory::PostDestroy(uintptr_t arg) {
|
||||||
KProcess* owner = reinterpret_cast<KProcess*>(arg);
|
KProcess* owner = reinterpret_cast<KProcess*>(arg);
|
||||||
|
@ -38,4 +61,54 @@ void KTransferMemory::PostDestroy(uintptr_t arg) {
|
||||||
owner->Close();
|
owner->Close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Result KTransferMemory::Map(KProcessAddress address, size_t size, Svc::MemoryPermission map_perm) {
|
||||||
|
// Validate the size.
|
||||||
|
R_UNLESS(m_page_group->GetNumPages() == Common::DivideUp(size, PageSize), ResultInvalidSize);
|
||||||
|
|
||||||
|
// Validate the permission.
|
||||||
|
R_UNLESS(m_owner_perm == map_perm, ResultInvalidState);
|
||||||
|
|
||||||
|
// Lock ourselves.
|
||||||
|
KScopedLightLock lk(m_lock);
|
||||||
|
|
||||||
|
// Ensure we're not already mapped.
|
||||||
|
R_UNLESS(!m_is_mapped, ResultInvalidState);
|
||||||
|
|
||||||
|
// Map the memory.
|
||||||
|
const KMemoryState state = (m_owner_perm == Svc::MemoryPermission::None)
|
||||||
|
? KMemoryState::Transfered
|
||||||
|
: KMemoryState::SharedTransfered;
|
||||||
|
R_TRY(GetCurrentProcess(m_kernel).GetPageTable().MapPageGroup(
|
||||||
|
address, *m_page_group, state, KMemoryPermission::UserReadWrite));
|
||||||
|
|
||||||
|
// Mark ourselves as mapped.
|
||||||
|
m_is_mapped = true;
|
||||||
|
|
||||||
|
R_SUCCEED();
|
||||||
|
}
|
||||||
|
|
||||||
|
Result KTransferMemory::Unmap(KProcessAddress address, size_t size) {
|
||||||
|
// Validate the size.
|
||||||
|
R_UNLESS(m_page_group->GetNumPages() == Common::DivideUp(size, PageSize), ResultInvalidSize);
|
||||||
|
|
||||||
|
// Lock ourselves.
|
||||||
|
KScopedLightLock lk(m_lock);
|
||||||
|
|
||||||
|
// Unmap the memory.
|
||||||
|
const KMemoryState state = (m_owner_perm == Svc::MemoryPermission::None)
|
||||||
|
? KMemoryState::Transfered
|
||||||
|
: KMemoryState::SharedTransfered;
|
||||||
|
R_TRY(GetCurrentProcess(m_kernel).GetPageTable().UnmapPageGroup(address, *m_page_group, state));
|
||||||
|
|
||||||
|
// Mark ourselves as unmapped.
|
||||||
|
ASSERT(m_is_mapped);
|
||||||
|
m_is_mapped = false;
|
||||||
|
|
||||||
|
R_SUCCEED();
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t KTransferMemory::GetSize() const {
|
||||||
|
return m_is_initialized ? m_page_group->GetNumPages() * PageSize : 0;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Kernel
|
} // namespace Kernel
|
||||||
|
|
|
@ -3,6 +3,9 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
|
#include "core/hle/kernel/k_page_group.h"
|
||||||
#include "core/hle/kernel/slab_helpers.h"
|
#include "core/hle/kernel/slab_helpers.h"
|
||||||
#include "core/hle/kernel/svc_types.h"
|
#include "core/hle/kernel/svc_types.h"
|
||||||
#include "core/hle/result.h"
|
#include "core/hle/result.h"
|
||||||
|
@ -48,16 +51,19 @@ public:
|
||||||
return m_address;
|
return m_address;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t GetSize() const {
|
size_t GetSize() const;
|
||||||
return m_is_initialized ? m_size : 0;
|
|
||||||
}
|
Result Map(KProcessAddress address, size_t size, Svc::MemoryPermission map_perm);
|
||||||
|
Result Unmap(KProcessAddress address, size_t size);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
std::optional<KPageGroup> m_page_group{};
|
||||||
KProcess* m_owner{};
|
KProcess* m_owner{};
|
||||||
KProcessAddress m_address{};
|
KProcessAddress m_address{};
|
||||||
|
KLightLock m_lock;
|
||||||
Svc::MemoryPermission m_owner_perm{};
|
Svc::MemoryPermission m_owner_perm{};
|
||||||
size_t m_size{};
|
|
||||||
bool m_is_initialized{};
|
bool m_is_initialized{};
|
||||||
|
bool m_is_mapped{};
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Kernel
|
} // namespace Kernel
|
||||||
|
|
|
@ -71,15 +71,59 @@ Result CreateTransferMemory(Core::System& system, Handle* out, u64 address, u64
|
||||||
}
|
}
|
||||||
|
|
||||||
Result MapTransferMemory(Core::System& system, Handle trmem_handle, uint64_t address, uint64_t size,
|
Result MapTransferMemory(Core::System& system, Handle trmem_handle, uint64_t address, uint64_t size,
|
||||||
MemoryPermission owner_perm) {
|
MemoryPermission map_perm) {
|
||||||
UNIMPLEMENTED();
|
// Validate the address/size.
|
||||||
R_THROW(ResultNotImplemented);
|
R_UNLESS(Common::IsAligned(address, PageSize), ResultInvalidAddress);
|
||||||
|
R_UNLESS(Common::IsAligned(size, PageSize), ResultInvalidSize);
|
||||||
|
R_UNLESS(size > 0, ResultInvalidSize);
|
||||||
|
R_UNLESS((address < address + size), ResultInvalidCurrentMemory);
|
||||||
|
|
||||||
|
// Validate the permission.
|
||||||
|
R_UNLESS(IsValidTransferMemoryPermission(map_perm), ResultInvalidState);
|
||||||
|
|
||||||
|
// Get the transfer memory.
|
||||||
|
KScopedAutoObject trmem = GetCurrentProcess(system.Kernel())
|
||||||
|
.GetHandleTable()
|
||||||
|
.GetObject<KTransferMemory>(trmem_handle);
|
||||||
|
R_UNLESS(trmem.IsNotNull(), ResultInvalidHandle);
|
||||||
|
|
||||||
|
// Verify that the mapping is in range.
|
||||||
|
R_UNLESS(GetCurrentProcess(system.Kernel())
|
||||||
|
.GetPageTable()
|
||||||
|
.CanContain(address, size, KMemoryState::Transfered),
|
||||||
|
ResultInvalidMemoryRegion);
|
||||||
|
|
||||||
|
// Map the transfer memory.
|
||||||
|
R_TRY(trmem->Map(address, size, map_perm));
|
||||||
|
|
||||||
|
// We succeeded.
|
||||||
|
R_SUCCEED();
|
||||||
}
|
}
|
||||||
|
|
||||||
Result UnmapTransferMemory(Core::System& system, Handle trmem_handle, uint64_t address,
|
Result UnmapTransferMemory(Core::System& system, Handle trmem_handle, uint64_t address,
|
||||||
uint64_t size) {
|
uint64_t size) {
|
||||||
UNIMPLEMENTED();
|
// Validate the address/size.
|
||||||
R_THROW(ResultNotImplemented);
|
R_UNLESS(Common::IsAligned(address, PageSize), ResultInvalidAddress);
|
||||||
|
R_UNLESS(Common::IsAligned(size, PageSize), ResultInvalidSize);
|
||||||
|
R_UNLESS(size > 0, ResultInvalidSize);
|
||||||
|
R_UNLESS((address < address + size), ResultInvalidCurrentMemory);
|
||||||
|
|
||||||
|
// Get the transfer memory.
|
||||||
|
KScopedAutoObject trmem = GetCurrentProcess(system.Kernel())
|
||||||
|
.GetHandleTable()
|
||||||
|
.GetObject<KTransferMemory>(trmem_handle);
|
||||||
|
R_UNLESS(trmem.IsNotNull(), ResultInvalidHandle);
|
||||||
|
|
||||||
|
// Verify that the mapping is in range.
|
||||||
|
R_UNLESS(GetCurrentProcess(system.Kernel())
|
||||||
|
.GetPageTable()
|
||||||
|
.CanContain(address, size, KMemoryState::Transfered),
|
||||||
|
ResultInvalidMemoryRegion);
|
||||||
|
|
||||||
|
// Unmap the transfer memory.
|
||||||
|
R_TRY(trmem->Unmap(address, size));
|
||||||
|
|
||||||
|
R_SUCCEED();
|
||||||
}
|
}
|
||||||
|
|
||||||
Result MapTransferMemory64(Core::System& system, Handle trmem_handle, uint64_t address,
|
Result MapTransferMemory64(Core::System& system, Handle trmem_handle, uint64_t address,
|
||||||
|
|
|
@ -549,7 +549,7 @@ void BufferCache<P>::CommitAsyncFlushesHigh() {
|
||||||
it++;
|
it++;
|
||||||
}
|
}
|
||||||
|
|
||||||
boost::container::small_vector<std::pair<BufferCopy, BufferId>, 1> downloads;
|
boost::container::small_vector<std::pair<BufferCopy, BufferId>, 16> downloads;
|
||||||
u64 total_size_bytes = 0;
|
u64 total_size_bytes = 0;
|
||||||
u64 largest_copy = 0;
|
u64 largest_copy = 0;
|
||||||
for (const IntervalSet& intervals : committed_ranges) {
|
for (const IntervalSet& intervals : committed_ranges) {
|
||||||
|
@ -925,6 +925,11 @@ void BufferCache<P>::BindHostGraphicsStorageBuffers(size_t stage) {
|
||||||
const u32 offset = buffer.Offset(binding.cpu_addr);
|
const u32 offset = buffer.Offset(binding.cpu_addr);
|
||||||
buffer.MarkUsage(offset, size);
|
buffer.MarkUsage(offset, size);
|
||||||
const bool is_written = ((channel_state->written_storage_buffers[stage] >> index) & 1) != 0;
|
const bool is_written = ((channel_state->written_storage_buffers[stage] >> index) & 1) != 0;
|
||||||
|
|
||||||
|
if (is_written) {
|
||||||
|
MarkWrittenBuffer(binding.buffer_id, binding.cpu_addr, size);
|
||||||
|
}
|
||||||
|
|
||||||
if constexpr (NEEDS_BIND_STORAGE_INDEX) {
|
if constexpr (NEEDS_BIND_STORAGE_INDEX) {
|
||||||
runtime.BindStorageBuffer(stage, binding_index, buffer, offset, size, is_written);
|
runtime.BindStorageBuffer(stage, binding_index, buffer, offset, size, is_written);
|
||||||
++binding_index;
|
++binding_index;
|
||||||
|
@ -942,6 +947,11 @@ void BufferCache<P>::BindHostGraphicsTextureBuffers(size_t stage) {
|
||||||
const u32 size = binding.size;
|
const u32 size = binding.size;
|
||||||
SynchronizeBuffer(buffer, binding.cpu_addr, size);
|
SynchronizeBuffer(buffer, binding.cpu_addr, size);
|
||||||
|
|
||||||
|
const bool is_written = ((channel_state->written_texture_buffers[stage] >> index) & 1) != 0;
|
||||||
|
if (is_written) {
|
||||||
|
MarkWrittenBuffer(binding.buffer_id, binding.cpu_addr, size);
|
||||||
|
}
|
||||||
|
|
||||||
const u32 offset = buffer.Offset(binding.cpu_addr);
|
const u32 offset = buffer.Offset(binding.cpu_addr);
|
||||||
const PixelFormat format = binding.format;
|
const PixelFormat format = binding.format;
|
||||||
buffer.MarkUsage(offset, size);
|
buffer.MarkUsage(offset, size);
|
||||||
|
@ -974,6 +984,8 @@ void BufferCache<P>::BindHostTransformFeedbackBuffers() {
|
||||||
const u32 size = binding.size;
|
const u32 size = binding.size;
|
||||||
SynchronizeBuffer(buffer, binding.cpu_addr, size);
|
SynchronizeBuffer(buffer, binding.cpu_addr, size);
|
||||||
|
|
||||||
|
MarkWrittenBuffer(binding.buffer_id, binding.cpu_addr, size);
|
||||||
|
|
||||||
const u32 offset = buffer.Offset(binding.cpu_addr);
|
const u32 offset = buffer.Offset(binding.cpu_addr);
|
||||||
buffer.MarkUsage(offset, size);
|
buffer.MarkUsage(offset, size);
|
||||||
host_bindings.buffers.push_back(&buffer);
|
host_bindings.buffers.push_back(&buffer);
|
||||||
|
@ -1026,6 +1038,11 @@ void BufferCache<P>::BindHostComputeStorageBuffers() {
|
||||||
buffer.MarkUsage(offset, size);
|
buffer.MarkUsage(offset, size);
|
||||||
const bool is_written =
|
const bool is_written =
|
||||||
((channel_state->written_compute_storage_buffers >> index) & 1) != 0;
|
((channel_state->written_compute_storage_buffers >> index) & 1) != 0;
|
||||||
|
|
||||||
|
if (is_written) {
|
||||||
|
MarkWrittenBuffer(binding.buffer_id, binding.cpu_addr, size);
|
||||||
|
}
|
||||||
|
|
||||||
if constexpr (NEEDS_BIND_STORAGE_INDEX) {
|
if constexpr (NEEDS_BIND_STORAGE_INDEX) {
|
||||||
runtime.BindComputeStorageBuffer(binding_index, buffer, offset, size, is_written);
|
runtime.BindComputeStorageBuffer(binding_index, buffer, offset, size, is_written);
|
||||||
++binding_index;
|
++binding_index;
|
||||||
|
@ -1043,6 +1060,12 @@ void BufferCache<P>::BindHostComputeTextureBuffers() {
|
||||||
const u32 size = binding.size;
|
const u32 size = binding.size;
|
||||||
SynchronizeBuffer(buffer, binding.cpu_addr, size);
|
SynchronizeBuffer(buffer, binding.cpu_addr, size);
|
||||||
|
|
||||||
|
const bool is_written =
|
||||||
|
((channel_state->written_compute_texture_buffers >> index) & 1) != 0;
|
||||||
|
if (is_written) {
|
||||||
|
MarkWrittenBuffer(binding.buffer_id, binding.cpu_addr, size);
|
||||||
|
}
|
||||||
|
|
||||||
const u32 offset = buffer.Offset(binding.cpu_addr);
|
const u32 offset = buffer.Offset(binding.cpu_addr);
|
||||||
const PixelFormat format = binding.format;
|
const PixelFormat format = binding.format;
|
||||||
buffer.MarkUsage(offset, size);
|
buffer.MarkUsage(offset, size);
|
||||||
|
@ -1218,16 +1241,11 @@ void BufferCache<P>::UpdateUniformBuffers(size_t stage) {
|
||||||
|
|
||||||
template <class P>
|
template <class P>
|
||||||
void BufferCache<P>::UpdateStorageBuffers(size_t stage) {
|
void BufferCache<P>::UpdateStorageBuffers(size_t stage) {
|
||||||
const u32 written_mask = channel_state->written_storage_buffers[stage];
|
|
||||||
ForEachEnabledBit(channel_state->enabled_storage_buffers[stage], [&](u32 index) {
|
ForEachEnabledBit(channel_state->enabled_storage_buffers[stage], [&](u32 index) {
|
||||||
// Resolve buffer
|
// Resolve buffer
|
||||||
Binding& binding = channel_state->storage_buffers[stage][index];
|
Binding& binding = channel_state->storage_buffers[stage][index];
|
||||||
const BufferId buffer_id = FindBuffer(binding.cpu_addr, binding.size);
|
const BufferId buffer_id = FindBuffer(binding.cpu_addr, binding.size);
|
||||||
binding.buffer_id = buffer_id;
|
binding.buffer_id = buffer_id;
|
||||||
// Mark buffer as written if needed
|
|
||||||
if (((written_mask >> index) & 1) != 0) {
|
|
||||||
MarkWrittenBuffer(buffer_id, binding.cpu_addr, binding.size);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1236,10 +1254,6 @@ void BufferCache<P>::UpdateTextureBuffers(size_t stage) {
|
||||||
ForEachEnabledBit(channel_state->enabled_texture_buffers[stage], [&](u32 index) {
|
ForEachEnabledBit(channel_state->enabled_texture_buffers[stage], [&](u32 index) {
|
||||||
Binding& binding = channel_state->texture_buffers[stage][index];
|
Binding& binding = channel_state->texture_buffers[stage][index];
|
||||||
binding.buffer_id = FindBuffer(binding.cpu_addr, binding.size);
|
binding.buffer_id = FindBuffer(binding.cpu_addr, binding.size);
|
||||||
// Mark buffer as written if needed
|
|
||||||
if (((channel_state->written_texture_buffers[stage] >> index) & 1) != 0) {
|
|
||||||
MarkWrittenBuffer(binding.buffer_id, binding.cpu_addr, binding.size);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1269,7 +1283,6 @@ void BufferCache<P>::UpdateTransformFeedbackBuffer(u32 index) {
|
||||||
.size = size,
|
.size = size,
|
||||||
.buffer_id = buffer_id,
|
.buffer_id = buffer_id,
|
||||||
};
|
};
|
||||||
MarkWrittenBuffer(buffer_id, *cpu_addr, size);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class P>
|
template <class P>
|
||||||
|
@ -1296,10 +1309,6 @@ void BufferCache<P>::UpdateComputeStorageBuffers() {
|
||||||
// Resolve buffer
|
// Resolve buffer
|
||||||
Binding& binding = channel_state->compute_storage_buffers[index];
|
Binding& binding = channel_state->compute_storage_buffers[index];
|
||||||
binding.buffer_id = FindBuffer(binding.cpu_addr, binding.size);
|
binding.buffer_id = FindBuffer(binding.cpu_addr, binding.size);
|
||||||
// Mark as written if needed
|
|
||||||
if (((channel_state->written_compute_storage_buffers >> index) & 1) != 0) {
|
|
||||||
MarkWrittenBuffer(binding.buffer_id, binding.cpu_addr, binding.size);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1308,18 +1317,11 @@ void BufferCache<P>::UpdateComputeTextureBuffers() {
|
||||||
ForEachEnabledBit(channel_state->enabled_compute_texture_buffers, [&](u32 index) {
|
ForEachEnabledBit(channel_state->enabled_compute_texture_buffers, [&](u32 index) {
|
||||||
Binding& binding = channel_state->compute_texture_buffers[index];
|
Binding& binding = channel_state->compute_texture_buffers[index];
|
||||||
binding.buffer_id = FindBuffer(binding.cpu_addr, binding.size);
|
binding.buffer_id = FindBuffer(binding.cpu_addr, binding.size);
|
||||||
// Mark as written if needed
|
|
||||||
if (((channel_state->written_compute_texture_buffers >> index) & 1) != 0) {
|
|
||||||
MarkWrittenBuffer(binding.buffer_id, binding.cpu_addr, binding.size);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class P>
|
template <class P>
|
||||||
void BufferCache<P>::MarkWrittenBuffer(BufferId buffer_id, VAddr cpu_addr, u32 size) {
|
void BufferCache<P>::MarkWrittenBuffer(BufferId buffer_id, VAddr cpu_addr, u32 size) {
|
||||||
if (memory_tracker.IsRegionCpuModified(cpu_addr, size)) {
|
|
||||||
SynchronizeBuffer(slot_buffers[buffer_id], cpu_addr, size);
|
|
||||||
}
|
|
||||||
memory_tracker.MarkRegionAsGpuModified(cpu_addr, size);
|
memory_tracker.MarkRegionAsGpuModified(cpu_addr, size);
|
||||||
|
|
||||||
const IntervalType base_interval{cpu_addr, cpu_addr + size};
|
const IntervalType base_interval{cpu_addr, cpu_addr + size};
|
||||||
|
|
|
@ -1357,6 +1357,8 @@ ImageId TextureCache<P>::JoinImages(const ImageInfo& info, GPUVAddr gpu_addr, VA
|
||||||
join_bad_overlap_ids.clear();
|
join_bad_overlap_ids.clear();
|
||||||
join_copies_to_do.clear();
|
join_copies_to_do.clear();
|
||||||
join_alias_indices.clear();
|
join_alias_indices.clear();
|
||||||
|
boost::container::small_vector<ImageId, 8> merge_mips;
|
||||||
|
ImageId merge_with_existing_id{};
|
||||||
const bool this_is_linear = info.type == ImageType::Linear;
|
const bool this_is_linear = info.type == ImageType::Linear;
|
||||||
const auto region_check = [&](ImageId overlap_id, ImageBase& overlap) {
|
const auto region_check = [&](ImageId overlap_id, ImageBase& overlap) {
|
||||||
if (True(overlap.flags & ImageFlagBits::Remapped)) {
|
if (True(overlap.flags & ImageFlagBits::Remapped)) {
|
||||||
|
@ -1397,6 +1399,12 @@ ImageId TextureCache<P>::JoinImages(const ImageInfo& info, GPUVAddr gpu_addr, VA
|
||||||
join_right_aliased_ids.push_back(overlap_id);
|
join_right_aliased_ids.push_back(overlap_id);
|
||||||
overlap.flags |= ImageFlagBits::Alias;
|
overlap.flags |= ImageFlagBits::Alias;
|
||||||
join_copies_to_do.emplace_back(JoinCopy{true, overlap_id});
|
join_copies_to_do.emplace_back(JoinCopy{true, overlap_id});
|
||||||
|
} else if (IsSubLevel(new_image_base, overlap)) {
|
||||||
|
if (new_image_base.info.resources.levels > overlap.info.resources.levels) {
|
||||||
|
merge_mips.push_back(overlap_id);
|
||||||
|
} else {
|
||||||
|
merge_with_existing_id = overlap_id;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
join_bad_overlap_ids.push_back(overlap_id);
|
join_bad_overlap_ids.push_back(overlap_id);
|
||||||
}
|
}
|
||||||
|
@ -1439,6 +1447,10 @@ ImageId TextureCache<P>::JoinImages(const ImageInfo& info, GPUVAddr gpu_addr, VA
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (merge_with_existing_id) {
|
||||||
|
return merge_with_existing_id;
|
||||||
|
}
|
||||||
|
|
||||||
const ImageId new_image_id = slot_images.insert(runtime, new_info, gpu_addr, cpu_addr);
|
const ImageId new_image_id = slot_images.insert(runtime, new_info, gpu_addr, cpu_addr);
|
||||||
Image& new_image = slot_images[new_image_id];
|
Image& new_image = slot_images[new_image_id];
|
||||||
|
|
||||||
|
@ -1467,6 +1479,32 @@ ImageId TextureCache<P>::JoinImages(const ImageInfo& info, GPUVAddr gpu_addr, VA
|
||||||
ScaleDown(new_image);
|
ScaleDown(new_image);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const auto& resolution = Settings::values.resolution_info;
|
||||||
|
const u32 up_scale = can_rescale ? resolution.up_scale : 1;
|
||||||
|
const u32 down_shift = can_rescale ? resolution.down_shift : 0;
|
||||||
|
|
||||||
|
for (auto overlap_id : merge_mips) {
|
||||||
|
auto& overlap = slot_images[overlap_id];
|
||||||
|
if (True(overlap.flags & ImageFlagBits::GpuModified)) {
|
||||||
|
new_image.flags |= ImageFlagBits::GpuModified;
|
||||||
|
new_image.modification_tick = overlap.modification_tick;
|
||||||
|
|
||||||
|
const SubresourceBase base = new_image.TryFindBase(overlap.gpu_addr).value();
|
||||||
|
auto copies =
|
||||||
|
MakeShrinkImageCopies(new_image.info, overlap.info, base, up_scale, down_shift);
|
||||||
|
if (new_image.info.num_samples != overlap.info.num_samples) {
|
||||||
|
runtime.CopyImageMSAA(new_image, overlap, std::move(copies));
|
||||||
|
} else {
|
||||||
|
runtime.CopyImage(new_image, overlap, std::move(copies));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (True(overlap.flags & ImageFlagBits::Tracked)) {
|
||||||
|
UntrackImage(overlap, overlap_id);
|
||||||
|
}
|
||||||
|
UnregisterImage(overlap_id);
|
||||||
|
DeleteImage(overlap_id);
|
||||||
|
}
|
||||||
|
|
||||||
std::ranges::sort(join_copies_to_do, [this](const JoinCopy& lhs, const JoinCopy& rhs) {
|
std::ranges::sort(join_copies_to_do, [this](const JoinCopy& lhs, const JoinCopy& rhs) {
|
||||||
const ImageBase& lhs_image = slot_images[lhs.id];
|
const ImageBase& lhs_image = slot_images[lhs.id];
|
||||||
const ImageBase& rhs_image = slot_images[rhs.id];
|
const ImageBase& rhs_image = slot_images[rhs.id];
|
||||||
|
@ -1523,10 +1561,7 @@ ImageId TextureCache<P>::JoinImages(const ImageInfo& info, GPUVAddr gpu_addr, VA
|
||||||
}
|
}
|
||||||
if (True(overlap.flags & ImageFlagBits::GpuModified)) {
|
if (True(overlap.flags & ImageFlagBits::GpuModified)) {
|
||||||
new_image.flags |= ImageFlagBits::GpuModified;
|
new_image.flags |= ImageFlagBits::GpuModified;
|
||||||
const auto& resolution = Settings::values.resolution_info;
|
|
||||||
const SubresourceBase base = new_image.TryFindBase(overlap.gpu_addr).value();
|
const SubresourceBase base = new_image.TryFindBase(overlap.gpu_addr).value();
|
||||||
const u32 up_scale = can_rescale ? resolution.up_scale : 1;
|
|
||||||
const u32 down_shift = can_rescale ? resolution.down_shift : 0;
|
|
||||||
auto copies = MakeShrinkImageCopies(new_info, overlap.info, base, up_scale, down_shift);
|
auto copies = MakeShrinkImageCopies(new_info, overlap.info, base, up_scale, down_shift);
|
||||||
if (overlap.info.num_samples != new_image.info.num_samples) {
|
if (overlap.info.num_samples != new_image.info.num_samples) {
|
||||||
runtime.CopyImageMSAA(new_image, overlap, std::move(copies));
|
runtime.CopyImageMSAA(new_image, overlap, std::move(copies));
|
||||||
|
|
|
@ -1233,6 +1233,33 @@ bool IsSubresource(const ImageInfo& candidate, const ImageBase& image, GPUVAddr
|
||||||
.has_value();
|
.has_value();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool IsSubLevel(const ImageBase& image, const ImageBase& overlap) {
|
||||||
|
const std::optional<SubresourceBase> base = image.TryFindBase(overlap.gpu_addr);
|
||||||
|
if (!base) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!IsViewCompatible(image.info.format, overlap.info.format, false, true)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (AdjustMipSize(image.info.size, base->level) != overlap.info.size) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto level_info = MakeLevelInfo(image.info);
|
||||||
|
auto level_sizes = CalculateLevelSizes(level_info, image.info.resources.levels);
|
||||||
|
auto total_size{0};
|
||||||
|
auto level = base->level;
|
||||||
|
while (level) {
|
||||||
|
total_size += level_sizes[level - 1];
|
||||||
|
level--;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (overlap.gpu_addr - total_size != image.gpu_addr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool IsSubCopy(const ImageInfo& candidate, const ImageBase& image, GPUVAddr candidate_addr) {
|
bool IsSubCopy(const ImageInfo& candidate, const ImageBase& image, GPUVAddr candidate_addr) {
|
||||||
const std::optional<SubresourceBase> base = image.TryFindBase(candidate_addr);
|
const std::optional<SubresourceBase> base = image.TryFindBase(candidate_addr);
|
||||||
if (!base) {
|
if (!base) {
|
||||||
|
|
|
@ -111,6 +111,8 @@ void SwizzleImage(Tegra::MemoryManager& gpu_memory, GPUVAddr gpu_addr, const Ima
|
||||||
GPUVAddr candidate_addr, RelaxedOptions options, bool broken_views,
|
GPUVAddr candidate_addr, RelaxedOptions options, bool broken_views,
|
||||||
bool native_bgr);
|
bool native_bgr);
|
||||||
|
|
||||||
|
[[nodiscard]] bool IsSubLevel(const ImageBase& image, const ImageBase& overlap);
|
||||||
|
|
||||||
[[nodiscard]] bool IsSubCopy(const ImageInfo& candidate, const ImageBase& image,
|
[[nodiscard]] bool IsSubCopy(const ImageInfo& candidate, const ImageBase& image,
|
||||||
GPUVAddr candidate_addr);
|
GPUVAddr candidate_addr);
|
||||||
|
|
||||||
|
|
|
@ -564,9 +564,9 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri
|
||||||
QAction* verify_integrity = context_menu.addAction(tr("Verify Integrity"));
|
QAction* verify_integrity = context_menu.addAction(tr("Verify Integrity"));
|
||||||
QAction* copy_tid = context_menu.addAction(tr("Copy Title ID to Clipboard"));
|
QAction* copy_tid = context_menu.addAction(tr("Copy Title ID to Clipboard"));
|
||||||
QAction* navigate_to_gamedb_entry = context_menu.addAction(tr("Navigate to GameDB entry"));
|
QAction* navigate_to_gamedb_entry = context_menu.addAction(tr("Navigate to GameDB entry"));
|
||||||
#ifndef WIN32
|
|
||||||
QMenu* shortcut_menu = context_menu.addMenu(tr("Create Shortcut"));
|
QMenu* shortcut_menu = context_menu.addMenu(tr("Create Shortcut"));
|
||||||
QAction* create_desktop_shortcut = shortcut_menu->addAction(tr("Add to Desktop"));
|
QAction* create_desktop_shortcut = shortcut_menu->addAction(tr("Add to Desktop"));
|
||||||
|
#ifndef WIN32
|
||||||
QAction* create_applications_menu_shortcut =
|
QAction* create_applications_menu_shortcut =
|
||||||
shortcut_menu->addAction(tr("Add to Applications Menu"));
|
shortcut_menu->addAction(tr("Add to Applications Menu"));
|
||||||
#endif
|
#endif
|
||||||
|
@ -644,10 +644,10 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri
|
||||||
connect(navigate_to_gamedb_entry, &QAction::triggered, [this, program_id]() {
|
connect(navigate_to_gamedb_entry, &QAction::triggered, [this, program_id]() {
|
||||||
emit NavigateToGamedbEntryRequested(program_id, compatibility_list);
|
emit NavigateToGamedbEntryRequested(program_id, compatibility_list);
|
||||||
});
|
});
|
||||||
#ifndef WIN32
|
|
||||||
connect(create_desktop_shortcut, &QAction::triggered, [this, program_id, path]() {
|
connect(create_desktop_shortcut, &QAction::triggered, [this, program_id, path]() {
|
||||||
emit CreateShortcut(program_id, path, GameListShortcutTarget::Desktop);
|
emit CreateShortcut(program_id, path, GameListShortcutTarget::Desktop);
|
||||||
});
|
});
|
||||||
|
#ifndef WIN32
|
||||||
connect(create_applications_menu_shortcut, &QAction::triggered, [this, program_id, path]() {
|
connect(create_applications_menu_shortcut, &QAction::triggered, [this, program_id, path]() {
|
||||||
emit CreateShortcut(program_id, path, GameListShortcutTarget::Applications);
|
emit CreateShortcut(program_id, path, GameListShortcutTarget::Applications);
|
||||||
});
|
});
|
||||||
|
|
|
@ -98,6 +98,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
|
||||||
#include "common/scm_rev.h"
|
#include "common/scm_rev.h"
|
||||||
#include "common/scope_exit.h"
|
#include "common/scope_exit.h"
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
|
#include <shlobj.h>
|
||||||
#include "common/windows/timer_resolution.h"
|
#include "common/windows/timer_resolution.h"
|
||||||
#endif
|
#endif
|
||||||
#ifdef ARCHITECTURE_x86_64
|
#ifdef ARCHITECTURE_x86_64
|
||||||
|
@ -2844,7 +2845,6 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga
|
||||||
const QStringList args = QApplication::arguments();
|
const QStringList args = QApplication::arguments();
|
||||||
std::filesystem::path yuzu_command = args[0].toStdString();
|
std::filesystem::path yuzu_command = args[0].toStdString();
|
||||||
|
|
||||||
#if defined(__linux__) || defined(__FreeBSD__)
|
|
||||||
// If relative path, make it an absolute path
|
// If relative path, make it an absolute path
|
||||||
if (yuzu_command.c_str()[0] == '.') {
|
if (yuzu_command.c_str()[0] == '.') {
|
||||||
yuzu_command = Common::FS::GetCurrentDir() / yuzu_command;
|
yuzu_command = Common::FS::GetCurrentDir() / yuzu_command;
|
||||||
|
@ -2867,12 +2867,14 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga
|
||||||
UISettings::values.shortcut_already_warned = true;
|
UISettings::values.shortcut_already_warned = true;
|
||||||
}
|
}
|
||||||
#endif // __linux__
|
#endif // __linux__
|
||||||
#endif // __linux__ || __FreeBSD__
|
|
||||||
|
|
||||||
std::filesystem::path target_directory{};
|
std::filesystem::path target_directory{};
|
||||||
// Determine target directory for shortcut
|
// Determine target directory for shortcut
|
||||||
#if defined(__linux__) || defined(__FreeBSD__)
|
#if defined(WIN32)
|
||||||
|
const char* home = std::getenv("USERPROFILE");
|
||||||
|
#else
|
||||||
const char* home = std::getenv("HOME");
|
const char* home = std::getenv("HOME");
|
||||||
|
#endif
|
||||||
const std::filesystem::path home_path = (home == nullptr ? "~" : home);
|
const std::filesystem::path home_path = (home == nullptr ? "~" : home);
|
||||||
const char* xdg_data_home = std::getenv("XDG_DATA_HOME");
|
const char* xdg_data_home = std::getenv("XDG_DATA_HOME");
|
||||||
|
|
||||||
|
@ -2882,7 +2884,7 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga
|
||||||
QMessageBox::critical(
|
QMessageBox::critical(
|
||||||
this, tr("Create Shortcut"),
|
this, tr("Create Shortcut"),
|
||||||
tr("Cannot create shortcut on desktop. Path \"%1\" does not exist.")
|
tr("Cannot create shortcut on desktop. Path \"%1\" does not exist.")
|
||||||
.arg(QString::fromStdString(target_directory)),
|
.arg(QString::fromStdString(target_directory.generic_string())),
|
||||||
QMessageBox::StandardButton::Ok);
|
QMessageBox::StandardButton::Ok);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -2890,15 +2892,15 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga
|
||||||
target_directory = (xdg_data_home == nullptr ? home_path / ".local/share" : xdg_data_home) /
|
target_directory = (xdg_data_home == nullptr ? home_path / ".local/share" : xdg_data_home) /
|
||||||
"applications";
|
"applications";
|
||||||
if (!Common::FS::CreateDirs(target_directory)) {
|
if (!Common::FS::CreateDirs(target_directory)) {
|
||||||
QMessageBox::critical(this, tr("Create Shortcut"),
|
QMessageBox::critical(
|
||||||
|
this, tr("Create Shortcut"),
|
||||||
tr("Cannot create shortcut in applications menu. Path \"%1\" "
|
tr("Cannot create shortcut in applications menu. Path \"%1\" "
|
||||||
"does not exist and cannot be created.")
|
"does not exist and cannot be created.")
|
||||||
.arg(QString::fromStdString(target_directory)),
|
.arg(QString::fromStdString(target_directory.generic_string())),
|
||||||
QMessageBox::StandardButton::Ok);
|
QMessageBox::StandardButton::Ok);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
const std::string game_file_name = std::filesystem::path(game_path).filename().string();
|
const std::string game_file_name = std::filesystem::path(game_path).filename().string();
|
||||||
// Determine full paths for icon and shortcut
|
// Determine full paths for icon and shortcut
|
||||||
|
@ -2920,9 +2922,14 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga
|
||||||
const std::filesystem::path shortcut_path =
|
const std::filesystem::path shortcut_path =
|
||||||
target_directory / (program_id == 0 ? fmt::format("yuzu-{}.desktop", game_file_name)
|
target_directory / (program_id == 0 ? fmt::format("yuzu-{}.desktop", game_file_name)
|
||||||
: fmt::format("yuzu-{:016X}.desktop", program_id));
|
: fmt::format("yuzu-{:016X}.desktop", program_id));
|
||||||
|
#elif defined(WIN32)
|
||||||
|
std::filesystem::path icons_path =
|
||||||
|
Common::FS::GetYuzuPathString(Common::FS::YuzuPath::IconsDir);
|
||||||
|
std::filesystem::path icon_path =
|
||||||
|
icons_path / ((program_id == 0 ? fmt::format("yuzu-{}.ico", game_file_name)
|
||||||
|
: fmt::format("yuzu-{:016X}.ico", program_id)));
|
||||||
#else
|
#else
|
||||||
const std::filesystem::path icon_path{};
|
std::string icon_extension;
|
||||||
const std::filesystem::path shortcut_path{};
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Get title from game file
|
// Get title from game file
|
||||||
|
@ -2947,29 +2954,37 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga
|
||||||
LOG_WARNING(Frontend, "Could not read icon from {:s}", game_path);
|
LOG_WARNING(Frontend, "Could not read icon from {:s}", game_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
QImage icon_jpeg =
|
QImage icon_data =
|
||||||
QImage::fromData(icon_image_file.data(), static_cast<int>(icon_image_file.size()));
|
QImage::fromData(icon_image_file.data(), static_cast<int>(icon_image_file.size()));
|
||||||
#if defined(__linux__) || defined(__FreeBSD__)
|
#if defined(__linux__) || defined(__FreeBSD__)
|
||||||
// Convert and write the icon as a PNG
|
// Convert and write the icon as a PNG
|
||||||
if (!icon_jpeg.save(QString::fromStdString(icon_path.string()))) {
|
if (!icon_data.save(QString::fromStdString(icon_path.string()))) {
|
||||||
LOG_ERROR(Frontend, "Could not write icon as PNG to file");
|
LOG_ERROR(Frontend, "Could not write icon as PNG to file");
|
||||||
} else {
|
} else {
|
||||||
LOG_INFO(Frontend, "Wrote an icon to {}", icon_path.string());
|
LOG_INFO(Frontend, "Wrote an icon to {}", icon_path.string());
|
||||||
}
|
}
|
||||||
|
#elif defined(WIN32)
|
||||||
|
if (!SaveIconToFile(icon_path.string(), icon_data)) {
|
||||||
|
LOG_ERROR(Frontend, "Could not write icon to file");
|
||||||
|
return;
|
||||||
|
}
|
||||||
#endif // __linux__
|
#endif // __linux__
|
||||||
|
|
||||||
#if defined(__linux__) || defined(__FreeBSD__)
|
#ifdef _WIN32
|
||||||
|
// Replace characters that are illegal in Windows filenames by a dash
|
||||||
|
const std::string illegal_chars = "<>:\"/\\|?*";
|
||||||
|
for (char c : illegal_chars) {
|
||||||
|
std::replace(title.begin(), title.end(), c, '_');
|
||||||
|
}
|
||||||
|
const std::filesystem::path shortcut_path = target_directory / (title + ".lnk").c_str();
|
||||||
|
#endif
|
||||||
|
|
||||||
const std::string comment =
|
const std::string comment =
|
||||||
tr("Start %1 with the yuzu Emulator").arg(QString::fromStdString(title)).toStdString();
|
tr("Start %1 with the yuzu Emulator").arg(QString::fromStdString(title)).toStdString();
|
||||||
const std::string arguments = fmt::format("-g \"{:s}\"", game_path);
|
const std::string arguments = fmt::format("-g \"{:s}\"", game_path);
|
||||||
const std::string categories = "Game;Emulator;Qt;";
|
const std::string categories = "Game;Emulator;Qt;";
|
||||||
const std::string keywords = "Switch;Nintendo;";
|
const std::string keywords = "Switch;Nintendo;";
|
||||||
#else
|
|
||||||
const std::string comment{};
|
|
||||||
const std::string arguments{};
|
|
||||||
const std::string categories{};
|
|
||||||
const std::string keywords{};
|
|
||||||
#endif
|
|
||||||
if (!CreateShortcut(shortcut_path.string(), title, comment, icon_path.string(),
|
if (!CreateShortcut(shortcut_path.string(), title, comment, icon_path.string(),
|
||||||
yuzu_command.string(), arguments, categories, keywords)) {
|
yuzu_command.string(), arguments, categories, keywords)) {
|
||||||
QMessageBox::critical(this, tr("Create Shortcut"),
|
QMessageBox::critical(this, tr("Create Shortcut"),
|
||||||
|
@ -3990,6 +4005,34 @@ bool GMainWindow::CreateShortcut(const std::string& shortcut_path, const std::st
|
||||||
shortcut_stream << shortcut_contents;
|
shortcut_stream << shortcut_contents;
|
||||||
shortcut_stream.close();
|
shortcut_stream.close();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
#elif defined(WIN32)
|
||||||
|
IShellLinkW* shell_link;
|
||||||
|
auto hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLinkW,
|
||||||
|
(void**)&shell_link);
|
||||||
|
if (FAILED(hres)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
shell_link->SetPath(
|
||||||
|
Common::UTF8ToUTF16W(command).data()); // Path to the object we are referring to
|
||||||
|
shell_link->SetArguments(Common::UTF8ToUTF16W(arguments).data());
|
||||||
|
shell_link->SetDescription(Common::UTF8ToUTF16W(comment).data());
|
||||||
|
shell_link->SetIconLocation(Common::UTF8ToUTF16W(icon_path).data(), 0);
|
||||||
|
|
||||||
|
IPersistFile* persist_file;
|
||||||
|
hres = shell_link->QueryInterface(IID_IPersistFile, (void**)&persist_file);
|
||||||
|
if (FAILED(hres)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
hres = persist_file->Save(Common::UTF8ToUTF16W(shortcut_path).data(), TRUE);
|
||||||
|
if (FAILED(hres)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
persist_file->Release();
|
||||||
|
shell_link->Release();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
#endif
|
#endif
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -5,6 +5,10 @@
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <QPainter>
|
#include <QPainter>
|
||||||
#include "yuzu/util/util.h"
|
#include "yuzu/util/util.h"
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include <windows.h>
|
||||||
|
#include "common/fs/file.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
QFont GetMonospaceFont() {
|
QFont GetMonospaceFont() {
|
||||||
QFont font(QStringLiteral("monospace"));
|
QFont font(QStringLiteral("monospace"));
|
||||||
|
@ -37,3 +41,76 @@ QPixmap CreateCirclePixmapFromColor(const QColor& color) {
|
||||||
painter.drawEllipse({circle_pixmap.width() / 2.0, circle_pixmap.height() / 2.0}, 7.0, 7.0);
|
painter.drawEllipse({circle_pixmap.width() / 2.0, circle_pixmap.height() / 2.0}, 7.0, 7.0);
|
||||||
return circle_pixmap;
|
return circle_pixmap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool SaveIconToFile(const std::string_view path, const QImage& image) {
|
||||||
|
#if defined(WIN32)
|
||||||
|
#pragma pack(push, 2)
|
||||||
|
struct IconDir {
|
||||||
|
WORD id_reserved;
|
||||||
|
WORD id_type;
|
||||||
|
WORD id_count;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct IconDirEntry {
|
||||||
|
BYTE width;
|
||||||
|
BYTE height;
|
||||||
|
BYTE color_count;
|
||||||
|
BYTE reserved;
|
||||||
|
WORD planes;
|
||||||
|
WORD bit_count;
|
||||||
|
DWORD bytes_in_res;
|
||||||
|
DWORD image_offset;
|
||||||
|
};
|
||||||
|
#pragma pack(pop)
|
||||||
|
|
||||||
|
QImage source_image = image.convertToFormat(QImage::Format_RGB32);
|
||||||
|
constexpr int bytes_per_pixel = 4;
|
||||||
|
const int image_size = source_image.width() * source_image.height() * bytes_per_pixel;
|
||||||
|
|
||||||
|
BITMAPINFOHEADER info_header{};
|
||||||
|
info_header.biSize = sizeof(BITMAPINFOHEADER), info_header.biWidth = source_image.width(),
|
||||||
|
info_header.biHeight = source_image.height() * 2, info_header.biPlanes = 1,
|
||||||
|
info_header.biBitCount = bytes_per_pixel * 8, info_header.biCompression = BI_RGB;
|
||||||
|
|
||||||
|
const IconDir icon_dir{.id_reserved = 0, .id_type = 1, .id_count = 1};
|
||||||
|
const IconDirEntry icon_entry{.width = static_cast<BYTE>(source_image.width()),
|
||||||
|
.height = static_cast<BYTE>(source_image.height() * 2),
|
||||||
|
.color_count = 0,
|
||||||
|
.reserved = 0,
|
||||||
|
.planes = 1,
|
||||||
|
.bit_count = bytes_per_pixel * 8,
|
||||||
|
.bytes_in_res =
|
||||||
|
static_cast<DWORD>(sizeof(BITMAPINFOHEADER) + image_size),
|
||||||
|
.image_offset = sizeof(IconDir) + sizeof(IconDirEntry)};
|
||||||
|
|
||||||
|
Common::FS::IOFile icon_file(path, Common::FS::FileAccessMode::Write,
|
||||||
|
Common::FS::FileType::BinaryFile);
|
||||||
|
if (!icon_file.IsOpen()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!icon_file.Write(icon_dir)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!icon_file.Write(icon_entry)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!icon_file.Write(info_header)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int y = 0; y < image.height(); y++) {
|
||||||
|
const auto* line = source_image.scanLine(source_image.height() - 1 - y);
|
||||||
|
std::vector<u8> line_data(source_image.width() * bytes_per_pixel);
|
||||||
|
std::memcpy(line_data.data(), line, line_data.size());
|
||||||
|
if (!icon_file.Write(line_data)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
icon_file.Close();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
#else
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
|
@ -7,14 +7,22 @@
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
|
||||||
/// Returns a QFont object appropriate to use as a monospace font for debugging widgets, etc.
|
/// Returns a QFont object appropriate to use as a monospace font for debugging widgets, etc.
|
||||||
QFont GetMonospaceFont();
|
[[nodiscard]] QFont GetMonospaceFont();
|
||||||
|
|
||||||
/// Convert a size in bytes into a readable format (KiB, MiB, etc.)
|
/// Convert a size in bytes into a readable format (KiB, MiB, etc.)
|
||||||
QString ReadableByteSize(qulonglong size);
|
[[nodiscard]] QString ReadableByteSize(qulonglong size);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a circle pixmap from a specified color
|
* Creates a circle pixmap from a specified color
|
||||||
* @param color The color the pixmap shall have
|
* @param color The color the pixmap shall have
|
||||||
* @return QPixmap circle pixmap
|
* @return QPixmap circle pixmap
|
||||||
*/
|
*/
|
||||||
QPixmap CreateCirclePixmapFromColor(const QColor& color);
|
[[nodiscard]] QPixmap CreateCirclePixmapFromColor(const QColor& color);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves a windows icon to a file
|
||||||
|
* @param path The icons path
|
||||||
|
* @param image The image to save
|
||||||
|
* @return bool If the operation succeeded
|
||||||
|
*/
|
||||||
|
[[nodiscard]] bool SaveIconToFile(const std::string_view path, const QImage& image);
|
||||||
|
|
Loading…
Reference in a new issue