VideoCommon: add asset library source for the graphics mod editor that pulls assets from memory instead of from disk

This commit is contained in:
iwubcode 2023-08-17 01:38:48 -05:00
commit 802877cb63
2 changed files with 988 additions and 0 deletions

View file

@ -0,0 +1,895 @@
// Copyright 2023 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "VideoCommon/GraphicsModEditor/EditorAssetSource.h"
#include <chrono>
#include <optional>
#include <fmt/format.h>
#include <fmt/std.h>
#include <picojson.h>
#include "Common/FileUtil.h"
#include "Common/IOFile.h"
#include "Common/JsonUtil.h"
#include "Common/StringUtil.h"
#include "Common/VariantUtil.h"
#include "Core/System.h"
#include "VideoCommon/AbstractGfx.h"
#include "VideoCommon/Assets/CustomAssetLoader.h"
#include "VideoCommon/Assets/CustomTextureData.h"
#include "VideoCommon/GraphicsModSystem/Runtime/CustomResourceManager.h"
namespace
{
std::size_t GetAssetSize(const VideoCommon::CustomTextureData& data)
{
std::size_t total = 0;
for (const auto& slice : data.m_slices)
{
for (const auto& level : slice.m_levels)
{
total += level.data.size();
}
}
return total;
}
std::chrono::system_clock::time_point FileTimeToSysTime(std::filesystem::file_time_type file_time)
{
// Note: all compilers should switch to chrono::clock_cast
// once it is available for use
const auto system_time_now = std::chrono::system_clock::now();
const auto file_time_now = decltype(file_time)::clock::now();
return std::chrono::time_point_cast<std::chrono::system_clock::duration>(
file_time - file_time_now + system_time_now);
}
std::optional<picojson::object> GetJsonObjectFromFile(const std::filesystem::path& path)
{
picojson::value root;
std::string error;
if (!JsonFromFile(PathToString(path), &root, &error))
{
ERROR_LOG_FMT(VIDEO, "Json file at path '{}' has error '{}'!", PathToString(path), error);
return std::nullopt;
}
if (!root.is<picojson::object>())
{
return std::nullopt;
}
return root.get<picojson::object>();
}
} // namespace
namespace GraphicsModEditor
{
EditorAssetSource::LoadInfo EditorAssetSource::LoadTexture(const AssetID& asset_id,
VideoCommon::TextureData* data)
{
std::filesystem::path texture_path;
{
std::lock_guard lk(m_asset_lock);
const auto asset = GetAssetFromID(asset_id);
if (!asset)
{
ERROR_LOG_FMT(VIDEO, "Asset with id '{}' not found!", asset_id);
return {};
}
if (const auto it = asset->m_asset_map.find("texture"); it != asset->m_asset_map.end())
{
texture_path = it->second;
}
else
{
ERROR_LOG_FMT(VIDEO, "Asset '{}' error - could not find 'texture' in asset map!", asset_id);
return {};
}
if (auto texture_data = std::get_if<std::unique_ptr<VideoCommon::TextureData>>(&asset->m_data))
{
data->m_sampler = texture_data->get()->m_sampler;
data->m_type = texture_data->get()->m_type;
}
}
auto ext = PathToString(texture_path.extension());
Common::ToLower(&ext);
if (ext == ".dds")
{
if (!LoadDDSTexture(&data->m_texture, PathToString(texture_path)))
{
ERROR_LOG_FMT(VIDEO, "Asset '{}' error - could not load dds texture!", asset_id);
return {};
}
// SetAssetPreviewData(asset_id, data->m_texture);
return LoadInfo{GetAssetSize(data->m_texture), GetLastAssetWriteTime(asset_id)};
}
else if (ext == ".png")
{
// PNG could support more complicated texture types in the future
// but for now just error
if (data->m_type != VideoCommon::TextureData::Type::Type_Texture2D)
{
ERROR_LOG_FMT(VIDEO, "Asset '{}' error - PNG is not supported for texture type '{}'!",
asset_id, data->m_type);
return {};
}
// If we have no slices, create one
if (data->m_texture.m_slices.empty())
data->m_texture.m_slices.push_back({});
auto& slice = data->m_texture.m_slices[0];
// If we have no levels, create one to pass into LoadPNGTexture
if (slice.m_levels.empty())
slice.m_levels.push_back({});
if (!LoadPNGTexture(&slice.m_levels[0], PathToString(texture_path)))
{
ERROR_LOG_FMT(VIDEO, "Asset '{}' error - could not load png texture!", asset_id);
return {};
}
// SetAssetPreviewData(asset_id, data->m_texture);
return LoadInfo{GetAssetSize(data->m_texture), GetLastAssetWriteTime(asset_id)};
}
return {};
}
EditorAssetSource::LoadInfo EditorAssetSource::LoadPixelShader(const AssetID& asset_id,
VideoCommon::PixelShaderData* data)
{
std::lock_guard lk(m_asset_lock);
auto asset = GetAssetFromID(asset_id);
if (asset)
{
if (auto pixel_shader_data =
std::get_if<std::unique_ptr<VideoCommon::PixelShaderData>>(&asset->m_data))
{
if (const auto it = asset->m_asset_map.find("shader"); it != asset->m_asset_map.end())
{
if (File::ReadFileToString(PathToString(it->second),
pixel_shader_data->get()->m_shader_source))
{
*data = *pixel_shader_data->get();
return LoadInfo{sizeof(VideoCommon::PixelShaderData), GetLastAssetWriteTime(asset_id)};
}
}
}
}
return {};
}
EditorAssetSource::LoadInfo EditorAssetSource::LoadShader(const AssetID& asset_id,
VideoCommon::RasterShaderData* data)
{
std::lock_guard lk(m_asset_lock);
auto asset = GetAssetFromID(asset_id);
if (asset)
{
if (auto shader_data =
std::get_if<std::unique_ptr<VideoCommon::RasterShaderData>>(&asset->m_data))
{
if (const auto it = asset->m_asset_map.find("vertex_shader"); it != asset->m_asset_map.end())
{
if (!File::ReadFileToString(PathToString(it->second), shader_data->get()->m_vertex_source))
{
return {};
}
}
if (const auto it = asset->m_asset_map.find("pixel_shader"); it != asset->m_asset_map.end())
{
if (!File::ReadFileToString(PathToString(it->second), shader_data->get()->m_pixel_source))
{
return {};
}
}
if (shader_data->get()->m_vertex_source != "" || shader_data->get()->m_pixel_source != "")
{
*data = *shader_data->get();
return LoadInfo{sizeof(VideoCommon::RasterShaderData), GetLastAssetWriteTime(asset_id)};
}
}
}
return {};
}
EditorAssetSource::LoadInfo EditorAssetSource::LoadMaterial(const AssetID& asset_id,
VideoCommon::MaterialData* data)
{
std::lock_guard lk(m_asset_lock);
auto asset = GetAssetFromID(asset_id);
if (asset)
{
if (auto material_data =
std::get_if<std::unique_ptr<VideoCommon::MaterialData>>(&asset->m_data))
{
*data = *material_data->get();
return LoadInfo{sizeof(VideoCommon::MaterialData), asset->m_last_data_write};
}
}
return {};
}
EditorAssetSource::LoadInfo EditorAssetSource::LoadMaterial(const AssetID& asset_id,
VideoCommon::RasterMaterialData* data)
{
std::lock_guard lk(m_asset_lock);
auto asset = GetAssetFromID(asset_id);
if (asset)
{
if (auto material_data =
std::get_if<std::unique_ptr<VideoCommon::RasterMaterialData>>(&asset->m_data))
{
*data = *material_data->get();
return LoadInfo{sizeof(VideoCommon::RasterMaterialData), asset->m_last_data_write};
}
}
return {};
}
EditorAssetSource::LoadInfo EditorAssetSource::LoadMesh(const AssetID& asset_id,
VideoCommon::MeshData* data)
{
std::filesystem::path mesh_path;
{
std::lock_guard lk(m_asset_lock);
const auto asset = GetAssetFromID(asset_id);
if (!asset)
{
ERROR_LOG_FMT(VIDEO, "Asset with id '{}' not found!", asset_id);
return {};
}
if (const auto it = asset->m_asset_map.find("mesh"); it != asset->m_asset_map.end())
{
mesh_path = it->second;
}
else
{
ERROR_LOG_FMT(VIDEO, "Asset '{}' error - could not find 'mesh' in asset map!", asset_id);
return {};
}
if (auto mesh_data = std::get_if<std::unique_ptr<VideoCommon::MeshData>>(&asset->m_data))
{
data->m_mesh_material_to_material_asset_id =
mesh_data->get()->m_mesh_material_to_material_asset_id;
}
}
auto ext = PathToString(mesh_path.extension());
Common::ToLower(&ext);
if (ext == ".dolmesh")
{
File::IOFile file(PathToString(mesh_path), "rb");
std::vector<u8> bytes;
bytes.reserve(file.GetSize());
file.ReadBytes(bytes.data(), file.GetSize());
if (!VideoCommon::MeshData::FromDolphinMesh(bytes, data))
{
ERROR_LOG_FMT(VIDEO, "Asset '{}' error - failed to load the mesh file '{}',", asset_id,
PathToString(mesh_path));
return {};
}
// SetAssetPreviewData(asset_id, data->m_texture);
return LoadInfo{1, GetLastAssetWriteTime(asset_id)};
}
return {};
}
EditorAssetSource::LoadInfo EditorAssetSource::LoadRenderTarget(const AssetID& asset_id,
VideoCommon::RenderTargetData* data)
{
std::lock_guard lk(m_asset_lock);
auto asset = GetAssetFromID(asset_id);
if (asset)
{
if (auto render_target_data =
std::get_if<std::unique_ptr<VideoCommon::RenderTargetData>>(&asset->m_data))
{
*data = *render_target_data->get();
return LoadInfo{sizeof(VideoCommon::RenderTargetData), asset->m_last_data_write};
}
}
return {};
}
EditorAssetSource::TimeType EditorAssetSource::GetLastAssetWriteTime(const AssetID& asset_id) const
{
std::lock_guard lk(m_asset_lock);
auto asset = GetAssetFromID(asset_id);
if (asset)
{
CustomAssetLibrary::TimeType max_entry = asset->m_last_data_write;
for (const auto& [name, path] : asset->m_asset_map)
{
std::error_code ec;
const auto tp = std::filesystem::last_write_time(path, ec);
if (ec)
continue;
auto tp_sys = FileTimeToSysTime(tp);
if (tp_sys > max_entry)
max_entry = tp_sys;
}
return max_entry;
}
return {};
}
EditorAsset* EditorAssetSource::GetAssetFromPath(const std::filesystem::path& asset_path)
{
std::lock_guard lk(m_asset_lock);
const auto it = m_path_to_editor_asset.find(asset_path);
if (it == m_path_to_editor_asset.end())
return nullptr;
return &(*it->second);
}
const EditorAsset* EditorAssetSource::GetAssetFromID(const AssetID& asset_id) const
{
std::lock_guard lk(m_asset_lock);
const auto asset_it = m_asset_id_to_file_path.find(asset_id);
if (asset_it == m_asset_id_to_file_path.end())
return nullptr;
const auto it = m_path_to_editor_asset.find(asset_it->second);
if (it == m_path_to_editor_asset.end())
return nullptr;
return &(*it->second);
}
EditorAsset* EditorAssetSource::GetAssetFromID(const AssetID& asset_id)
{
std::lock_guard lk(m_asset_lock);
const auto asset_it = m_asset_id_to_file_path.find(asset_id);
if (asset_it == m_asset_id_to_file_path.end())
return nullptr;
const auto it = m_path_to_editor_asset.find(asset_it->second);
if (it == m_path_to_editor_asset.end())
return nullptr;
return &(*it->second);
}
void EditorAssetSource::AddAsset(const std::filesystem::path& asset_path)
{
std::string uuid_str =
fmt::format("{}", std::chrono::system_clock::now().time_since_epoch().count());
AddAsset(asset_path, std::move(uuid_str));
}
void EditorAssetSource::AddAsset(const std::filesystem::path& asset_path, AssetID uuid)
{
std::lock_guard lk(m_asset_lock);
EditorAsset asset;
asset.m_valid = true;
bool add = false;
const std::string filename = PathToString(asset_path.filename());
auto ext = PathToString(asset_path.extension());
Common::ToLower(&ext);
if (ext == ".dds" || ext == ".png")
{
auto texture_data = std::make_unique<VideoCommon::TextureData>();
// Metadata is optional and will be created
// if it doesn't exist
const auto root = asset_path.parent_path();
const auto name = asset_path.stem();
const auto extension = asset_path.extension();
auto result = root / name;
result += ".texture";
if (std::filesystem::exists(result))
{
if (auto json = GetJsonObjectFromFile(result))
{
VideoCommon::TextureData::FromJson(uuid, *json, texture_data.get());
}
}
else
{
texture_data->m_type = VideoCommon::TextureData::Type::Type_Texture2D;
// Write initial data to a file, so that if any changes are made
// they get picked up and written on save
picojson::object obj;
VideoCommon::TextureData::ToJson(&obj, *texture_data);
JsonToFile(PathToString(result), picojson::value{obj}, true);
}
asset.m_asset_map["metadata"] = result;
add = true;
asset.m_data = std::move(texture_data);
asset.m_data_type = Texture;
asset.m_asset_map["texture"] = asset_path;
}
else if (ext == ".glsl")
{
// Only valid if metadata file exists
const auto root = asset_path.parent_path();
const auto name = asset_path.stem();
const auto extension = asset_path.extension();
auto result = root / name;
result += ".shader";
if (std::filesystem::exists(result))
{
if (auto json = GetJsonObjectFromFile(result))
{
auto pixel_data = std::make_unique<VideoCommon::PixelShaderData>();
if (File::ReadFileToString(PathToString(asset_path), pixel_data->m_shader_source))
{
if (VideoCommon::PixelShaderData::FromJson(uuid, *json, pixel_data.get()))
{
add = true;
asset.m_data = std::move(pixel_data);
asset.m_data_type = PixelShader;
asset.m_asset_map["shader"] = asset_path;
asset.m_asset_map["metadata"] = result;
}
}
}
}
}
else if (ext == ".rastershader")
{
// Either the vertex or pixel shaders may exist
const auto root = asset_path.parent_path();
const auto name = asset_path.stem();
const auto extension = asset_path.extension();
const auto pixel_shader = (root / name).replace_extension(".ps.glsl");
const auto vertex_shader = (root / name).replace_extension(".vs.glsl");
if (auto json = GetJsonObjectFromFile(asset_path))
{
auto shader_data = std::make_unique<VideoCommon::RasterShaderData>();
bool uses_custom_shader = false;
if (File::ReadFileToString(PathToString(pixel_shader), shader_data->m_pixel_source))
{
asset.m_asset_map["pixel_shader"] = pixel_shader;
uses_custom_shader = true;
}
if (File::ReadFileToString(PathToString(vertex_shader), shader_data->m_vertex_source))
{
asset.m_asset_map["vertex_shader"] = vertex_shader;
uses_custom_shader = true;
}
if (uses_custom_shader &&
VideoCommon::RasterShaderData::FromJson(uuid, *json, shader_data.get()))
{
asset.m_data = std::move(shader_data);
asset.m_data_type = Shader;
asset.m_asset_map["metadata"] = asset_path;
add = true;
}
}
}
else if (ext == ".dolmesh")
{
auto mesh_data = std::make_unique<VideoCommon::MeshData>();
// Only valid if metadata file exists
const auto root = asset_path.parent_path();
const auto name = asset_path.stem();
const auto extension = asset_path.extension();
auto result = root / name;
result += ".metadata";
if (std::filesystem::exists(result))
{
if (auto json = GetJsonObjectFromFile(result))
{
VideoCommon::MeshData::FromJson(uuid, *json, mesh_data.get());
add = true;
asset.m_data = std::move(mesh_data);
asset.m_data_type = Mesh;
asset.m_asset_map["mesh"] = asset_path;
}
asset.m_asset_map["metadata"] = result;
}
}
else if (ext == ".material")
{
if (auto json = GetJsonObjectFromFile(asset_path))
{
auto material_data = std::make_unique<VideoCommon::MaterialData>();
if (VideoCommon::MaterialData::FromJson(uuid, *json, material_data.get()))
{
add = true;
asset.m_data = std::move(material_data);
asset.m_data_type = Material;
asset.m_asset_map["metadata"] = asset_path;
}
}
}
else if (ext == ".rastermaterial")
{
if (auto json = GetJsonObjectFromFile(asset_path))
{
auto material_data = std::make_unique<VideoCommon::RasterMaterialData>();
if (VideoCommon::RasterMaterialData::FromJson(uuid, *json, material_data.get()))
{
add = true;
asset.m_data = std::move(material_data);
asset.m_data_type = RasterMaterial;
asset.m_asset_map["metadata"] = asset_path;
}
}
}
else if (ext == ".rendertarget")
{
if (auto json = GetJsonObjectFromFile(asset_path))
{
auto render_target_data = std::make_unique<VideoCommon::RenderTargetData>();
if (VideoCommon::RenderTargetData::FromJson(uuid, *json, render_target_data.get()))
{
add = true;
asset.m_data = std::move(render_target_data);
asset.m_data_type = RenderTarget;
asset.m_asset_map["metadata"] = asset_path;
}
}
}
if (add)
{
asset.m_asset_id = uuid;
asset.m_asset_path = asset_path;
asset.m_last_data_write = std::chrono::system_clock::now();
m_assets.emplace_back(std::move(asset));
for (const auto& [name, path] : m_assets.back().m_asset_map)
{
m_path_to_editor_asset.insert_or_assign(path, std::prev(m_assets.end()));
}
m_asset_id_to_file_path.insert_or_assign(uuid, asset_path);
}
}
void EditorAssetSource::RemoveAsset(const std::filesystem::path& asset_path)
{
std::lock_guard lk(m_asset_lock);
if (const auto it = m_path_to_editor_asset.find(asset_path); it != m_path_to_editor_asset.end())
{
const auto& asset_it = it->second;
m_asset_id_to_file_path.erase(asset_it->m_asset_id);
// Make a copy as we are going to remove the found iterator
const auto asset_map = asset_it->m_asset_map;
for (const auto& [name, path] : asset_map)
{
m_path_to_editor_asset.erase(path);
}
m_assets.erase(asset_it);
}
}
bool EditorAssetSource::RenameAsset(const std::filesystem::path& old_path,
const std::filesystem::path& new_path)
{
// If old path and new path are the same
// abort the rename
if (old_path == new_path)
return true;
if (std::filesystem::exists(new_path))
return false;
std::lock_guard lk(m_asset_lock);
const auto it = m_path_to_editor_asset.find(old_path);
if (it == m_path_to_editor_asset.end())
return false;
it->second->m_asset_path = new_path;
auto ext = PathToString(old_path.extension());
Common::ToLower(&ext);
if (ext == ".dds" || ext == ".png")
{
const auto old_root = old_path.parent_path();
const auto old_name = old_path.stem();
auto old_result = old_root / old_name;
old_result += ".texture";
if (std::filesystem::exists(old_result))
{
const auto new_root = new_path.parent_path();
const auto new_name = new_path.stem();
auto new_result = new_root / new_name;
new_result += ".texture";
std::filesystem::rename(old_result, new_result);
it->second->m_asset_map["metadata"] = new_result;
}
it->second->m_asset_map["texture"] = new_path;
}
else if (ext == ".gltf")
{
const auto old_root = old_path.parent_path();
const auto old_name = old_path.stem();
auto old_result = old_root / old_name;
old_result += ".metadata";
if (std::filesystem::exists(old_result))
{
const auto new_root = new_path.parent_path();
const auto new_name = new_path.stem();
auto new_result = new_root / new_name;
new_result += ".metadata";
std::filesystem::rename(old_result, new_result);
it->second->m_asset_map["metadata"] = new_result;
}
it->second->m_asset_map["mesh"] = new_path;
}
else if (ext == ".rastershader")
{
it->second->m_asset_map["metadata"] = new_path;
const auto old_root = old_path.parent_path();
const auto old_name = old_path.stem();
const auto old_pixel_shader_path = (old_root / old_name).replace_extension(".ps.glsl");
if (std::filesystem::exists(old_pixel_shader_path))
{
const auto new_root = new_path.parent_path();
const auto new_name = new_path.stem();
const auto new_pixel_shader_path = (new_root / new_name).replace_extension(".ps.glsl");
std::filesystem::rename(old_pixel_shader_path, new_pixel_shader_path);
it->second->m_asset_map["pixel_shader"] = new_pixel_shader_path;
}
const auto old_vertex_shader_path = (old_root / old_name).replace_extension(".vs.glsl");
if (std::filesystem::exists(old_vertex_shader_path))
{
const auto new_root = new_path.parent_path();
const auto new_name = new_path.stem();
const auto new_vertex_shader_path = (new_root / new_name).replace_extension(".vs.glsl");
std::filesystem::rename(old_vertex_shader_path, new_vertex_shader_path);
it->second->m_asset_map["vertex_shader"] = new_vertex_shader_path;
}
}
else if (ext == ".material")
{
it->second->m_asset_map["metadata"] = new_path;
}
else if (ext == ".rastermaterial")
{
it->second->m_asset_map["metadata"] = new_path;
}
else if (ext == ".rendertarget")
{
it->second->m_asset_map["metadata"] = new_path;
}
m_asset_id_to_file_path[it->second->m_asset_id] = new_path;
for (const auto [name, path] : it->second->m_asset_map)
{
m_path_to_editor_asset.erase(path);
}
std::filesystem::rename(old_path, new_path);
return true;
}
void EditorAssetSource::AddAssets(
std::span<const GraphicsModSystem::Config::GraphicsModAsset> assets,
const std::filesystem::path& root)
{
for (const auto& asset : assets)
{
for (const auto& [name, path] : asset.m_map)
{
AddAsset(root / path, asset.m_asset_id);
}
}
}
std::vector<GraphicsModSystem::Config::GraphicsModAsset>
EditorAssetSource::GetAssets(const std::filesystem::path& root) const
{
std::lock_guard lk(m_asset_lock);
std::vector<GraphicsModSystem::Config::GraphicsModAsset> assets;
for (const auto& [asset_id, path] : m_asset_id_to_file_path)
{
GraphicsModSystem::Config::GraphicsModAsset asset_config;
asset_config.m_asset_id = asset_id;
if (const auto it = m_path_to_editor_asset.find(path); it != m_path_to_editor_asset.end())
{
for (const auto& [name, am_path] : it->second->m_asset_map)
{
asset_config.m_map[name] = std::filesystem::relative(am_path, root);
}
}
assets.push_back(std::move(asset_config));
}
return assets;
}
void EditorAssetSource::SaveAssetDataAsFiles() const
{
const auto save_json_to_file = [](const std::string& file_path,
const picojson::object& serialized_root) {
std::ofstream json_stream;
File::OpenFStream(json_stream, file_path, std::ios_base::out);
if (!json_stream.is_open())
{
ERROR_LOG_FMT(VIDEO, "Failed to open file '{}' for writing", file_path);
return;
}
const auto output = picojson::value{serialized_root}.serialize(true);
json_stream << output;
};
std::lock_guard lk(m_asset_lock);
for (const auto& pair : m_path_to_editor_asset)
{
// Workaround for some compilers not being able
// to capture structured bindings in lambda
const auto& asset = *pair.second;
std::visit(
overloaded{[&](const std::unique_ptr<VideoCommon::MaterialData>& material_data) {
if (const auto metadata_it = asset.m_asset_map.find("metadata");
metadata_it != asset.m_asset_map.end())
{
picojson::object serialized_root;
VideoCommon::MaterialData::ToJson(&serialized_root, *material_data);
save_json_to_file(PathToString(metadata_it->second), serialized_root);
}
},
[&](const std::unique_ptr<VideoCommon::RasterMaterialData>& material_data) {
if (const auto metadata_it = asset.m_asset_map.find("metadata");
metadata_it != asset.m_asset_map.end())
{
picojson::object serialized_root;
VideoCommon::RasterMaterialData::ToJson(&serialized_root, *material_data);
save_json_to_file(PathToString(metadata_it->second), serialized_root);
}
},
[&](const std::unique_ptr<VideoCommon::PixelShaderData>& pixel_shader_data) {
if (const auto metadata_it = asset.m_asset_map.find("metadata");
metadata_it != asset.m_asset_map.end())
{
picojson::object serialized_root;
VideoCommon::PixelShaderData::ToJson(serialized_root, *pixel_shader_data);
save_json_to_file(PathToString(metadata_it->second), serialized_root);
}
},
[&](const std::unique_ptr<VideoCommon::RasterShaderData>& shader_data) {
if (const auto metadata_it = asset.m_asset_map.find("metadata");
metadata_it != asset.m_asset_map.end())
{
picojson::object serialized_root;
VideoCommon::RasterShaderData::ToJson(serialized_root, *shader_data);
save_json_to_file(PathToString(metadata_it->second), serialized_root);
}
},
[&](const std::unique_ptr<VideoCommon::TextureData>& texture_data) {
if (const auto metadata_it = asset.m_asset_map.find("metadata");
metadata_it != asset.m_asset_map.end())
{
picojson::object serialized_root;
VideoCommon::TextureData::ToJson(&serialized_root, *texture_data);
save_json_to_file(PathToString(metadata_it->second), serialized_root);
}
},
[&](const std::unique_ptr<VideoCommon::MeshData>& mesh_data) {
if (const auto metadata_it = asset.m_asset_map.find("metadata");
metadata_it != asset.m_asset_map.end())
{
picojson::object serialized_root;
VideoCommon::MeshData::ToJson(serialized_root, *mesh_data);
save_json_to_file(PathToString(metadata_it->second), serialized_root);
}
},
[&](const std::unique_ptr<VideoCommon::RenderTargetData>& render_target_data) {
if (const auto metadata_it = asset.m_asset_map.find("metadata");
metadata_it != asset.m_asset_map.end())
{
picojson::object serialized_root;
VideoCommon::RenderTargetData::ToJson(&serialized_root, *render_target_data);
save_json_to_file(PathToString(metadata_it->second), serialized_root);
}
}},
asset.m_data);
}
}
const std::list<EditorAsset>& EditorAssetSource::GetAllAssets() const
{
std::lock_guard lk(m_asset_lock);
return m_assets;
}
void EditorAssetSource::PathAdded(std::string_view path)
{
// TODO: notify our preview that it needs to be calculated
}
void EditorAssetSource::PathModified(std::string_view path)
{
std::lock_guard lk(m_asset_lock);
if (const auto iter = m_path_to_editor_asset.find(path); iter != m_path_to_editor_asset.end())
{
auto& system = Core::System::GetInstance();
auto& custom_resource_manager = system.GetCustomResourceManager();
custom_resource_manager.ReloadAsset(iter->second->m_asset_id);
// auto& loader = system.GetCustomAssetLoader();
// loader.ReloadAsset(iter->second->m_asset_id);
}
// TODO: notify our preview that something changed
}
void EditorAssetSource::PathRenamed(std::string_view old_path, std::string_view new_path)
{
// TODO: notify our preview that our path changed
}
void EditorAssetSource::PathDeleted(std::string_view path)
{
// TODO: notify our preview that we no longer care about this path
}
AbstractTexture* EditorAssetSource::GetAssetPreview(const AssetID& asset_id)
{
std::lock_guard lk(m_preview_lock);
if (const auto it = m_asset_id_to_preview.find(asset_id); it != m_asset_id_to_preview.end())
{
if (it->second.m_preview_data)
{
if (it->second.m_preview_data->m_slices.empty()) [[unlikely]]
return nullptr;
auto& first_slice = it->second.m_preview_data->m_slices[0];
if (first_slice.m_levels.empty()) [[unlikely]]
return nullptr;
auto& first_level = first_slice.m_levels[0];
it->second.m_preview_texture = g_gfx->CreateTexture(
TextureConfig{first_level.width, first_level.height, 1, 1, 1,
AbstractTextureFormat::RGBA8, 0, AbstractTextureType::Texture_2DArray});
it->second.m_preview_texture->Load(0, first_level.width, first_level.height,
first_level.row_length, first_level.data.data(),
first_level.data.size());
it->second.m_preview_data.reset();
}
return it->second.m_preview_texture.get();
}
return nullptr;
}
void EditorAssetSource::SetAssetPreviewData(const AssetID& asset_id,
const VideoCommon::CustomTextureData& preview_data)
{
std::lock_guard lk(m_preview_lock);
const auto [it, inserted] = m_asset_id_to_preview.try_emplace(
asset_id, AssetPreview{.m_preview_data = {}, .m_preview_texture = nullptr});
it->second.m_preview_data = preview_data;
}
} // namespace GraphicsModEditor

View file

@ -0,0 +1,93 @@
// Copyright 2023 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <filesystem>
#include <list>
#include <map>
#include <memory>
#include <mutex>
#include <span>
#include <string>
#include <variant>
#include <vector>
#include <picojson.h>
#include "VideoCommon/Assets/MaterialAsset.h"
#include "VideoCommon/Assets/MeshAsset.h"
#include "VideoCommon/Assets/RenderTargetAsset.h"
#include "VideoCommon/Assets/ShaderAsset.h"
#include "VideoCommon/Assets/TextureAsset.h"
#include "VideoCommon/Assets/WatchableFilesystemAssetLibrary.h"
#include "VideoCommon/GraphicsModEditor/EditorTypes.h"
#include "VideoCommon/GraphicsModSystem/Config/GraphicsModAsset.h"
class AbstractTexture;
namespace VideoCommon
{
class CustomAsset;
class CustomTextureData;
} // namespace VideoCommon
namespace GraphicsModEditor
{
class EditorAssetSource final : public VideoCommon::WatchableFilesystemAssetLibrary
{
public:
// CustomAssetLibrary interface
LoadInfo LoadTexture(const AssetID& asset_id, VideoCommon::TextureData* data) override;
LoadInfo LoadPixelShader(const AssetID& asset_id, VideoCommon::PixelShaderData* data) override;
LoadInfo LoadShader(const AssetID& asset_id, VideoCommon::RasterShaderData* data) override;
LoadInfo LoadMaterial(const AssetID& asset_id, VideoCommon::MaterialData* data) override;
LoadInfo LoadMaterial(const AssetID& asset_id, VideoCommon::RasterMaterialData* data) override;
LoadInfo LoadMesh(const AssetID& asset_id, VideoCommon::MeshData* data) override;
LoadInfo LoadRenderTarget(const AssetID& asset_id, VideoCommon::RenderTargetData* data) override;
TimeType GetLastAssetWriteTime(const AssetID& asset_id) const override;
// Editor interface
EditorAsset* GetAssetFromPath(const std::filesystem::path& asset_path);
const EditorAsset* GetAssetFromID(const AssetID& asset_id) const;
EditorAsset* GetAssetFromID(const AssetID& asset_id);
void AddAsset(const std::filesystem::path& asset_path);
void RemoveAsset(const std::filesystem::path& asset_path);
bool RenameAsset(const std::filesystem::path& old_path, const std::filesystem::path& new_path);
void AddAssets(std::span<const GraphicsModSystem::Config::GraphicsModAsset> assets,
const std::filesystem::path& root);
std::vector<GraphicsModSystem::Config::GraphicsModAsset>
GetAssets(const std::filesystem::path& root) const;
void SaveAssetDataAsFiles() const;
const std::list<EditorAsset>& GetAllAssets() const;
// Gets a texture associated with the asset preview
// Will create a new texture if data is available
AbstractTexture* GetAssetPreview(const AssetID& asset_id);
private:
void PathAdded(std::string_view path) override;
void PathModified(std::string_view path) override;
void PathRenamed(std::string_view old_path, std::string_view new_path) override;
void PathDeleted(std::string_view path) override;
void AddAsset(const std::filesystem::path& asset_path, AssetID uuid);
void SetAssetPreviewData(const AssetID& asset_id,
const VideoCommon::CustomTextureData& preview_data);
std::map<AssetID, std::filesystem::path> m_asset_id_to_file_path;
std::map<std::filesystem::path, std::list<EditorAsset>::iterator> m_path_to_editor_asset;
std::list<EditorAsset> m_assets;
mutable std::recursive_mutex m_asset_lock;
struct AssetPreview
{
std::optional<VideoCommon::CustomTextureData> m_preview_data;
std::unique_ptr<AbstractTexture> m_preview_texture;
};
std::map<AssetID, AssetPreview> m_asset_id_to_preview;
mutable std::mutex m_preview_lock;
};
} // namespace GraphicsModEditor