Browse Source
Merge pull request 'Add changes of my fork into newer repo' (#32) from MrPurple666/eden:purple into master
Merge pull request 'Add changes of my fork into newer repo' (#32) from MrPurple666/eden:purple into master
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/32 Reviewed NCE modifications and implementations, those makes a good addition; merging also Briar features.nce_cpp
70 changed files with 4086 additions and 369 deletions
-
16src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt
-
7src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt
-
16src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt
-
191src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt
-
191src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt
-
320src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt
-
20src/android/app/src/main/jni/android_settings.h
-
85src/android/app/src/main/res/drawable/ic_eden_veil.xml
-
32src/android/app/src/main/res/drawable/ic_frames.xml
-
6src/android/app/src/main/res/layout/fragment_emulation.xml
-
66src/android/app/src/main/res/layout/header_in_game.xml
-
9src/android/app/src/main/res/menu/menu_overlay_options.xml
-
1src/android/app/src/main/res/values-night/yuzu_colors.xml
-
94src/android/app/src/main/res/values/arrays.xml
-
83src/android/app/src/main/res/values/strings.xml
-
1src/android/app/src/main/res/values/yuzu_colors.xml
-
1src/common/common_types.h
-
1src/common/settings.cpp
-
33src/common/settings.h
-
2src/common/settings_enums.h
-
13src/core/arm/arm_interface.cpp
-
70src/core/arm/nce/arm_nce.cpp
-
105src/core/arm/nce/interpreter_visitor.cpp
-
109src/core/arm/nce/lru_cache.h
-
15src/core/arm/nce/patcher.h
-
15src/core/core_timing.cpp
-
12src/core/hle/api_version.h
-
31src/core/hle/service/am/process_creation.cpp
-
3src/core/hle/service/friend/friend.cpp
-
20src/core/hle/service/vi/application_display_service.cpp
-
5src/shader_recompiler/backend/glasm/emit_glasm_context_get_set.cpp
-
4src/shader_recompiler/backend/glsl/emit_glsl_context_get_set.cpp
-
5src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp
-
4src/shader_recompiler/ir_opt/global_memory_to_storage_buffer_pass.cpp
-
18src/shader_recompiler/runtime_info.h
-
5src/video_core/CMakeLists.txt
-
10src/video_core/host_shaders/CMakeLists.txt
-
46src/video_core/host_shaders/convert_abgr8_srgb_to_d24s8.frag
-
28src/video_core/host_shaders/convert_astc_hdr_to_rgba16f.comp
-
29src/video_core/host_shaders/convert_bc7_to_rgba8.comp
-
29src/video_core/host_shaders/convert_rgb_to_yuv420.comp
-
31src/video_core/host_shaders/convert_rgba16f_to_rgba8.frag
-
11src/video_core/host_shaders/convert_rgba8_to_bgra8.frag
-
30src/video_core/host_shaders/convert_yuv420_to_rgb.comp
-
29src/video_core/host_shaders/dither_temporal.frag
-
68src/video_core/host_shaders/dynamic_resolution_scale.comp
-
87src/video_core/renderer_opengl/gl_graphics_pipeline.cpp
-
3src/video_core/renderer_opengl/gl_graphics_pipeline.h
-
31src/video_core/renderer_opengl/gl_shader_cache.cpp
-
1src/video_core/renderer_opengl/gl_shader_cache.h
-
165src/video_core/renderer_vulkan/blit_image.cpp
-
30src/video_core/renderer_vulkan/blit_image.h
-
402src/video_core/renderer_vulkan/renderer_vulkan.cpp
-
26src/video_core/renderer_vulkan/renderer_vulkan.h
-
37src/video_core/renderer_vulkan/vk_compute_pipeline.cpp
-
29src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp
-
79src/video_core/renderer_vulkan/vk_pipeline_cache.cpp
-
1src/video_core/renderer_vulkan/vk_pipeline_cache.h
-
490src/video_core/renderer_vulkan/vk_shader_util.cpp
-
48src/video_core/renderer_vulkan/vk_shader_util.h
-
211src/video_core/renderer_vulkan/vk_texture_cache.cpp
-
33src/video_core/renderer_vulkan/vk_texture_cache.h
-
145src/video_core/renderer_vulkan/vk_texture_manager.cpp
-
57src/video_core/renderer_vulkan/vk_texture_manager.h
-
446src/video_core/vulkan_common/hybrid_memory.cpp
-
119src/video_core/vulkan_common/hybrid_memory.h
-
8src/video_core/vulkan_common/vulkan_device.cpp
-
71src/video_core/vulkan_common/vulkan_memory_allocator.cpp
-
15src/yuzu/configuration/shared_translation.cpp
-
1src/yuzu/uisettings.h
@ -0,0 +1,85 @@ |
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android" |
|||
android:width="24dp" |
|||
android:height="24dp" |
|||
android:viewportWidth="960" |
|||
android:viewportHeight="960"> |
|||
|
|||
<group |
|||
android:scaleX="0.1" |
|||
android:scaleY="-0.1" |
|||
android:translateY="960"> |
|||
|
|||
<path |
|||
android:fillColor="?attr/colorControlNormal" |
|||
android:pathData="M3006,9500 c-276,-35 -669,-178 -886,-322 -102,-68 -96,-75 91,-114 |
|||
335,-68 658,-178 1036,-350 90,-41 165,-74 168,-74 3,0 62,25 132,54 71,30 |
|||
166,66 213,79 102,30 200,44 200,29 0,-6 -7,-45 -15,-87 -9,-50 -11,-83 -5 |
|||
-94 5,-9 124,-131 266,-271 141,-140 273,-275 294,-300 l38,-45 -112,8 c-165 |
|||
12 -727,1 -881,-18 -222,-27 -322,-46 -489,-97 -247,-75 -491,-218 -801,-473 |
|||
-174,-143 -492,-484 -553,-593 -22,-40 -22,-41 -4,-51 16,-8 40,0 124,44 230 |
|||
120,818,340,1127,420 316,83,691,146,951,160 l125,7 -70,-65 c-146,-135 -328 |
|||
-353 -464,-557 -277,-415 -526,-1018 -602,-1460 -30,-171 -8,-451 38,-502 40 |
|||
-44,69,-6,124,160 97,295,229,598,339,777 207,339,558,745,799,924 143,106 |
|||
352,222,500,278 l64,24 -7,-23 c-3,-13 -13,-52 -21,-88 -8,-36 -30,-129 -48 |
|||
-207 l-34,-143 212,0 212,0 38,153 c21,83 49,187 62,230 l24,77 72,-49 c157 |
|||
-106,270,-202,452,-385 407,-408,671,-778,943,-1321 39,-77 74,-152 77,-167 |
|||
26,-104,113,113,137,338 9,89,9,149,0,267 -34,427 -135,757 -320,1042 -62,95 |
|||
-247,278 -352,347 -141,93 -398,214 -588,278 l-113,38 48,11 c246,55,405,67 |
|||
681,52 620,-35,1063,-138,1615,-376 105,-45,127,-39,82,22 -118,156 -331,374 |
|||
-470,479 -325,247 -572,353 -985,425 -165,29 -465,32 -579,6 -114,-27 -246 |
|||
-73 -359,-127 l-103,-49 22,37 c100,165,255,346,515,602 277,273,518,462,815 |
|||
641,179,107,178,126 -6,128 -238,3 -576,-84 -872,-224 -331,-157 -698,-421 |
|||
-880,-634 -35,-42 -68,-76 -73,-76 -4,0 -54,61 -110,135 -449,594 -924,958 |
|||
-1375,1055 -84,18 -362,27 -459,15z"/> |
|||
|
|||
<path |
|||
android:fillColor="?attr/colorControlNormal" |
|||
android:pathData="M4613,6403 c-4,-16 -30,-129 -58,-253 l-52,-225 220,-3 c121,-1 222 |
|||
0,225,3 3,2 20,87 38,187 18,101 38,207 43,237 6,29 11,60 11,67 0,11 -39,14 |
|||
-210,14 l-210,0 -7,-27z"/> |
|||
|
|||
<path |
|||
android:fillColor="?attr/colorControlNormal" |
|||
android:pathData="M4472,5773 c-6,-32 -32,-157 -57,-277 -25,-121 -45,-228 -45,-238 0 |
|||
-17,15,-18,239,-18 l239,0 5,33 c22,151,59,393,67,442 5,33,10,72,10,88 l0,27 |
|||
-224,0 -224,0 -10,-57z"/> |
|||
|
|||
<path |
|||
android:fillColor="?attr/colorControlNormal" |
|||
android:pathData="M4346,5128 c-22,-102 -76,-441 -76,-477 0,-8 76,-11 264,-11 l264,0 |
|||
7,98 c12,186,26,347,31,380 l6,32 -246,0 -246,0 -4,-22z"/> |
|||
|
|||
<path |
|||
android:fillColor="?attr/colorControlNormal" |
|||
android:pathData="M4256,4528 c-4,-12 -20,-182 -46,-471 -6,-65 -8,-122 -5,-128 10,-15 |
|||
565,-12 569,4 3,6 7,146 10,310 l7,297 -266,0 c-204,0 -267,-3 -269,-12z"/> |
|||
|
|||
<path |
|||
android:fillColor="?attr/colorControlNormal" |
|||
android:pathData="M4196,3798 c-4,-31 -26,-512 -26,-570 l0,-28 330,0 331,0 -5,33 c-6 |
|||
35,-22,228,-36,452 -5,77,-10,141,-10,143 0,1 -130,2 -290,2 l-289,0 -5,-32z"/> |
|||
|
|||
<path |
|||
android:fillColor="?attr/colorControlNormal" |
|||
android:pathData="M4170,2845 l0,-265 365,0 c353,0,365,1,365,19 0,21 -25,219 -40,321 |
|||
-5,36,-13,93,-16,128 l-7,62 -333,0 -334,0 0,-265z"/> |
|||
|
|||
<path |
|||
android:fillColor="?attr/colorControlNormal" |
|||
android:pathData="M4180,2473 c1,-59 42,-436 57,-520 l5,-33 390,0 c366,0,389,1,384,18 |
|||
-2,9,-12,55,-20,102 -9,47,-20,108,-26,135 -5,28,-16,93,-25,145 -8,52,-18 |
|||
110,-21,128 l-5,32 -369,0 c-204,0,-370,-3,-370,-7z"/> |
|||
|
|||
<path |
|||
android:fillColor="?attr/colorControlNormal" |
|||
android:pathData="M4260,1806 c0,-43 87,-485 121,-613 l12,-43 413,0 c229,0,414,4,414 |
|||
9 0,5,-15,60,-34,122 -35,114,-108,377,-120,434 -4,17,-12,47,-17,68 l-10,37 |
|||
-389,0 c-323,0,-390,-2,-390,-14z"/> |
|||
|
|||
<path |
|||
android:fillColor="?attr/colorControlNormal" |
|||
android:pathData="M4420,1037 c0,-56 211,-761 297,-994 l16,-43 493,0 c272,0,494,1,494 |
|||
3 0,2,-28,53,-61,113 -140,250,-260,520,-356,802 l-45,132 -419,0 c-339,0 |
|||
-419,-2,-419,-13z"/> |
|||
</group> |
|||
</vector> |
|||
@ -0,0 +1,32 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android" |
|||
android:width="24dp" |
|||
android:height="24dp" |
|||
android:viewportWidth="24" |
|||
android:viewportHeight="24"> |
|||
<path |
|||
android:fillColor="#00000000" |
|||
android:strokeColor="#FF000000" |
|||
android:strokeWidth="1" |
|||
android:pathData="M4,4 L4,20 L20,20" /> |
|||
<path |
|||
android:fillColor="#00000000" |
|||
android:strokeColor="#FF000000" |
|||
android:strokeWidth="2" |
|||
android:pathData="M4,16 L8,12 L12,14 L16,8 L20,10" /> |
|||
<path |
|||
android:fillColor="#FF000000" |
|||
android:pathData="M4,16 C3.45,16 3,15.55 3,15 C3,14.45 3.45,14 4,14 C4.55,14 5,14.45 5,15 C5,15.55 4.55,16 4,16" /> |
|||
<path |
|||
android:fillColor="#FF000000" |
|||
android:pathData="M8,12 C7.45,12 7,11.55 7,11 C7,10.45 7.45,10 8,10 C8.55,10 9,10.45 9,11 C9,11.55 8.55,12 8,12" /> |
|||
<path |
|||
android:fillColor="#FF000000" |
|||
android:pathData="M12,14 C11.45,14 11,13.55 11,13 C11,12.45 11.45,12 12,12 C12.55,12 13,12.45 13,13 C13,13.55 12.55,14 12,14" /> |
|||
<path |
|||
android:fillColor="#FF000000" |
|||
android:pathData="M16,8 C15.45,8 15,7.55 15,7 C15,6.45 15.45,6 16,6 C16.55,6 17,6.45 17,7 C17,7.55 16.55,8 16,8" /> |
|||
<path |
|||
android:fillColor="#FF000000" |
|||
android:pathData="M20,10 C19.45,10 19,9.55 19,9 C19,8.45 19.45,8 20,8 C20.55,8 21,8.45 21,9 C21,9.55 20.55,10 20,10" /> |
|||
</vector> |
|||
@ -1,14 +1,64 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<com.google.android.material.textview.MaterialTextView |
|||
xmlns:android="http://schemas.android.com/apk/res/android" |
|||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" |
|||
xmlns:tools="http://schemas.android.com/tools" |
|||
android:id="@+id/text_game_title" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="wrap_content" |
|||
android:orientation="vertical" |
|||
android:layout_marginTop="24dp" |
|||
android:layout_marginStart="24dp" |
|||
android:layout_marginEnd="24dp" |
|||
android:textAppearance="?attr/textAppearanceHeadlineMedium" |
|||
android:textColor="?attr/colorOnSurface" |
|||
android:textAlignment="viewStart" |
|||
tools:text="Super Mario Odyssey" /> |
|||
android:layout_marginEnd="24dp"> |
|||
|
|||
<com.google.android.material.textview.MaterialTextView |
|||
android:id="@+id/text_game_title" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="wrap_content" |
|||
android:textAppearance="?attr/textAppearanceHeadlineMedium" |
|||
android:textColor="?attr/colorOnSurface" |
|||
android:textAlignment="viewStart" |
|||
android:layout_marginBottom="8dp" |
|||
tools:text="text_game_title" /> |
|||
|
|||
<LinearLayout |
|||
android:layout_width="match_parent" |
|||
android:layout_height="wrap_content" |
|||
android:orientation="horizontal"> |
|||
|
|||
<com.google.android.material.textview.MaterialTextView |
|||
android:layout_width="wrap_content" |
|||
android:layout_height="wrap_content" |
|||
android:textAppearance="?attr/textAppearanceBodyMedium" |
|||
android:textColor="?attr/colorOnSurfaceVariant" |
|||
android:textAlignment="viewStart" |
|||
android:layout_marginEnd="4dp" |
|||
android:text="System Info:" /> |
|||
|
|||
<com.google.android.material.textview.MaterialTextView |
|||
android:id="@+id/cpu_backend" |
|||
android:layout_width="wrap_content" |
|||
android:layout_height="wrap_content" |
|||
android:textAppearance="?attr/textAppearanceBodyMedium" |
|||
android:textColor="?attr/colorOnSurfaceVariant" |
|||
android:textAlignment="viewStart" |
|||
tools:text="cpu_backend" /> |
|||
|
|||
<com.google.android.material.textview.MaterialTextView |
|||
android:layout_width="wrap_content" |
|||
android:layout_height="wrap_content" |
|||
android:textAppearance="?attr/textAppearanceBodyMedium" |
|||
android:textColor="?attr/colorOnSurfaceVariant" |
|||
android:layout_marginStart="4dp" |
|||
android:layout_marginEnd="4dp" |
|||
android:text="|" /> |
|||
|
|||
<com.google.android.material.textview.MaterialTextView |
|||
android:id="@+id/gpu_vendor" |
|||
android:layout_width="wrap_content" |
|||
android:layout_height="wrap_content" |
|||
android:textAppearance="?attr/textAppearanceBodyMedium" |
|||
android:textColor="?attr/colorOnSurfaceVariant" |
|||
android:textAlignment="viewStart" |
|||
tools:text="gpu_vendor" /> |
|||
|
|||
</LinearLayout> |
|||
|
|||
</LinearLayout> |
|||
@ -0,0 +1,109 @@ |
|||
#pragma once |
|||
|
|||
#include <list> |
|||
#include <unordered_map> |
|||
#include <optional> |
|||
|
|||
template<typename KeyType, typename ValueType> |
|||
class LRUCache { |
|||
public: |
|||
explicit LRUCache(size_t capacity) : capacity(capacity) { |
|||
cache_map.reserve(capacity); |
|||
} |
|||
|
|||
// Returns pointer to value if found, nullptr otherwise |
|||
ValueType* get(const KeyType& key) { |
|||
auto it = cache_map.find(key); |
|||
if (it == cache_map.end()) { |
|||
return nullptr; |
|||
} |
|||
|
|||
// Move the accessed item to the front of the list (most recently used) |
|||
cache_list.splice(cache_list.begin(), cache_list, it->second.first); |
|||
return &(it->second.second); |
|||
} |
|||
|
|||
// Returns pointer to value if found (without promoting it), nullptr otherwise |
|||
ValueType* peek(const KeyType& key) const { |
|||
auto it = cache_map.find(key); |
|||
return it != cache_map.end() ? &(it->second.second) : nullptr; |
|||
} |
|||
|
|||
// Inserts or updates a key-value pair |
|||
void put(const KeyType& key, const ValueType& value) { |
|||
auto it = cache_map.find(key); |
|||
|
|||
if (it != cache_map.end()) { |
|||
// Key exists, update value and move to front |
|||
it->second.second = value; |
|||
cache_list.splice(cache_list.begin(), cache_list, it->second.first); |
|||
return; |
|||
} |
|||
|
|||
// Remove the least recently used item if cache is full |
|||
if (cache_map.size() >= capacity) { |
|||
auto last = cache_list.back(); |
|||
cache_map.erase(last); |
|||
cache_list.pop_back(); |
|||
} |
|||
|
|||
// Insert new item at the front |
|||
cache_list.push_front(key); |
|||
cache_map[key] = {cache_list.begin(), value}; |
|||
} |
|||
|
|||
// Attempts to get value, returns std::nullopt if not found |
|||
std::optional<ValueType> try_get(const KeyType& key) { |
|||
auto* val = get(key); |
|||
return val ? std::optional<ValueType>(*val) : std::nullopt; |
|||
} |
|||
|
|||
// Checks if key exists in cache |
|||
bool contains(const KeyType& key) const { |
|||
return cache_map.find(key) != cache_map.end(); |
|||
} |
|||
|
|||
// Removes a key from the cache if it exists |
|||
bool erase(const KeyType& key) { |
|||
auto it = cache_map.find(key); |
|||
if (it == cache_map.end()) { |
|||
return false; |
|||
} |
|||
|
|||
cache_list.erase(it->second.first); |
|||
cache_map.erase(it); |
|||
return true; |
|||
} |
|||
|
|||
// Removes all elements from the cache |
|||
void clear() { |
|||
cache_map.clear(); |
|||
cache_list.clear(); |
|||
} |
|||
|
|||
// Returns current number of elements in cache |
|||
size_t size() const { |
|||
return cache_map.size(); |
|||
} |
|||
|
|||
// Returns maximum capacity of cache |
|||
size_t get_capacity() const { |
|||
return capacity; |
|||
} |
|||
|
|||
// Resizes the cache, evicting LRU items if new capacity is smaller |
|||
void resize(size_t new_capacity) { |
|||
capacity = new_capacity; |
|||
while (cache_map.size() > capacity) { |
|||
auto last = cache_list.back(); |
|||
cache_map.erase(last); |
|||
cache_list.pop_back(); |
|||
} |
|||
cache_map.reserve(capacity); |
|||
} |
|||
|
|||
private: |
|||
size_t capacity; |
|||
std::list<KeyType> cache_list; |
|||
std::unordered_map<KeyType, std::pair<typename std::list<KeyType>::iterator, ValueType>> cache_map; |
|||
}; |
|||
@ -0,0 +1,46 @@ |
|||
// SPDX-FileCopyrightText: 2025 citron Emulator Project |
|||
// SPDX-License-Identifier: GPL-2.0-or-later |
|||
|
|||
#version 450 |
|||
#extension GL_ARB_shader_stencil_export : require |
|||
|
|||
layout(binding = 0) uniform sampler2D color_texture; |
|||
|
|||
// More accurate sRGB to linear conversion |
|||
float srgbToLinear(float srgb) { |
|||
if (srgb <= 0.04045) { |
|||
return srgb / 12.92; |
|||
} else { |
|||
return pow((srgb + 0.055) / 1.055, 2.4); |
|||
} |
|||
} |
|||
|
|||
void main() { |
|||
ivec2 coord = ivec2(gl_FragCoord.xy); |
|||
vec4 srgbColor = texelFetch(color_texture, coord, 0); |
|||
|
|||
// Convert sRGB to linear space with proper gamma correction |
|||
vec3 linearColor = vec3( |
|||
srgbToLinear(srgbColor.r), |
|||
srgbToLinear(srgbColor.g), |
|||
srgbToLinear(srgbColor.b) |
|||
); |
|||
|
|||
// Use standard luminance coefficients |
|||
float luminance = dot(linearColor, vec3(0.2126, 0.7152, 0.0722)); |
|||
|
|||
// Ensure proper depth range |
|||
luminance = clamp(luminance, 0.0, 1.0); |
|||
|
|||
// Convert to 24-bit depth value |
|||
uint depth_val = uint(luminance * float(0xFFFFFF)); |
|||
|
|||
// Extract 8-bit stencil from alpha |
|||
uint stencil_val = uint(srgbColor.a * 255.0); |
|||
|
|||
// Pack values efficiently |
|||
uint depth_stencil = (stencil_val << 24) | (depth_val & 0x00FFFFFF); |
|||
|
|||
gl_FragDepth = float(depth_val) / float(0xFFFFFF); |
|||
gl_FragStencilRefARB = int(stencil_val); |
|||
} |
|||
@ -0,0 +1,28 @@ |
|||
#version 450 |
|||
|
|||
layout(local_size_x = 8, local_size_y = 8) in; |
|||
|
|||
layout(binding = 0) uniform samplerBuffer astc_data; |
|||
layout(binding = 1, rgba16f) uniform writeonly image2D output_image; |
|||
|
|||
// Note: This is a simplified version. Real ASTC HDR decompression is more complex |
|||
void main() { |
|||
ivec2 pos = ivec2(gl_GlobalInvocationID.xy); |
|||
ivec2 size = imageSize(output_image); |
|||
|
|||
if (pos.x >= size.x || pos.y >= size.y) { |
|||
return; |
|||
} |
|||
|
|||
// Calculate block and pixel within block |
|||
ivec2 block = pos / 8; // Assuming 8x8 ASTC blocks |
|||
ivec2 pixel = pos % 8; |
|||
|
|||
// Each ASTC block is 16 bytes |
|||
int block_index = block.y * (size.x / 8) + block.x; |
|||
|
|||
// Simplified ASTC HDR decoding - you'll need to implement full ASTC decoding |
|||
vec4 color = texelFetch(astc_data, block_index * 8 + pixel.y * 8 + pixel.x); |
|||
|
|||
imageStore(output_image, pos, color); |
|||
} |
|||
@ -0,0 +1,29 @@ |
|||
#version 450 |
|||
#extension GL_ARB_shader_ballot : require |
|||
|
|||
layout(local_size_x = 8, local_size_y = 8) in; |
|||
|
|||
layout(binding = 0) uniform samplerBuffer bc7_data; |
|||
layout(binding = 1, rgba8) uniform writeonly image2D output_image; |
|||
|
|||
// Note: This is a simplified version. Real BC7 decompression is more complex |
|||
void main() { |
|||
ivec2 pos = ivec2(gl_GlobalInvocationID.xy); |
|||
ivec2 size = imageSize(output_image); |
|||
|
|||
if (pos.x >= size.x || pos.y >= size.y) { |
|||
return; |
|||
} |
|||
|
|||
// Calculate block and pixel within block |
|||
ivec2 block = pos / 4; |
|||
ivec2 pixel = pos % 4; |
|||
|
|||
// Each BC7 block is 16 bytes |
|||
int block_index = block.y * (size.x / 4) + block.x; |
|||
|
|||
// Simplified BC7 decoding - you'll need to implement full BC7 decoding |
|||
vec4 color = texelFetch(bc7_data, block_index * 4 + pixel.y * 4 + pixel.x); |
|||
|
|||
imageStore(output_image, pos, color); |
|||
} |
|||
@ -0,0 +1,29 @@ |
|||
#version 450 |
|||
|
|||
layout(local_size_x = 8, local_size_y = 8) in; |
|||
|
|||
layout(binding = 0) uniform sampler2D input_texture; |
|||
layout(binding = 1, r8) uniform writeonly image2D y_output; |
|||
layout(binding = 2, r8) uniform writeonly image2D u_output; |
|||
layout(binding = 3, r8) uniform writeonly image2D v_output; |
|||
|
|||
void main() { |
|||
ivec2 pos = ivec2(gl_GlobalInvocationID.xy); |
|||
ivec2 size = imageSize(y_output); |
|||
|
|||
if (pos.x >= size.x || pos.y >= size.y) { |
|||
return; |
|||
} |
|||
|
|||
vec2 tex_coord = vec2(pos) / vec2(size); |
|||
vec3 rgb = texture(input_texture, tex_coord).rgb; |
|||
|
|||
// RGB to YUV conversion |
|||
float y = 0.299 * rgb.r + 0.587 * rgb.g + 0.114 * rgb.b; |
|||
float u = -0.147 * rgb.r - 0.289 * rgb.g + 0.436 * rgb.b + 0.5; |
|||
float v = 0.615 * rgb.r - 0.515 * rgb.g - 0.100 * rgb.b + 0.5; |
|||
|
|||
imageStore(y_output, pos, vec4(y)); |
|||
imageStore(u_output, pos / 2, vec4(u)); |
|||
imageStore(v_output, pos / 2, vec4(v)); |
|||
} |
|||
@ -0,0 +1,31 @@ |
|||
#version 450 |
|||
|
|||
layout(location = 0) in vec2 texcoord; |
|||
layout(location = 0) out vec4 color; |
|||
|
|||
layout(binding = 0) uniform sampler2D input_texture; |
|||
|
|||
layout(push_constant) uniform PushConstants { |
|||
float exposure; |
|||
float gamma; |
|||
} constants; |
|||
|
|||
vec3 tonemap(vec3 hdr) { |
|||
// Reinhard tonemapping |
|||
return hdr / (hdr + vec3(1.0)); |
|||
} |
|||
|
|||
void main() { |
|||
vec4 hdr = texture(input_texture, texcoord); |
|||
|
|||
// Apply exposure |
|||
vec3 exposed = hdr.rgb * constants.exposure; |
|||
|
|||
// Tonemap |
|||
vec3 tonemapped = tonemap(exposed); |
|||
|
|||
// Gamma correction |
|||
vec3 gamma_corrected = pow(tonemapped, vec3(1.0 / constants.gamma)); |
|||
|
|||
color = vec4(gamma_corrected, hdr.a); |
|||
} |
|||
@ -0,0 +1,11 @@ |
|||
#version 450 |
|||
|
|||
layout(location = 0) in vec2 texcoord; |
|||
layout(location = 0) out vec4 color; |
|||
|
|||
layout(binding = 0) uniform sampler2D input_texture; |
|||
|
|||
void main() { |
|||
vec4 rgba = texture(input_texture, texcoord); |
|||
color = rgba.bgra; // Swap red and blue channels |
|||
} |
|||
@ -0,0 +1,30 @@ |
|||
#version 450 |
|||
|
|||
layout(local_size_x = 8, local_size_y = 8) in; |
|||
|
|||
layout(binding = 0) uniform sampler2D y_texture; |
|||
layout(binding = 1) uniform sampler2D u_texture; |
|||
layout(binding = 2) uniform sampler2D v_texture; |
|||
layout(binding = 3, rgba8) uniform writeonly image2D output_image; |
|||
|
|||
void main() { |
|||
ivec2 pos = ivec2(gl_GlobalInvocationID.xy); |
|||
ivec2 size = imageSize(output_image); |
|||
|
|||
if (pos.x >= size.x || pos.y >= size.y) { |
|||
return; |
|||
} |
|||
|
|||
vec2 tex_coord = vec2(pos) / vec2(size); |
|||
float y = texture(y_texture, tex_coord).r; |
|||
float u = texture(u_texture, tex_coord).r - 0.5; |
|||
float v = texture(v_texture, tex_coord).r - 0.5; |
|||
|
|||
// YUV to RGB conversion |
|||
vec3 rgb; |
|||
rgb.r = y + 1.402 * v; |
|||
rgb.g = y - 0.344 * u - 0.714 * v; |
|||
rgb.b = y + 1.772 * u; |
|||
|
|||
imageStore(output_image, pos, vec4(rgb, 1.0)); |
|||
} |
|||
@ -0,0 +1,29 @@ |
|||
#version 450 |
|||
|
|||
layout(location = 0) in vec2 texcoord; |
|||
layout(location = 0) out vec4 color; |
|||
|
|||
layout(binding = 0) uniform sampler2D input_texture; |
|||
|
|||
layout(push_constant) uniform PushConstants { |
|||
float frame_count; |
|||
float dither_strength; |
|||
} constants; |
|||
|
|||
// Pseudo-random number generator |
|||
float rand(vec2 co) { |
|||
return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453); |
|||
} |
|||
|
|||
void main() { |
|||
vec4 input_color = texture(input_texture, texcoord); |
|||
|
|||
// Generate temporal noise based on frame count |
|||
vec2 noise_coord = gl_FragCoord.xy + vec2(constants.frame_count); |
|||
float noise = rand(noise_coord) * 2.0 - 1.0; |
|||
|
|||
// Apply dithering |
|||
vec3 dithered = input_color.rgb + noise * constants.dither_strength; |
|||
|
|||
color = vec4(dithered, input_color.a); |
|||
} |
|||
@ -0,0 +1,68 @@ |
|||
#version 450 |
|||
|
|||
layout(local_size_x = 8, local_size_y = 8) in; |
|||
|
|||
layout(binding = 0) uniform sampler2D input_texture; |
|||
layout(binding = 1, rgba8) uniform writeonly image2D output_image; |
|||
|
|||
layout(push_constant) uniform PushConstants { |
|||
vec2 scale_factor; |
|||
vec2 input_size; |
|||
} constants; |
|||
|
|||
vec4 cubic(float v) { |
|||
vec4 n = vec4(1.0, 2.0, 3.0, 4.0) - v; |
|||
vec4 s = n * n * n; |
|||
float x = s.x; |
|||
float y = s.y - 4.0 * s.x; |
|||
float z = s.z - 4.0 * s.y + 6.0 * s.x; |
|||
float w = s.w - 4.0 * s.z + 6.0 * s.y - 4.0 * s.x; |
|||
return vec4(x, y, z, w) * (1.0/6.0); |
|||
} |
|||
|
|||
vec4 bicubic_sample(sampler2D tex, vec2 tex_coord) { |
|||
vec2 tex_size = constants.input_size; |
|||
vec2 inv_tex_size = 1.0 / tex_size; |
|||
|
|||
tex_coord = tex_coord * tex_size - 0.5; |
|||
|
|||
vec2 fxy = fract(tex_coord); |
|||
tex_coord -= fxy; |
|||
|
|||
vec4 xcubic = cubic(fxy.x); |
|||
vec4 ycubic = cubic(fxy.y); |
|||
|
|||
vec4 c = tex_coord.xxyy + vec2(-0.5, +1.5).xyxy; |
|||
vec4 s = vec4(xcubic.xz + xcubic.yw, ycubic.xz + ycubic.yw); |
|||
vec4 offset = c + vec4(xcubic.yw, ycubic.yw) / s; |
|||
|
|||
offset *= inv_tex_size.xxyy; |
|||
|
|||
vec4 sample0 = texture(tex, offset.xz); |
|||
vec4 sample1 = texture(tex, offset.yz); |
|||
vec4 sample2 = texture(tex, offset.xw); |
|||
vec4 sample3 = texture(tex, offset.yw); |
|||
|
|||
float sx = s.x / (s.x + s.y); |
|||
float sy = s.z / (s.z + s.w); |
|||
|
|||
return mix( |
|||
mix(sample3, sample2, sx), |
|||
mix(sample1, sample0, sx), |
|||
sy |
|||
); |
|||
} |
|||
|
|||
void main() { |
|||
ivec2 pos = ivec2(gl_GlobalInvocationID.xy); |
|||
ivec2 size = imageSize(output_image); |
|||
|
|||
if (pos.x >= size.x || pos.y >= size.y) { |
|||
return; |
|||
} |
|||
|
|||
vec2 tex_coord = vec2(pos) / vec2(size); |
|||
vec4 color = bicubic_sample(input_texture, tex_coord); |
|||
|
|||
imageStore(output_image, pos, color); |
|||
} |
|||
@ -0,0 +1,145 @@ |
|||
// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|||
|
|||
#include <filesystem>
|
|||
|
|||
#include "common/assert.h"
|
|||
#include "common/logging/log.h"
|
|||
#include "video_core/renderer_vulkan/vk_texture_manager.h"
|
|||
#include "video_core/vulkan_common/vulkan_device.h"
|
|||
#include "video_core/vulkan_common/vulkan_memory_allocator.h"
|
|||
#include "video_core/vulkan_common/vulkan_wrapper.h"
|
|||
|
|||
namespace Vulkan { |
|||
|
|||
TextureManager::TextureManager(const Device& device_, MemoryAllocator& memory_allocator_) |
|||
: device(device_), memory_allocator(memory_allocator_) { |
|||
|
|||
// Create a default texture for fallback in case of errors
|
|||
default_texture = CreateDefaultTexture(); |
|||
} |
|||
|
|||
TextureManager::~TextureManager() { |
|||
std::lock_guard<std::mutex> lock(texture_mutex); |
|||
// Clear all cached textures
|
|||
texture_cache.clear(); |
|||
|
|||
// Default texture will be cleaned up automatically by vk::Image's destructor
|
|||
} |
|||
|
|||
VkImage TextureManager::GetTexture(const std::string& texture_path) { |
|||
std::lock_guard<std::mutex> lock(texture_mutex); |
|||
|
|||
// Check if the texture is already in the cache
|
|||
auto it = texture_cache.find(texture_path); |
|||
if (it != texture_cache.end()) { |
|||
return *it->second; |
|||
} |
|||
|
|||
// Load the texture and add it to the cache
|
|||
vk::Image new_texture = LoadTexture(texture_path); |
|||
if (new_texture) { |
|||
VkImage raw_handle = *new_texture; |
|||
texture_cache.emplace(texture_path, std::move(new_texture)); |
|||
return raw_handle; |
|||
} |
|||
|
|||
// If loading fails, return the default texture if it exists
|
|||
LOG_WARNING(Render_Vulkan, "Failed to load texture: {}, using default", texture_path); |
|||
if (default_texture.has_value()) { |
|||
return *(*default_texture); |
|||
} |
|||
return VK_NULL_HANDLE; |
|||
} |
|||
|
|||
void TextureManager::ReloadTexture(const std::string& texture_path) { |
|||
std::lock_guard<std::mutex> lock(texture_mutex); |
|||
|
|||
// Remove the texture from cache if it exists
|
|||
auto it = texture_cache.find(texture_path); |
|||
if (it != texture_cache.end()) { |
|||
LOG_INFO(Render_Vulkan, "Reloading texture: {}", texture_path); |
|||
texture_cache.erase(it); |
|||
} |
|||
|
|||
// The texture will be reloaded on next GetTexture call
|
|||
} |
|||
|
|||
bool TextureManager::IsTextureLoadedCorrectly(VkImage texture) { |
|||
// Check if the texture handle is valid
|
|||
static const VkImage null_handle = VK_NULL_HANDLE; |
|||
return texture != null_handle; |
|||
} |
|||
|
|||
void TextureManager::CleanupTextureCache() { |
|||
std::lock_guard<std::mutex> lock(texture_mutex); |
|||
|
|||
// TODO: track usage and remove unused textures [ZEP]
|
|||
|
|||
LOG_INFO(Render_Vulkan, "Handling texture cache cleanup, current size: {}", texture_cache.size()); |
|||
} |
|||
|
|||
void TextureManager::HandleTextureRendering(const std::string& texture_path, |
|||
std::function<void(VkImage)> render_callback) { |
|||
VkImage texture = GetTexture(texture_path); |
|||
|
|||
if (!IsTextureLoadedCorrectly(texture)) { |
|||
LOG_ERROR(Render_Vulkan, "Texture failed to load correctly: {}, attempting reload", texture_path); |
|||
ReloadTexture(texture_path); |
|||
texture = GetTexture(texture_path); |
|||
} |
|||
|
|||
// Execute the rendering callback with the texture
|
|||
render_callback(texture); |
|||
} |
|||
|
|||
vk::Image TextureManager::LoadTexture(const std::string& texture_path) { |
|||
// TODO: load image data from disk
|
|||
// and create a proper Vulkan texture [ZEP]
|
|||
|
|||
if (!std::filesystem::exists(texture_path)) { |
|||
LOG_ERROR(Render_Vulkan, "Texture file not found: {}", texture_path); |
|||
return {}; |
|||
} |
|||
|
|||
try { |
|||
|
|||
LOG_INFO(Render_Vulkan, "Loaded texture: {}", texture_path); |
|||
|
|||
// TODO: create an actual VkImage [ZEP]
|
|||
return CreateDefaultTexture(); |
|||
} catch (const std::exception& e) { |
|||
LOG_ERROR(Render_Vulkan, "Error loading texture {}: {}", texture_path, e.what()); |
|||
return {}; |
|||
} |
|||
} |
|||
|
|||
vk::Image TextureManager::CreateDefaultTexture() { |
|||
// Create a small default texture (1x1 pixel) to use as a fallback
|
|||
// const VkExtent2D extent{1, 1};
|
|||
|
|||
/* // Create image
|
|||
const VkImageCreateInfo image_ci{ |
|||
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, |
|||
.pNext = nullptr, |
|||
.flags = 0, |
|||
.imageType = VK_IMAGE_TYPE_2D, |
|||
.format = texture_format, |
|||
.extent = {extent.width, extent.height, 1}, |
|||
.mipLevels = 1, |
|||
.arrayLayers = 1, |
|||
.samples = VK_SAMPLE_COUNT_1_BIT, |
|||
.tiling = VK_IMAGE_TILING_OPTIMAL, |
|||
.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, |
|||
.sharingMode = VK_SHARING_MODE_EXCLUSIVE, |
|||
.queueFamilyIndexCount = 0, |
|||
.pQueueFamilyIndices = nullptr, |
|||
.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, |
|||
}; */ |
|||
|
|||
// TODO: create an actual VkImage [ZEP]
|
|||
LOG_INFO(Render_Vulkan, "Created default fallback texture"); |
|||
return {}; |
|||
} |
|||
|
|||
} // namespace Vulkan
|
|||
@ -0,0 +1,57 @@ |
|||
// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project |
|||
// SPDX-License-Identifier: GPL-3.0-or-later |
|||
|
|||
#pragma once |
|||
|
|||
#include <mutex> |
|||
#include <string> |
|||
#include <unordered_map> |
|||
#include <functional> |
|||
#include <atomic> |
|||
#include <optional> |
|||
|
|||
#include "video_core/vulkan_common/vulkan_wrapper.h" |
|||
|
|||
namespace Vulkan { |
|||
|
|||
class Device; |
|||
class MemoryAllocator; |
|||
|
|||
// Enhanced texture manager for better error handling and thread safety |
|||
class TextureManager { |
|||
public: |
|||
explicit TextureManager(const Device& device, MemoryAllocator& memory_allocator); |
|||
~TextureManager(); |
|||
|
|||
// Get a texture from the cache, loading it if necessary |
|||
VkImage GetTexture(const std::string& texture_path); |
|||
|
|||
// Force a texture to reload from disk |
|||
void ReloadTexture(const std::string& texture_path); |
|||
|
|||
// Check if a texture is loaded correctly |
|||
bool IsTextureLoadedCorrectly(VkImage texture); |
|||
|
|||
// Remove old textures from the cache |
|||
void CleanupTextureCache(); |
|||
|
|||
// Handle texture rendering, with automatic reload if needed |
|||
void HandleTextureRendering(const std::string& texture_path, |
|||
std::function<void(VkImage)> render_callback); |
|||
|
|||
private: |
|||
// Load a texture from disk and create a Vulkan image |
|||
vk::Image LoadTexture(const std::string& texture_path); |
|||
|
|||
// Create a default texture to use in case of errors |
|||
vk::Image CreateDefaultTexture(); |
|||
|
|||
const Device& device; |
|||
MemoryAllocator& memory_allocator; |
|||
std::mutex texture_mutex; |
|||
std::unordered_map<std::string, vk::Image> texture_cache; |
|||
std::optional<vk::Image> default_texture; |
|||
VkFormat texture_format = VK_FORMAT_B8G8R8A8_SRGB; |
|||
}; |
|||
|
|||
} // namespace Vulkan |
|||
@ -0,0 +1,446 @@ |
|||
// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|||
|
|||
#include <cstring>
|
|||
#include <fstream>
|
|||
#include <algorithm>
|
|||
|
|||
#include "common/logging/log.h"
|
|||
#include "video_core/vulkan_common/hybrid_memory.h"
|
|||
|
|||
#if defined(__linux__) || defined(__ANDROID__)
|
|||
#include <sys/mman.h>
|
|||
#include <unistd.h>
|
|||
#include <poll.h>
|
|||
#include <sys/syscall.h>
|
|||
#include <linux/userfaultfd.h>
|
|||
#include <sys/ioctl.h>
|
|||
#include <fcntl.h>
|
|||
#elif defined(_WIN32)
|
|||
#include <windows.h>
|
|||
#endif
|
|||
|
|||
namespace Vulkan { |
|||
|
|||
void PredictiveReuseManager::RecordUsage(u64 address, u64 size, bool write_access) { |
|||
std::lock_guard<std::mutex> guard(mutex); |
|||
|
|||
// Add to history, removing oldest entries if we're past max_history
|
|||
access_history.push_back({address, size, write_access, current_timestamp++}); |
|||
if (access_history.size() > max_history) { |
|||
access_history.erase(access_history.begin()); |
|||
} |
|||
} |
|||
|
|||
bool PredictiveReuseManager::IsHotRegion(u64 address, u64 size) const { |
|||
std::lock_guard<std::mutex> guard(mutex); |
|||
|
|||
// Check if this memory region has been accessed frequently
|
|||
const u64 end_address = address + size; |
|||
int access_count = 0; |
|||
|
|||
for (const auto& access : access_history) { |
|||
const u64 access_end = access.address + access.size; |
|||
|
|||
// Check for overlap
|
|||
if (!(end_address <= access.address || address >= access_end)) { |
|||
access_count++; |
|||
} |
|||
} |
|||
|
|||
// Consider a region "hot" if it has been accessed in at least 10% of recent accesses
|
|||
return access_count >= static_cast<int>(std::max<size_t>(1, max_history / 10)); |
|||
} |
|||
|
|||
void PredictiveReuseManager::EvictRegion(u64 address, u64 size) { |
|||
std::lock_guard<std::mutex> guard(mutex); |
|||
|
|||
// Remove any history entries that overlap with this region
|
|||
const u64 end_address = address + size; |
|||
|
|||
access_history.erase( |
|||
std::remove_if(access_history.begin(), access_history.end(), |
|||
[address, end_address](const MemoryAccess& access) { |
|||
const u64 access_end = access.address + access.size; |
|||
// Check for overlap
|
|||
return !(end_address <= access.address || address >= access_end); |
|||
}), |
|||
access_history.end() |
|||
); |
|||
} |
|||
|
|||
void PredictiveReuseManager::ClearHistory() { |
|||
std::lock_guard<std::mutex> guard(mutex); |
|||
access_history.clear(); |
|||
current_timestamp = 0; |
|||
} |
|||
|
|||
#if defined(__linux__) || defined(__ANDROID__) || defined(_WIN32)
|
|||
void FaultManagedAllocator::Touch(size_t addr) { |
|||
lru.remove(addr); |
|||
lru.push_front(addr); |
|||
dirty_set.insert(addr); |
|||
} |
|||
|
|||
void FaultManagedAllocator::EnforceLimit() { |
|||
while (lru.size() > MaxPages) { |
|||
size_t evict = lru.back(); |
|||
lru.pop_back(); |
|||
|
|||
auto it = page_map.find(evict); |
|||
if (it != page_map.end()) { |
|||
if (dirty_set.count(evict)) { |
|||
// Compress and store dirty page before evicting
|
|||
std::vector<u8> compressed((u8*)it->second, (u8*)it->second + PageSize); |
|||
compressed_store[evict] = std::move(compressed); |
|||
dirty_set.erase(evict); |
|||
} |
|||
|
|||
#if defined(__linux__) || defined(__ANDROID__)
|
|||
munmap(it->second, PageSize); |
|||
#elif defined(_WIN32)
|
|||
VirtualFree(it->second, 0, MEM_RELEASE); |
|||
#endif
|
|||
page_map.erase(it); |
|||
} |
|||
} |
|||
} |
|||
|
|||
void* FaultManagedAllocator::GetOrAlloc(size_t addr) { |
|||
std::lock_guard<std::mutex> guard(lock); |
|||
|
|||
if (page_map.count(addr)) { |
|||
Touch(addr); |
|||
return page_map[addr]; |
|||
} |
|||
|
|||
#if defined(__linux__) || defined(__ANDROID__)
|
|||
void* mem = mmap(nullptr, PageSize, PROT_READ | PROT_WRITE, |
|||
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); |
|||
|
|||
if (mem == MAP_FAILED) { |
|||
LOG_ERROR(Render_Vulkan, "Failed to mmap memory for fault handler"); |
|||
return nullptr; |
|||
} |
|||
#elif defined(_WIN32)
|
|||
void* mem = VirtualAlloc(nullptr, PageSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); |
|||
if (!mem) { |
|||
LOG_ERROR(Render_Vulkan, "Failed to VirtualAlloc memory for fault handler"); |
|||
return nullptr; |
|||
} |
|||
#endif
|
|||
|
|||
if (compressed_store.count(addr)) { |
|||
// Decompress stored page data
|
|||
std::memcpy(mem, compressed_store[addr].data(), compressed_store[addr].size()); |
|||
compressed_store.erase(addr); |
|||
} else { |
|||
std::memset(mem, 0, PageSize); |
|||
} |
|||
|
|||
page_map[addr] = mem; |
|||
lru.push_front(addr); |
|||
dirty_set.insert(addr); |
|||
EnforceLimit(); |
|||
|
|||
return mem; |
|||
} |
|||
|
|||
#if defined(_WIN32)
|
|||
// Static member initialization
|
|||
FaultManagedAllocator* FaultManagedAllocator::current_instance = nullptr; |
|||
|
|||
LONG WINAPI FaultManagedAllocator::VectoredExceptionHandler(PEXCEPTION_POINTERS exception_info) { |
|||
// Only handle access violations (page faults)
|
|||
if (exception_info->ExceptionRecord->ExceptionCode != EXCEPTION_ACCESS_VIOLATION) { |
|||
return EXCEPTION_CONTINUE_SEARCH; |
|||
} |
|||
|
|||
if (!current_instance) { |
|||
return EXCEPTION_CONTINUE_SEARCH; |
|||
} |
|||
|
|||
// Get the faulting address - use ULONG_PTR for Windows
|
|||
const ULONG_PTR fault_addr = static_cast<ULONG_PTR>(exception_info->ExceptionRecord->ExceptionInformation[1]); |
|||
const ULONG_PTR base_addr = reinterpret_cast<ULONG_PTR>(current_instance->base_address); |
|||
|
|||
// Check if the address is within our managed range
|
|||
if (fault_addr < base_addr || |
|||
fault_addr >= (base_addr + static_cast<ULONG_PTR>(current_instance->memory_size))) { |
|||
return EXCEPTION_CONTINUE_SEARCH; |
|||
} |
|||
|
|||
// Calculate the base address of the page
|
|||
const ULONG_PTR page_addr = fault_addr & ~(static_cast<ULONG_PTR>(PageSize) - 1); |
|||
const size_t relative_addr = static_cast<size_t>(page_addr - base_addr); |
|||
|
|||
// Handle the fault by allocating memory
|
|||
void* page = current_instance->GetOrAlloc(relative_addr); |
|||
if (!page) { |
|||
return EXCEPTION_CONTINUE_SEARCH; |
|||
} |
|||
|
|||
// Copy the page data to the faulting address
|
|||
DWORD old_protect; |
|||
void* target_addr = reinterpret_cast<void*>(page_addr); |
|||
|
|||
// Make the target page writable
|
|||
if (VirtualProtect(target_addr, PageSize, PAGE_READWRITE, &old_protect)) { |
|||
std::memcpy(target_addr, page, PageSize); |
|||
// Restore original protection
|
|||
VirtualProtect(target_addr, PageSize, old_protect, &old_protect); |
|||
return EXCEPTION_CONTINUE_EXECUTION; |
|||
} |
|||
|
|||
return EXCEPTION_CONTINUE_SEARCH; |
|||
} |
|||
|
|||
void FaultManagedAllocator::ExceptionHandlerThread() { |
|||
while (running) { |
|||
// Sleep to avoid busy waiting
|
|||
Sleep(10); |
|||
} |
|||
} |
|||
#endif
|
|||
|
|||
void FaultManagedAllocator::Initialize(void* base, size_t size) { |
|||
#if defined(__linux__) || defined(__ANDROID__)
|
|||
uffd = static_cast<int>(syscall(SYS_userfaultfd, O_CLOEXEC | O_NONBLOCK)); |
|||
if (uffd < 0) { |
|||
LOG_ERROR(Render_Vulkan, "Failed to create userfaultfd, fault handling disabled"); |
|||
return; |
|||
} |
|||
|
|||
struct uffdio_api api = { .api = UFFD_API }; |
|||
ioctl(uffd, UFFDIO_API, &api); |
|||
|
|||
struct uffdio_register reg = { |
|||
.range = { .start = (uintptr_t)base, .len = size }, |
|||
.mode = UFFDIO_REGISTER_MODE_MISSING |
|||
}; |
|||
|
|||
if (ioctl(uffd, UFFDIO_REGISTER, ®) < 0) { |
|||
LOG_ERROR(Render_Vulkan, "Failed to register memory range with userfaultfd"); |
|||
close(uffd); |
|||
uffd = -1; |
|||
return; |
|||
} |
|||
|
|||
running = true; |
|||
fault_handler = std::thread(&FaultManagedAllocator::FaultThread, this); |
|||
#elif defined(_WIN32)
|
|||
// Setup Windows memory for fault handling
|
|||
base_address = base; |
|||
memory_size = size; |
|||
|
|||
// Reserve memory range but don't commit it yet - it will be demand-paged
|
|||
DWORD oldProtect; |
|||
VirtualProtect(base, size, PAGE_NOACCESS, &oldProtect); |
|||
|
|||
// Install a vectored exception handler
|
|||
current_instance = this; |
|||
AddVectoredExceptionHandler(1, VectoredExceptionHandler); |
|||
|
|||
running = true; |
|||
exception_handler = std::thread(&FaultManagedAllocator::ExceptionHandlerThread, this); |
|||
|
|||
LOG_INFO(Render_Vulkan, "Windows fault-managed memory initialized at {:p}, size: {}", |
|||
base, size); |
|||
#endif
|
|||
} |
|||
|
|||
#if defined(__linux__) || defined(__ANDROID__)
|
|||
void FaultManagedAllocator::FaultThread() { |
|||
struct pollfd pfd = { uffd, POLLIN, 0 }; |
|||
|
|||
while (running) { |
|||
if (poll(&pfd, 1, 10) > 0) { |
|||
struct uffd_msg msg; |
|||
read(uffd, &msg, sizeof(msg)); |
|||
|
|||
if (msg.event == UFFD_EVENT_PAGEFAULT) { |
|||
size_t addr = msg.arg.pagefault.address & ~(PageSize - 1); |
|||
void* page = GetOrAlloc(addr); |
|||
|
|||
if (page) { |
|||
struct uffdio_copy copy = { |
|||
.dst = (uintptr_t)addr, |
|||
.src = (uintptr_t)page, |
|||
.len = PageSize, |
|||
.mode = 0 |
|||
}; |
|||
|
|||
ioctl(uffd, UFFDIO_COPY, ©); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
#endif
|
|||
|
|||
void* FaultManagedAllocator::Translate(size_t addr) { |
|||
std::lock_guard<std::mutex> guard(lock); |
|||
|
|||
size_t base = addr & ~(PageSize - 1); |
|||
if (!page_map.count(base)) { |
|||
return nullptr; |
|||
} |
|||
|
|||
Touch(base); |
|||
return (u8*)page_map[base] + (addr % PageSize); |
|||
} |
|||
|
|||
void FaultManagedAllocator::SaveSnapshot(const std::string& path) { |
|||
std::lock_guard<std::mutex> guard(lock); |
|||
|
|||
std::ofstream out(path, std::ios::binary); |
|||
if (!out) { |
|||
LOG_ERROR(Render_Vulkan, "Failed to open snapshot file for writing: {}", path); |
|||
return; |
|||
} |
|||
|
|||
for (auto& [addr, mem] : page_map) { |
|||
out.write(reinterpret_cast<const char*>(&addr), sizeof(addr)); |
|||
out.write(reinterpret_cast<const char*>(mem), PageSize); |
|||
} |
|||
|
|||
LOG_INFO(Render_Vulkan, "Saved memory snapshot to {}", path); |
|||
} |
|||
|
|||
void FaultManagedAllocator::SaveDifferentialSnapshot(const std::string& path) { |
|||
std::lock_guard<std::mutex> guard(lock); |
|||
|
|||
std::ofstream out(path, std::ios::binary); |
|||
if (!out) { |
|||
LOG_ERROR(Render_Vulkan, "Failed to open diff snapshot file for writing: {}", path); |
|||
return; |
|||
} |
|||
|
|||
size_t dirty_count = 0; |
|||
for (const auto& addr : dirty_set) { |
|||
if (page_map.count(addr)) { |
|||
out.write(reinterpret_cast<const char*>(&addr), sizeof(addr)); |
|||
out.write(reinterpret_cast<const char*>(page_map[addr]), PageSize); |
|||
dirty_count++; |
|||
} |
|||
} |
|||
|
|||
LOG_INFO(Render_Vulkan, "Saved differential snapshot to {} ({} dirty pages)", |
|||
path, dirty_count); |
|||
} |
|||
|
|||
void FaultManagedAllocator::ClearDirtySet() { |
|||
std::lock_guard<std::mutex> guard(lock); |
|||
dirty_set.clear(); |
|||
LOG_DEBUG(Render_Vulkan, "Cleared dirty page tracking"); |
|||
} |
|||
|
|||
FaultManagedAllocator::~FaultManagedAllocator() { |
|||
running = false; |
|||
|
|||
#if defined(__linux__) || defined(__ANDROID__)
|
|||
if (fault_handler.joinable()) { |
|||
fault_handler.join(); |
|||
} |
|||
|
|||
for (auto& [addr, mem] : page_map) { |
|||
munmap(mem, PageSize); |
|||
} |
|||
|
|||
if (uffd != -1) { |
|||
close(uffd); |
|||
} |
|||
#elif defined(_WIN32)
|
|||
if (exception_handler.joinable()) { |
|||
exception_handler.join(); |
|||
} |
|||
|
|||
// Remove the vectored exception handler
|
|||
RemoveVectoredExceptionHandler(VectoredExceptionHandler); |
|||
current_instance = nullptr; |
|||
|
|||
for (auto& [addr, mem] : page_map) { |
|||
VirtualFree(mem, 0, MEM_RELEASE); |
|||
} |
|||
|
|||
// Free the base memory if needed
|
|||
if (base_address) { |
|||
VirtualFree(base_address, 0, MEM_RELEASE); |
|||
base_address = nullptr; |
|||
} |
|||
#endif
|
|||
} |
|||
#endif // defined(__linux__) || defined(__ANDROID__) || defined(_WIN32)
|
|||
|
|||
HybridMemory::HybridMemory(const Device& device_, MemoryAllocator& allocator, size_t reuse_history) |
|||
: device(device_), memory_allocator(allocator), reuse_manager(reuse_history) { |
|||
} |
|||
|
|||
HybridMemory::~HybridMemory() = default; |
|||
|
|||
void HybridMemory::InitializeGuestMemory(void* base, size_t size) { |
|||
#if defined(__linux__) || defined(__ANDROID__) || defined(_WIN32)
|
|||
fmaa.Initialize(base, size); |
|||
LOG_INFO(Render_Vulkan, "Initialized fault-managed guest memory at {:p}, size: {}", |
|||
base, size); |
|||
#else
|
|||
LOG_INFO(Render_Vulkan, "Fault-managed memory not supported on this platform"); |
|||
#endif
|
|||
} |
|||
|
|||
void* HybridMemory::TranslateAddress(size_t addr) { |
|||
#if defined(__linux__) || defined(__ANDROID__) || defined(_WIN32)
|
|||
return fmaa.Translate(addr); |
|||
#else
|
|||
return nullptr; |
|||
#endif
|
|||
} |
|||
|
|||
ComputeBuffer HybridMemory::CreateComputeBuffer(VkDeviceSize size, VkBufferUsageFlags usage, |
|||
MemoryUsage memory_type) { |
|||
ComputeBuffer buffer; |
|||
buffer.size = size; |
|||
|
|||
VkBufferCreateInfo buffer_ci = { |
|||
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, |
|||
.pNext = nullptr, |
|||
.flags = 0, |
|||
.size = size, |
|||
.usage = usage | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, |
|||
.sharingMode = VK_SHARING_MODE_EXCLUSIVE, |
|||
.queueFamilyIndexCount = 0, |
|||
.pQueueFamilyIndices = nullptr, |
|||
}; |
|||
|
|||
// Using CreateBuffer directly handles memory allocation internally
|
|||
buffer.buffer = memory_allocator.CreateBuffer(buffer_ci, memory_type); |
|||
|
|||
LOG_DEBUG(Render_Vulkan, "Created compute buffer: size={}, usage={:x}", |
|||
size, usage); |
|||
|
|||
return buffer; |
|||
} |
|||
|
|||
void HybridMemory::SaveSnapshot(const std::string& path) { |
|||
#if defined(__linux__) || defined(__ANDROID__) || defined(_WIN32)
|
|||
fmaa.SaveSnapshot(path); |
|||
#else
|
|||
LOG_ERROR(Render_Vulkan, "Memory snapshots not supported on this platform"); |
|||
#endif
|
|||
} |
|||
|
|||
void HybridMemory::SaveDifferentialSnapshot(const std::string& path) { |
|||
#if defined(__linux__) || defined(__ANDROID__) || defined(_WIN32)
|
|||
fmaa.SaveDifferentialSnapshot(path); |
|||
#else
|
|||
LOG_ERROR(Render_Vulkan, "Differential memory snapshots not supported on this platform"); |
|||
#endif
|
|||
} |
|||
|
|||
void HybridMemory::ResetDirtyTracking() { |
|||
#if defined(__linux__) || defined(__ANDROID__) || defined(_WIN32)
|
|||
fmaa.ClearDirtySet(); |
|||
#endif
|
|||
} |
|||
|
|||
} // namespace Vulkan
|
|||
@ -0,0 +1,119 @@ |
|||
// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project |
|||
// SPDX-License-Identifier: GPL-2.0-or-later |
|||
|
|||
#pragma once |
|||
|
|||
#include <memory> |
|||
#include <string> |
|||
#include <vector> |
|||
#include <unordered_map> |
|||
#include <mutex> |
|||
#include <atomic> |
|||
#include <functional> |
|||
#include <list> |
|||
#include <set> |
|||
#include <map> |
|||
#include <thread> |
|||
|
|||
#include "common/common_types.h" |
|||
#include "video_core/vulkan_common/vulkan_device.h" |
|||
#include "video_core/vulkan_common/vulkan_memory_allocator.h" |
|||
#include "video_core/vulkan_common/vulkan_wrapper.h" |
|||
|
|||
namespace Vulkan { |
|||
|
|||
struct ComputeBuffer { |
|||
vk::Buffer buffer{}; |
|||
VkDeviceSize size = 0; |
|||
}; |
|||
|
|||
class PredictiveReuseManager { |
|||
public: |
|||
explicit PredictiveReuseManager(size_t history_size) : max_history{history_size} {} |
|||
|
|||
void RecordUsage(u64 address, u64 size, bool write_access); |
|||
bool IsHotRegion(u64 address, u64 size) const; |
|||
void EvictRegion(u64 address, u64 size); |
|||
void ClearHistory(); |
|||
|
|||
private: |
|||
struct MemoryAccess { |
|||
u64 address; |
|||
u64 size; |
|||
bool write_access; |
|||
u64 timestamp; |
|||
}; |
|||
|
|||
std::vector<MemoryAccess> access_history; |
|||
const size_t max_history; |
|||
u64 current_timestamp{0}; |
|||
mutable std::mutex mutex; |
|||
}; |
|||
|
|||
#if defined(__linux__) || defined(__ANDROID__) || defined(_WIN32) |
|||
class FaultManagedAllocator { |
|||
public: |
|||
static constexpr size_t PageSize = 0x1000; |
|||
static constexpr size_t MaxPages = 16384; |
|||
|
|||
void Initialize(void* base, size_t size); |
|||
void* Translate(size_t addr); |
|||
void SaveSnapshot(const std::string& path); |
|||
void SaveDifferentialSnapshot(const std::string& path); |
|||
void ClearDirtySet(); |
|||
~FaultManagedAllocator(); |
|||
|
|||
private: |
|||
std::map<size_t, void*> page_map; |
|||
std::list<size_t> lru; |
|||
std::set<size_t> dirty_set; |
|||
std::unordered_map<size_t, std::vector<u8>> compressed_store; |
|||
std::mutex lock; |
|||
|
|||
#if defined(__linux__) || defined(__ANDROID__) |
|||
int uffd = -1; |
|||
std::atomic<bool> running{false}; |
|||
std::thread fault_handler; |
|||
void FaultThread(); |
|||
#elif defined(_WIN32) |
|||
void* base_address = nullptr; |
|||
size_t memory_size = 0; |
|||
HANDLE exception_port = nullptr; |
|||
std::atomic<bool> running{false}; |
|||
std::thread exception_handler; |
|||
void ExceptionHandlerThread(); |
|||
static LONG WINAPI VectoredExceptionHandler(PEXCEPTION_POINTERS exception_info); |
|||
static FaultManagedAllocator* current_instance; |
|||
#endif |
|||
|
|||
void Touch(size_t addr); |
|||
void EnforceLimit(); |
|||
void* GetOrAlloc(size_t addr); |
|||
}; |
|||
#endif |
|||
|
|||
class HybridMemory { |
|||
public: |
|||
explicit HybridMemory(const Device& device, MemoryAllocator& allocator, size_t reuse_history = 32); |
|||
~HybridMemory(); |
|||
|
|||
void InitializeGuestMemory(void* base, size_t size); |
|||
void* TranslateAddress(size_t addr); |
|||
|
|||
ComputeBuffer CreateComputeBuffer(VkDeviceSize size, VkBufferUsageFlags usage, MemoryUsage memory_type); |
|||
|
|||
void SaveSnapshot(const std::string& path); |
|||
void SaveDifferentialSnapshot(const std::string& path); |
|||
void ResetDirtyTracking(); |
|||
|
|||
private: |
|||
const Device& device; |
|||
MemoryAllocator& memory_allocator; |
|||
PredictiveReuseManager reuse_manager; |
|||
|
|||
#if defined(__linux__) || defined(__ANDROID__) || defined(_WIN32) |
|||
FaultManagedAllocator fmaa; |
|||
#endif |
|||
}; |
|||
|
|||
} // namespace Vulkan |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue