Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
71379825d6 |
No files matched your search
@@ -81,7 +81,6 @@ set(HASH_FILES
|
||||
"${VIDEO_CORE}/shader/decode/register_set_predicate.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/shift.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/video.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/warp.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/xmad.cpp"
|
||||
"${VIDEO_CORE}/shader/control_flow.cpp"
|
||||
"${VIDEO_CORE}/shader/control_flow.h"
|
||||
|
||||
@@ -55,7 +55,6 @@ add_custom_command(OUTPUT scm_rev.cpp
|
||||
"${VIDEO_CORE}/shader/decode/register_set_predicate.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/shift.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/video.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/warp.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/xmad.cpp"
|
||||
"${VIDEO_CORE}/shader/control_flow.cpp"
|
||||
"${VIDEO_CORE}/shader/control_flow.h"
|
||||
|
||||
+17
-8
@@ -159,10 +159,6 @@ struct System::Impl {
|
||||
|
||||
LOG_DEBUG(Core, "Initialized OK");
|
||||
|
||||
// Reset counters and set time origin to current frame
|
||||
GetAndResetPerfStats();
|
||||
perf_stats.BeginSystemFrame();
|
||||
|
||||
return ResultStatus::Success;
|
||||
}
|
||||
|
||||
@@ -205,6 +201,16 @@ struct System::Impl {
|
||||
main_process->Run(load_parameters->main_thread_priority,
|
||||
load_parameters->main_thread_stack_size);
|
||||
|
||||
u64 title_id{0};
|
||||
if (app_loader->ReadProgramId(title_id) != Loader::ResultStatus::Success) {
|
||||
LOG_ERROR(Core, "Failed to find title id for ROM (Error {})",
|
||||
static_cast<u32>(load_result));
|
||||
}
|
||||
perf_stats = std::make_unique<PerfStats>(title_id);
|
||||
// Reset counters and set time origin to current frame
|
||||
GetAndResetPerfStats();
|
||||
perf_stats->BeginSystemFrame();
|
||||
|
||||
status = ResultStatus::Success;
|
||||
return status;
|
||||
}
|
||||
@@ -218,6 +224,8 @@ struct System::Impl {
|
||||
perf_results.game_fps);
|
||||
telemetry_session->AddField(Telemetry::FieldType::Performance, "Shutdown_Frametime",
|
||||
perf_results.frametime * 1000.0);
|
||||
telemetry_session->AddField(Telemetry::FieldType::Performance, "Mean_Frametime_MS",
|
||||
perf_stats->GetMeanFrametime());
|
||||
|
||||
is_powered_on = false;
|
||||
|
||||
@@ -228,6 +236,7 @@ struct System::Impl {
|
||||
service_manager.reset();
|
||||
cheat_engine.reset();
|
||||
telemetry_session.reset();
|
||||
perf_stats.reset();
|
||||
gpu_core.reset();
|
||||
|
||||
// Close all CPU/threading state
|
||||
@@ -285,7 +294,7 @@ struct System::Impl {
|
||||
}
|
||||
|
||||
PerfStatsResults GetAndResetPerfStats() {
|
||||
return perf_stats.GetAndResetStats(core_timing.GetGlobalTimeUs());
|
||||
return perf_stats->GetAndResetStats(core_timing.GetGlobalTimeUs());
|
||||
}
|
||||
|
||||
Timing::CoreTiming core_timing;
|
||||
@@ -326,7 +335,7 @@ struct System::Impl {
|
||||
ResultStatus status = ResultStatus::Success;
|
||||
std::string status_details = "";
|
||||
|
||||
Core::PerfStats perf_stats;
|
||||
std::unique_ptr<Core::PerfStats> perf_stats;
|
||||
Core::FrameLimiter frame_limiter;
|
||||
};
|
||||
|
||||
@@ -479,11 +488,11 @@ const Timing::CoreTiming& System::CoreTiming() const {
|
||||
}
|
||||
|
||||
Core::PerfStats& System::GetPerfStats() {
|
||||
return impl->perf_stats;
|
||||
return *impl->perf_stats;
|
||||
}
|
||||
|
||||
const Core::PerfStats& System::GetPerfStats() const {
|
||||
return impl->perf_stats;
|
||||
return *impl->perf_stats;
|
||||
}
|
||||
|
||||
Core::FrameLimiter& System::FrameLimiter() {
|
||||
|
||||
+44
-1
@@ -4,8 +4,14 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <iterator>
|
||||
#include <mutex>
|
||||
#include <numeric>
|
||||
#include <sstream>
|
||||
#include <thread>
|
||||
#include <fmt/format.h>
|
||||
#include <fmt/time.h>
|
||||
#include "common/file_util.h"
|
||||
#include "common/math_util.h"
|
||||
#include "core/perf_stats.h"
|
||||
#include "core/settings.h"
|
||||
@@ -15,8 +21,31 @@ using DoubleSecs = std::chrono::duration<double, std::chrono::seconds::period>;
|
||||
using std::chrono::duration_cast;
|
||||
using std::chrono::microseconds;
|
||||
|
||||
// Purposefully ignore the first five frames, as there's a significant amount of overhead in
|
||||
// booting that we shouldn't account for
|
||||
constexpr std::size_t IgnoreFrames = 5;
|
||||
|
||||
namespace Core {
|
||||
|
||||
PerfStats::PerfStats(u64 title_id) : title_id(title_id) {}
|
||||
|
||||
PerfStats::~PerfStats() {
|
||||
if (!Settings::values.record_frame_times || title_id == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::time_t t = std::time(nullptr);
|
||||
std::ostringstream stream;
|
||||
std::copy(perf_history.begin() + IgnoreFrames, perf_history.begin() + current_index,
|
||||
std::ostream_iterator<double>(stream, "\n"));
|
||||
std::string path = FileUtil::GetUserPath(FileUtil::UserPath::LogDir);
|
||||
// %F Date format expanded is "%Y-%m-%d"
|
||||
std::string filename =
|
||||
fmt::format("{}/{:%F-%H-%M}_{:016X}.csv", path, *std::localtime(&t), title_id);
|
||||
FileUtil::IOFile file(filename, "w");
|
||||
file.WriteString(stream.str());
|
||||
}
|
||||
|
||||
void PerfStats::BeginSystemFrame() {
|
||||
std::lock_guard lock{object_mutex};
|
||||
|
||||
@@ -27,7 +56,12 @@ void PerfStats::EndSystemFrame() {
|
||||
std::lock_guard lock{object_mutex};
|
||||
|
||||
auto frame_end = Clock::now();
|
||||
accumulated_frametime += frame_end - frame_begin;
|
||||
const auto frame_time = frame_end - frame_begin;
|
||||
if (current_index < perf_history.size()) {
|
||||
perf_history[current_index++] =
|
||||
std::chrono::duration<double, std::milli>(frame_time).count();
|
||||
}
|
||||
accumulated_frametime += frame_time;
|
||||
system_frames += 1;
|
||||
|
||||
previous_frame_length = frame_end - previous_frame_end;
|
||||
@@ -40,6 +74,15 @@ void PerfStats::EndGameFrame() {
|
||||
game_frames += 1;
|
||||
}
|
||||
|
||||
double PerfStats::GetMeanFrametime() {
|
||||
if (current_index <= IgnoreFrames) {
|
||||
return 0;
|
||||
}
|
||||
double sum = std::accumulate(perf_history.begin() + IgnoreFrames,
|
||||
perf_history.begin() + current_index, 0);
|
||||
return sum / (current_index - IgnoreFrames);
|
||||
}
|
||||
|
||||
PerfStatsResults PerfStats::GetAndResetStats(microseconds current_system_time_us) {
|
||||
std::lock_guard lock{object_mutex};
|
||||
|
||||
|
||||
+19
-1
@@ -4,6 +4,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <chrono>
|
||||
#include <mutex>
|
||||
#include "common/common_types.h"
|
||||
@@ -27,6 +28,10 @@ struct PerfStatsResults {
|
||||
*/
|
||||
class PerfStats {
|
||||
public:
|
||||
explicit PerfStats(u64 title_id);
|
||||
|
||||
~PerfStats();
|
||||
|
||||
using Clock = std::chrono::high_resolution_clock;
|
||||
|
||||
void BeginSystemFrame();
|
||||
@@ -35,6 +40,11 @@ public:
|
||||
|
||||
PerfStatsResults GetAndResetStats(std::chrono::microseconds current_system_time_us);
|
||||
|
||||
/**
|
||||
* Returns the Arthimetic Mean of all frametime values stored in the performance history.
|
||||
*/
|
||||
double GetMeanFrametime();
|
||||
|
||||
/**
|
||||
* Gets the ratio between walltime and the emulated time of the previous system frame. This is
|
||||
* useful for scaling inputs or outputs moving between the two time domains.
|
||||
@@ -42,7 +52,15 @@ public:
|
||||
double GetLastFrameTimeScale();
|
||||
|
||||
private:
|
||||
std::mutex object_mutex;
|
||||
std::mutex object_mutex{};
|
||||
|
||||
/// Title ID for the game that is running. 0 if there is no game running yet
|
||||
u64 title_id{0};
|
||||
/// Current index for writing to the perf_history array
|
||||
std::size_t current_index{0};
|
||||
/// Stores an hour of historical frametime data useful for processing and tracking performance
|
||||
/// regressions with code changes.
|
||||
std::array<double, 216000> perf_history = {};
|
||||
|
||||
/// Point when the cumulative counters were reset
|
||||
Clock::time_point reset_point = Clock::now();
|
||||
|
||||
@@ -409,6 +409,7 @@ struct Values {
|
||||
float volume;
|
||||
|
||||
// Debugging
|
||||
bool record_frame_times;
|
||||
bool use_gdbstub;
|
||||
u16 gdbstub_port;
|
||||
std::string program_args;
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
add_library(video_core STATIC
|
||||
buffer_cache/buffer_block.h
|
||||
buffer_cache/buffer_cache.h
|
||||
buffer_cache/map_interval.h
|
||||
buffer_cache.h
|
||||
dma_pusher.cpp
|
||||
dma_pusher.h
|
||||
debug_utils/debug_utils.cpp
|
||||
@@ -102,7 +100,6 @@ add_library(video_core STATIC
|
||||
shader/decode/integer_set.cpp
|
||||
shader/decode/half_set.cpp
|
||||
shader/decode/video.cpp
|
||||
shader/decode/warp.cpp
|
||||
shader/decode/xmad.cpp
|
||||
shader/decode/other.cpp
|
||||
shader/control_flow.cpp
|
||||
|
||||
@@ -0,0 +1,299 @@
|
||||
// Copyright 2019 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "common/alignment.h"
|
||||
#include "common/common_types.h"
|
||||
#include "core/core.h"
|
||||
#include "video_core/memory_manager.h"
|
||||
#include "video_core/rasterizer_cache.h"
|
||||
|
||||
namespace VideoCore {
|
||||
class RasterizerInterface;
|
||||
}
|
||||
|
||||
namespace VideoCommon {
|
||||
|
||||
template <typename BufferStorageType>
|
||||
class CachedBuffer final : public RasterizerCacheObject {
|
||||
public:
|
||||
explicit CachedBuffer(VAddr cpu_addr, u8* host_ptr)
|
||||
: RasterizerCacheObject{host_ptr}, host_ptr{host_ptr}, cpu_addr{cpu_addr} {}
|
||||
~CachedBuffer() override = default;
|
||||
|
||||
VAddr GetCpuAddr() const override {
|
||||
return cpu_addr;
|
||||
}
|
||||
|
||||
std::size_t GetSizeInBytes() const override {
|
||||
return size;
|
||||
}
|
||||
|
||||
u8* GetWritableHostPtr() const {
|
||||
return host_ptr;
|
||||
}
|
||||
|
||||
std::size_t GetSize() const {
|
||||
return size;
|
||||
}
|
||||
|
||||
std::size_t GetCapacity() const {
|
||||
return capacity;
|
||||
}
|
||||
|
||||
bool IsInternalized() const {
|
||||
return is_internal;
|
||||
}
|
||||
|
||||
const BufferStorageType& GetBuffer() const {
|
||||
return buffer;
|
||||
}
|
||||
|
||||
void SetSize(std::size_t new_size) {
|
||||
size = new_size;
|
||||
}
|
||||
|
||||
void SetInternalState(bool is_internal_) {
|
||||
is_internal = is_internal_;
|
||||
}
|
||||
|
||||
BufferStorageType ExchangeBuffer(BufferStorageType buffer_, std::size_t new_capacity) {
|
||||
capacity = new_capacity;
|
||||
std::swap(buffer, buffer_);
|
||||
return buffer_;
|
||||
}
|
||||
|
||||
private:
|
||||
u8* host_ptr{};
|
||||
VAddr cpu_addr{};
|
||||
std::size_t size{};
|
||||
std::size_t capacity{};
|
||||
bool is_internal{};
|
||||
BufferStorageType buffer;
|
||||
};
|
||||
|
||||
template <typename BufferStorageType, typename BufferType, typename StreamBuffer>
|
||||
class BufferCache : public RasterizerCache<std::shared_ptr<CachedBuffer<BufferStorageType>>> {
|
||||
public:
|
||||
using Buffer = std::shared_ptr<CachedBuffer<BufferStorageType>>;
|
||||
using BufferInfo = std::pair<const BufferType*, u64>;
|
||||
|
||||
explicit BufferCache(VideoCore::RasterizerInterface& rasterizer, Core::System& system,
|
||||
std::unique_ptr<StreamBuffer> stream_buffer)
|
||||
: RasterizerCache<Buffer>{rasterizer}, system{system},
|
||||
stream_buffer{std::move(stream_buffer)}, stream_buffer_handle{
|
||||
this->stream_buffer->GetHandle()} {}
|
||||
~BufferCache() = default;
|
||||
|
||||
void Unregister(const Buffer& entry) override {
|
||||
std::lock_guard lock{RasterizerCache<Buffer>::mutex};
|
||||
if (entry->IsInternalized()) {
|
||||
internalized_entries.erase(entry->GetCacheAddr());
|
||||
}
|
||||
ReserveBuffer(entry);
|
||||
RasterizerCache<Buffer>::Unregister(entry);
|
||||
}
|
||||
|
||||
void TickFrame() {
|
||||
marked_for_destruction_index =
|
||||
(marked_for_destruction_index + 1) % marked_for_destruction_ring_buffer.size();
|
||||
MarkedForDestruction().clear();
|
||||
}
|
||||
|
||||
BufferInfo UploadMemory(GPUVAddr gpu_addr, std::size_t size, std::size_t alignment = 4,
|
||||
bool internalize = false, bool is_written = false) {
|
||||
std::lock_guard lock{RasterizerCache<Buffer>::mutex};
|
||||
|
||||
auto& memory_manager = system.GPU().MemoryManager();
|
||||
const auto host_ptr = memory_manager.GetPointer(gpu_addr);
|
||||
if (!host_ptr) {
|
||||
return {GetEmptyBuffer(size), 0};
|
||||
}
|
||||
const auto cache_addr = ToCacheAddr(host_ptr);
|
||||
|
||||
// Cache management is a big overhead, so only cache entries with a given size.
|
||||
// TODO: Figure out which size is the best for given games.
|
||||
constexpr std::size_t max_stream_size = 0x800;
|
||||
if (!internalize && size < max_stream_size &&
|
||||
internalized_entries.find(cache_addr) == internalized_entries.end()) {
|
||||
return StreamBufferUpload(host_ptr, size, alignment);
|
||||
}
|
||||
|
||||
auto entry = RasterizerCache<Buffer>::TryGet(cache_addr);
|
||||
if (!entry) {
|
||||
return FixedBufferUpload(gpu_addr, host_ptr, size, internalize, is_written);
|
||||
}
|
||||
|
||||
if (entry->GetSize() < size) {
|
||||
IncreaseBufferSize(entry, size);
|
||||
}
|
||||
if (is_written) {
|
||||
entry->MarkAsModified(true, *this);
|
||||
}
|
||||
return {ToHandle(entry->GetBuffer()), 0};
|
||||
}
|
||||
|
||||
/// Uploads from a host memory. Returns the OpenGL buffer where it's located and its offset.
|
||||
BufferInfo UploadHostMemory(const void* raw_pointer, std::size_t size,
|
||||
std::size_t alignment = 4) {
|
||||
std::lock_guard lock{RasterizerCache<Buffer>::mutex};
|
||||
return StreamBufferUpload(raw_pointer, size, alignment);
|
||||
}
|
||||
|
||||
void Map(std::size_t max_size) {
|
||||
std::tie(buffer_ptr, buffer_offset_base, invalidated) = stream_buffer->Map(max_size, 4);
|
||||
buffer_offset = buffer_offset_base;
|
||||
}
|
||||
|
||||
/// Finishes the upload stream, returns true on bindings invalidation.
|
||||
bool Unmap() {
|
||||
stream_buffer->Unmap(buffer_offset - buffer_offset_base);
|
||||
return std::exchange(invalidated, false);
|
||||
}
|
||||
|
||||
virtual const BufferType* GetEmptyBuffer(std::size_t size) = 0;
|
||||
|
||||
protected:
|
||||
void FlushObjectInner(const Buffer& entry) override {
|
||||
DownloadBufferData(entry->GetBuffer(), 0, entry->GetSize(), entry->GetWritableHostPtr());
|
||||
}
|
||||
|
||||
virtual BufferStorageType CreateBuffer(std::size_t size) = 0;
|
||||
|
||||
virtual const BufferType* ToHandle(const BufferStorageType& storage) = 0;
|
||||
|
||||
virtual void UploadBufferData(const BufferStorageType& buffer, std::size_t offset,
|
||||
std::size_t size, const u8* data) = 0;
|
||||
|
||||
virtual void DownloadBufferData(const BufferStorageType& buffer, std::size_t offset,
|
||||
std::size_t size, u8* data) = 0;
|
||||
|
||||
virtual void CopyBufferData(const BufferStorageType& src, const BufferStorageType& dst,
|
||||
std::size_t src_offset, std::size_t dst_offset,
|
||||
std::size_t size) = 0;
|
||||
|
||||
private:
|
||||
BufferInfo StreamBufferUpload(const void* raw_pointer, std::size_t size,
|
||||
std::size_t alignment) {
|
||||
AlignBuffer(alignment);
|
||||
const std::size_t uploaded_offset = buffer_offset;
|
||||
std::memcpy(buffer_ptr, raw_pointer, size);
|
||||
|
||||
buffer_ptr += size;
|
||||
buffer_offset += size;
|
||||
return {&stream_buffer_handle, uploaded_offset};
|
||||
}
|
||||
|
||||
BufferInfo FixedBufferUpload(GPUVAddr gpu_addr, u8* host_ptr, std::size_t size,
|
||||
bool internalize, bool is_written) {
|
||||
auto& memory_manager = Core::System::GetInstance().GPU().MemoryManager();
|
||||
const auto cpu_addr = memory_manager.GpuToCpuAddress(gpu_addr);
|
||||
ASSERT(cpu_addr);
|
||||
|
||||
auto entry = GetUncachedBuffer(*cpu_addr, host_ptr);
|
||||
entry->SetSize(size);
|
||||
entry->SetInternalState(internalize);
|
||||
RasterizerCache<Buffer>::Register(entry);
|
||||
|
||||
if (internalize) {
|
||||
internalized_entries.emplace(ToCacheAddr(host_ptr));
|
||||
}
|
||||
if (is_written) {
|
||||
entry->MarkAsModified(true, *this);
|
||||
}
|
||||
|
||||
if (entry->GetCapacity() < size) {
|
||||
MarkedForDestruction().push_back(entry->ExchangeBuffer(CreateBuffer(size), size));
|
||||
}
|
||||
|
||||
UploadBufferData(entry->GetBuffer(), 0, size, host_ptr);
|
||||
return {ToHandle(entry->GetBuffer()), 0};
|
||||
}
|
||||
|
||||
void IncreaseBufferSize(Buffer& entry, std::size_t new_size) {
|
||||
const std::size_t old_size = entry->GetSize();
|
||||
if (entry->GetCapacity() < new_size) {
|
||||
const auto& old_buffer = entry->GetBuffer();
|
||||
auto new_buffer = CreateBuffer(new_size);
|
||||
|
||||
// Copy bits from the old buffer to the new buffer.
|
||||
CopyBufferData(old_buffer, new_buffer, 0, 0, old_size);
|
||||
MarkedForDestruction().push_back(
|
||||
entry->ExchangeBuffer(std::move(new_buffer), new_size));
|
||||
|
||||
// This buffer could have been used
|
||||
invalidated = true;
|
||||
}
|
||||
// Upload the new bits.
|
||||
const std::size_t size_diff = new_size - old_size;
|
||||
UploadBufferData(entry->GetBuffer(), old_size, size_diff, entry->GetHostPtr() + old_size);
|
||||
|
||||
// Update entry's size in the object and in the cache.
|
||||
Unregister(entry);
|
||||
|
||||
entry->SetSize(new_size);
|
||||
RasterizerCache<Buffer>::Register(entry);
|
||||
}
|
||||
|
||||
Buffer GetUncachedBuffer(VAddr cpu_addr, u8* host_ptr) {
|
||||
if (auto entry = TryGetReservedBuffer(host_ptr)) {
|
||||
return entry;
|
||||
}
|
||||
return std::make_shared<CachedBuffer<BufferStorageType>>(cpu_addr, host_ptr);
|
||||
}
|
||||
|
||||
Buffer TryGetReservedBuffer(u8* host_ptr) {
|
||||
const auto it = buffer_reserve.find(ToCacheAddr(host_ptr));
|
||||
if (it == buffer_reserve.end()) {
|
||||
return {};
|
||||
}
|
||||
auto& reserve = it->second;
|
||||
auto entry = reserve.back();
|
||||
reserve.pop_back();
|
||||
return entry;
|
||||
}
|
||||
|
||||
void ReserveBuffer(Buffer entry) {
|
||||
buffer_reserve[entry->GetCacheAddr()].push_back(std::move(entry));
|
||||
}
|
||||
|
||||
void AlignBuffer(std::size_t alignment) {
|
||||
// Align the offset, not the mapped pointer
|
||||
const std::size_t offset_aligned = Common::AlignUp(buffer_offset, alignment);
|
||||
buffer_ptr += offset_aligned - buffer_offset;
|
||||
buffer_offset = offset_aligned;
|
||||
}
|
||||
|
||||
std::vector<BufferStorageType>& MarkedForDestruction() {
|
||||
return marked_for_destruction_ring_buffer[marked_for_destruction_index];
|
||||
}
|
||||
|
||||
Core::System& system;
|
||||
|
||||
std::unique_ptr<StreamBuffer> stream_buffer;
|
||||
BufferType stream_buffer_handle{};
|
||||
|
||||
bool invalidated = false;
|
||||
|
||||
u8* buffer_ptr = nullptr;
|
||||
u64 buffer_offset = 0;
|
||||
u64 buffer_offset_base = 0;
|
||||
|
||||
std::size_t marked_for_destruction_index = 0;
|
||||
std::array<std::vector<BufferStorageType>, 4> marked_for_destruction_ring_buffer;
|
||||
|
||||
std::unordered_set<CacheAddr> internalized_entries;
|
||||
std::unordered_map<CacheAddr, std::vector<Buffer>> buffer_reserve;
|
||||
};
|
||||
|
||||
} // namespace VideoCommon
|
||||
@@ -1,76 +0,0 @@
|
||||
// Copyright 2019 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
|
||||
#include "common/alignment.h"
|
||||
#include "common/common_types.h"
|
||||
#include "video_core/gpu.h"
|
||||
|
||||
namespace VideoCommon {
|
||||
|
||||
class BufferBlock {
|
||||
public:
|
||||
bool Overlaps(const CacheAddr start, const CacheAddr end) const {
|
||||
return (cache_addr < end) && (cache_addr_end > start);
|
||||
}
|
||||
|
||||
bool IsInside(const CacheAddr other_start, const CacheAddr other_end) const {
|
||||
return cache_addr <= other_start && other_end <= cache_addr_end;
|
||||
}
|
||||
|
||||
u8* GetWritableHostPtr() const {
|
||||
return FromCacheAddr(cache_addr);
|
||||
}
|
||||
|
||||
u8* GetWritableHostPtr(std::size_t offset) const {
|
||||
return FromCacheAddr(cache_addr + offset);
|
||||
}
|
||||
|
||||
std::size_t GetOffset(const CacheAddr in_addr) {
|
||||
return static_cast<std::size_t>(in_addr - cache_addr);
|
||||
}
|
||||
|
||||
CacheAddr GetCacheAddr() const {
|
||||
return cache_addr;
|
||||
}
|
||||
|
||||
CacheAddr GetCacheAddrEnd() const {
|
||||
return cache_addr_end;
|
||||
}
|
||||
|
||||
void SetCacheAddr(const CacheAddr new_addr) {
|
||||
cache_addr = new_addr;
|
||||
cache_addr_end = new_addr + size;
|
||||
}
|
||||
|
||||
std::size_t GetSize() const {
|
||||
return size;
|
||||
}
|
||||
|
||||
void SetEpoch(u64 new_epoch) {
|
||||
epoch = new_epoch;
|
||||
}
|
||||
|
||||
u64 GetEpoch() {
|
||||
return epoch;
|
||||
}
|
||||
|
||||
protected:
|
||||
explicit BufferBlock(CacheAddr cache_addr, const std::size_t size) : size{size} {
|
||||
SetCacheAddr(cache_addr);
|
||||
}
|
||||
~BufferBlock() = default;
|
||||
|
||||
private:
|
||||
CacheAddr cache_addr{};
|
||||
CacheAddr cache_addr_end{};
|
||||
std::size_t size{};
|
||||
u64 epoch{};
|
||||
};
|
||||
|
||||
} // namespace VideoCommon
|
||||
@@ -1,447 +0,0 @@
|
||||
// Copyright 2019 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "common/alignment.h"
|
||||
#include "common/common_types.h"
|
||||
#include "core/core.h"
|
||||
#include "video_core/buffer_cache/buffer_block.h"
|
||||
#include "video_core/buffer_cache/map_interval.h"
|
||||
#include "video_core/memory_manager.h"
|
||||
#include "video_core/rasterizer_interface.h"
|
||||
|
||||
namespace VideoCommon {
|
||||
|
||||
using MapInterval = std::shared_ptr<MapIntervalBase>;
|
||||
|
||||
template <typename TBuffer, typename TBufferType, typename StreamBuffer>
|
||||
class BufferCache {
|
||||
public:
|
||||
using BufferInfo = std::pair<const TBufferType*, u64>;
|
||||
|
||||
BufferInfo UploadMemory(GPUVAddr gpu_addr, std::size_t size, std::size_t alignment = 4,
|
||||
bool is_written = false) {
|
||||
std::lock_guard lock{mutex};
|
||||
|
||||
auto& memory_manager = system.GPU().MemoryManager();
|
||||
const auto host_ptr = memory_manager.GetPointer(gpu_addr);
|
||||
if (!host_ptr) {
|
||||
return {GetEmptyBuffer(size), 0};
|
||||
}
|
||||
const auto cache_addr = ToCacheAddr(host_ptr);
|
||||
|
||||
// Cache management is a big overhead, so only cache entries with a given size.
|
||||
// TODO: Figure out which size is the best for given games.
|
||||
constexpr std::size_t max_stream_size = 0x800;
|
||||
if (size < max_stream_size) {
|
||||
if (!is_written && !IsRegionWritten(cache_addr, cache_addr + size - 1)) {
|
||||
return StreamBufferUpload(host_ptr, size, alignment);
|
||||
}
|
||||
}
|
||||
|
||||
auto block = GetBlock(cache_addr, size);
|
||||
auto map = MapAddress(block, gpu_addr, cache_addr, size);
|
||||
if (is_written) {
|
||||
map->MarkAsModified(true, GetModifiedTicks());
|
||||
if (!map->IsWritten()) {
|
||||
map->MarkAsWritten(true);
|
||||
MarkRegionAsWritten(map->GetStart(), map->GetEnd() - 1);
|
||||
}
|
||||
} else {
|
||||
if (map->IsWritten()) {
|
||||
WriteBarrier();
|
||||
}
|
||||
}
|
||||
|
||||
const u64 offset = static_cast<u64>(block->GetOffset(cache_addr));
|
||||
|
||||
return {ToHandle(block), offset};
|
||||
}
|
||||
|
||||
/// Uploads from a host memory. Returns the OpenGL buffer where it's located and its offset.
|
||||
BufferInfo UploadHostMemory(const void* raw_pointer, std::size_t size,
|
||||
std::size_t alignment = 4) {
|
||||
std::lock_guard lock{mutex};
|
||||
return StreamBufferUpload(raw_pointer, size, alignment);
|
||||
}
|
||||
|
||||
void Map(std::size_t max_size) {
|
||||
std::lock_guard lock{mutex};
|
||||
|
||||
std::tie(buffer_ptr, buffer_offset_base, invalidated) = stream_buffer->Map(max_size, 4);
|
||||
buffer_offset = buffer_offset_base;
|
||||
}
|
||||
|
||||
/// Finishes the upload stream, returns true on bindings invalidation.
|
||||
bool Unmap() {
|
||||
std::lock_guard lock{mutex};
|
||||
|
||||
stream_buffer->Unmap(buffer_offset - buffer_offset_base);
|
||||
return std::exchange(invalidated, false);
|
||||
}
|
||||
|
||||
void TickFrame() {
|
||||
++epoch;
|
||||
while (!pending_destruction.empty()) {
|
||||
if (pending_destruction.front()->GetEpoch() + 1 > epoch) {
|
||||
break;
|
||||
}
|
||||
pending_destruction.pop_front();
|
||||
}
|
||||
}
|
||||
|
||||
/// Write any cached resources overlapping the specified region back to memory
|
||||
void FlushRegion(CacheAddr addr, std::size_t size) {
|
||||
std::lock_guard lock{mutex};
|
||||
|
||||
std::vector<MapInterval> objects = GetMapsInRange(addr, size);
|
||||
std::sort(objects.begin(), objects.end(), [](const MapInterval& a, const MapInterval& b) {
|
||||
return a->GetModificationTick() < b->GetModificationTick();
|
||||
});
|
||||
for (auto& object : objects) {
|
||||
if (object->IsModified() && object->IsRegistered()) {
|
||||
FlushMap(object);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Mark the specified region as being invalidated
|
||||
void InvalidateRegion(CacheAddr addr, u64 size) {
|
||||
std::lock_guard lock{mutex};
|
||||
|
||||
std::vector<MapInterval> objects = GetMapsInRange(addr, size);
|
||||
for (auto& object : objects) {
|
||||
if (object->IsRegistered()) {
|
||||
Unregister(object);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
virtual const TBufferType* GetEmptyBuffer(std::size_t size) = 0;
|
||||
|
||||
protected:
|
||||
explicit BufferCache(VideoCore::RasterizerInterface& rasterizer, Core::System& system,
|
||||
std::unique_ptr<StreamBuffer> stream_buffer)
|
||||
: rasterizer{rasterizer}, system{system}, stream_buffer{std::move(stream_buffer)},
|
||||
stream_buffer_handle{this->stream_buffer->GetHandle()} {}
|
||||
|
||||
~BufferCache() = default;
|
||||
|
||||
virtual const TBufferType* ToHandle(const TBuffer& storage) = 0;
|
||||
|
||||
virtual void WriteBarrier() = 0;
|
||||
|
||||
virtual TBuffer CreateBlock(CacheAddr cache_addr, std::size_t size) = 0;
|
||||
|
||||
virtual void UploadBlockData(const TBuffer& buffer, std::size_t offset, std::size_t size,
|
||||
const u8* data) = 0;
|
||||
|
||||
virtual void DownloadBlockData(const TBuffer& buffer, std::size_t offset, std::size_t size,
|
||||
u8* data) = 0;
|
||||
|
||||
virtual void CopyBlock(const TBuffer& src, const TBuffer& dst, std::size_t src_offset,
|
||||
std::size_t dst_offset, std::size_t size) = 0;
|
||||
|
||||
/// Register an object into the cache
|
||||
void Register(const MapInterval& new_map, bool inherit_written = false) {
|
||||
const CacheAddr cache_ptr = new_map->GetStart();
|
||||
const std::optional<VAddr> cpu_addr =
|
||||
system.GPU().MemoryManager().GpuToCpuAddress(new_map->GetGpuAddress());
|
||||
if (!cache_ptr || !cpu_addr) {
|
||||
LOG_CRITICAL(HW_GPU, "Failed to register buffer with unmapped gpu_address 0x{:016x}",
|
||||
new_map->GetGpuAddress());
|
||||
return;
|
||||
}
|
||||
const std::size_t size = new_map->GetEnd() - new_map->GetStart();
|
||||
new_map->SetCpuAddress(*cpu_addr);
|
||||
new_map->MarkAsRegistered(true);
|
||||
const IntervalType interval{new_map->GetStart(), new_map->GetEnd()};
|
||||
mapped_addresses.insert({interval, new_map});
|
||||
rasterizer.UpdatePagesCachedCount(*cpu_addr, size, 1);
|
||||
if (inherit_written) {
|
||||
MarkRegionAsWritten(new_map->GetStart(), new_map->GetEnd() - 1);
|
||||
new_map->MarkAsWritten(true);
|
||||
}
|
||||
}
|
||||
|
||||
/// Unregisters an object from the cache
|
||||
void Unregister(MapInterval& map) {
|
||||
const std::size_t size = map->GetEnd() - map->GetStart();
|
||||
rasterizer.UpdatePagesCachedCount(map->GetCpuAddress(), size, -1);
|
||||
map->MarkAsRegistered(false);
|
||||
if (map->IsWritten()) {
|
||||
UnmarkRegionAsWritten(map->GetStart(), map->GetEnd() - 1);
|
||||
}
|
||||
const IntervalType delete_interval{map->GetStart(), map->GetEnd()};
|
||||
mapped_addresses.erase(delete_interval);
|
||||
}
|
||||
|
||||
private:
|
||||
MapInterval CreateMap(const CacheAddr start, const CacheAddr end, const GPUVAddr gpu_addr) {
|
||||
return std::make_shared<MapIntervalBase>(start, end, gpu_addr);
|
||||
}
|
||||
|
||||
MapInterval MapAddress(const TBuffer& block, const GPUVAddr gpu_addr,
|
||||
const CacheAddr cache_addr, const std::size_t size) {
|
||||
|
||||
std::vector<MapInterval> overlaps = GetMapsInRange(cache_addr, size);
|
||||
if (overlaps.empty()) {
|
||||
const CacheAddr cache_addr_end = cache_addr + size;
|
||||
MapInterval new_map = CreateMap(cache_addr, cache_addr_end, gpu_addr);
|
||||
u8* host_ptr = FromCacheAddr(cache_addr);
|
||||
UploadBlockData(block, block->GetOffset(cache_addr), size, host_ptr);
|
||||
Register(new_map);
|
||||
return new_map;
|
||||
}
|
||||
|
||||
const CacheAddr cache_addr_end = cache_addr + size;
|
||||
if (overlaps.size() == 1) {
|
||||
MapInterval& current_map = overlaps[0];
|
||||
if (current_map->IsInside(cache_addr, cache_addr_end)) {
|
||||
return current_map;
|
||||
}
|
||||
}
|
||||
CacheAddr new_start = cache_addr;
|
||||
CacheAddr new_end = cache_addr_end;
|
||||
bool write_inheritance = false;
|
||||
bool modified_inheritance = false;
|
||||
// Calculate new buffer parameters
|
||||
for (auto& overlap : overlaps) {
|
||||
new_start = std::min(overlap->GetStart(), new_start);
|
||||
new_end = std::max(overlap->GetEnd(), new_end);
|
||||
write_inheritance |= overlap->IsWritten();
|
||||
modified_inheritance |= overlap->IsModified();
|
||||
}
|
||||
GPUVAddr new_gpu_addr = gpu_addr + new_start - cache_addr;
|
||||
for (auto& overlap : overlaps) {
|
||||
Unregister(overlap);
|
||||
}
|
||||
UpdateBlock(block, new_start, new_end, overlaps);
|
||||
MapInterval new_map = CreateMap(new_start, new_end, new_gpu_addr);
|
||||
if (modified_inheritance) {
|
||||
new_map->MarkAsModified(true, GetModifiedTicks());
|
||||
}
|
||||
Register(new_map, write_inheritance);
|
||||
return new_map;
|
||||
}
|
||||
|
||||
void UpdateBlock(const TBuffer& block, CacheAddr start, CacheAddr end,
|
||||
std::vector<MapInterval>& overlaps) {
|
||||
const IntervalType base_interval{start, end};
|
||||
IntervalSet interval_set{};
|
||||
interval_set.add(base_interval);
|
||||
for (auto& overlap : overlaps) {
|
||||
const IntervalType subtract{overlap->GetStart(), overlap->GetEnd()};
|
||||
interval_set.subtract(subtract);
|
||||
}
|
||||
for (auto& interval : interval_set) {
|
||||
std::size_t size = interval.upper() - interval.lower();
|
||||
if (size > 0) {
|
||||
u8* host_ptr = FromCacheAddr(interval.lower());
|
||||
UploadBlockData(block, block->GetOffset(interval.lower()), size, host_ptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<MapInterval> GetMapsInRange(CacheAddr addr, std::size_t size) {
|
||||
if (size == 0) {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<MapInterval> objects{};
|
||||
const IntervalType interval{addr, addr + size};
|
||||
for (auto& pair : boost::make_iterator_range(mapped_addresses.equal_range(interval))) {
|
||||
objects.push_back(pair.second);
|
||||
}
|
||||
|
||||
return objects;
|
||||
}
|
||||
|
||||
/// Returns a ticks counter used for tracking when cached objects were last modified
|
||||
u64 GetModifiedTicks() {
|
||||
return ++modified_ticks;
|
||||
}
|
||||
|
||||
void FlushMap(MapInterval map) {
|
||||
std::size_t size = map->GetEnd() - map->GetStart();
|
||||
TBuffer block = blocks[map->GetStart() >> block_page_bits];
|
||||
u8* host_ptr = FromCacheAddr(map->GetStart());
|
||||
DownloadBlockData(block, block->GetOffset(map->GetStart()), size, host_ptr);
|
||||
map->MarkAsModified(false, 0);
|
||||
}
|
||||
|
||||
BufferInfo StreamBufferUpload(const void* raw_pointer, std::size_t size,
|
||||
std::size_t alignment) {
|
||||
AlignBuffer(alignment);
|
||||
const std::size_t uploaded_offset = buffer_offset;
|
||||
std::memcpy(buffer_ptr, raw_pointer, size);
|
||||
|
||||
buffer_ptr += size;
|
||||
buffer_offset += size;
|
||||
return {&stream_buffer_handle, uploaded_offset};
|
||||
}
|
||||
|
||||
void AlignBuffer(std::size_t alignment) {
|
||||
// Align the offset, not the mapped pointer
|
||||
const std::size_t offset_aligned = Common::AlignUp(buffer_offset, alignment);
|
||||
buffer_ptr += offset_aligned - buffer_offset;
|
||||
buffer_offset = offset_aligned;
|
||||
}
|
||||
|
||||
TBuffer EnlargeBlock(TBuffer buffer) {
|
||||
const std::size_t old_size = buffer->GetSize();
|
||||
const std::size_t new_size = old_size + block_page_size;
|
||||
const CacheAddr cache_addr = buffer->GetCacheAddr();
|
||||
TBuffer new_buffer = CreateBlock(cache_addr, new_size);
|
||||
CopyBlock(buffer, new_buffer, 0, 0, old_size);
|
||||
buffer->SetEpoch(epoch);
|
||||
pending_destruction.push_back(buffer);
|
||||
const CacheAddr cache_addr_end = cache_addr + new_size - 1;
|
||||
u64 page_start = cache_addr >> block_page_bits;
|
||||
const u64 page_end = cache_addr_end >> block_page_bits;
|
||||
while (page_start <= page_end) {
|
||||
blocks[page_start] = new_buffer;
|
||||
++page_start;
|
||||
}
|
||||
return new_buffer;
|
||||
}
|
||||
|
||||
TBuffer MergeBlocks(TBuffer first, TBuffer second) {
|
||||
const std::size_t size_1 = first->GetSize();
|
||||
const std::size_t size_2 = second->GetSize();
|
||||
const CacheAddr first_addr = first->GetCacheAddr();
|
||||
const CacheAddr second_addr = second->GetCacheAddr();
|
||||
const CacheAddr new_addr = std::min(first_addr, second_addr);
|
||||
const std::size_t new_size = size_1 + size_2;
|
||||
TBuffer new_buffer = CreateBlock(new_addr, new_size);
|
||||
CopyBlock(first, new_buffer, 0, new_buffer->GetOffset(first_addr), size_1);
|
||||
CopyBlock(second, new_buffer, 0, new_buffer->GetOffset(second_addr), size_2);
|
||||
first->SetEpoch(epoch);
|
||||
second->SetEpoch(epoch);
|
||||
pending_destruction.push_back(first);
|
||||
pending_destruction.push_back(second);
|
||||
const CacheAddr cache_addr_end = new_addr + new_size - 1;
|
||||
u64 page_start = new_addr >> block_page_bits;
|
||||
const u64 page_end = cache_addr_end >> block_page_bits;
|
||||
while (page_start <= page_end) {
|
||||
blocks[page_start] = new_buffer;
|
||||
++page_start;
|
||||
}
|
||||
return new_buffer;
|
||||
}
|
||||
|
||||
TBuffer GetBlock(const CacheAddr cache_addr, const std::size_t size) {
|
||||
TBuffer found{};
|
||||
const CacheAddr cache_addr_end = cache_addr + size - 1;
|
||||
u64 page_start = cache_addr >> block_page_bits;
|
||||
const u64 page_end = cache_addr_end >> block_page_bits;
|
||||
while (page_start <= page_end) {
|
||||
auto it = blocks.find(page_start);
|
||||
if (it == blocks.end()) {
|
||||
if (found) {
|
||||
found = EnlargeBlock(found);
|
||||
} else {
|
||||
const CacheAddr start_addr = (page_start << block_page_bits);
|
||||
found = CreateBlock(start_addr, block_page_size);
|
||||
blocks[page_start] = found;
|
||||
}
|
||||
} else {
|
||||
if (found) {
|
||||
if (found == it->second) {
|
||||
++page_start;
|
||||
continue;
|
||||
}
|
||||
found = MergeBlocks(found, it->second);
|
||||
} else {
|
||||
found = it->second;
|
||||
}
|
||||
}
|
||||
++page_start;
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
void MarkRegionAsWritten(const CacheAddr start, const CacheAddr end) {
|
||||
u64 page_start = start >> write_page_bit;
|
||||
const u64 page_end = end >> write_page_bit;
|
||||
while (page_start <= page_end) {
|
||||
auto it = written_pages.find(page_start);
|
||||
if (it != written_pages.end()) {
|
||||
it->second = it->second + 1;
|
||||
} else {
|
||||
written_pages[page_start] = 1;
|
||||
}
|
||||
page_start++;
|
||||
}
|
||||
}
|
||||
|
||||
void UnmarkRegionAsWritten(const CacheAddr start, const CacheAddr end) {
|
||||
u64 page_start = start >> write_page_bit;
|
||||
const u64 page_end = end >> write_page_bit;
|
||||
while (page_start <= page_end) {
|
||||
auto it = written_pages.find(page_start);
|
||||
if (it != written_pages.end()) {
|
||||
if (it->second > 1) {
|
||||
it->second = it->second - 1;
|
||||
} else {
|
||||
written_pages.erase(it);
|
||||
}
|
||||
}
|
||||
page_start++;
|
||||
}
|
||||
}
|
||||
|
||||
bool IsRegionWritten(const CacheAddr start, const CacheAddr end) const {
|
||||
u64 page_start = start >> write_page_bit;
|
||||
const u64 page_end = end >> write_page_bit;
|
||||
while (page_start <= page_end) {
|
||||
if (written_pages.count(page_start) > 0) {
|
||||
return true;
|
||||
}
|
||||
page_start++;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
VideoCore::RasterizerInterface& rasterizer;
|
||||
Core::System& system;
|
||||
std::unique_ptr<StreamBuffer> stream_buffer;
|
||||
|
||||
TBufferType stream_buffer_handle{};
|
||||
|
||||
bool invalidated = false;
|
||||
|
||||
u8* buffer_ptr = nullptr;
|
||||
u64 buffer_offset = 0;
|
||||
u64 buffer_offset_base = 0;
|
||||
|
||||
using IntervalSet = boost::icl::interval_set<CacheAddr>;
|
||||
using IntervalCache = boost::icl::interval_map<CacheAddr, MapInterval>;
|
||||
using IntervalType = typename IntervalCache::interval_type;
|
||||
IntervalCache mapped_addresses{};
|
||||
|
||||
static constexpr u64 write_page_bit{11};
|
||||
std::unordered_map<u64, u32> written_pages{};
|
||||
|
||||
static constexpr u64 block_page_bits{21};
|
||||
static constexpr u64 block_page_size{1 << block_page_bits};
|
||||
std::unordered_map<u64, TBuffer> blocks{};
|
||||
|
||||
std::list<TBuffer> pending_destruction{};
|
||||
u64 epoch{};
|
||||
u64 modified_ticks{};
|
||||
|
||||
std::recursive_mutex mutex;
|
||||
};
|
||||
|
||||
} // namespace VideoCommon
|
||||
@@ -1,89 +0,0 @@
|
||||
// Copyright 2019 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "video_core/gpu.h"
|
||||
|
||||
namespace VideoCommon {
|
||||
|
||||
class MapIntervalBase {
|
||||
public:
|
||||
MapIntervalBase(const CacheAddr start, const CacheAddr end, const GPUVAddr gpu_addr)
|
||||
: start{start}, end{end}, gpu_addr{gpu_addr} {}
|
||||
|
||||
void SetCpuAddress(VAddr new_cpu_addr) {
|
||||
cpu_addr = new_cpu_addr;
|
||||
}
|
||||
|
||||
VAddr GetCpuAddress() const {
|
||||
return cpu_addr;
|
||||
}
|
||||
|
||||
GPUVAddr GetGpuAddress() const {
|
||||
return gpu_addr;
|
||||
}
|
||||
|
||||
bool IsInside(const CacheAddr other_start, const CacheAddr other_end) const {
|
||||
return (start <= other_start && other_end <= end);
|
||||
}
|
||||
|
||||
bool operator==(const MapIntervalBase& rhs) const {
|
||||
return std::tie(start, end) == std::tie(rhs.start, rhs.end);
|
||||
}
|
||||
|
||||
bool operator!=(const MapIntervalBase& rhs) const {
|
||||
return !operator==(rhs);
|
||||
}
|
||||
|
||||
void MarkAsRegistered(const bool registered) {
|
||||
is_registered = registered;
|
||||
}
|
||||
|
||||
bool IsRegistered() const {
|
||||
return is_registered;
|
||||
}
|
||||
|
||||
CacheAddr GetStart() const {
|
||||
return start;
|
||||
}
|
||||
|
||||
CacheAddr GetEnd() const {
|
||||
return end;
|
||||
}
|
||||
|
||||
void MarkAsModified(const bool is_modified_, const u64 tick) {
|
||||
is_modified = is_modified_;
|
||||
ticks = tick;
|
||||
}
|
||||
|
||||
bool IsModified() const {
|
||||
return is_modified;
|
||||
}
|
||||
|
||||
u64 GetModificationTick() const {
|
||||
return ticks;
|
||||
}
|
||||
|
||||
void MarkAsWritten(const bool is_written_) {
|
||||
is_written = is_written_;
|
||||
}
|
||||
|
||||
bool IsWritten() const {
|
||||
return is_written;
|
||||
}
|
||||
|
||||
private:
|
||||
CacheAddr start;
|
||||
CacheAddr end;
|
||||
GPUVAddr gpu_addr;
|
||||
VAddr cpu_addr{};
|
||||
bool is_written{};
|
||||
bool is_modified{};
|
||||
bool is_registered{};
|
||||
u64 ticks{};
|
||||
};
|
||||
|
||||
} // namespace VideoCommon
|
||||
@@ -10,7 +10,8 @@
|
||||
|
||||
namespace Tegra::Engines {
|
||||
|
||||
Fermi2D::Fermi2D(VideoCore::RasterizerInterface& rasterizer) : rasterizer{rasterizer} {}
|
||||
Fermi2D::Fermi2D(VideoCore::RasterizerInterface& rasterizer, MemoryManager& memory_manager)
|
||||
: rasterizer{rasterizer}, memory_manager{memory_manager} {}
|
||||
|
||||
void Fermi2D::CallMethod(const GPU::MethodCall& method_call) {
|
||||
ASSERT_MSG(method_call.method < Regs::NUM_REGS,
|
||||
|
||||
@@ -33,7 +33,7 @@ namespace Tegra::Engines {
|
||||
|
||||
class Fermi2D final {
|
||||
public:
|
||||
explicit Fermi2D(VideoCore::RasterizerInterface& rasterizer);
|
||||
explicit Fermi2D(VideoCore::RasterizerInterface& rasterizer, MemoryManager& memory_manager);
|
||||
~Fermi2D() = default;
|
||||
|
||||
/// Write the value to the register identified by method.
|
||||
@@ -145,6 +145,7 @@ public:
|
||||
|
||||
private:
|
||||
VideoCore::RasterizerInterface& rasterizer;
|
||||
MemoryManager& memory_manager;
|
||||
|
||||
/// Performs the copy from the source surface to the destination surface as configured in the
|
||||
/// registers.
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
namespace Tegra::Engines {
|
||||
|
||||
KeplerMemory::KeplerMemory(Core::System& system, MemoryManager& memory_manager)
|
||||
: system{system}, upload_state{memory_manager, regs.upload} {}
|
||||
: system{system}, memory_manager{memory_manager}, upload_state{memory_manager, regs.upload} {}
|
||||
|
||||
KeplerMemory::~KeplerMemory() = default;
|
||||
|
||||
|
||||
@@ -65,6 +65,7 @@ public:
|
||||
|
||||
private:
|
||||
Core::System& system;
|
||||
MemoryManager& memory_manager;
|
||||
Upload::State upload_state;
|
||||
};
|
||||
|
||||
|
||||
@@ -524,7 +524,7 @@ void Maxwell3D::ProcessQueryCondition() {
|
||||
void Maxwell3D::ProcessSyncPoint() {
|
||||
const u32 sync_point = regs.sync_info.sync_point.Value();
|
||||
const u32 increment = regs.sync_info.increment.Value();
|
||||
[[maybe_unused]] const u32 cache_flush = regs.sync_info.unknown.Value();
|
||||
const u32 cache_flush = regs.sync_info.unknown.Value();
|
||||
if (increment) {
|
||||
system.GPU().IncrementSyncPoint(sync_point);
|
||||
}
|
||||
@@ -626,10 +626,10 @@ Texture::TICEntry Maxwell3D::GetTICEntry(u32 tic_index) const {
|
||||
Texture::TICEntry tic_entry;
|
||||
memory_manager.ReadBlockUnsafe(tic_address_gpu, &tic_entry, sizeof(Texture::TICEntry));
|
||||
|
||||
[[maybe_unused]] const auto r_type{tic_entry.r_type.Value()};
|
||||
[[maybe_unused]] const auto g_type{tic_entry.g_type.Value()};
|
||||
[[maybe_unused]] const auto b_type{tic_entry.b_type.Value()};
|
||||
[[maybe_unused]] const auto a_type{tic_entry.a_type.Value()};
|
||||
const auto r_type{tic_entry.r_type.Value()};
|
||||
const auto g_type{tic_entry.g_type.Value()};
|
||||
const auto b_type{tic_entry.b_type.Value()};
|
||||
const auto a_type{tic_entry.a_type.Value()};
|
||||
|
||||
// TODO(Subv): Different data types for separate components are not supported
|
||||
DEBUG_ASSERT(r_type == g_type && r_type == b_type && r_type == a_type);
|
||||
|
||||
@@ -5,17 +5,18 @@
|
||||
#include "common/assert.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/core.h"
|
||||
#include "core/settings.h"
|
||||
#include "video_core/engines/maxwell_3d.h"
|
||||
#include "video_core/engines/maxwell_dma.h"
|
||||
#include "video_core/memory_manager.h"
|
||||
#include "video_core/rasterizer_interface.h"
|
||||
#include "video_core/renderer_base.h"
|
||||
#include "video_core/textures/decoders.h"
|
||||
|
||||
namespace Tegra::Engines {
|
||||
|
||||
MaxwellDMA::MaxwellDMA(Core::System& system, MemoryManager& memory_manager)
|
||||
: system{system}, memory_manager{memory_manager} {}
|
||||
MaxwellDMA::MaxwellDMA(Core::System& system, VideoCore::RasterizerInterface& rasterizer,
|
||||
MemoryManager& memory_manager)
|
||||
: system{system}, rasterizer{rasterizer}, memory_manager{memory_manager} {}
|
||||
|
||||
void MaxwellDMA::CallMethod(const GPU::MethodCall& method_call) {
|
||||
ASSERT_MSG(method_call.method < Regs::NUM_REGS,
|
||||
@@ -83,17 +84,13 @@ void MaxwellDMA::HandleCopy() {
|
||||
ASSERT(regs.exec.enable_2d == 1);
|
||||
|
||||
if (regs.exec.is_dst_linear && !regs.exec.is_src_linear) {
|
||||
ASSERT(regs.src_params.BlockDepth() == 0);
|
||||
ASSERT(regs.src_params.size_z == 1);
|
||||
// If the input is tiled and the output is linear, deswizzle the input and copy it over.
|
||||
const u32 bytes_per_pixel = regs.dst_pitch / regs.x_count;
|
||||
const u32 src_bytes_per_pixel = regs.src_pitch / regs.src_params.size_x;
|
||||
const std::size_t src_size = Texture::CalculateSize(
|
||||
true, bytes_per_pixel, regs.src_params.size_x, regs.src_params.size_y,
|
||||
true, src_bytes_per_pixel, regs.src_params.size_x, regs.src_params.size_y,
|
||||
regs.src_params.size_z, regs.src_params.BlockHeight(), regs.src_params.BlockDepth());
|
||||
|
||||
const std::size_t src_layer_size = Texture::CalculateSize(
|
||||
true, bytes_per_pixel, regs.src_params.size_x, regs.src_params.size_y, 1,
|
||||
regs.src_params.BlockHeight(), regs.src_params.BlockDepth());
|
||||
|
||||
const std::size_t dst_size = regs.dst_pitch * regs.y_count;
|
||||
|
||||
if (read_buffer.size() < src_size) {
|
||||
@@ -107,23 +104,23 @@ void MaxwellDMA::HandleCopy() {
|
||||
memory_manager.ReadBlock(source, read_buffer.data(), src_size);
|
||||
memory_manager.ReadBlock(dest, write_buffer.data(), dst_size);
|
||||
|
||||
Texture::UnswizzleSubrect(
|
||||
regs.x_count, regs.y_count, regs.dst_pitch, regs.src_params.size_x, bytes_per_pixel,
|
||||
read_buffer.data() + src_layer_size * regs.src_params.pos_z, write_buffer.data(),
|
||||
regs.src_params.BlockHeight(), regs.src_params.pos_x, regs.src_params.pos_y);
|
||||
Texture::UnswizzleSubrect(regs.x_count, regs.y_count, regs.dst_pitch,
|
||||
regs.src_params.size_x, src_bytes_per_pixel, read_buffer.data(),
|
||||
write_buffer.data(), regs.src_params.BlockHeight(),
|
||||
regs.src_params.pos_x, regs.src_params.pos_y);
|
||||
|
||||
memory_manager.WriteBlock(dest, write_buffer.data(), dst_size);
|
||||
} else {
|
||||
ASSERT(regs.dst_params.BlockDepth() == 0);
|
||||
|
||||
const u32 bytes_per_pixel = regs.src_pitch / regs.x_count;
|
||||
const u32 src_bytes_per_pixel = regs.src_pitch / regs.x_count;
|
||||
|
||||
const std::size_t dst_size = Texture::CalculateSize(
|
||||
true, bytes_per_pixel, regs.dst_params.size_x, regs.dst_params.size_y,
|
||||
true, src_bytes_per_pixel, regs.dst_params.size_x, regs.dst_params.size_y,
|
||||
regs.dst_params.size_z, regs.dst_params.BlockHeight(), regs.dst_params.BlockDepth());
|
||||
|
||||
const std::size_t dst_layer_size = Texture::CalculateSize(
|
||||
true, bytes_per_pixel, regs.dst_params.size_x, regs.dst_params.size_y, 1,
|
||||
true, src_bytes_per_pixel, regs.dst_params.size_x, regs.dst_params.size_y, 1,
|
||||
regs.dst_params.BlockHeight(), regs.dst_params.BlockDepth());
|
||||
|
||||
const std::size_t src_size = regs.src_pitch * regs.y_count;
|
||||
@@ -136,19 +133,14 @@ void MaxwellDMA::HandleCopy() {
|
||||
write_buffer.resize(dst_size);
|
||||
}
|
||||
|
||||
if (Settings::values.use_accurate_gpu_emulation) {
|
||||
memory_manager.ReadBlock(source, read_buffer.data(), src_size);
|
||||
memory_manager.ReadBlock(dest, write_buffer.data(), dst_size);
|
||||
} else {
|
||||
memory_manager.ReadBlockUnsafe(source, read_buffer.data(), src_size);
|
||||
memory_manager.ReadBlockUnsafe(dest, write_buffer.data(), dst_size);
|
||||
}
|
||||
memory_manager.ReadBlock(source, read_buffer.data(), src_size);
|
||||
memory_manager.ReadBlock(dest, write_buffer.data(), dst_size);
|
||||
|
||||
// If the input is linear and the output is tiled, swizzle the input and copy it over.
|
||||
Texture::SwizzleSubrect(
|
||||
regs.x_count, regs.y_count, regs.src_pitch, regs.dst_params.size_x, bytes_per_pixel,
|
||||
write_buffer.data() + dst_layer_size * regs.dst_params.pos_z, read_buffer.data(),
|
||||
regs.dst_params.BlockHeight(), regs.dst_params.pos_x, regs.dst_params.pos_y);
|
||||
Texture::SwizzleSubrect(regs.x_count, regs.y_count, regs.src_pitch, regs.dst_params.size_x,
|
||||
src_bytes_per_pixel,
|
||||
write_buffer.data() + dst_layer_size * regs.dst_params.pos_z,
|
||||
read_buffer.data(), regs.dst_params.BlockHeight());
|
||||
|
||||
memory_manager.WriteBlock(dest, write_buffer.data(), dst_size);
|
||||
}
|
||||
|
||||
@@ -20,6 +20,10 @@ namespace Tegra {
|
||||
class MemoryManager;
|
||||
}
|
||||
|
||||
namespace VideoCore {
|
||||
class RasterizerInterface;
|
||||
}
|
||||
|
||||
namespace Tegra::Engines {
|
||||
|
||||
/**
|
||||
@@ -29,7 +33,8 @@ namespace Tegra::Engines {
|
||||
|
||||
class MaxwellDMA final {
|
||||
public:
|
||||
explicit MaxwellDMA(Core::System& system, MemoryManager& memory_manager);
|
||||
explicit MaxwellDMA(Core::System& system, VideoCore::RasterizerInterface& rasterizer,
|
||||
MemoryManager& memory_manager);
|
||||
~MaxwellDMA() = default;
|
||||
|
||||
/// Write the value to the register identified by method.
|
||||
@@ -175,6 +180,8 @@ public:
|
||||
private:
|
||||
Core::System& system;
|
||||
|
||||
VideoCore::RasterizerInterface& rasterizer;
|
||||
|
||||
MemoryManager& memory_manager;
|
||||
|
||||
std::vector<u8> read_buffer;
|
||||
|
||||
@@ -538,12 +538,6 @@ enum class PhysicalAttributeDirection : u64 {
|
||||
Output = 1,
|
||||
};
|
||||
|
||||
enum class VoteOperation : u64 {
|
||||
All = 0, // allThreadsNV
|
||||
Any = 1, // anyThreadNV
|
||||
Eq = 2, // allThreadsEqualNV
|
||||
};
|
||||
|
||||
union Instruction {
|
||||
Instruction& operator=(const Instruction& instr) {
|
||||
value = instr.value;
|
||||
@@ -570,13 +564,6 @@ union Instruction {
|
||||
BitField<13, 1, u64> trigger;
|
||||
} nop;
|
||||
|
||||
union {
|
||||
BitField<48, 2, VoteOperation> operation;
|
||||
BitField<45, 3, u64> dest_pred;
|
||||
BitField<39, 3, u64> value;
|
||||
BitField<42, 1, u64> negate_value;
|
||||
} vote;
|
||||
|
||||
union {
|
||||
BitField<8, 8, Register> gpr;
|
||||
BitField<20, 24, s64> offset;
|
||||
@@ -1500,7 +1487,6 @@ public:
|
||||
SYNC,
|
||||
BRK,
|
||||
DEPBAR,
|
||||
VOTE,
|
||||
BFE_C,
|
||||
BFE_R,
|
||||
BFE_IMM,
|
||||
@@ -1663,7 +1649,6 @@ public:
|
||||
Hfma2,
|
||||
Flow,
|
||||
Synch,
|
||||
Warp,
|
||||
Memory,
|
||||
Texture,
|
||||
Image,
|
||||
@@ -1790,7 +1775,6 @@ private:
|
||||
INST("111000110100---", Id::BRK, Type::Flow, "BRK"),
|
||||
INST("111000110000----", Id::EXIT, Type::Flow, "EXIT"),
|
||||
INST("1111000011110---", Id::DEPBAR, Type::Synch, "DEPBAR"),
|
||||
INST("0101000011011---", Id::VOTE, Type::Warp, "VOTE"),
|
||||
INST("1110111111011---", Id::LD_A, Type::Memory, "LD_A"),
|
||||
INST("1110111101001---", Id::LD_S, Type::Memory, "LD_S"),
|
||||
INST("1110111101000---", Id::LD_L, Type::Memory, "LD_L"),
|
||||
|
||||
@@ -35,9 +35,9 @@ GPU::GPU(Core::System& system, VideoCore::RendererBase& renderer, bool is_async)
|
||||
memory_manager = std::make_unique<Tegra::MemoryManager>(system, rasterizer);
|
||||
dma_pusher = std::make_unique<Tegra::DmaPusher>(*this);
|
||||
maxwell_3d = std::make_unique<Engines::Maxwell3D>(system, rasterizer, *memory_manager);
|
||||
fermi_2d = std::make_unique<Engines::Fermi2D>(rasterizer);
|
||||
fermi_2d = std::make_unique<Engines::Fermi2D>(rasterizer, *memory_manager);
|
||||
kepler_compute = std::make_unique<Engines::KeplerCompute>(system, rasterizer, *memory_manager);
|
||||
maxwell_dma = std::make_unique<Engines::MaxwellDMA>(system, *memory_manager);
|
||||
maxwell_dma = std::make_unique<Engines::MaxwellDMA>(system, rasterizer, *memory_manager);
|
||||
kepler_memory = std::make_unique<Engines::KeplerMemory>(system, *memory_manager);
|
||||
}
|
||||
|
||||
|
||||
@@ -19,10 +19,6 @@ inline CacheAddr ToCacheAddr(const void* host_ptr) {
|
||||
return reinterpret_cast<CacheAddr>(host_ptr);
|
||||
}
|
||||
|
||||
inline u8* FromCacheAddr(CacheAddr cache_addr) {
|
||||
return reinterpret_cast<u8*>(cache_addr);
|
||||
}
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
}
|
||||
@@ -285,8 +281,8 @@ private:
|
||||
|
||||
protected:
|
||||
std::unique_ptr<Tegra::DmaPusher> dma_pusher;
|
||||
Core::System& system;
|
||||
VideoCore::RendererBase& renderer;
|
||||
Core::System& system;
|
||||
|
||||
private:
|
||||
std::unique_ptr<Tegra::MemoryManager> memory_manager;
|
||||
|
||||
@@ -50,7 +50,7 @@ public:
|
||||
/// and invalidated
|
||||
virtual void FlushAndInvalidateRegion(CacheAddr addr, u64 size) = 0;
|
||||
|
||||
/// Notify the rasterizer to send all written commands to the host GPU.
|
||||
// Notify the rasterizer to send all written commands to the host GPU.
|
||||
virtual void FlushCommands() = 0;
|
||||
|
||||
/// Notify rasterizer that a frame is about to finish
|
||||
|
||||
@@ -7,41 +7,28 @@
|
||||
#include <glad/glad.h>
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/microprofile.h"
|
||||
#include "video_core/rasterizer_interface.h"
|
||||
#include "video_core/renderer_opengl/gl_buffer_cache.h"
|
||||
#include "video_core/renderer_opengl/gl_rasterizer.h"
|
||||
#include "video_core/renderer_opengl/gl_resource_manager.h"
|
||||
|
||||
namespace OpenGL {
|
||||
|
||||
MICROPROFILE_DEFINE(OpenGL_Buffer_Download, "OpenGL", "Buffer Download", MP_RGB(192, 192, 128));
|
||||
|
||||
CachedBufferBlock::CachedBufferBlock(CacheAddr cache_addr, const std::size_t size)
|
||||
: VideoCommon::BufferBlock{cache_addr, size} {
|
||||
gl_buffer.Create();
|
||||
glNamedBufferData(gl_buffer.handle, static_cast<GLsizeiptr>(size), nullptr, GL_DYNAMIC_DRAW);
|
||||
}
|
||||
|
||||
CachedBufferBlock::~CachedBufferBlock() = default;
|
||||
|
||||
OGLBufferCache::OGLBufferCache(RasterizerOpenGL& rasterizer, Core::System& system,
|
||||
std::size_t stream_size)
|
||||
: VideoCommon::BufferCache<Buffer, GLuint, OGLStreamBuffer>{
|
||||
: VideoCommon::BufferCache<OGLBuffer, GLuint, OGLStreamBuffer>{
|
||||
rasterizer, system, std::make_unique<OGLStreamBuffer>(stream_size, true)} {}
|
||||
|
||||
OGLBufferCache::~OGLBufferCache() = default;
|
||||
|
||||
Buffer OGLBufferCache::CreateBlock(CacheAddr cache_addr, std::size_t size) {
|
||||
return std::make_shared<CachedBufferBlock>(cache_addr, size);
|
||||
OGLBuffer OGLBufferCache::CreateBuffer(std::size_t size) {
|
||||
OGLBuffer buffer;
|
||||
buffer.Create();
|
||||
glNamedBufferData(buffer.handle, static_cast<GLsizeiptr>(size), nullptr, GL_DYNAMIC_DRAW);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
void OGLBufferCache::WriteBarrier() {
|
||||
glMemoryBarrier(GL_ALL_BARRIER_BITS);
|
||||
}
|
||||
|
||||
const GLuint* OGLBufferCache::ToHandle(const Buffer& buffer) {
|
||||
return buffer->GetHandle();
|
||||
const GLuint* OGLBufferCache::ToHandle(const OGLBuffer& buffer) {
|
||||
return &buffer.handle;
|
||||
}
|
||||
|
||||
const GLuint* OGLBufferCache::GetEmptyBuffer(std::size_t) {
|
||||
@@ -49,24 +36,23 @@ const GLuint* OGLBufferCache::GetEmptyBuffer(std::size_t) {
|
||||
return &null_buffer;
|
||||
}
|
||||
|
||||
void OGLBufferCache::UploadBlockData(const Buffer& buffer, std::size_t offset, std::size_t size,
|
||||
const u8* data) {
|
||||
glNamedBufferSubData(*buffer->GetHandle(), static_cast<GLintptr>(offset),
|
||||
void OGLBufferCache::UploadBufferData(const OGLBuffer& buffer, std::size_t offset, std::size_t size,
|
||||
const u8* data) {
|
||||
glNamedBufferSubData(buffer.handle, static_cast<GLintptr>(offset),
|
||||
static_cast<GLsizeiptr>(size), data);
|
||||
}
|
||||
|
||||
void OGLBufferCache::DownloadBlockData(const Buffer& buffer, std::size_t offset, std::size_t size,
|
||||
u8* data) {
|
||||
MICROPROFILE_SCOPE(OpenGL_Buffer_Download);
|
||||
glGetNamedBufferSubData(*buffer->GetHandle(), static_cast<GLintptr>(offset),
|
||||
void OGLBufferCache::DownloadBufferData(const OGLBuffer& buffer, std::size_t offset,
|
||||
std::size_t size, u8* data) {
|
||||
glGetNamedBufferSubData(buffer.handle, static_cast<GLintptr>(offset),
|
||||
static_cast<GLsizeiptr>(size), data);
|
||||
}
|
||||
|
||||
void OGLBufferCache::CopyBlock(const Buffer& src, const Buffer& dst, std::size_t src_offset,
|
||||
std::size_t dst_offset, std::size_t size) {
|
||||
glCopyNamedBufferSubData(*src->GetHandle(), *dst->GetHandle(),
|
||||
static_cast<GLintptr>(src_offset), static_cast<GLintptr>(dst_offset),
|
||||
static_cast<GLsizeiptr>(size));
|
||||
void OGLBufferCache::CopyBufferData(const OGLBuffer& src, const OGLBuffer& dst,
|
||||
std::size_t src_offset, std::size_t dst_offset,
|
||||
std::size_t size) {
|
||||
glCopyNamedBufferSubData(src.handle, dst.handle, static_cast<GLintptr>(src_offset),
|
||||
static_cast<GLintptr>(dst_offset), static_cast<GLsizeiptr>(size));
|
||||
}
|
||||
|
||||
} // namespace OpenGL
|
||||
@@ -7,7 +7,7 @@
|
||||
#include <memory>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "video_core/buffer_cache/buffer_cache.h"
|
||||
#include "video_core/buffer_cache.h"
|
||||
#include "video_core/rasterizer_cache.h"
|
||||
#include "video_core/renderer_opengl/gl_resource_manager.h"
|
||||
#include "video_core/renderer_opengl/gl_stream_buffer.h"
|
||||
@@ -21,24 +21,7 @@ namespace OpenGL {
|
||||
class OGLStreamBuffer;
|
||||
class RasterizerOpenGL;
|
||||
|
||||
class CachedBufferBlock;
|
||||
|
||||
using Buffer = std::shared_ptr<CachedBufferBlock>;
|
||||
|
||||
class CachedBufferBlock : public VideoCommon::BufferBlock {
|
||||
public:
|
||||
explicit CachedBufferBlock(CacheAddr cache_addr, const std::size_t size);
|
||||
~CachedBufferBlock();
|
||||
|
||||
const GLuint* GetHandle() const {
|
||||
return &gl_buffer.handle;
|
||||
}
|
||||
|
||||
private:
|
||||
OGLBuffer gl_buffer{};
|
||||
};
|
||||
|
||||
class OGLBufferCache final : public VideoCommon::BufferCache<Buffer, GLuint, OGLStreamBuffer> {
|
||||
class OGLBufferCache final : public VideoCommon::BufferCache<OGLBuffer, GLuint, OGLStreamBuffer> {
|
||||
public:
|
||||
explicit OGLBufferCache(RasterizerOpenGL& rasterizer, Core::System& system,
|
||||
std::size_t stream_size);
|
||||
@@ -47,20 +30,18 @@ public:
|
||||
const GLuint* GetEmptyBuffer(std::size_t) override;
|
||||
|
||||
protected:
|
||||
Buffer CreateBlock(CacheAddr cache_addr, std::size_t size) override;
|
||||
OGLBuffer CreateBuffer(std::size_t size) override;
|
||||
|
||||
void WriteBarrier() override;
|
||||
const GLuint* ToHandle(const OGLBuffer& buffer) override;
|
||||
|
||||
const GLuint* ToHandle(const Buffer& buffer) override;
|
||||
void UploadBufferData(const OGLBuffer& buffer, std::size_t offset, std::size_t size,
|
||||
const u8* data) override;
|
||||
|
||||
void UploadBlockData(const Buffer& buffer, std::size_t offset, std::size_t size,
|
||||
const u8* data) override;
|
||||
void DownloadBufferData(const OGLBuffer& buffer, std::size_t offset, std::size_t size,
|
||||
u8* data) override;
|
||||
|
||||
void DownloadBlockData(const Buffer& buffer, std::size_t offset, std::size_t size,
|
||||
u8* data) override;
|
||||
|
||||
void CopyBlock(const Buffer& src, const Buffer& dst, std::size_t src_offset,
|
||||
std::size_t dst_offset, std::size_t size) override;
|
||||
void CopyBufferData(const OGLBuffer& src, const OGLBuffer& dst, std::size_t src_offset,
|
||||
std::size_t dst_offset, std::size_t size) override;
|
||||
};
|
||||
|
||||
} // namespace OpenGL
|
||||
@@ -27,8 +27,6 @@ Device::Device() {
|
||||
shader_storage_alignment = GetInteger<std::size_t>(GL_SHADER_STORAGE_BUFFER_OFFSET_ALIGNMENT);
|
||||
max_vertex_attributes = GetInteger<u32>(GL_MAX_VERTEX_ATTRIBS);
|
||||
max_varyings = GetInteger<u32>(GL_MAX_VARYING_VECTORS);
|
||||
has_warp_intrinsics = GLAD_GL_NV_gpu_shader5 && GLAD_GL_NV_shader_thread_group &&
|
||||
GLAD_GL_NV_shader_thread_shuffle;
|
||||
has_vertex_viewport_layer = GLAD_GL_ARB_shader_viewport_layer_array;
|
||||
has_variable_aoffi = TestVariableAoffi();
|
||||
has_component_indexing_bug = TestComponentIndexingBug();
|
||||
@@ -38,7 +36,6 @@ Device::Device(std::nullptr_t) {
|
||||
uniform_buffer_alignment = 0;
|
||||
max_vertex_attributes = 16;
|
||||
max_varyings = 15;
|
||||
has_warp_intrinsics = true;
|
||||
has_vertex_viewport_layer = true;
|
||||
has_variable_aoffi = true;
|
||||
has_component_indexing_bug = false;
|
||||
|
||||
@@ -30,10 +30,6 @@ public:
|
||||
return max_varyings;
|
||||
}
|
||||
|
||||
bool HasWarpIntrinsics() const {
|
||||
return has_warp_intrinsics;
|
||||
}
|
||||
|
||||
bool HasVertexViewportLayer() const {
|
||||
return has_vertex_viewport_layer;
|
||||
}
|
||||
@@ -54,7 +50,6 @@ private:
|
||||
std::size_t shader_storage_alignment{};
|
||||
u32 max_vertex_attributes{};
|
||||
u32 max_varyings{};
|
||||
bool has_warp_intrinsics{};
|
||||
bool has_vertex_viewport_layer{};
|
||||
bool has_variable_aoffi{};
|
||||
bool has_component_indexing_bug{};
|
||||
|
||||
@@ -708,6 +708,8 @@ void RasterizerOpenGL::DrawArrays() {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& regs = gpu.regs;
|
||||
|
||||
SyncColorMask();
|
||||
SyncFragmentColorClampState();
|
||||
SyncMultiSampleState();
|
||||
@@ -978,7 +980,7 @@ void RasterizerOpenGL::SetupGlobalMemory(const GLShader::GlobalMemoryEntry& entr
|
||||
GPUVAddr gpu_addr, std::size_t size) {
|
||||
const auto alignment{device.GetShaderStorageBufferAlignment()};
|
||||
const auto [ssbo, buffer_offset] =
|
||||
buffer_cache.UploadMemory(gpu_addr, size, alignment, entry.IsWritten());
|
||||
buffer_cache.UploadMemory(gpu_addr, size, alignment, true, entry.IsWritten());
|
||||
bind_ssbo_pushbuffer.Push(ssbo, buffer_offset, static_cast<GLsizeiptr>(size));
|
||||
}
|
||||
|
||||
|
||||
@@ -212,9 +212,7 @@ CachedProgram SpecializeShader(const std::string& code, const GLShader::ShaderEn
|
||||
const auto texture_buffer_usage{variant.texture_buffer_usage};
|
||||
|
||||
std::string source = "#version 430 core\n"
|
||||
"#extension GL_ARB_separate_shader_objects : enable\n"
|
||||
"#extension GL_NV_gpu_shader5 : enable\n"
|
||||
"#extension GL_NV_shader_thread_group : enable\n";
|
||||
"#extension GL_ARB_separate_shader_objects : enable\n";
|
||||
if (entries.shader_viewport_layer_array) {
|
||||
source += "#extension GL_ARB_shader_viewport_layer_array : enable\n";
|
||||
}
|
||||
@@ -249,24 +247,20 @@ CachedProgram SpecializeShader(const std::string& code, const GLShader::ShaderEn
|
||||
if (!texture_buffer_usage.test(i)) {
|
||||
continue;
|
||||
}
|
||||
source += fmt::format("#define SAMPLER_{}_IS_BUFFER\n", i);
|
||||
}
|
||||
if (texture_buffer_usage.any()) {
|
||||
source += '\n';
|
||||
source += fmt::format("#define SAMPLER_{}_IS_BUFFER", i);
|
||||
}
|
||||
|
||||
if (program_type == ProgramType::Geometry) {
|
||||
const auto [glsl_topology, debug_name, max_vertices] =
|
||||
GetPrimitiveDescription(primitive_mode);
|
||||
|
||||
source += "layout (" + std::string(glsl_topology) + ") in;\n\n";
|
||||
source += "layout (" + std::string(glsl_topology) + ") in;\n";
|
||||
source += "#define MAX_VERTEX_INPUT " + std::to_string(max_vertices) + '\n';
|
||||
}
|
||||
if (program_type == ProgramType::Compute) {
|
||||
source += "layout (local_size_variable) in;\n";
|
||||
}
|
||||
|
||||
source += '\n';
|
||||
source += code;
|
||||
|
||||
OGLShader shader;
|
||||
@@ -295,7 +289,7 @@ std::set<GLenum> GetSupportedFormats() {
|
||||
|
||||
CachedShader::CachedShader(const ShaderParameters& params, ProgramType program_type,
|
||||
GLShader::ProgramResult result)
|
||||
: RasterizerCacheObject{params.host_ptr}, cpu_addr{params.cpu_addr},
|
||||
: RasterizerCacheObject{params.host_ptr}, host_ptr{params.host_ptr}, cpu_addr{params.cpu_addr},
|
||||
unique_identifier{params.unique_identifier}, program_type{program_type},
|
||||
disk_cache{params.disk_cache}, precompiled_programs{params.precompiled_programs},
|
||||
entries{result.second}, code{std::move(result.first)}, shader_length{entries.shader_length} {}
|
||||
|
||||
@@ -106,6 +106,7 @@ private:
|
||||
|
||||
ShaderDiskCacheUsage GetUsage(const ProgramVariant& variant) const;
|
||||
|
||||
u8* host_ptr{};
|
||||
VAddr cpu_addr{};
|
||||
u64 unique_identifier{};
|
||||
ProgramType program_type{};
|
||||
|
||||
@@ -565,7 +565,7 @@ private:
|
||||
case Tegra::Shader::ImageType::Texture1D:
|
||||
return "image1D";
|
||||
case Tegra::Shader::ImageType::TextureBuffer:
|
||||
return "imageBuffer";
|
||||
return "bufferImage";
|
||||
case Tegra::Shader::ImageType::Texture1DArray:
|
||||
return "image1DArray";
|
||||
case Tegra::Shader::ImageType::Texture2D:
|
||||
@@ -1735,48 +1735,6 @@ private:
|
||||
return "utof(gl_WorkGroupID"s + GetSwizzle(element) + ')';
|
||||
}
|
||||
|
||||
std::string BallotThread(Operation operation) {
|
||||
const std::string value = VisitOperand(operation, 0, Type::Bool);
|
||||
if (!device.HasWarpIntrinsics()) {
|
||||
LOG_ERROR(Render_OpenGL,
|
||||
"Nvidia warp intrinsics are not available and its required by a shader");
|
||||
// Stub on non-Nvidia devices by simulating all threads voting the same as the active
|
||||
// one.
|
||||
return fmt::format("utof({} ? 0xFFFFFFFFU : 0U)", value);
|
||||
}
|
||||
return fmt::format("utof(ballotThreadNV({}))", value);
|
||||
}
|
||||
|
||||
std::string Vote(Operation operation, const char* func) {
|
||||
const std::string value = VisitOperand(operation, 0, Type::Bool);
|
||||
if (!device.HasWarpIntrinsics()) {
|
||||
LOG_ERROR(Render_OpenGL,
|
||||
"Nvidia vote intrinsics are not available and its required by a shader");
|
||||
// Stub with a warp size of one.
|
||||
return value;
|
||||
}
|
||||
return fmt::format("{}({})", func, value);
|
||||
}
|
||||
|
||||
std::string VoteAll(Operation operation) {
|
||||
return Vote(operation, "allThreadsNV");
|
||||
}
|
||||
|
||||
std::string VoteAny(Operation operation) {
|
||||
return Vote(operation, "anyThreadNV");
|
||||
}
|
||||
|
||||
std::string VoteEqual(Operation operation) {
|
||||
if (!device.HasWarpIntrinsics()) {
|
||||
LOG_ERROR(Render_OpenGL,
|
||||
"Nvidia vote intrinsics are not available and its required by a shader");
|
||||
// We must return true here since a stub for a theoretical warp size of 1 will always
|
||||
// return an equal result for all its votes.
|
||||
return "true";
|
||||
}
|
||||
return Vote(operation, "allThreadsEqualNV");
|
||||
}
|
||||
|
||||
static constexpr std::array operation_decompilers = {
|
||||
&GLSLDecompiler::Assign,
|
||||
|
||||
@@ -1927,11 +1885,6 @@ private:
|
||||
&GLSLDecompiler::WorkGroupId<0>,
|
||||
&GLSLDecompiler::WorkGroupId<1>,
|
||||
&GLSLDecompiler::WorkGroupId<2>,
|
||||
|
||||
&GLSLDecompiler::BallotThread,
|
||||
&GLSLDecompiler::VoteAll,
|
||||
&GLSLDecompiler::VoteAny,
|
||||
&GLSLDecompiler::VoteEqual,
|
||||
};
|
||||
static_assert(operation_decompilers.size() == static_cast<std::size_t>(OperationCode::Amount));
|
||||
|
||||
|
||||
@@ -184,9 +184,6 @@ GLint GetSwizzleSource(SwizzleSource source) {
|
||||
}
|
||||
|
||||
void ApplyTextureDefaults(const SurfaceParams& params, GLuint texture) {
|
||||
if (params.IsBuffer()) {
|
||||
return;
|
||||
}
|
||||
glTextureParameteri(texture, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTextureParameteri(texture, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glTextureParameteri(texture, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
@@ -211,7 +208,6 @@ OGLTexture CreateTexture(const SurfaceParams& params, GLenum target, GLenum inte
|
||||
glNamedBufferStorage(texture_buffer.handle, params.width * params.GetBytesPerPixel(),
|
||||
nullptr, GL_DYNAMIC_STORAGE_BIT);
|
||||
glTextureBuffer(texture.handle, internal_format, texture_buffer.handle);
|
||||
break;
|
||||
case SurfaceTarget::Texture2D:
|
||||
case SurfaceTarget::TextureCubemap:
|
||||
glTextureStorage2D(texture.handle, params.emulated_levels, internal_format, params.width,
|
||||
|
||||
@@ -51,7 +51,7 @@ public:
|
||||
}
|
||||
|
||||
protected:
|
||||
void DecorateSurfaceName() override;
|
||||
void DecorateSurfaceName();
|
||||
|
||||
View CreateView(const ViewParams& view_key) override;
|
||||
View CreateViewInner(const ViewParams& view_key, bool is_proxy);
|
||||
|
||||
@@ -1072,26 +1072,6 @@ private:
|
||||
return {};
|
||||
}
|
||||
|
||||
Id BallotThread(Operation) {
|
||||
UNIMPLEMENTED();
|
||||
return {};
|
||||
}
|
||||
|
||||
Id VoteAll(Operation) {
|
||||
UNIMPLEMENTED();
|
||||
return {};
|
||||
}
|
||||
|
||||
Id VoteAny(Operation) {
|
||||
UNIMPLEMENTED();
|
||||
return {};
|
||||
}
|
||||
|
||||
Id VoteEqual(Operation) {
|
||||
UNIMPLEMENTED();
|
||||
return {};
|
||||
}
|
||||
|
||||
Id DeclareBuiltIn(spv::BuiltIn builtin, spv::StorageClass storage, Id type,
|
||||
const std::string& name) {
|
||||
const Id id = OpVariable(type, storage);
|
||||
@@ -1384,11 +1364,6 @@ private:
|
||||
&SPIRVDecompiler::WorkGroupId<0>,
|
||||
&SPIRVDecompiler::WorkGroupId<1>,
|
||||
&SPIRVDecompiler::WorkGroupId<2>,
|
||||
|
||||
&SPIRVDecompiler::BallotThread,
|
||||
&SPIRVDecompiler::VoteAll,
|
||||
&SPIRVDecompiler::VoteAny,
|
||||
&SPIRVDecompiler::VoteEqual,
|
||||
};
|
||||
static_assert(operation_decompilers.size() == static_cast<std::size_t>(OperationCode::Amount));
|
||||
|
||||
|
||||
@@ -176,7 +176,6 @@ u32 ShaderIR::DecodeInstr(NodeBlock& bb, u32 pc) {
|
||||
{OpCode::Type::Ffma, &ShaderIR::DecodeFfma},
|
||||
{OpCode::Type::Hfma2, &ShaderIR::DecodeHfma2},
|
||||
{OpCode::Type::Conversion, &ShaderIR::DecodeConversion},
|
||||
{OpCode::Type::Warp, &ShaderIR::DecodeWarp},
|
||||
{OpCode::Type::Memory, &ShaderIR::DecodeMemory},
|
||||
{OpCode::Type::Texture, &ShaderIR::DecodeTexture},
|
||||
{OpCode::Type::Image, &ShaderIR::DecodeImage},
|
||||
|
||||
@@ -15,6 +15,7 @@ using Tegra::Shader::OpCode;
|
||||
|
||||
u32 ShaderIR::DecodeFloatSet(NodeBlock& bb, u32 pc) {
|
||||
const Instruction instr = {program_code[pc]};
|
||||
const auto opcode = OpCode::Decode(instr);
|
||||
|
||||
const Node op_a = GetOperandAbsNegFloat(GetRegister(instr.gpr8), instr.fset.abs_a != 0,
|
||||
instr.fset.neg_a != 0);
|
||||
|
||||
@@ -16,6 +16,7 @@ using Tegra::Shader::Pred;
|
||||
|
||||
u32 ShaderIR::DecodeFloatSetPredicate(NodeBlock& bb, u32 pc) {
|
||||
const Instruction instr = {program_code[pc]};
|
||||
const auto opcode = OpCode::Decode(instr);
|
||||
|
||||
const Node op_a = GetOperandAbsNegFloat(GetRegister(instr.gpr8), instr.fsetp.abs_a != 0,
|
||||
instr.fsetp.neg_a != 0);
|
||||
|
||||
@@ -14,6 +14,7 @@ using Tegra::Shader::OpCode;
|
||||
|
||||
u32 ShaderIR::DecodeIntegerSet(NodeBlock& bb, u32 pc) {
|
||||
const Instruction instr = {program_code[pc]};
|
||||
const auto opcode = OpCode::Decode(instr);
|
||||
|
||||
const Node op_a = GetRegister(instr.gpr8);
|
||||
const Node op_b = [&]() {
|
||||
|
||||
@@ -16,6 +16,7 @@ using Tegra::Shader::Pred;
|
||||
|
||||
u32 ShaderIR::DecodeIntegerSetPredicate(NodeBlock& bb, u32 pc) {
|
||||
const Instruction instr = {program_code[pc]};
|
||||
const auto opcode = OpCode::Decode(instr);
|
||||
|
||||
const Node op_a = GetRegister(instr.gpr8);
|
||||
|
||||
|
||||
@@ -74,13 +74,6 @@ u32 ShaderIR::DecodeOther(NodeBlock& bb, u32 pc) {
|
||||
case SystemVariable::InvocationInfo:
|
||||
LOG_WARNING(HW_GPU, "MOV_SYS instruction with InvocationInfo is incomplete");
|
||||
return Immediate(0u);
|
||||
case SystemVariable::Tid: {
|
||||
Node value = Immediate(0);
|
||||
value = BitfieldInsert(value, Operation(OperationCode::LocalInvocationIdX), 0, 9);
|
||||
value = BitfieldInsert(value, Operation(OperationCode::LocalInvocationIdY), 16, 9);
|
||||
value = BitfieldInsert(value, Operation(OperationCode::LocalInvocationIdZ), 26, 5);
|
||||
return value;
|
||||
}
|
||||
case SystemVariable::TidX:
|
||||
return Operation(OperationCode::LocalInvocationIdX);
|
||||
case SystemVariable::TidY:
|
||||
|
||||
@@ -15,6 +15,7 @@ using Tegra::Shader::OpCode;
|
||||
|
||||
u32 ShaderIR::DecodePredicateSetRegister(NodeBlock& bb, u32 pc) {
|
||||
const Instruction instr = {program_code[pc]};
|
||||
const auto opcode = OpCode::Decode(instr);
|
||||
|
||||
UNIMPLEMENTED_IF_MSG(instr.generates_cc,
|
||||
"Condition codes generation in PSET is not implemented");
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
// Copyright 2019 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/common_types.h"
|
||||
#include "video_core/engines/shader_bytecode.h"
|
||||
#include "video_core/shader/node_helper.h"
|
||||
#include "video_core/shader/shader_ir.h"
|
||||
|
||||
namespace VideoCommon::Shader {
|
||||
|
||||
using Tegra::Shader::Instruction;
|
||||
using Tegra::Shader::OpCode;
|
||||
using Tegra::Shader::Pred;
|
||||
using Tegra::Shader::VoteOperation;
|
||||
|
||||
namespace {
|
||||
OperationCode GetOperationCode(VoteOperation vote_op) {
|
||||
switch (vote_op) {
|
||||
case VoteOperation::All:
|
||||
return OperationCode::VoteAll;
|
||||
case VoteOperation::Any:
|
||||
return OperationCode::VoteAny;
|
||||
case VoteOperation::Eq:
|
||||
return OperationCode::VoteEqual;
|
||||
default:
|
||||
UNREACHABLE_MSG("Invalid vote operation={}", static_cast<u64>(vote_op));
|
||||
return OperationCode::VoteAll;
|
||||
}
|
||||
}
|
||||
} // Anonymous namespace
|
||||
|
||||
u32 ShaderIR::DecodeWarp(NodeBlock& bb, u32 pc) {
|
||||
const Instruction instr = {program_code[pc]};
|
||||
const auto opcode = OpCode::Decode(instr);
|
||||
|
||||
switch (opcode->get().GetId()) {
|
||||
case OpCode::Id::VOTE: {
|
||||
const Node value = GetPredicate(instr.vote.value, instr.vote.negate_value != 0);
|
||||
const Node active = Operation(OperationCode::BallotThread, value);
|
||||
const Node vote = Operation(GetOperationCode(instr.vote.operation), value);
|
||||
SetRegister(bb, instr.gpr0, active);
|
||||
SetPredicate(bb, instr.vote.dest_pred, vote);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
UNIMPLEMENTED_MSG("Unhandled warp instruction: {}", opcode->get().GetName());
|
||||
break;
|
||||
}
|
||||
|
||||
return pc;
|
||||
}
|
||||
|
||||
} // namespace VideoCommon::Shader
|
||||
@@ -168,11 +168,6 @@ enum class OperationCode {
|
||||
WorkGroupIdY, /// () -> uint
|
||||
WorkGroupIdZ, /// () -> uint
|
||||
|
||||
BallotThread, /// (bool) -> uint
|
||||
VoteAll, /// (bool) -> bool
|
||||
VoteAny, /// (bool) -> bool
|
||||
VoteEqual, /// (bool) -> bool
|
||||
|
||||
Amount,
|
||||
};
|
||||
|
||||
|
||||
@@ -405,9 +405,4 @@ Node ShaderIR::BitfieldExtract(Node value, u32 offset, u32 bits) {
|
||||
Immediate(offset), Immediate(bits));
|
||||
}
|
||||
|
||||
Node ShaderIR::BitfieldInsert(Node base, Node insert, u32 offset, u32 bits) {
|
||||
return Operation(OperationCode::UBitfieldInsert, NO_PRECISE, base, insert, Immediate(offset),
|
||||
Immediate(bits));
|
||||
}
|
||||
|
||||
} // namespace VideoCommon::Shader
|
||||
@@ -167,7 +167,6 @@ private:
|
||||
u32 DecodeFfma(NodeBlock& bb, u32 pc);
|
||||
u32 DecodeHfma2(NodeBlock& bb, u32 pc);
|
||||
u32 DecodeConversion(NodeBlock& bb, u32 pc);
|
||||
u32 DecodeWarp(NodeBlock& bb, u32 pc);
|
||||
u32 DecodeMemory(NodeBlock& bb, u32 pc);
|
||||
u32 DecodeTexture(NodeBlock& bb, u32 pc);
|
||||
u32 DecodeImage(NodeBlock& bb, u32 pc);
|
||||
@@ -280,9 +279,6 @@ private:
|
||||
/// Extracts a sequence of bits from a node
|
||||
Node BitfieldExtract(Node value, u32 offset, u32 bits);
|
||||
|
||||
/// Inserts a sequence of bits from a node
|
||||
Node BitfieldInsert(Node base, Node insert, u32 offset, u32 bits);
|
||||
|
||||
void WriteTexInstructionFloat(NodeBlock& bb, Tegra::Shader::Instruction instr,
|
||||
const Node4& components);
|
||||
|
||||
|
||||
@@ -58,6 +58,7 @@ public:
|
||||
std::size_t GetHostSizeInBytes() const {
|
||||
std::size_t host_size_in_bytes;
|
||||
if (GetCompressionType() == SurfaceCompression::Converted) {
|
||||
constexpr std::size_t rgb8_bpp = 4ULL;
|
||||
// ASTC is uncompressed in software, in emulated as RGBA8
|
||||
host_size_in_bytes = 0;
|
||||
for (u32 level = 0; level < num_levels; ++level) {
|
||||
|
||||
@@ -308,6 +308,8 @@ protected:
|
||||
if (!guard_render_targets && surface->IsRenderTarget()) {
|
||||
ManageRenderTargetUnregister(surface);
|
||||
}
|
||||
const GPUVAddr gpu_addr = surface->GetGpuAddr();
|
||||
const CacheAddr cache_ptr = surface->GetCacheAddr();
|
||||
const std::size_t size = surface->GetSizeInBytes();
|
||||
const VAddr cpu_addr = surface->GetCpuAddr();
|
||||
rasterizer.UpdatePagesCachedCount(cpu_addr, size, -1);
|
||||
|
||||
@@ -257,21 +257,19 @@ std::vector<u8> UnswizzleTexture(u8* address, u32 tile_size_x, u32 tile_size_y,
|
||||
|
||||
void SwizzleSubrect(u32 subrect_width, u32 subrect_height, u32 source_pitch, u32 swizzled_width,
|
||||
u32 bytes_per_pixel, u8* swizzled_data, u8* unswizzled_data,
|
||||
u32 block_height_bit, u32 offset_x, u32 offset_y) {
|
||||
u32 block_height_bit) {
|
||||
const u32 block_height = 1U << block_height_bit;
|
||||
const u32 image_width_in_gobs{(swizzled_width * bytes_per_pixel + (gob_size_x - 1)) /
|
||||
gob_size_x};
|
||||
for (u32 line = 0; line < subrect_height; ++line) {
|
||||
const u32 dst_y = line + offset_y;
|
||||
const u32 gob_address_y =
|
||||
(dst_y / (gob_size_y * block_height)) * gob_size * block_height * image_width_in_gobs +
|
||||
((dst_y % (gob_size_y * block_height)) / gob_size_y) * gob_size;
|
||||
const auto& table = legacy_swizzle_table[dst_y % gob_size_y];
|
||||
(line / (gob_size_y * block_height)) * gob_size * block_height * image_width_in_gobs +
|
||||
((line % (gob_size_y * block_height)) / gob_size_y) * gob_size;
|
||||
const auto& table = legacy_swizzle_table[line % gob_size_y];
|
||||
for (u32 x = 0; x < subrect_width; ++x) {
|
||||
const u32 dst_x = x + offset_x;
|
||||
const u32 gob_address =
|
||||
gob_address_y + (dst_x * bytes_per_pixel / gob_size_x) * gob_size * block_height;
|
||||
const u32 swizzled_offset = gob_address + table[(dst_x * bytes_per_pixel) % gob_size_x];
|
||||
gob_address_y + (x * bytes_per_pixel / gob_size_x) * gob_size * block_height;
|
||||
const u32 swizzled_offset = gob_address + table[(x * bytes_per_pixel) % gob_size_x];
|
||||
u8* source_line = unswizzled_data + line * source_pitch + x * bytes_per_pixel;
|
||||
u8* dest_addr = swizzled_data + swizzled_offset;
|
||||
|
||||
|
||||
@@ -44,8 +44,7 @@ std::size_t CalculateSize(bool tiled, u32 bytes_per_pixel, u32 width, u32 height
|
||||
|
||||
/// Copies an untiled subrectangle into a tiled surface.
|
||||
void SwizzleSubrect(u32 subrect_width, u32 subrect_height, u32 source_pitch, u32 swizzled_width,
|
||||
u32 bytes_per_pixel, u8* swizzled_data, u8* unswizzled_data, u32 block_height,
|
||||
u32 offset_x, u32 offset_y);
|
||||
u32 bytes_per_pixel, u8* swizzled_data, u8* unswizzled_data, u32 block_height);
|
||||
|
||||
/// Copies a tiled subrectangle into a linear surface.
|
||||
void UnswizzleSubrect(u32 subrect_width, u32 subrect_height, u32 dest_pitch, u32 swizzled_width,
|
||||
|
||||
@@ -213,7 +213,7 @@ struct TICEntry {
|
||||
if (header_version != TICHeaderVersion::OneDBuffer) {
|
||||
return width_minus_1 + 1;
|
||||
}
|
||||
return ((buffer_high_width_minus_one << 16) | buffer_low_width_minus_one) + 1;
|
||||
return (buffer_high_width_minus_one << 16) | buffer_low_width_minus_one;
|
||||
}
|
||||
|
||||
u32 Height() const {
|
||||
|
||||
@@ -466,6 +466,9 @@ void Config::ReadDataStorageValues() {
|
||||
void Config::ReadDebuggingValues() {
|
||||
qt_config->beginGroup(QStringLiteral("Debugging"));
|
||||
|
||||
// Intentionally not using the QT default setting as this is intended to be changed in the ini
|
||||
Settings::values.record_frame_times =
|
||||
qt_config->value(QStringLiteral("record_frame_times"), false).toBool();
|
||||
Settings::values.use_gdbstub = ReadSetting(QStringLiteral("use_gdbstub"), false).toBool();
|
||||
Settings::values.gdbstub_port = ReadSetting(QStringLiteral("gdbstub_port"), 24689).toInt();
|
||||
Settings::values.program_args =
|
||||
@@ -851,6 +854,8 @@ void Config::SaveDataStorageValues() {
|
||||
void Config::SaveDebuggingValues() {
|
||||
qt_config->beginGroup(QStringLiteral("Debugging"));
|
||||
|
||||
// Intentionally not using the QT default setting as this is intended to be changed in the ini
|
||||
qt_config->setValue(QStringLiteral("record_frame_times"), Settings::values.record_frame_times);
|
||||
WriteSetting(QStringLiteral("use_gdbstub"), Settings::values.use_gdbstub, false);
|
||||
WriteSetting(QStringLiteral("gdbstub_port"), Settings::values.gdbstub_port, 24689);
|
||||
WriteSetting(QStringLiteral("program_args"),
|
||||
|
||||
@@ -374,6 +374,8 @@ void Config::ReadValues() {
|
||||
Settings::values.use_dev_keys = sdl2_config->GetBoolean("Miscellaneous", "use_dev_keys", false);
|
||||
|
||||
// Debugging
|
||||
Settings::values.record_frame_times =
|
||||
sdl2_config->GetBoolean("Debugging", "record_frame_times", false);
|
||||
Settings::values.use_gdbstub = sdl2_config->GetBoolean("Debugging", "use_gdbstub", false);
|
||||
Settings::values.gdbstub_port =
|
||||
static_cast<u16>(sdl2_config->GetInteger("Debugging", "gdbstub_port", 24689));
|
||||
|
||||
@@ -213,6 +213,8 @@ region_value =
|
||||
log_filter = *:Trace
|
||||
|
||||
[Debugging]
|
||||
# Record frame time data, can be found in the log directory. Boolean value
|
||||
record_frame_times =
|
||||
# Port for listening to GDB connections.
|
||||
use_gdbstub=false
|
||||
gdbstub_port=24689
|
||||
|
||||
Reference in new issue
Block a user