Compare commits
3 Commits
layout-fix
...
file-watch
Author | SHA1 | Date | |
---|---|---|---|
aa79505ddd | |||
2b7faf60a3 | |||
fd32a82b4e |
@ -89,6 +89,8 @@ add_library(citra_common STATIC
|
||||
expected.h
|
||||
file_util.cpp
|
||||
file_util.h
|
||||
file_watcher.cpp
|
||||
file_watcher.h
|
||||
hash.h
|
||||
linear_disk_cache.h
|
||||
literals.h
|
||||
|
@ -534,7 +534,6 @@ u64 ScanDirectoryTree(const std::string& directory, FSTEntry& parent_entry,
|
||||
}
|
||||
|
||||
void GetAllFilesFromNestedEntries(FSTEntry& directory, std::vector<FSTEntry>& output) {
|
||||
std::vector<FSTEntry> files;
|
||||
for (auto& entry : directory.children) {
|
||||
if (entry.isDirectory) {
|
||||
GetAllFilesFromNestedEntries(entry, output);
|
||||
|
148
src/common/file_watcher.cpp
Normal file
148
src/common/file_watcher.cpp
Normal file
@ -0,0 +1,148 @@
|
||||
// Copyright 2023 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <windows.h>
|
||||
#include <thread>
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/file_watcher.h"
|
||||
|
||||
namespace Common {
|
||||
|
||||
static FileAction Win32ActionToFileAction(DWORD action) {
|
||||
switch (action) {
|
||||
case FILE_ACTION_ADDED:
|
||||
return FileAction::Added;
|
||||
case FILE_ACTION_REMOVED:
|
||||
return FileAction::Removed;
|
||||
case FILE_ACTION_MODIFIED:
|
||||
return FileAction::Modified;
|
||||
case FILE_ACTION_RENAMED_OLD_NAME:
|
||||
return FileAction::RenamedOldName;
|
||||
case FILE_ACTION_RENAMED_NEW_NAME:
|
||||
return FileAction::RenamedNewName;
|
||||
default:
|
||||
UNREACHABLE_MSG("Unknown action {}", action);
|
||||
return FileAction::Invalid;
|
||||
}
|
||||
}
|
||||
|
||||
struct FileWatcher::Impl {
|
||||
explicit Impl(const std::string& path, FileWatcher::Callback&& callback_)
|
||||
: callback{callback_} {
|
||||
// Create file handle for the directory we are watching.
|
||||
dir_handle =
|
||||
CreateFile(path.c_str(), FILE_LIST_DIRECTORY | GENERIC_READ,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING,
|
||||
FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, NULL);
|
||||
ASSERT_MSG(dir_handle != INVALID_HANDLE_VALUE, "Unable to create watch file");
|
||||
|
||||
// Create an event that will terminate the thread when fired.
|
||||
termination_event = CreateEvent(NULL, TRUE, FALSE, NULL);
|
||||
ASSERT_MSG(termination_event != INVALID_HANDLE_VALUE, "Unable to create watch event");
|
||||
|
||||
// Create an event that will wake up the watcher thread on filesystem changes.
|
||||
overlapped.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
|
||||
ASSERT_MSG(overlapped.hEvent != INVALID_HANDLE_VALUE, "Unable to create watch event");
|
||||
|
||||
// Create the watcher thread.
|
||||
watch_thread = std::thread([this] { WatcherThread(); });
|
||||
}
|
||||
|
||||
~Impl() {
|
||||
// Signal watcher thread to terminate.
|
||||
SetEvent(termination_event);
|
||||
|
||||
// Wait for said termination.
|
||||
if (watch_thread.joinable()) {
|
||||
watch_thread.join();
|
||||
}
|
||||
|
||||
// Close used handles.
|
||||
CancelIo(dir_handle);
|
||||
GetOverlappedResult(dir_handle, &overlapped, &num_bytes_read, TRUE);
|
||||
CloseHandle(termination_event);
|
||||
CloseHandle(overlapped.hEvent);
|
||||
}
|
||||
|
||||
void WatcherThread() {
|
||||
const std::array wait_handles{overlapped.hEvent, termination_event};
|
||||
while (is_running) {
|
||||
bool result =
|
||||
ReadDirectoryChangesW(dir_handle, buffer.data(), buffer.size(), TRUE,
|
||||
FILE_NOTIFY_CHANGE_FILE_NAME |
|
||||
FILE_NOTIFY_CHANGE_DIR_NAME |
|
||||
FILE_NOTIFY_CHANGE_LAST_WRITE, NULL, &overlapped, NULL);
|
||||
ASSERT_MSG(result, "Unable to read directory changes: {}", GetLastErrorMsg());
|
||||
|
||||
// Sleep until we receive a file changed notification or a termination event.
|
||||
switch (
|
||||
WaitForMultipleObjects(wait_handles.size(), wait_handles.data(), FALSE, INFINITE)) {
|
||||
case WAIT_OBJECT_0: {
|
||||
// Retrieve asynchronously the data from ReadDirectoryChangesW.
|
||||
result = GetOverlappedResult(dir_handle, &overlapped, &num_bytes_read, TRUE);
|
||||
ASSERT_MSG(result, "Unable to retrieve overlapped result: {}", GetLastErrorMsg());
|
||||
|
||||
// Notify about file changes.
|
||||
NotifyFileChanges();
|
||||
break;
|
||||
}
|
||||
case WAIT_OBJECT_0 + 1:
|
||||
is_running = false;
|
||||
break;
|
||||
case WAIT_FAILED:
|
||||
UNREACHABLE_MSG("Failed waiting for file watcher events: {}", GetLastErrorMsg());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NotifyFileChanges() {
|
||||
// If no data was read we have nothing to do.
|
||||
if (num_bytes_read == 0) [[unlikely]] {
|
||||
return;
|
||||
}
|
||||
|
||||
u32 next_entry_offset{};
|
||||
while (true) {
|
||||
// Retrieve file notify information.
|
||||
auto fni = reinterpret_cast<FILE_NOTIFY_INFORMATION*>(buffer.data() + next_entry_offset);
|
||||
|
||||
// Call the callback function informing about the change.
|
||||
if (fni->Action != 0) {
|
||||
std::string file_name(fni->FileNameLength / sizeof(WCHAR), ' ');
|
||||
WideCharToMultiByte(CP_UTF8, 0, fni->FileName,
|
||||
fni->FileNameLength / sizeof(WCHAR), file_name.data(), file_name.size(), NULL, NULL);
|
||||
const FileAction action = Win32ActionToFileAction(fni->Action);
|
||||
callback(file_name, action);
|
||||
}
|
||||
|
||||
// If this was the last action, break.
|
||||
if (fni->NextEntryOffset == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Move to next fni structure.
|
||||
next_entry_offset += fni->NextEntryOffset;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
static constexpr size_t DirectoryWatcherBufferSize = 4096;
|
||||
FileWatcher::Callback callback;
|
||||
HANDLE dir_handle{};
|
||||
HANDLE termination_event{};
|
||||
OVERLAPPED overlapped{};
|
||||
std::array<u8, DirectoryWatcherBufferSize> buffer{};
|
||||
std::atomic_bool is_running{true};
|
||||
DWORD num_bytes_read{};
|
||||
std::thread watch_thread;
|
||||
};
|
||||
|
||||
FileWatcher::FileWatcher(const std::string& log_dir, Callback&& callback)
|
||||
: impl{std::make_unique<Impl>(log_dir, std::move(callback))} {}
|
||||
|
||||
FileWatcher::~FileWatcher() = default;
|
||||
|
||||
} // namespace Common
|
33
src/common/file_watcher.h
Normal file
33
src/common/file_watcher.h
Normal file
@ -0,0 +1,33 @@
|
||||
// Copyright 2023 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
|
||||
namespace Common {
|
||||
|
||||
enum class FileAction : u8 {
|
||||
Added,
|
||||
Removed,
|
||||
Modified,
|
||||
RenamedOldName,
|
||||
RenamedNewName,
|
||||
Invalid = std::numeric_limits<u8>::max(),
|
||||
};
|
||||
|
||||
class FileWatcher {
|
||||
using Callback = std::function<void(const std::string&, FileAction)>;
|
||||
|
||||
public:
|
||||
explicit FileWatcher(const std::string& log_dir, Callback&& callback);
|
||||
~FileWatcher();
|
||||
|
||||
private:
|
||||
struct Impl;
|
||||
std::unique_ptr<Impl> impl;
|
||||
};
|
||||
|
||||
} // namespace Common
|
@ -1,9 +1,10 @@
|
||||
// Copyright 2023 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma clang optimize off
|
||||
#include <json.hpp>
|
||||
#include "common/file_util.h"
|
||||
#include "common/file_watcher.h"
|
||||
#include "common/memory_detect.h"
|
||||
#include "common/microprofile.h"
|
||||
#include "common/settings.h"
|
||||
@ -16,6 +17,7 @@
|
||||
#include "video_core/custom_textures/custom_tex_manager.h"
|
||||
#include "video_core/rasterizer_cache/surface_params.h"
|
||||
#include "video_core/rasterizer_cache/utils.h"
|
||||
#include "video_core/renderer_base.h"
|
||||
|
||||
namespace VideoCore {
|
||||
|
||||
@ -63,17 +65,17 @@ void CustomTexManager::TickFrame() {
|
||||
return;
|
||||
}
|
||||
std::size_t num_uploads = 0;
|
||||
for (auto it = async_uploads.begin(); it != async_uploads.end();) {
|
||||
for (auto it = async_actions.begin(); it != async_actions.end();) {
|
||||
if (num_uploads >= MAX_UPLOADS_PER_TICK) {
|
||||
return;
|
||||
}
|
||||
switch (it->material->state) {
|
||||
case DecodeState::Decoded:
|
||||
it->func();
|
||||
it->func(it->material);
|
||||
num_uploads++;
|
||||
[[fallthrough]];
|
||||
case DecodeState::Failed:
|
||||
it = async_uploads.erase(it);
|
||||
it = async_actions.erase(it);
|
||||
continue;
|
||||
default:
|
||||
it++;
|
||||
@ -102,7 +104,7 @@ void CustomTexManager::FindCustomTextures() {
|
||||
if (file.isDirectory) {
|
||||
continue;
|
||||
}
|
||||
custom_textures.push_back(std::make_unique<CustomTexture>(image_interface));
|
||||
custom_textures.emplace_back(std::make_unique<CustomTexture>(image_interface));
|
||||
CustomTexture* const texture{custom_textures.back().get()};
|
||||
if (!ParseFilename(file, texture)) {
|
||||
continue;
|
||||
@ -292,16 +294,17 @@ Material* CustomTexManager::GetMaterial(u64 data_hash) {
|
||||
return it->second.get();
|
||||
}
|
||||
|
||||
bool CustomTexManager::Decode(Material* material, std::function<bool()>&& upload) {
|
||||
bool CustomTexManager::Decode(Material* material, AsyncFunc&& upload) {
|
||||
if (!async_custom_loading) {
|
||||
material->LoadFromDisk(flip_png_files);
|
||||
return upload();
|
||||
return upload(material);
|
||||
}
|
||||
if (material->IsUnloaded()) {
|
||||
material->state = DecodeState::Pending;
|
||||
workers->QueueWork([material, this] { material->LoadFromDisk(flip_png_files); });
|
||||
}
|
||||
async_uploads.push_back({
|
||||
std::scoped_lock lock{async_actions_mutex};
|
||||
async_actions.push_back({
|
||||
.material = material,
|
||||
.func = std::move(upload),
|
||||
});
|
||||
@ -374,6 +377,14 @@ std::vector<FileUtil::FSTEntry> CustomTexManager::GetTextures(u64 title_id) {
|
||||
FileUtil::CreateFullPath(load_path);
|
||||
}
|
||||
|
||||
const auto callback = [this](const std::string& file, Common::FileAction action) {
|
||||
OnFileAction(file, action);
|
||||
};
|
||||
|
||||
// Create a file watcher to monitor any changes to the textures directory for hot-reloading.
|
||||
file_watcher = std::make_unique<Common::FileWatcher>(load_path, callback);
|
||||
|
||||
// Retrieve all texture files.
|
||||
FileUtil::FSTEntry texture_dir;
|
||||
std::vector<FileUtil::FSTEntry> textures;
|
||||
FileUtil::ScanDirectoryTree(load_path, texture_dir, 64);
|
||||
@ -386,4 +397,24 @@ void CustomTexManager::CreateWorkers() {
|
||||
workers = std::make_unique<Common::ThreadWorker>(num_workers, "Custom textures");
|
||||
}
|
||||
|
||||
void CustomTexManager::OnFileAction(const std::string& file, Common::FileAction action) {
|
||||
const auto invalidate = [this](const Material* material) -> bool {
|
||||
for (const SurfaceParams* params : material->loaded_to) {
|
||||
system.Renderer().Rasterizer()->InvalidateRegion(params->addr, params->size);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
const std::string filename{FileUtil::GetFilename(file)};
|
||||
const auto& hashes = path_to_hash_map[filename];
|
||||
std::scoped_lock lock{async_actions_mutex};
|
||||
|
||||
for (const Hash hash : hashes) {
|
||||
async_actions.push_back({
|
||||
.material = material_map[hash].get(),
|
||||
.func = std::move(invalidate),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace VideoCore
|
||||
|
@ -12,6 +12,11 @@
|
||||
#include "video_core/custom_textures/material.h"
|
||||
#include "video_core/rasterizer_interface.h"
|
||||
|
||||
namespace Common {
|
||||
class FileWatcher;
|
||||
enum class FileAction : u8;
|
||||
} // namespace Common
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
}
|
||||
@ -24,12 +29,10 @@ namespace VideoCore {
|
||||
|
||||
class SurfaceParams;
|
||||
|
||||
struct AsyncUpload {
|
||||
const Material* material;
|
||||
std::function<bool()> func;
|
||||
};
|
||||
|
||||
class CustomTexManager {
|
||||
using Hash = u64;
|
||||
using AsyncFunc = std::function<bool(const Material*)>;
|
||||
|
||||
public:
|
||||
explicit CustomTexManager(Core::System& system);
|
||||
~CustomTexManager();
|
||||
@ -57,7 +60,7 @@ public:
|
||||
Material* GetMaterial(u64 data_hash);
|
||||
|
||||
/// Decodes the textures in material to a consumable format and uploads it.
|
||||
bool Decode(Material* material, std::function<bool()>&& upload);
|
||||
bool Decode(Material* material, AsyncFunc&& func);
|
||||
|
||||
/// True when mipmap uploads should be skipped (legacy packs only)
|
||||
bool SkipMipmaps() const noexcept {
|
||||
@ -79,15 +82,25 @@ private:
|
||||
/// Creates the thread workers.
|
||||
void CreateWorkers();
|
||||
|
||||
/// Callback for when a custom texture file is modified.
|
||||
void OnFileAction(const std::string& file, Common::FileAction action);
|
||||
|
||||
private:
|
||||
struct AsyncAction {
|
||||
const Material* material;
|
||||
AsyncFunc func;
|
||||
};
|
||||
|
||||
Core::System& system;
|
||||
Frontend::ImageInterface& image_interface;
|
||||
std::unordered_set<u64> dumped_textures;
|
||||
std::unordered_map<u64, std::unique_ptr<Material>> material_map;
|
||||
std::unordered_map<std::string, std::vector<u64>> path_to_hash_map;
|
||||
std::unordered_set<Hash> dumped_textures;
|
||||
std::unordered_map<Hash, std::unique_ptr<Material>> material_map;
|
||||
std::unordered_map<std::string, std::vector<Hash>> path_to_hash_map;
|
||||
std::vector<std::unique_ptr<CustomTexture>> custom_textures;
|
||||
std::list<AsyncUpload> async_uploads;
|
||||
std::mutex async_actions_mutex;
|
||||
std::list<AsyncAction> async_actions;
|
||||
std::unique_ptr<Common::ThreadWorker> workers;
|
||||
std::unique_ptr<Common::FileWatcher> file_watcher;
|
||||
bool textures_loaded{false};
|
||||
bool async_custom_loading{true};
|
||||
bool skip_mipmap{false};
|
||||
|
@ -18,6 +18,8 @@ class ImageInterface;
|
||||
|
||||
namespace VideoCore {
|
||||
|
||||
class SurfaceParams;
|
||||
|
||||
enum class MapType : u32 {
|
||||
Color = 0,
|
||||
Normal = 1,
|
||||
@ -72,6 +74,7 @@ struct Material {
|
||||
u64 hash;
|
||||
CustomPixelFormat format;
|
||||
std::array<CustomTexture*, MAX_MAPS> textures;
|
||||
mutable std::vector<SurfaceParams*> loaded_to;
|
||||
std::atomic<DecodeState> state{};
|
||||
|
||||
void LoadFromDisk(bool flip_png) noexcept;
|
||||
|
@ -653,7 +653,6 @@ FramebufferHelper<T> RasterizerCache<T>::GetFramebufferSurfaces(bool using_color
|
||||
static_cast<u32>(std::clamp(viewport_rect.bottom, 0, framebuffer_height)),
|
||||
};
|
||||
|
||||
// get color and depth surfaces
|
||||
SurfaceParams color_params;
|
||||
color_params.is_tiled = true;
|
||||
color_params.res_scale = resolution_scale_factor;
|
||||
@ -672,14 +671,6 @@ FramebufferHelper<T> RasterizerCache<T>::GetFramebufferSurfaces(bool using_color
|
||||
auto color_vp_interval = color_params.GetSubRectInterval(viewport_clamped);
|
||||
auto depth_vp_interval = depth_params.GetSubRectInterval(viewport_clamped);
|
||||
|
||||
// Make sure that framebuffers don't overlap if both color and depth are being used
|
||||
if (using_color_fb && using_depth_fb &&
|
||||
boost::icl::length(color_vp_interval & depth_vp_interval)) {
|
||||
LOG_CRITICAL(HW_GPU, "Color and depth framebuffer memory regions overlap; "
|
||||
"overlapping framebuffers not supported!");
|
||||
using_depth_fb = false;
|
||||
}
|
||||
|
||||
Common::Rectangle<u32> color_rect{};
|
||||
SurfaceId color_id{};
|
||||
u32 color_level{};
|
||||
@ -713,11 +704,13 @@ FramebufferHelper<T> RasterizerCache<T>::GetFramebufferSurfaces(bool using_color
|
||||
|
||||
if (color_id) {
|
||||
color_level = color_surface->LevelOf(color_params.addr);
|
||||
color_surface->flags |= SurfaceFlagBits::RenderTarget;
|
||||
ValidateSurface(color_id, boost::icl::first(color_vp_interval),
|
||||
boost::icl::length(color_vp_interval));
|
||||
}
|
||||
if (depth_id) {
|
||||
depth_level = depth_surface->LevelOf(depth_params.addr);
|
||||
depth_surface->flags |= SurfaceFlagBits::RenderTarget;
|
||||
ValidateSurface(depth_id, boost::icl::first(depth_vp_interval),
|
||||
boost::icl::length(depth_vp_interval));
|
||||
}
|
||||
@ -991,7 +984,9 @@ void RasterizerCache<T>::UploadSurface(Surface& surface, SurfaceInterval interva
|
||||
DecodeTexture(load_info, load_info.addr, load_info.end, upload_data, staging.mapped,
|
||||
runtime.NeedsConversion(surface.pixel_format));
|
||||
|
||||
if (dump_textures && False(surface.flags & SurfaceFlagBits::Custom)) {
|
||||
const bool should_dump = False(surface.flags & SurfaceFlagBits::Custom) &&
|
||||
False(surface.flags & SurfaceFlagBits::RenderTarget);
|
||||
if (dump_textures && should_dump) {
|
||||
const u64 hash = ComputeHash(load_info, upload_data);
|
||||
const u32 level = surface.LevelOf(load_info.addr);
|
||||
custom_tex_manager.DumpTexture(load_info, level, upload_data, hash);
|
||||
@ -1048,7 +1043,7 @@ bool RasterizerCache<T>::UploadCustomSurface(SurfaceId surface_id, SurfaceInterv
|
||||
|
||||
surface.flags |= SurfaceFlagBits::Custom;
|
||||
|
||||
const auto upload = [this, level, surface_id, material]() -> bool {
|
||||
const auto upload = [this, level, surface_id](const Material* material) -> bool {
|
||||
ASSERT_MSG(True(slot_surfaces[surface_id].flags & SurfaceFlagBits::Custom),
|
||||
"Surface is not suitable for custom upload, aborting!");
|
||||
if (!slot_surfaces[surface_id].IsCustom()) {
|
||||
|
@ -11,7 +11,18 @@ namespace VideoCore {
|
||||
|
||||
SurfaceBase::SurfaceBase(const SurfaceParams& params) : SurfaceParams{params} {}
|
||||
|
||||
SurfaceBase::~SurfaceBase() = default;
|
||||
SurfaceBase::SurfaceBase(const SurfaceParams& params, const Material* mat)
|
||||
: SurfaceParams{params}, material{mat} {
|
||||
custom_format = material->format;
|
||||
material->loaded_to.push_back(this);
|
||||
}
|
||||
|
||||
SurfaceBase::~SurfaceBase() {
|
||||
if (!material) {
|
||||
return;
|
||||
}
|
||||
std::erase_if(material->loaded_to, [this](SurfaceParams* params) { return params == this; });
|
||||
}
|
||||
|
||||
bool SurfaceBase::CanFill(const SurfaceParams& dest_surface, SurfaceInterval fill_interval) const {
|
||||
if (type == SurfaceType::Fill && IsRegionValid(fill_interval) &&
|
||||
|
@ -15,17 +15,19 @@ using SurfaceRegions = boost::icl::interval_set<PAddr, std::less, SurfaceInterva
|
||||
struct Material;
|
||||
|
||||
enum class SurfaceFlagBits : u32 {
|
||||
Registered = 1 << 0, ///< Surface is registed in the rasterizer cache.
|
||||
Picked = 1 << 1, ///< Surface has been picked when searching for a match.
|
||||
Tracked = 1 << 2, ///< Surface is part of a texture cube and should be tracked.
|
||||
Custom = 1 << 3, ///< Surface texture has been replaced with a custom texture.
|
||||
ShadowMap = 1 << 4, ///< Surface is used during shadow rendering.
|
||||
Registered = 1 << 0, ///< Surface is registed in the rasterizer cache.
|
||||
Picked = 1 << 1, ///< Surface has been picked when searching for a match.
|
||||
Tracked = 1 << 2, ///< Surface is part of a texture cube and should be tracked.
|
||||
Custom = 1 << 3, ///< Surface texture has been replaced with a custom texture.
|
||||
ShadowMap = 1 << 4, ///< Surface is used during shadow rendering.
|
||||
RenderTarget = 1 << 5, ///< Surface was a render target.
|
||||
};
|
||||
DECLARE_ENUM_FLAG_OPERATORS(SurfaceFlagBits);
|
||||
|
||||
class SurfaceBase : public SurfaceParams {
|
||||
public:
|
||||
SurfaceBase(const SurfaceParams& params);
|
||||
explicit SurfaceBase(const SurfaceParams& params);
|
||||
explicit SurfaceBase(const SurfaceParams& params, const Material* mat);
|
||||
~SurfaceBase();
|
||||
|
||||
/// Returns true when this surface can be used to fill the fill_interval of dest_surface
|
||||
|
@ -333,7 +333,7 @@ Surface::Surface(TextureRuntime& runtime_, const VideoCore::SurfaceParams& param
|
||||
|
||||
Surface::Surface(TextureRuntime& runtime, const VideoCore::SurfaceBase& surface,
|
||||
const VideoCore::Material* mat)
|
||||
: SurfaceBase{surface}, tuple{runtime.GetFormatTuple(mat->format)} {
|
||||
: SurfaceBase{surface, mat}, tuple{runtime.GetFormatTuple(mat->format)} {
|
||||
if (mat && !driver->IsCustomFormatSupported(mat->format)) {
|
||||
return;
|
||||
}
|
||||
@ -342,9 +342,6 @@ Surface::Surface(TextureRuntime& runtime, const VideoCore::SurfaceBase& surface,
|
||||
const GLenum target =
|
||||
texture_type == VideoCore::TextureType::CubeMap ? GL_TEXTURE_CUBE_MAP : GL_TEXTURE_2D;
|
||||
|
||||
custom_format = mat->format;
|
||||
material = mat;
|
||||
|
||||
textures[0] = MakeHandle(target, mat->width, mat->height, levels, tuple, DebugName(false));
|
||||
if (res_scale != 1) {
|
||||
textures[1] = MakeHandle(target, mat->width, mat->height, levels, DEFAULT_TUPLE,
|
||||
|
@ -3,10 +3,10 @@
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/file_watcher.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/microprofile.h"
|
||||
#include "common/settings.h"
|
||||
#include "common/texture.h"
|
||||
#include "core/core.h"
|
||||
#include "core/frontend/emu_window.h"
|
||||
#include "core/hw/gpu.h"
|
||||
@ -19,7 +19,6 @@
|
||||
#include "video_core/host_shaders/vulkan_present_frag_spv.h"
|
||||
#include "video_core/host_shaders/vulkan_present_interlaced_frag_spv.h"
|
||||
#include "video_core/host_shaders/vulkan_present_vert_spv.h"
|
||||
#include "vulkan/vulkan_format_traits.hpp"
|
||||
|
||||
#include <vk_mem_alloc.h>
|
||||
|
||||
|
@ -750,7 +750,7 @@ Surface::Surface(TextureRuntime& runtime_, const VideoCore::SurfaceParams& param
|
||||
|
||||
Surface::Surface(TextureRuntime& runtime_, const VideoCore::SurfaceBase& surface,
|
||||
const VideoCore::Material* mat)
|
||||
: SurfaceBase{surface}, runtime{&runtime_}, instance{&runtime_.GetInstance()},
|
||||
: SurfaceBase{surface, mat}, runtime{&runtime_}, instance{&runtime_.GetInstance()},
|
||||
scheduler{&runtime_.GetScheduler()}, traits{instance->GetTraits(mat->format)} {
|
||||
if (!traits.transfer_support) {
|
||||
return;
|
||||
@ -791,9 +791,6 @@ Surface::Surface(TextureRuntime& runtime_, const VideoCore::SurfaceBase& surface
|
||||
vk::PipelineStageFlagBits::eTopOfPipe,
|
||||
vk::DependencyFlagBits::eByRegion, {}, {}, barriers);
|
||||
});
|
||||
|
||||
custom_format = mat->format;
|
||||
material = mat;
|
||||
}
|
||||
|
||||
Surface::~Surface() {
|
||||
|
Reference in New Issue
Block a user