RetroArch/gfx/drivers_shader/shader_vulkan.cpp

3167 lines
99 KiB
C++

/* RetroArch - A frontend for libretro.
* Copyright (C) 2010-2016 - Hans-Kristian Arntzen
* Copyright (C) 2011-2017 - Daniel De Matteis
*
* RetroArch is free software: you can redistribute it and/or modify it under the terms
* of the GNU General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with RetroArch.
* If not, see <http://www.gnu.org/licenses/>.
*/
#include "shader_vulkan.h"
#include "glslang_util.h"
#include <vector>
#include <memory>
#include <functional>
#include <utility>
#include <algorithm>
#include <string.h>
#include <math.h>
#include <compat/strl.h>
#include <formats/image.h>
#include <retro_miscellaneous.h>
#include "slang_reflection.h"
#include "slang_reflection.hpp"
#include "../video_driver.h"
#include "../../verbosity.h"
#include "../../msg_hash.h"
using namespace std;
static const uint32_t opaque_vert[] =
#include "../drivers/vulkan_shaders/opaque.vert.inc"
;
static const uint32_t opaque_frag[] =
#include "../drivers/vulkan_shaders/opaque.frag.inc"
;
static unsigned num_miplevels(unsigned width, unsigned height)
{
unsigned size = MAX(width, height);
unsigned levels = 0;
while (size)
{
levels++;
size >>= 1;
}
return levels;
}
static uint32_t find_memory_type_fallback(
const VkPhysicalDeviceMemoryProperties &mem_props,
uint32_t device_reqs, uint32_t host_reqs)
{
uint32_t i;
for (i = 0; i < VK_MAX_MEMORY_TYPES; i++)
{
if ((device_reqs & (1u << i)) &&
(mem_props.memoryTypes[i].propertyFlags & host_reqs) == host_reqs)
return i;
}
return vulkan_find_memory_type(&mem_props, device_reqs, 0);
}
static void build_identity_matrix(float *data)
{
data[ 0] = 1.0f;
data[ 1] = 0.0f;
data[ 2] = 0.0f;
data[ 3] = 0.0f;
data[ 4] = 0.0f;
data[ 5] = 1.0f;
data[ 6] = 0.0f;
data[ 7] = 0.0f;
data[ 8] = 0.0f;
data[ 9] = 0.0f;
data[10] = 1.0f;
data[11] = 0.0f;
data[12] = 0.0f;
data[13] = 0.0f;
data[14] = 0.0f;
data[15] = 1.0f;
}
static void build_vec4(float *data, unsigned width, unsigned height)
{
data[0] = float(width);
data[1] = float(height);
data[2] = 1.0f / float(width);
data[3] = 1.0f / float(height);
}
struct Size2D
{
unsigned width, height;
};
struct Texture
{
vulkan_filter_chain_texture texture;
vulkan_filter_chain_filter filter;
vulkan_filter_chain_filter mip_filter;
vulkan_filter_chain_address address;
};
class DeferredDisposer
{
public:
DeferredDisposer(vector<function<void ()>> &calls) : calls(calls) {}
void defer(function<void ()> func)
{
calls.push_back(move(func));
}
private:
vector<function<void ()>> &calls;
};
class Buffer
{
public:
Buffer(VkDevice device,
const VkPhysicalDeviceMemoryProperties &mem_props,
size_t size, VkBufferUsageFlags usage);
~Buffer();
size_t get_size() const { return size; }
void *map();
void unmap();
const VkBuffer &get_buffer() const { return buffer; }
Buffer(Buffer&&) = delete;
void operator=(Buffer&&) = delete;
private:
VkDevice device;
VkBuffer buffer;
VkDeviceMemory memory;
size_t size;
void *mapped = nullptr;
};
class StaticTexture
{
public:
StaticTexture(string id,
VkDevice device,
VkImage image,
VkImageView view,
VkDeviceMemory memory,
unique_ptr<Buffer> buffer,
unsigned width, unsigned height,
bool linear,
bool mipmap,
vulkan_filter_chain_address address);
~StaticTexture();
StaticTexture(StaticTexture&&) = delete;
void operator=(StaticTexture&&) = delete;
void release_staging_buffer()
{
buffer.reset();
}
void set_id(string name)
{
id = move(name);
}
const string &get_id() const
{
return id;
}
const Texture &get_texture() const
{
return texture;
}
private:
VkDevice device;
VkImage image;
VkImageView view;
VkDeviceMemory memory;
unique_ptr<Buffer> buffer;
string id;
Texture texture;
};
class Framebuffer
{
public:
Framebuffer(VkDevice device,
const VkPhysicalDeviceMemoryProperties &mem_props,
const Size2D &max_size, VkFormat format, unsigned max_levels);
~Framebuffer();
Framebuffer(Framebuffer&&) = delete;
void operator=(Framebuffer&&) = delete;
void set_size(DeferredDisposer &disposer, const Size2D &size, VkFormat format = VK_FORMAT_UNDEFINED);
const Size2D &get_size() const { return size; }
VkFormat get_format() const { return format; }
VkImage get_image() const { return image; }
VkImageView get_view() const { return view; }
VkFramebuffer get_framebuffer() const { return framebuffer; }
VkRenderPass get_render_pass() const { return render_pass; }
void clear(VkCommandBuffer cmd);
void copy(VkCommandBuffer cmd, VkImage image, VkImageLayout layout);
unsigned get_levels() const { return levels; }
void generate_mips(VkCommandBuffer cmd);
private:
const VkPhysicalDeviceMemoryProperties &memory_properties;
VkDevice device = VK_NULL_HANDLE;
VkImage image = VK_NULL_HANDLE;
VkImageView view = VK_NULL_HANDLE;
VkImageView fb_view = VK_NULL_HANDLE;
Size2D size;
VkFormat format;
unsigned max_levels;
unsigned levels = 0;
VkFramebuffer framebuffer = VK_NULL_HANDLE;
VkRenderPass render_pass = VK_NULL_HANDLE;
struct
{
size_t size = 0;
uint32_t type = 0;
VkDeviceMemory memory = VK_NULL_HANDLE;
} memory;
void init(DeferredDisposer *disposer);
void init_framebuffer();
void init_render_pass();
};
struct CommonResources
{
CommonResources(VkDevice device,
const VkPhysicalDeviceMemoryProperties &memory_properties);
~CommonResources();
unique_ptr<Buffer> vbo;
unique_ptr<Buffer> ubo;
uint8_t *ubo_mapped = nullptr;
size_t ubo_sync_index_stride = 0;
size_t ubo_offset = 0;
size_t ubo_alignment = 1;
VkSampler samplers[VULKAN_FILTER_CHAIN_COUNT][VULKAN_FILTER_CHAIN_COUNT][VULKAN_FILTER_CHAIN_ADDRESS_COUNT];
vector<Texture> original_history;
vector<Texture> framebuffer_feedback;
vector<Texture> pass_outputs;
vector<unique_ptr<StaticTexture>> luts;
unordered_map<string, slang_texture_semantic_map> texture_semantic_map;
unordered_map<string, slang_texture_semantic_map> texture_semantic_uniform_map;
unique_ptr<video_shader> shader_preset;
VkDevice device;
};
class Pass
{
public:
Pass(VkDevice device,
const VkPhysicalDeviceMemoryProperties &memory_properties,
VkPipelineCache cache, unsigned num_sync_indices, bool final_pass) :
device(device),
memory_properties(memory_properties),
cache(cache),
num_sync_indices(num_sync_indices),
final_pass(final_pass)
{}
~Pass();
Pass(Pass&&) = delete;
void operator=(Pass&&) = delete;
const Framebuffer &get_framebuffer() const
{
return *framebuffer;
}
Framebuffer *get_feedback_framebuffer()
{
return framebuffer_feedback.get();
}
Size2D set_pass_info(
const Size2D &max_original,
const Size2D &max_source,
const vulkan_filter_chain_swapchain_info &swapchain,
const vulkan_filter_chain_pass_info &info);
void set_shader(VkShaderStageFlags stage,
const uint32_t *spirv,
size_t spirv_words);
bool build();
bool init_feedback();
void build_commands(
DeferredDisposer &disposer,
VkCommandBuffer cmd,
const Texture &original,
const Texture &source,
const VkViewport &vp,
const float *mvp);
void notify_sync_index(unsigned index)
{
sync_index = index;
}
void set_frame_count(uint64_t count)
{
frame_count = count;
}
void set_frame_count_period(unsigned period)
{
frame_count_period = period;
}
void set_name(const char *name)
{
pass_name = name;
}
const string &get_name() const
{
return pass_name;
}
vulkan_filter_chain_filter get_source_filter() const
{
return pass_info.source_filter;
}
vulkan_filter_chain_filter get_mip_filter() const
{
return pass_info.mip_filter;
}
vulkan_filter_chain_address get_address_mode() const
{
return pass_info.address;
}
void set_common_resources(CommonResources *common)
{
this->common = common;
}
const slang_reflection &get_reflection() const
{
return reflection;
}
void set_pass_number(unsigned pass)
{
pass_number = pass;
}
void add_parameter(unsigned parameter_index, const std::string &id);
void end_frame();
void allocate_buffers();
private:
VkDevice device;
const VkPhysicalDeviceMemoryProperties &memory_properties;
VkPipelineCache cache;
unsigned num_sync_indices;
unsigned sync_index;
bool final_pass;
Size2D get_output_size(const Size2D &original_size,
const Size2D &max_source) const;
VkPipeline pipeline = VK_NULL_HANDLE;
VkPipelineLayout pipeline_layout = VK_NULL_HANDLE;
VkDescriptorSetLayout set_layout = VK_NULL_HANDLE;
VkDescriptorPool pool = VK_NULL_HANDLE;
vector<VkDescriptorSet> sets;
CommonResources *common = nullptr;
Size2D current_framebuffer_size;
VkViewport current_viewport;
vulkan_filter_chain_pass_info pass_info;
vector<uint32_t> vertex_shader;
vector<uint32_t> fragment_shader;
unique_ptr<Framebuffer> framebuffer;
unique_ptr<Framebuffer> framebuffer_feedback;
VkRenderPass swapchain_render_pass;
void clear_vk();
bool init_pipeline();
bool init_pipeline_layout();
void set_texture(VkDescriptorSet set, unsigned binding,
const Texture &texture);
void set_semantic_texture(VkDescriptorSet set, slang_texture_semantic semantic,
const Texture &texture);
void set_semantic_texture_array(VkDescriptorSet set,
slang_texture_semantic semantic, unsigned index,
const Texture &texture);
void set_uniform_buffer(VkDescriptorSet set, unsigned binding,
VkBuffer buffer,
VkDeviceSize offset,
VkDeviceSize range);
slang_reflection reflection;
void build_semantics(VkDescriptorSet set, uint8_t *buffer,
const float *mvp, const Texture &original, const Texture &source);
void build_semantic_vec4(uint8_t *data, slang_semantic semantic,
unsigned width, unsigned height);
void build_semantic_uint(uint8_t *data, slang_semantic semantic, uint32_t value);
void build_semantic_parameter(uint8_t *data, unsigned index, float value);
void build_semantic_texture_vec4(uint8_t *data,
slang_texture_semantic semantic,
unsigned width, unsigned height);
void build_semantic_texture_array_vec4(uint8_t *data,
slang_texture_semantic semantic, unsigned index,
unsigned width, unsigned height);
void build_semantic_texture(VkDescriptorSet set, uint8_t *buffer,
slang_texture_semantic semantic, const Texture &texture);
void build_semantic_texture_array(VkDescriptorSet set, uint8_t *buffer,
slang_texture_semantic semantic, unsigned index, const Texture &texture);
uint64_t frame_count = 0;
unsigned frame_count_period = 0;
unsigned pass_number = 0;
size_t ubo_offset = 0;
string pass_name;
struct Parameter
{
string id;
unsigned index;
unsigned semantic_index;
};
vector<Parameter> parameters;
vector<Parameter> filtered_parameters;
struct PushConstant
{
VkShaderStageFlags stages = 0;
vector<uint32_t> buffer; // uint32_t to have correct alignment.
};
PushConstant push;
};
// struct here since we're implementing the opaque typedef from C.
struct vulkan_filter_chain
{
public:
vulkan_filter_chain(const vulkan_filter_chain_create_info &info);
~vulkan_filter_chain();
inline void set_shader_preset(unique_ptr<video_shader> shader)
{
common.shader_preset = move(shader);
}
inline video_shader *get_shader_preset()
{
return common.shader_preset.get();
}
void set_pass_info(unsigned pass,
const vulkan_filter_chain_pass_info &info);
void set_shader(unsigned pass, VkShaderStageFlags stage,
const uint32_t *spirv, size_t spirv_words);
bool init();
bool update_swapchain_info(
const vulkan_filter_chain_swapchain_info &info);
void notify_sync_index(unsigned index);
void set_input_texture(const vulkan_filter_chain_texture &texture);
void build_offscreen_passes(VkCommandBuffer cmd, const VkViewport &vp);
void build_viewport_pass(VkCommandBuffer cmd,
const VkViewport &vp, const float *mvp);
void end_frame(VkCommandBuffer cmd);
void set_frame_count(uint64_t count);
void set_frame_count_period(unsigned pass, unsigned period);
void set_pass_name(unsigned pass, const char *name);
void add_static_texture(unique_ptr<StaticTexture> texture);
void add_parameter(unsigned pass, unsigned parameter_index, const std::string &id);
void release_staging_buffers();
private:
VkDevice device;
VkPhysicalDevice gpu;
const VkPhysicalDeviceMemoryProperties &memory_properties;
VkPipelineCache cache;
vector<unique_ptr<Pass>> passes;
vector<vulkan_filter_chain_pass_info> pass_info;
vector<vector<function<void ()>>> deferred_calls;
CommonResources common;
VkFormat original_format;
vulkan_filter_chain_texture input_texture;
Size2D max_input_size;
vulkan_filter_chain_swapchain_info swapchain_info;
unsigned current_sync_index;
void flush();
void set_num_passes(unsigned passes);
void execute_deferred();
void set_num_sync_indices(unsigned num_indices);
void set_swapchain_info(const vulkan_filter_chain_swapchain_info &info);
bool init_ubo();
bool init_history();
bool init_feedback();
bool init_alias();
void update_history(DeferredDisposer &disposer, VkCommandBuffer cmd);
vector<unique_ptr<Framebuffer>> original_history;
bool require_clear = false;
void clear_history_and_feedback(VkCommandBuffer cmd);
void update_feedback_info();
void update_history_info();
};
vulkan_filter_chain::vulkan_filter_chain(
const vulkan_filter_chain_create_info &info)
: device(info.device),
gpu(info.gpu),
memory_properties(*info.memory_properties),
cache(info.pipeline_cache),
common(info.device, *info.memory_properties),
original_format(info.original_format)
{
max_input_size = { info.max_input_size.width, info.max_input_size.height };
set_swapchain_info(info.swapchain);
set_num_passes(info.num_passes);
}
vulkan_filter_chain::~vulkan_filter_chain()
{
flush();
}
void vulkan_filter_chain::set_swapchain_info(
const vulkan_filter_chain_swapchain_info &info)
{
swapchain_info = info;
set_num_sync_indices(info.num_indices);
}
void vulkan_filter_chain::add_parameter(unsigned pass, unsigned index, const std::string &id)
{
passes[pass]->add_parameter(index, id);
}
void vulkan_filter_chain::set_num_sync_indices(unsigned num_indices)
{
execute_deferred();
deferred_calls.resize(num_indices);
}
void vulkan_filter_chain::notify_sync_index(unsigned index)
{
auto &calls = deferred_calls[index];
for (auto &call : calls)
call();
calls.clear();
current_sync_index = index;
for (auto &pass : passes)
pass->notify_sync_index(index);
}
void vulkan_filter_chain::set_num_passes(unsigned num_passes)
{
pass_info.resize(num_passes);
passes.reserve(num_passes);
for (unsigned i = 0; i < num_passes; i++)
{
passes.emplace_back(new Pass(device, memory_properties,
cache, deferred_calls.size(), i + 1 == num_passes));
passes.back()->set_common_resources(&common);
passes.back()->set_pass_number(i);
}
}
bool vulkan_filter_chain::update_swapchain_info(
const vulkan_filter_chain_swapchain_info &info)
{
flush();
set_swapchain_info(info);
return init();
}
void vulkan_filter_chain::set_pass_info(unsigned pass,
const vulkan_filter_chain_pass_info &info)
{
pass_info[pass] = info;
}
void vulkan_filter_chain::set_shader(
unsigned pass,
VkShaderStageFlags stage,
const uint32_t *spirv,
size_t spirv_words)
{
passes[pass]->set_shader(stage, spirv, spirv_words);
}
void vulkan_filter_chain::set_input_texture(
const vulkan_filter_chain_texture &texture)
{
input_texture = texture;
}
void vulkan_filter_chain::add_static_texture(unique_ptr<StaticTexture> texture)
{
common.luts.push_back(move(texture));
}
void vulkan_filter_chain::release_staging_buffers()
{
for (auto &lut : common.luts)
lut->release_staging_buffer();
}
void vulkan_filter_chain::set_frame_count(uint64_t count)
{
for (auto &pass : passes)
pass->set_frame_count(count);
}
void vulkan_filter_chain::set_frame_count_period(unsigned pass, unsigned period)
{
passes[pass]->set_frame_count_period(period);
}
void vulkan_filter_chain::set_pass_name(unsigned pass, const char *name)
{
passes[pass]->set_name(name);
}
void vulkan_filter_chain::execute_deferred()
{
for (auto &calls : deferred_calls)
{
for (auto &call : calls)
call();
calls.clear();
}
}
void vulkan_filter_chain::flush()
{
vkDeviceWaitIdle(device);
execute_deferred();
}
void vulkan_filter_chain::update_history_info()
{
unsigned i = 0;
for (auto &texture : original_history)
{
Texture &source = common.original_history[i];
source.texture.image = texture->get_image();
source.texture.view = texture->get_view();
source.texture.layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
source.texture.width = texture->get_size().width;
source.texture.height = texture->get_size().height;
source.filter = passes.front()->get_source_filter();
source.mip_filter = passes.front()->get_mip_filter();
source.address = passes.front()->get_address_mode();
i++;
}
}
void vulkan_filter_chain::update_feedback_info()
{
if (common.framebuffer_feedback.empty())
return;
for (unsigned i = 0; i < passes.size() - 1; i++)
{
auto fb = passes[i]->get_feedback_framebuffer();
if (!fb)
continue;
auto &source = common.framebuffer_feedback[i];
source.texture.image = fb->get_image();
source.texture.view = fb->get_view();
source.texture.layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
source.texture.width = fb->get_size().width;
source.texture.height = fb->get_size().height;
source.filter = passes[i]->get_source_filter();
source.mip_filter = passes[i]->get_mip_filter();
source.address = passes[i]->get_address_mode();
}
}
bool vulkan_filter_chain::init_history()
{
original_history.clear();
common.original_history.clear();
size_t required_images = 0;
for (auto &pass : passes)
{
required_images =
max(required_images,
pass->get_reflection().semantic_textures[SLANG_TEXTURE_SEMANTIC_ORIGINAL_HISTORY].size());
}
if (required_images < 2)
{
RARCH_LOG("[Vulkan filter chain]: Not using frame history.\n");
return true;
}
// We don't need to store array element #0, since it's aliased with the actual original.
required_images--;
original_history.reserve(required_images);
common.original_history.resize(required_images);
for (unsigned i = 0; i < required_images; i++)
{
original_history.emplace_back(new Framebuffer(device, memory_properties,
max_input_size, original_format, 1));
}
RARCH_LOG("[Vulkan filter chain]: Using history of %u frames.\n", required_images);
// On first frame, we need to clear the textures to a known state, but we need
// a command buffer for that, so just defer to first frame.
require_clear = true;
return true;
}
bool vulkan_filter_chain::init_feedback()
{
common.framebuffer_feedback.clear();
bool use_feedbacks = false;
// Final pass cannot have feedback.
for (unsigned i = 0; i < passes.size() - 1; i++)
{
bool use_feedback = false;
for (auto &pass : passes)
{
auto &r = pass->get_reflection();
auto &feedbacks = r.semantic_textures[SLANG_TEXTURE_SEMANTIC_PASS_FEEDBACK];
if (i < feedbacks.size() && feedbacks[i].texture)
{
use_feedback = true;
use_feedbacks = true;
break;
}
}
if (use_feedback && !passes[i]->init_feedback())
return false;
if (use_feedback)
RARCH_LOG("[Vulkan filter chain]: Using framebuffer feedback for pass #%u.\n", i);
}
if (!use_feedbacks)
{
RARCH_LOG("[Vulkan filter chain]: Not using framebuffer feedback.\n");
return true;
}
common.framebuffer_feedback.resize(passes.size() - 1);
require_clear = true;
return true;
}
template <typename P>
static bool vk_shader_set_unique_map(unordered_map<string, P> &m, const string &name, const P &p)
{
auto itr = m.find(name);
if (itr != end(m))
{
RARCH_ERR("[slang]: Alias \"%s\" already exists.\n",
name.c_str());
return false;
}
m[name] = p;
return true;
}
bool vulkan_filter_chain::init_alias()
{
common.texture_semantic_map.clear();
common.texture_semantic_uniform_map.clear();
unsigned i = 0;
for (auto &pass : passes)
{
auto &name = pass->get_name();
if (name.empty())
continue;
unsigned i = &pass - passes.data();
if (!vk_shader_set_unique_map(common.texture_semantic_map, name,
slang_texture_semantic_map{ SLANG_TEXTURE_SEMANTIC_PASS_OUTPUT, i }))
return false;
if (!vk_shader_set_unique_map(common.texture_semantic_uniform_map, name + "Size",
slang_texture_semantic_map{ SLANG_TEXTURE_SEMANTIC_PASS_OUTPUT, i }))
return false;
if (!vk_shader_set_unique_map(common.texture_semantic_map, name + "Feedback",
slang_texture_semantic_map{ SLANG_TEXTURE_SEMANTIC_PASS_FEEDBACK, i }))
return false;
if (!vk_shader_set_unique_map(common.texture_semantic_uniform_map, name + "FeedbackSize",
slang_texture_semantic_map{ SLANG_TEXTURE_SEMANTIC_PASS_FEEDBACK, i }))
return false;
}
for (auto &lut : common.luts)
{
unsigned i = &lut - common.luts.data();
if (!vk_shader_set_unique_map(common.texture_semantic_map, lut->get_id(),
slang_texture_semantic_map{ SLANG_TEXTURE_SEMANTIC_USER, i }))
return false;
if (!vk_shader_set_unique_map(common.texture_semantic_uniform_map, lut->get_id() + "Size",
slang_texture_semantic_map{ SLANG_TEXTURE_SEMANTIC_USER, i }))
return false;
}
return true;
}
bool vulkan_filter_chain::init_ubo()
{
common.ubo.reset();
common.ubo_offset = 0;
VkPhysicalDeviceProperties props;
vkGetPhysicalDeviceProperties(gpu, &props);
common.ubo_alignment = props.limits.minUniformBufferOffsetAlignment;
// Who knows. :)
if (common.ubo_alignment == 0)
common.ubo_alignment = 1;
for (auto &pass : passes)
pass->allocate_buffers();
common.ubo_offset = (common.ubo_offset + common.ubo_alignment - 1) &
~(common.ubo_alignment - 1);
common.ubo_sync_index_stride = common.ubo_offset;
if (common.ubo_offset != 0)
{
common.ubo = unique_ptr<Buffer>(new Buffer(device,
memory_properties, common.ubo_offset * deferred_calls.size(),
VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT));
}
common.ubo_mapped = static_cast<uint8_t*>(common.ubo->map());
return true;
}
bool vulkan_filter_chain::init()
{
Size2D source = max_input_size;
if (!init_alias())
return false;
for (unsigned i = 0; i < passes.size(); i++)
{
auto &pass = passes[i];
RARCH_LOG("[slang]: Building pass #%u (%s)\n", i,
pass->get_name().empty() ?
msg_hash_to_str(MENU_ENUM_LABEL_VALUE_NOT_AVAILABLE) :
pass->get_name().c_str());
source = pass->set_pass_info(max_input_size,
source, swapchain_info, pass_info[i]);
if (!pass->build())
return false;
}
require_clear = false;
if (!init_ubo())
return false;
if (!init_history())
return false;
if (!init_feedback())
return false;
common.pass_outputs.resize(passes.size());
return true;
}
void vulkan_filter_chain::clear_history_and_feedback(VkCommandBuffer cmd)
{
for (auto &texture : original_history)
texture->clear(cmd);
for (auto &pass : passes)
{
auto *fb = pass->get_feedback_framebuffer();
if (fb)
fb->clear(cmd);
}
}
void vulkan_filter_chain::build_offscreen_passes(VkCommandBuffer cmd,
const VkViewport &vp)
{
// First frame, make sure our history and feedback textures are in a clean state.
if (require_clear)
{
clear_history_and_feedback(cmd);
require_clear = false;
}
update_history_info();
update_feedback_info();
unsigned i;
DeferredDisposer disposer(deferred_calls[current_sync_index]);
const Texture original = {
input_texture,
passes.front()->get_source_filter(),
passes.front()->get_mip_filter(),
passes.front()->get_address_mode(),
};
Texture source = original;
for (i = 0; i < passes.size() - 1; i++)
{
passes[i]->build_commands(disposer, cmd,
original, source, vp, nullptr);
auto &fb = passes[i]->get_framebuffer();
source.texture.view = fb.get_view();
source.texture.layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
source.texture.width = fb.get_size().width;
source.texture.height = fb.get_size().height;
source.filter = passes[i + 1]->get_source_filter();
source.mip_filter = passes[i + 1]->get_mip_filter();
source.address = passes[i + 1]->get_address_mode();
common.pass_outputs[i] = source;
}
}
void vulkan_filter_chain::update_history(DeferredDisposer &disposer, VkCommandBuffer cmd)
{
VkImageLayout src_layout = input_texture.layout;
// Transition input texture to something appropriate.
if (input_texture.layout != VK_IMAGE_LAYOUT_GENERAL)
{
vulkan_image_layout_transition_levels(cmd,
input_texture.image,VK_REMAINING_MIP_LEVELS,
input_texture.layout,
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
0,
VK_ACCESS_TRANSFER_READ_BIT,
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
VK_PIPELINE_STAGE_TRANSFER_BIT);
src_layout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
}
unique_ptr<Framebuffer> tmp;
unique_ptr<Framebuffer> &back = original_history.back();
swap(back, tmp);
if (input_texture.width != tmp->get_size().width ||
input_texture.height != tmp->get_size().height ||
(input_texture.format != VK_FORMAT_UNDEFINED && input_texture.format != tmp->get_format()))
{
tmp->set_size(disposer, { input_texture.width, input_texture.height }, input_texture.format);
}
tmp->copy(cmd, input_texture.image, src_layout);
// Transition input texture back.
if (input_texture.layout != VK_IMAGE_LAYOUT_GENERAL)
{
vulkan_image_layout_transition_levels(cmd,
input_texture.image,VK_REMAINING_MIP_LEVELS,
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
input_texture.layout,
0,
VK_ACCESS_SHADER_READ_BIT,
VK_PIPELINE_STAGE_TRANSFER_BIT,
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT);
}
// Should ring buffer, but we don't have *that* many passes.
move_backward(begin(original_history), end(original_history) - 1, end(original_history));
swap(original_history.front(), tmp);
}
void vulkan_filter_chain::end_frame(VkCommandBuffer cmd)
{
// If we need to keep old frames, copy it after fragment is complete.
// TODO: We can improve pipelining by figuring out which pass is the last that reads from
// the history and dispatch the copy earlier.
if (!original_history.empty())
{
DeferredDisposer disposer(deferred_calls[current_sync_index]);
update_history(disposer, cmd);
}
}
void vulkan_filter_chain::build_viewport_pass(
VkCommandBuffer cmd, const VkViewport &vp, const float *mvp)
{
// First frame, make sure our history and feedback textures are in a clean state.
if (require_clear)
{
clear_history_and_feedback(cmd);
require_clear = false;
}
Texture source;
DeferredDisposer disposer(deferred_calls[current_sync_index]);
const Texture original = {
input_texture,
passes.front()->get_source_filter(),
passes.front()->get_mip_filter(),
passes.front()->get_address_mode(),
};
if (passes.size() == 1)
{
source = {
input_texture,
passes.back()->get_source_filter(),
passes.back()->get_mip_filter(),
passes.back()->get_address_mode(),
};
}
else
{
auto &fb = passes[passes.size() - 2]->get_framebuffer();
source.texture.view = fb.get_view();
source.texture.layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
source.texture.width = fb.get_size().width;
source.texture.height = fb.get_size().height;
source.filter = passes.back()->get_source_filter();
source.mip_filter = passes.back()->get_mip_filter();
source.address = passes.back()->get_address_mode();
}
passes.back()->build_commands(disposer, cmd,
original, source, vp, mvp);
// For feedback FBOs, swap current and previous.
for (auto &pass : passes)
pass->end_frame();
}
StaticTexture::StaticTexture(string id,
VkDevice device,
VkImage image,
VkImageView view,
VkDeviceMemory memory,
unique_ptr<Buffer> buffer,
unsigned width, unsigned height,
bool linear,
bool mipmap,
vulkan_filter_chain_address address)
: device(device),
image(image),
view(view),
memory(memory),
buffer(move(buffer)),
id(move(id))
{
texture.filter = linear ? VULKAN_FILTER_CHAIN_LINEAR : VULKAN_FILTER_CHAIN_NEAREST;
texture.mip_filter =
mipmap && linear ? VULKAN_FILTER_CHAIN_LINEAR : VULKAN_FILTER_CHAIN_NEAREST;
texture.address = address;
texture.texture.image = image;
texture.texture.view = view;
texture.texture.layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
texture.texture.width = width;
texture.texture.height = height;
}
StaticTexture::~StaticTexture()
{
if (view != VK_NULL_HANDLE)
vkDestroyImageView(device, view, nullptr);
if (image != VK_NULL_HANDLE)
vkDestroyImage(device, image, nullptr);
if (memory != VK_NULL_HANDLE)
vkFreeMemory(device, memory, nullptr);
}
Buffer::Buffer(VkDevice device,
const VkPhysicalDeviceMemoryProperties &mem_props,
size_t size, VkBufferUsageFlags usage) :
device(device), size(size)
{
VkMemoryRequirements mem_reqs;
VkBufferCreateInfo info = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
info.size = size;
info.usage = usage;
info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
vkCreateBuffer(device, &info, nullptr, &buffer);
vkGetBufferMemoryRequirements(device, buffer, &mem_reqs);
VkMemoryAllocateInfo alloc = { VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO };
alloc.allocationSize = mem_reqs.size;
alloc.memoryTypeIndex = vulkan_find_memory_type(
&mem_props, mem_reqs.memoryTypeBits,
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT
| VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
vkAllocateMemory(device, &alloc, NULL, &memory);
vkBindBufferMemory(device, buffer, memory, 0);
}
void *Buffer::map()
{
if (!mapped)
{
if (vkMapMemory(device, memory, 0, size, 0, &mapped) == VK_SUCCESS)
return mapped;
return nullptr;
}
return mapped;
}
void Buffer::unmap()
{
if (mapped)
vkUnmapMemory(device, memory);
mapped = nullptr;
}
Buffer::~Buffer()
{
if (mapped)
unmap();
if (memory != VK_NULL_HANDLE)
vkFreeMemory(device, memory, nullptr);
if (buffer != VK_NULL_HANDLE)
vkDestroyBuffer(device, buffer, nullptr);
}
Pass::~Pass()
{
clear_vk();
}
void Pass::add_parameter(unsigned index, const std::string &id)
{
parameters.push_back({ id, index, unsigned(parameters.size()) });
}
void Pass::set_shader(VkShaderStageFlags stage,
const uint32_t *spirv,
size_t spirv_words)
{
if (stage == VK_SHADER_STAGE_VERTEX_BIT)
{
vertex_shader.clear();
vertex_shader.insert(end(vertex_shader),
spirv, spirv + spirv_words);
}
else if (stage == VK_SHADER_STAGE_FRAGMENT_BIT)
{
fragment_shader.clear();
fragment_shader.insert(end(fragment_shader),
spirv, spirv + spirv_words);
}
}
Size2D Pass::get_output_size(const Size2D &original,
const Size2D &source) const
{
float width, height;
switch (pass_info.scale_type_x)
{
case VULKAN_FILTER_CHAIN_SCALE_ORIGINAL:
width = float(original.width) * pass_info.scale_x;
break;
case VULKAN_FILTER_CHAIN_SCALE_SOURCE:
width = float(source.width) * pass_info.scale_x;
break;
case VULKAN_FILTER_CHAIN_SCALE_VIEWPORT:
width = current_viewport.width * pass_info.scale_x;
break;
case VULKAN_FILTER_CHAIN_SCALE_ABSOLUTE:
width = pass_info.scale_x;
break;
default:
width = 0.0f;
}
switch (pass_info.scale_type_y)
{
case VULKAN_FILTER_CHAIN_SCALE_ORIGINAL:
height = float(original.height) * pass_info.scale_y;
break;
case VULKAN_FILTER_CHAIN_SCALE_SOURCE:
height = float(source.height) * pass_info.scale_y;
break;
case VULKAN_FILTER_CHAIN_SCALE_VIEWPORT:
height = current_viewport.height * pass_info.scale_y;
break;
case VULKAN_FILTER_CHAIN_SCALE_ABSOLUTE:
height = pass_info.scale_y;
break;
default:
height = 0.0f;
}
return { unsigned(roundf(width)), unsigned(roundf(height)) };
}
Size2D Pass::set_pass_info(
const Size2D &max_original,
const Size2D &max_source,
const vulkan_filter_chain_swapchain_info &swapchain,
const vulkan_filter_chain_pass_info &info)
{
clear_vk();
current_viewport = swapchain.viewport;
pass_info = info;
num_sync_indices = swapchain.num_indices;
sync_index = 0;
current_framebuffer_size = get_output_size(max_original, max_source);
swapchain_render_pass = swapchain.render_pass;
return current_framebuffer_size;
}
void Pass::clear_vk()
{
if (pool != VK_NULL_HANDLE)
vkDestroyDescriptorPool(device, pool, nullptr);
if (pipeline != VK_NULL_HANDLE)
vkDestroyPipeline(device, pipeline, nullptr);
if (set_layout != VK_NULL_HANDLE)
vkDestroyDescriptorSetLayout(device, set_layout, nullptr);
if (pipeline_layout != VK_NULL_HANDLE)
vkDestroyPipelineLayout(device, pipeline_layout, nullptr);
pool = VK_NULL_HANDLE;
pipeline = VK_NULL_HANDLE;
set_layout = VK_NULL_HANDLE;
}
bool Pass::init_pipeline_layout()
{
vector<VkDescriptorSetLayoutBinding> bindings;
vector<VkDescriptorPoolSize> desc_counts;
// Main UBO.
VkShaderStageFlags ubo_mask = 0;
if (reflection.ubo_stage_mask & SLANG_STAGE_VERTEX_MASK)
ubo_mask |= VK_SHADER_STAGE_VERTEX_BIT;
if (reflection.ubo_stage_mask & SLANG_STAGE_FRAGMENT_MASK)
ubo_mask |= VK_SHADER_STAGE_FRAGMENT_BIT;
if (ubo_mask != 0)
{
bindings.push_back({ reflection.ubo_binding,
VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1,
ubo_mask, nullptr });
desc_counts.push_back({ VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, num_sync_indices });
}
// Semantic textures.
for (auto &semantic : reflection.semantic_textures)
{
for (auto &texture : semantic)
{
if (!texture.texture)
continue;
VkShaderStageFlags stages = 0;
if (texture.stage_mask & SLANG_STAGE_VERTEX_MASK)
stages |= VK_SHADER_STAGE_VERTEX_BIT;
if (texture.stage_mask & SLANG_STAGE_FRAGMENT_MASK)
stages |= VK_SHADER_STAGE_FRAGMENT_BIT;
bindings.push_back({ texture.binding,
VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1,
stages, nullptr });
desc_counts.push_back({ VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, num_sync_indices });
}
}
VkDescriptorSetLayoutCreateInfo set_layout_info = {
VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO };
set_layout_info.bindingCount = bindings.size();
set_layout_info.pBindings = bindings.data();
if (vkCreateDescriptorSetLayout(device,
&set_layout_info, NULL, &set_layout) != VK_SUCCESS)
return false;
VkPipelineLayoutCreateInfo layout_info = {
VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO };
layout_info.setLayoutCount = 1;
layout_info.pSetLayouts = &set_layout;
// Push constants
VkPushConstantRange push_range = {};
if (reflection.push_constant_stage_mask && reflection.push_constant_size)
{
if (reflection.push_constant_stage_mask & SLANG_STAGE_VERTEX_MASK)
push_range.stageFlags |= VK_SHADER_STAGE_VERTEX_BIT;
if (reflection.push_constant_stage_mask & SLANG_STAGE_FRAGMENT_MASK)
push_range.stageFlags |= VK_SHADER_STAGE_FRAGMENT_BIT;
RARCH_LOG("[Vulkan]: Push Constant Block: %u bytes.\n", reflection.push_constant_size);
layout_info.pushConstantRangeCount = 1;
layout_info.pPushConstantRanges = &push_range;
push.buffer.resize((reflection.push_constant_size + sizeof(uint32_t) - 1) / sizeof(uint32_t));
}
push.stages = push_range.stageFlags;
push_range.size = reflection.push_constant_size;
if (vkCreatePipelineLayout(device,
&layout_info, NULL, &pipeline_layout) != VK_SUCCESS)
return false;
VkDescriptorPoolCreateInfo pool_info = {
VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO };
pool_info.maxSets = num_sync_indices;
pool_info.poolSizeCount = desc_counts.size();
pool_info.pPoolSizes = desc_counts.data();
if (vkCreateDescriptorPool(device, &pool_info, nullptr, &pool) != VK_SUCCESS)
return false;
VkDescriptorSetAllocateInfo alloc_info = {
VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO };
alloc_info.descriptorPool = pool;
alloc_info.descriptorSetCount = 1;
alloc_info.pSetLayouts = &set_layout;
sets.resize(num_sync_indices);
for (unsigned i = 0; i < num_sync_indices; i++)
vkAllocateDescriptorSets(device, &alloc_info, &sets[i]);
return true;
}
bool Pass::init_pipeline()
{
if (!init_pipeline_layout())
return false;
// Input assembly
VkPipelineInputAssemblyStateCreateInfo input_assembly = {
VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO };
input_assembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP;
// VAO state
VkVertexInputAttributeDescription attributes[2] = {{0}};
VkVertexInputBindingDescription binding = {0};
attributes[0].location = 0;
attributes[0].binding = 0;
attributes[0].format = VK_FORMAT_R32G32_SFLOAT;
attributes[0].offset = 0;
attributes[1].location = 1;
attributes[1].binding = 0;
attributes[1].format = VK_FORMAT_R32G32_SFLOAT;
attributes[1].offset = 2 * sizeof(float);
binding.binding = 0;
binding.stride = 4 * sizeof(float);
binding.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
VkPipelineVertexInputStateCreateInfo vertex_input = {
VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO };
vertex_input.vertexBindingDescriptionCount = 1;
vertex_input.pVertexBindingDescriptions = &binding;
vertex_input.vertexAttributeDescriptionCount = 2;
vertex_input.pVertexAttributeDescriptions = attributes;
// Raster state
VkPipelineRasterizationStateCreateInfo raster = {
VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO };
raster.polygonMode = VK_POLYGON_MODE_FILL;
raster.cullMode = VK_CULL_MODE_NONE;
raster.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
raster.depthClampEnable = false;
raster.rasterizerDiscardEnable = false;
raster.depthBiasEnable = false;
raster.lineWidth = 1.0f;
// Blend state
VkPipelineColorBlendAttachmentState blend_attachment = {0};
VkPipelineColorBlendStateCreateInfo blend = {
VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO };
blend_attachment.blendEnable = false;
blend_attachment.colorWriteMask = 0xf;
blend.attachmentCount = 1;
blend.pAttachments = &blend_attachment;
// Viewport state
VkPipelineViewportStateCreateInfo viewport = {
VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO };
viewport.viewportCount = 1;
viewport.scissorCount = 1;
// Depth-stencil state
VkPipelineDepthStencilStateCreateInfo depth_stencil = {
VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO };
depth_stencil.depthTestEnable = false;
depth_stencil.depthWriteEnable = false;
depth_stencil.depthBoundsTestEnable = false;
depth_stencil.stencilTestEnable = false;
depth_stencil.minDepthBounds = 0.0f;
depth_stencil.maxDepthBounds = 1.0f;
// Multisample state
VkPipelineMultisampleStateCreateInfo multisample = {
VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO };
multisample.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
// Dynamic state
VkPipelineDynamicStateCreateInfo dynamic = {
VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO };
static const VkDynamicState dynamics[] = {
VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR };
dynamic.pDynamicStates = dynamics;
dynamic.dynamicStateCount = sizeof(dynamics) / sizeof(dynamics[0]);
// Shaders
VkPipelineShaderStageCreateInfo shader_stages[2] = {
{ VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO },
{ VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO },
};
VkShaderModuleCreateInfo module_info = {
VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO };
module_info.codeSize = vertex_shader.size() * sizeof(uint32_t);
module_info.pCode = vertex_shader.data();
shader_stages[0].stage = VK_SHADER_STAGE_VERTEX_BIT;
shader_stages[0].pName = "main";
vkCreateShaderModule(device, &module_info, NULL, &shader_stages[0].module);
module_info.codeSize = fragment_shader.size() * sizeof(uint32_t);
module_info.pCode = fragment_shader.data();
shader_stages[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT;
shader_stages[1].pName = "main";
vkCreateShaderModule(device, &module_info, NULL, &shader_stages[1].module);
VkGraphicsPipelineCreateInfo pipe = {
VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO };
pipe.stageCount = 2;
pipe.pStages = shader_stages;
pipe.pVertexInputState = &vertex_input;
pipe.pInputAssemblyState = &input_assembly;
pipe.pRasterizationState = &raster;
pipe.pColorBlendState = &blend;
pipe.pMultisampleState = &multisample;
pipe.pViewportState = &viewport;
pipe.pDepthStencilState = &depth_stencil;
pipe.pDynamicState = &dynamic;
pipe.renderPass = final_pass ? swapchain_render_pass :
framebuffer->get_render_pass();
pipe.layout = pipeline_layout;
if (vkCreateGraphicsPipelines(device,
cache, 1, &pipe, NULL, &pipeline) != VK_SUCCESS)
{
vkDestroyShaderModule(device, shader_stages[0].module, NULL);
vkDestroyShaderModule(device, shader_stages[1].module, NULL);
return false;
}
vkDestroyShaderModule(device, shader_stages[0].module, NULL);
vkDestroyShaderModule(device, shader_stages[1].module, NULL);
return true;
}
CommonResources::CommonResources(VkDevice device,
const VkPhysicalDeviceMemoryProperties &memory_properties)
: device(device)
{
// The final pass uses an MVP designed for [0, 1] range VBO.
// For in-between passes, we just go with identity matrices, so keep it simple.
const float vbo_data[] = {
// Offscreen
-1.0f, -1.0f, 0.0f, 0.0f,
-1.0f, +1.0f, 0.0f, 1.0f,
1.0f, -1.0f, 1.0f, 0.0f,
1.0f, +1.0f, 1.0f, 1.0f,
// Final
0.0f, 0.0f, 0.0f, 0.0f,
0.0f, +1.0f, 0.0f, 1.0f,
1.0f, 0.0f, 1.0f, 0.0f,
1.0f, +1.0f, 1.0f, 1.0f,
};
vbo = unique_ptr<Buffer>(new Buffer(device,
memory_properties, sizeof(vbo_data), VK_BUFFER_USAGE_VERTEX_BUFFER_BIT));
void *ptr = vbo->map();
memcpy(ptr, vbo_data, sizeof(vbo_data));
vbo->unmap();
VkSamplerCreateInfo info = { VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO };
info.mipLodBias = 0.0f;
info.maxAnisotropy = 1.0f;
info.compareEnable = false;
info.minLod = 0.0f;
info.maxLod = VK_LOD_CLAMP_NONE;
info.unnormalizedCoordinates = false;
info.borderColor = VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK;
for (unsigned i = 0; i < VULKAN_FILTER_CHAIN_COUNT; i++)
{
switch (static_cast<vulkan_filter_chain_filter>(i))
{
case VULKAN_FILTER_CHAIN_LINEAR:
info.magFilter = VK_FILTER_LINEAR;
info.minFilter = VK_FILTER_LINEAR;
break;
case VULKAN_FILTER_CHAIN_NEAREST:
info.magFilter = VK_FILTER_NEAREST;
info.minFilter = VK_FILTER_NEAREST;
break;
default:
break;
}
for (unsigned j = 0; j < VULKAN_FILTER_CHAIN_COUNT; j++)
{
switch (static_cast<vulkan_filter_chain_filter>(j))
{
case VULKAN_FILTER_CHAIN_LINEAR:
info.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
break;
case VULKAN_FILTER_CHAIN_NEAREST:
info.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST;
break;
default:
break;
}
for (unsigned k = 0; k < VULKAN_FILTER_CHAIN_ADDRESS_COUNT; k++)
{
VkSamplerAddressMode mode = VK_SAMPLER_ADDRESS_MODE_MAX_ENUM;
switch (static_cast<vulkan_filter_chain_address>(k))
{
case VULKAN_FILTER_CHAIN_ADDRESS_REPEAT:
mode = VK_SAMPLER_ADDRESS_MODE_REPEAT;
break;
case VULKAN_FILTER_CHAIN_ADDRESS_MIRRORED_REPEAT:
mode = VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT;
break;
case VULKAN_FILTER_CHAIN_ADDRESS_CLAMP_TO_EDGE:
mode = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
break;
case VULKAN_FILTER_CHAIN_ADDRESS_CLAMP_TO_BORDER:
mode = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER;
break;
case VULKAN_FILTER_CHAIN_ADDRESS_MIRROR_CLAMP_TO_EDGE:
mode = VK_SAMPLER_ADDRESS_MODE_MIRROR_CLAMP_TO_EDGE;
break;
default:
break;
}
info.addressModeU = mode;
info.addressModeV = mode;
info.addressModeW = mode;
vkCreateSampler(device, &info, nullptr, &samplers[i][j][k]);
}
}
}
}
CommonResources::~CommonResources()
{
for (auto &i : samplers)
for (auto &j : i)
for (auto &k : j)
if (k != VK_NULL_HANDLE)
vkDestroySampler(device, k, nullptr);
}
void Pass::allocate_buffers()
{
if (reflection.ubo_stage_mask)
{
// Align
common->ubo_offset = (common->ubo_offset + common->ubo_alignment - 1) &
~(common->ubo_alignment - 1);
ubo_offset = common->ubo_offset;
// Allocate
common->ubo_offset += reflection.ubo_size;
}
}
void Pass::end_frame()
{
if (framebuffer_feedback)
swap(framebuffer, framebuffer_feedback);
}
bool Pass::init_feedback()
{
if (final_pass)
return false;
framebuffer_feedback = unique_ptr<Framebuffer>(
new Framebuffer(device, memory_properties,
current_framebuffer_size,
pass_info.rt_format, pass_info.max_levels));
return true;
}
bool Pass::build()
{
unordered_map<string, slang_semantic_map> semantic_map;
unsigned i;
unsigned j = 0;
framebuffer.reset();
framebuffer_feedback.reset();
if (!final_pass)
{
framebuffer = unique_ptr<Framebuffer>(
new Framebuffer(device, memory_properties,
current_framebuffer_size,
pass_info.rt_format, pass_info.max_levels));
}
for (auto &param : parameters)
{
if (!vk_shader_set_unique_map(semantic_map, param.id,
slang_semantic_map{ SLANG_SEMANTIC_FLOAT_PARAMETER, j }))
return false;
j++;
}
reflection = slang_reflection{};
reflection.pass_number = pass_number;
reflection.texture_semantic_map = &common->texture_semantic_map;
reflection.texture_semantic_uniform_map = &common->texture_semantic_uniform_map;
reflection.semantic_map = &semantic_map;
if (!slang_reflect_spirv(vertex_shader, fragment_shader, &reflection))
return false;
// Filter out parameters which we will never use anyways.
filtered_parameters.clear();
for (i = 0; i < reflection.semantic_float_parameters.size(); i++)
{
if (reflection.semantic_float_parameters[i].uniform ||
reflection.semantic_float_parameters[i].push_constant)
filtered_parameters.push_back(parameters[i]);
}
if (!init_pipeline())
return false;
return true;
}
void Pass::set_uniform_buffer(VkDescriptorSet set, unsigned binding,
VkBuffer buffer,
VkDeviceSize offset,
VkDeviceSize range)
{
VkDescriptorBufferInfo buffer_info;
buffer_info.buffer = buffer;
buffer_info.offset = offset;
buffer_info.range = range;
VkWriteDescriptorSet write = { VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET };
write.dstSet = set;
write.dstBinding = binding;
write.descriptorCount = 1;
write.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
write.pBufferInfo = &buffer_info;
vkUpdateDescriptorSets(device, 1, &write, 0, NULL);
}
void Pass::set_texture(VkDescriptorSet set, unsigned binding,
const Texture &texture)
{
VkDescriptorImageInfo image_info;
image_info.sampler = common->samplers[texture.filter][texture.mip_filter][texture.address];
image_info.imageView = texture.texture.view;
image_info.imageLayout = texture.texture.layout;
VkWriteDescriptorSet write = { VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET };
write.dstSet = set;
write.dstBinding = binding;
write.descriptorCount = 1;
write.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
write.pImageInfo = &image_info;
vkUpdateDescriptorSets(device, 1, &write, 0, nullptr);
}
void Pass::set_semantic_texture(VkDescriptorSet set,
slang_texture_semantic semantic, const Texture &texture)
{
if (reflection.semantic_textures[semantic][0].texture)
set_texture(set, reflection.semantic_textures[semantic][0].binding, texture);
}
void Pass::set_semantic_texture_array(VkDescriptorSet set,
slang_texture_semantic semantic, unsigned index,
const Texture &texture)
{
if (index < reflection.semantic_textures[semantic].size() &&
reflection.semantic_textures[semantic][index].texture)
set_texture(set, reflection.semantic_textures[semantic][index].binding, texture);
}
void Pass::build_semantic_texture_array_vec4(uint8_t *data, slang_texture_semantic semantic,
unsigned index, unsigned width, unsigned height)
{
auto &refl = reflection.semantic_textures[semantic];
if (index >= refl.size())
return;
if (data && refl[index].uniform)
build_vec4(
reinterpret_cast<float *>(data + refl[index].ubo_offset),
width,
height);
if (refl[index].push_constant)
build_vec4(
reinterpret_cast<float *>(push.buffer.data() + (refl[index].push_constant_offset >> 2)),
width,
height);
}
void Pass::build_semantic_texture_vec4(uint8_t *data, slang_texture_semantic semantic,
unsigned width, unsigned height)
{
build_semantic_texture_array_vec4(data, semantic, 0, width, height);
}
void Pass::build_semantic_vec4(uint8_t *data, slang_semantic semantic,
unsigned width, unsigned height)
{
auto &refl = reflection.semantics[semantic];
if (data && refl.uniform)
build_vec4(
reinterpret_cast<float *>(data + refl.ubo_offset),
width,
height);
if (refl.push_constant)
build_vec4(
reinterpret_cast<float *>(push.buffer.data() + (refl.push_constant_offset >> 2)),
width,
height);
}
void Pass::build_semantic_parameter(uint8_t *data, unsigned index, float value)
{
auto &refl = reflection.semantic_float_parameters[index];
// We will have filtered out stale parameters.
if (data && refl.uniform)
*reinterpret_cast<float*>(data + refl.ubo_offset) = value;
if (refl.push_constant)
*reinterpret_cast<float*>(push.buffer.data() + (refl.push_constant_offset >> 2)) = value;
}
void Pass::build_semantic_uint(uint8_t *data, slang_semantic semantic,
uint32_t value)
{
auto &refl = reflection.semantics[semantic];
if (data && refl.uniform)
*reinterpret_cast<uint32_t*>(data + reflection.semantics[semantic].ubo_offset) = value;
if (refl.push_constant)
*reinterpret_cast<uint32_t*>(push.buffer.data() + (refl.push_constant_offset >> 2)) = value;
}
void Pass::build_semantic_texture(VkDescriptorSet set, uint8_t *buffer,
slang_texture_semantic semantic, const Texture &texture)
{
build_semantic_texture_vec4(buffer, semantic,
texture.texture.width, texture.texture.height);
set_semantic_texture(set, semantic, texture);
}
void Pass::build_semantic_texture_array(VkDescriptorSet set, uint8_t *buffer,
slang_texture_semantic semantic, unsigned index, const Texture &texture)
{
build_semantic_texture_array_vec4(buffer, semantic, index,
texture.texture.width, texture.texture.height);
set_semantic_texture_array(set, semantic, index, texture);
}
void Pass::build_semantics(VkDescriptorSet set, uint8_t *buffer,
const float *mvp, const Texture &original, const Texture &source)
{
// MVP
if (buffer && reflection.semantics[SLANG_SEMANTIC_MVP].uniform)
{
size_t offset = reflection.semantics[SLANG_SEMANTIC_MVP].ubo_offset;
if (mvp)
memcpy(buffer + offset, mvp, sizeof(float) * 16);
else
build_identity_matrix(reinterpret_cast<float *>(buffer + offset));
}
if (reflection.semantics[SLANG_SEMANTIC_MVP].push_constant)
{
size_t offset = reflection.semantics[SLANG_SEMANTIC_MVP].push_constant_offset;
if (mvp)
memcpy(push.buffer.data() + (offset >> 2), mvp, sizeof(float) * 16);
else
build_identity_matrix(reinterpret_cast<float *>(push.buffer.data() + (offset >> 2)));
}
// Output information
build_semantic_vec4(buffer, SLANG_SEMANTIC_OUTPUT,
current_framebuffer_size.width, current_framebuffer_size.height);
build_semantic_vec4(buffer, SLANG_SEMANTIC_FINAL_VIEWPORT,
unsigned(current_viewport.width), unsigned(current_viewport.height));
build_semantic_uint(buffer, SLANG_SEMANTIC_FRAME_COUNT,
frame_count_period ? uint32_t(frame_count % frame_count_period) : uint32_t(frame_count));
// Standard inputs
build_semantic_texture(set, buffer, SLANG_TEXTURE_SEMANTIC_ORIGINAL, original);
build_semantic_texture(set, buffer, SLANG_TEXTURE_SEMANTIC_SOURCE, source);
// ORIGINAL_HISTORY[0] is an alias of ORIGINAL.
build_semantic_texture_array(set, buffer, SLANG_TEXTURE_SEMANTIC_ORIGINAL_HISTORY, 0, original);
// Parameters.
for (auto &param : filtered_parameters)
{
float value = common->shader_preset->parameters[param.index].current;
build_semantic_parameter(buffer, param.semantic_index, value);
}
// Previous inputs.
unsigned i = 0;
for (auto &texture : common->original_history)
{
build_semantic_texture_array(set, buffer,
SLANG_TEXTURE_SEMANTIC_ORIGINAL_HISTORY, i + 1,
texture);
i++;
}
// Previous passes.
i = 0;
for (auto &texture : common->pass_outputs)
{
build_semantic_texture_array(set, buffer,
SLANG_TEXTURE_SEMANTIC_PASS_OUTPUT, i,
texture);
i++;
}
// Feedback FBOs.
i = 0;
for (auto &texture : common->framebuffer_feedback)
{
build_semantic_texture_array(set, buffer,
SLANG_TEXTURE_SEMANTIC_PASS_FEEDBACK, i,
texture);
i++;
}
// LUTs.
i = 0;
for (auto &lut : common->luts)
{
build_semantic_texture_array(set, buffer,
SLANG_TEXTURE_SEMANTIC_USER, i,
lut->get_texture());
i++;
}
}
void Pass::build_commands(
DeferredDisposer &disposer,
VkCommandBuffer cmd,
const Texture &original,
const Texture &source,
const VkViewport &vp,
const float *mvp)
{
current_viewport = vp;
auto size = get_output_size(
{ original.texture.width, original.texture.height },
{ source.texture.width, source.texture.height });
if (framebuffer &&
(size.width != framebuffer->get_size().width ||
size.height != framebuffer->get_size().height))
{
framebuffer->set_size(disposer, size);
}
current_framebuffer_size = size;
if (reflection.ubo_stage_mask && common->ubo_mapped)
{
uint8_t *u = common->ubo_mapped + ubo_offset +
sync_index * common->ubo_sync_index_stride;
build_semantics(sets[sync_index], u, mvp, original, source);
}
else
build_semantics(sets[sync_index], nullptr, mvp, original, source);
if (reflection.ubo_stage_mask)
{
set_uniform_buffer(sets[sync_index], reflection.ubo_binding,
common->ubo->get_buffer(),
ubo_offset + sync_index * common->ubo_sync_index_stride,
reflection.ubo_size);
}
// The final pass is always executed inside
// another render pass since the frontend will
// want to overlay various things on top for
// the passes that end up on-screen.
if (!final_pass)
{
/* Render. */
vulkan_image_layout_transition_levels(cmd,
framebuffer->get_image(), 1,
VK_IMAGE_LAYOUT_UNDEFINED,
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
0,
VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT,
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT);
VkRenderPassBeginInfo rp_info = {
VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO };
rp_info.renderPass = framebuffer->get_render_pass();
rp_info.framebuffer = framebuffer->get_framebuffer();
rp_info.renderArea.extent.width = current_framebuffer_size.width;
rp_info.renderArea.extent.height = current_framebuffer_size.height;
rp_info.clearValueCount = 0;
rp_info.pClearValues = nullptr;
vkCmdBeginRenderPass(cmd, &rp_info, VK_SUBPASS_CONTENTS_INLINE);
}
vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_layout,
0, 1, &sets[sync_index], 0, nullptr);
if (push.stages != 0)
{
vkCmdPushConstants(cmd, pipeline_layout,
push.stages, 0, reflection.push_constant_size,
push.buffer.data());
}
VkDeviceSize offset = final_pass ? 16 * sizeof(float) : 0;
vkCmdBindVertexBuffers(cmd, 0, 1,
&common->vbo->get_buffer(),
&offset);
if (final_pass)
{
const VkRect2D sci = {
{
int32_t(current_viewport.x),
int32_t(current_viewport.y)
},
{
uint32_t(current_viewport.width),
uint32_t(current_viewport.height)
},
};
vkCmdSetViewport(cmd, 0, 1, &current_viewport);
vkCmdSetScissor(cmd, 0, 1, &sci);
}
else
{
const VkViewport vp = {
0.0f, 0.0f,
float(current_framebuffer_size.width),
float(current_framebuffer_size.height),
0.0f, 1.0f
};
const VkRect2D sci = {
{ 0, 0 },
{
current_framebuffer_size.width,
current_framebuffer_size.height
},
};
vkCmdSetViewport(cmd, 0, 1, &vp);
vkCmdSetScissor(cmd, 0, 1, &sci);
}
vkCmdDraw(cmd, 4, 1, 0, 0);
if (!final_pass)
{
vkCmdEndRenderPass(cmd);
if (framebuffer->get_levels() > 1)
framebuffer->generate_mips(cmd);
else
{
// Barrier to sync with next pass.
vulkan_image_layout_transition_levels(
cmd,
framebuffer->get_image(),VK_REMAINING_MIP_LEVELS,
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
VK_ACCESS_SHADER_READ_BIT,
VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT,
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT);
}
}
}
Framebuffer::Framebuffer(
VkDevice device,
const VkPhysicalDeviceMemoryProperties &mem_props,
const Size2D &max_size, VkFormat format,
unsigned max_levels) :
memory_properties(mem_props),
device(device),
size(max_size),
format(format),
max_levels(max(max_levels, 1u))
{
RARCH_LOG("[Vulkan filter chain]: Creating framebuffer %u x %u (max %u level(s)).\n",
max_size.width, max_size.height, max_levels);
init_render_pass();
init(nullptr);
}
void Framebuffer::clear(VkCommandBuffer cmd)
{
VkClearColorValue color;
VkImageSubresourceRange range;
vulkan_image_layout_transition_levels(cmd, image,VK_REMAINING_MIP_LEVELS,
VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
0, VK_ACCESS_TRANSFER_WRITE_BIT,
VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
VK_PIPELINE_STAGE_TRANSFER_BIT);
memset(&color, 0, sizeof(color));
memset(&range, 0, sizeof(range));
range.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
range.levelCount = 1;
range.layerCount = 1;
vkCmdClearColorImage(cmd, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
&color, 1, &range);
vulkan_image_layout_transition_levels(cmd, image,VK_REMAINING_MIP_LEVELS,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT,
VK_PIPELINE_STAGE_TRANSFER_BIT,
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT);
}
void Framebuffer::generate_mips(VkCommandBuffer cmd)
{
unsigned i;
// This is run every frame, so make sure
// we aren't opting into the "lazy" way of doing this. :)
VkImageMemoryBarrier barriers[2] = {
{ VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER },
{ VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER },
};
// First, transfer the input mip level to TRANSFER_SRC_OPTIMAL.
// This should allow the surface to stay compressed.
// All subsequent mip-layers are now transferred into DST_OPTIMAL from
// UNDEFINED at this point.
// Input
barriers[0].srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
barriers[0].dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
barriers[0].oldLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
barriers[0].newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
barriers[0].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barriers[0].dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barriers[0].image = image;
barriers[0].subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
barriers[0].subresourceRange.baseMipLevel = 0;
barriers[0].subresourceRange.levelCount = 1;
barriers[0].subresourceRange.layerCount = VK_REMAINING_ARRAY_LAYERS;
// The rest of the mip chain
barriers[1].srcAccessMask = 0;
barriers[1].dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
barriers[1].oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
barriers[1].newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
barriers[1].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barriers[1].dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barriers[1].image = image;
barriers[1].subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
barriers[1].subresourceRange.baseMipLevel = 1;
barriers[1].subresourceRange.levelCount = VK_REMAINING_MIP_LEVELS;
barriers[1].subresourceRange.layerCount = VK_REMAINING_ARRAY_LAYERS;
vkCmdPipelineBarrier(cmd,
VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT,
VK_PIPELINE_STAGE_TRANSFER_BIT,
false,
0, nullptr,
0, nullptr,
2, barriers);
for (i = 1; i < levels; i++)
{
// For subsequent passes, we have to transition from DST_OPTIMAL to SRC_OPTIMAL,
// but only do so one mip-level at a time.
if (i > 1)
{
barriers[0].srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
barriers[0].dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
barriers[0].subresourceRange.baseMipLevel = i - 1;
barriers[0].subresourceRange.levelCount = 1;
barriers[0].oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
barriers[0].newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
vkCmdPipelineBarrier(cmd,
VK_PIPELINE_STAGE_TRANSFER_BIT,
VK_PIPELINE_STAGE_TRANSFER_BIT,
false,
0, nullptr,
0, nullptr,
1, barriers);
}
VkImageBlit blit_region = {};
unsigned src_width = MAX(size.width >> (i - 1), 1u);
unsigned src_height = MAX(size.height >> (i - 1), 1u);
unsigned target_width = MAX(size.width >> i, 1u);
unsigned target_height = MAX(size.height >> i, 1u);
blit_region.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
blit_region.srcSubresource.mipLevel = i - 1;
blit_region.srcSubresource.baseArrayLayer = 0;
blit_region.srcSubresource.layerCount = 1;
blit_region.dstSubresource = blit_region.srcSubresource;
blit_region.dstSubresource.mipLevel = i;
blit_region.srcOffsets[1].x = src_width;
blit_region.srcOffsets[1].y = src_height;
blit_region.srcOffsets[1].z = 1;
blit_region.dstOffsets[1].x = target_width;
blit_region.dstOffsets[1].y = target_height;
blit_region.dstOffsets[1].z = 1;
vkCmdBlitImage(cmd,
image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
1, &blit_region, VK_FILTER_LINEAR);
}
// We are now done, and we have all mip-levels except the last in TRANSFER_SRC_OPTIMAL,
// and the last one still on TRANSFER_DST_OPTIMAL, so do a final barrier which
// moves everything to SHADER_READ_ONLY_OPTIMAL in one go along with the execution barrier to next pass.
// Read-to-read memory barrier, so only need execution barrier for first transition.
barriers[0].srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
barriers[0].dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
barriers[0].subresourceRange.baseMipLevel = 0;
barriers[0].subresourceRange.levelCount = levels - 1;
barriers[0].oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
barriers[0].newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
// This is read-after-write barrier.
barriers[1].srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
barriers[1].dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
barriers[1].subresourceRange.baseMipLevel = levels - 1;
barriers[1].subresourceRange.levelCount = 1;
barriers[1].oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
barriers[1].newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
vkCmdPipelineBarrier(cmd,
VK_PIPELINE_STAGE_TRANSFER_BIT,
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
false,
0, nullptr,
0, nullptr,
2, barriers);
// Next pass will wait for ALL_GRAPHICS_BIT, and since we have dstStage as FRAGMENT_SHADER,
// the dependency chain will ensure we don't start next pass until the mipchain is complete.
}
void Framebuffer::copy(VkCommandBuffer cmd,
VkImage src_image, VkImageLayout src_layout)
{
VkImageCopy region;
vulkan_image_layout_transition_levels(cmd, image,VK_REMAINING_MIP_LEVELS,
VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
0, VK_ACCESS_TRANSFER_WRITE_BIT,
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
VK_PIPELINE_STAGE_TRANSFER_BIT);
memset(&region, 0, sizeof(region));
region.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
region.srcSubresource.layerCount = 1;
region.dstSubresource = region.srcSubresource;
region.extent.width = size.width;
region.extent.height = size.height;
region.extent.depth = 1;
vkCmdCopyImage(cmd,
src_image, src_layout,
image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
1, &region);
vulkan_image_layout_transition_levels(cmd, image,VK_REMAINING_MIP_LEVELS,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT,
VK_PIPELINE_STAGE_TRANSFER_BIT,
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT);
}
void Framebuffer::init(DeferredDisposer *disposer)
{
VkMemoryRequirements mem_reqs;
VkImageCreateInfo info = { VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO };
info.imageType = VK_IMAGE_TYPE_2D;
info.format = format;
info.extent.width = size.width;
info.extent.height = size.height;
info.extent.depth = 1;
info.mipLevels = min(max_levels, num_miplevels(size.width, size.height));
info.arrayLayers = 1;
info.samples = VK_SAMPLE_COUNT_1_BIT;
info.tiling = VK_IMAGE_TILING_OPTIMAL;
info.usage = VK_IMAGE_USAGE_SAMPLED_BIT |
VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT |
VK_IMAGE_USAGE_TRANSFER_DST_BIT |
VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
levels = info.mipLevels;
vkCreateImage(device, &info, nullptr, &image);
vkGetImageMemoryRequirements(device, image, &mem_reqs);
VkMemoryAllocateInfo alloc = { VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO };
alloc.allocationSize = mem_reqs.size;
alloc.memoryTypeIndex = find_memory_type_fallback(
memory_properties, mem_reqs.memoryTypeBits,
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
// Can reuse already allocated memory.
if (memory.size < mem_reqs.size || memory.type != alloc.memoryTypeIndex)
{
// Memory might still be in use since we don't want to totally stall
// the world for framebuffer recreation.
if (memory.memory != VK_NULL_HANDLE && disposer)
{
auto d = device;
auto m = memory.memory;
disposer->defer([=] { vkFreeMemory(d, m, nullptr); });
}
memory.type = alloc.memoryTypeIndex;
memory.size = mem_reqs.size;
vkAllocateMemory(device, &alloc, nullptr, &memory.memory);
}
vkBindImageMemory(device, image, memory.memory, 0);
VkImageViewCreateInfo view_info = {
VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO };
view_info.viewType = VK_IMAGE_VIEW_TYPE_2D;
view_info.format = format;
view_info.image = image;
view_info.subresourceRange.baseMipLevel = 0;
view_info.subresourceRange.baseArrayLayer = 0;
view_info.subresourceRange.levelCount = levels;
view_info.subresourceRange.layerCount = 1;
view_info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
view_info.components.r = VK_COMPONENT_SWIZZLE_R;
view_info.components.g = VK_COMPONENT_SWIZZLE_G;
view_info.components.b = VK_COMPONENT_SWIZZLE_B;
view_info.components.a = VK_COMPONENT_SWIZZLE_A;
vkCreateImageView(device, &view_info, nullptr, &view);
view_info.subresourceRange.levelCount = 1;
vkCreateImageView(device, &view_info, nullptr, &fb_view);
init_framebuffer();
}
void Framebuffer::init_render_pass()
{
VkRenderPassCreateInfo rp_info = {
VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO };
VkAttachmentReference color_ref = { 0,
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL };
// We will always write to the entire framebuffer,
// so we don't really need to clear.
VkAttachmentDescription attachment = {0};
attachment.format = format;
attachment.samples = VK_SAMPLE_COUNT_1_BIT;
attachment.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
attachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
attachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
attachment.initialLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
attachment.finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
VkSubpassDescription subpass = {0};
subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
subpass.colorAttachmentCount = 1;
subpass.pColorAttachments = &color_ref;
rp_info.attachmentCount = 1;
rp_info.pAttachments = &attachment;
rp_info.subpassCount = 1;
rp_info.pSubpasses = &subpass;
vkCreateRenderPass(device, &rp_info, nullptr, &render_pass);
}
void Framebuffer::init_framebuffer()
{
VkFramebufferCreateInfo info = {
VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO };
info.renderPass = render_pass;
info.attachmentCount = 1;
info.pAttachments = &fb_view;
info.width = size.width;
info.height = size.height;
info.layers = 1;
vkCreateFramebuffer(device, &info, nullptr, &framebuffer);
}
void Framebuffer::set_size(DeferredDisposer &disposer, const Size2D &size, VkFormat format)
{
this->size = size;
if (format != VK_FORMAT_UNDEFINED)
this->format = format;
RARCH_LOG("[Vulkan filter chain]: Updating framebuffer size %u x %u (format: %u).\n",
size.width, size.height, (unsigned)this->format);
{
// The current framebuffers, etc, might still be in use
// so defer deletion.
// We'll most likely be able to reuse the memory,
// so don't free it here.
//
// Fake lambda init captures for C++11.
//
auto d = device;
auto i = image;
auto v = view;
auto fbv = fb_view;
auto fb = framebuffer;
disposer.defer([=]
{
if (fb != VK_NULL_HANDLE)
vkDestroyFramebuffer(d, fb, nullptr);
if (v != VK_NULL_HANDLE)
vkDestroyImageView(d, v, nullptr);
if (fbv != VK_NULL_HANDLE)
vkDestroyImageView(d, fbv, nullptr);
if (i != VK_NULL_HANDLE)
vkDestroyImage(d, i, nullptr);
});
}
init(&disposer);
}
Framebuffer::~Framebuffer()
{
if (framebuffer != VK_NULL_HANDLE)
vkDestroyFramebuffer(device, framebuffer, nullptr);
if (render_pass != VK_NULL_HANDLE)
vkDestroyRenderPass(device, render_pass, nullptr);
if (view != VK_NULL_HANDLE)
vkDestroyImageView(device, view, nullptr);
if (fb_view != VK_NULL_HANDLE)
vkDestroyImageView(device, fb_view, nullptr);
if (image != VK_NULL_HANDLE)
vkDestroyImage(device, image, nullptr);
if (memory.memory != VK_NULL_HANDLE)
vkFreeMemory(device, memory.memory, nullptr);
}
// C glue
vulkan_filter_chain_t *vulkan_filter_chain_new(
const vulkan_filter_chain_create_info *info)
{
return new vulkan_filter_chain(*info);
}
vulkan_filter_chain_t *vulkan_filter_chain_create_default(
const struct vulkan_filter_chain_create_info *info,
vulkan_filter_chain_filter filter)
{
struct vulkan_filter_chain_pass_info pass_info;
auto tmpinfo = *info;
tmpinfo.num_passes = 1;
unique_ptr<vulkan_filter_chain> chain{ new vulkan_filter_chain(tmpinfo) };
if (!chain)
return nullptr;
pass_info.scale_type_x = VULKAN_FILTER_CHAIN_SCALE_VIEWPORT;
pass_info.scale_type_y = VULKAN_FILTER_CHAIN_SCALE_VIEWPORT;
pass_info.scale_x = 1.0f;
pass_info.scale_y = 1.0f;
pass_info.rt_format = tmpinfo.swapchain.format;
pass_info.source_filter = filter;
pass_info.mip_filter = VULKAN_FILTER_CHAIN_NEAREST;
pass_info.address = VULKAN_FILTER_CHAIN_ADDRESS_CLAMP_TO_EDGE;
pass_info.max_levels = 0;
chain->set_pass_info(0, pass_info);
chain->set_shader(0, VK_SHADER_STAGE_VERTEX_BIT,
opaque_vert,
sizeof(opaque_vert) / sizeof(uint32_t));
chain->set_shader(0, VK_SHADER_STAGE_FRAGMENT_BIT,
opaque_frag,
sizeof(opaque_frag) / sizeof(uint32_t));
if (!chain->init())
return nullptr;
return chain.release();
}
struct ConfigDeleter
{
void operator()(config_file_t *conf)
{
if (conf)
config_file_free(conf);
}
};
static VkFormat glslang_format_to_vk(glslang_format fmt)
{
#undef FMT
#define FMT(x) case SLANG_FORMAT_##x: return VK_FORMAT_##x
switch (fmt)
{
FMT(R8_UNORM);
FMT(R8_SINT);
FMT(R8_UINT);
FMT(R8G8_UNORM);
FMT(R8G8_SINT);
FMT(R8G8_UINT);
FMT(R8G8B8A8_UNORM);
FMT(R8G8B8A8_SINT);
FMT(R8G8B8A8_UINT);
FMT(R8G8B8A8_SRGB);
FMT(A2B10G10R10_UNORM_PACK32);
FMT(A2B10G10R10_UINT_PACK32);
FMT(R16_UINT);
FMT(R16_SINT);
FMT(R16_SFLOAT);
FMT(R16G16_UINT);
FMT(R16G16_SINT);
FMT(R16G16_SFLOAT);
FMT(R16G16B16A16_UINT);
FMT(R16G16B16A16_SINT);
FMT(R16G16B16A16_SFLOAT);
FMT(R32_UINT);
FMT(R32_SINT);
FMT(R32_SFLOAT);
FMT(R32G32_UINT);
FMT(R32G32_SINT);
FMT(R32G32_SFLOAT);
FMT(R32G32B32A32_UINT);
FMT(R32G32B32A32_SINT);
FMT(R32G32B32A32_SFLOAT);
default:
return VK_FORMAT_UNDEFINED;
}
}
static vulkan_filter_chain_address wrap_to_address(gfx_wrap_type type)
{
switch (type)
{
default:
case RARCH_WRAP_EDGE:
return VULKAN_FILTER_CHAIN_ADDRESS_CLAMP_TO_EDGE;
case RARCH_WRAP_BORDER:
return VULKAN_FILTER_CHAIN_ADDRESS_CLAMP_TO_BORDER;
case RARCH_WRAP_REPEAT:
return VULKAN_FILTER_CHAIN_ADDRESS_REPEAT;
case RARCH_WRAP_MIRRORED_REPEAT:
return VULKAN_FILTER_CHAIN_ADDRESS_MIRRORED_REPEAT;
}
}
static unique_ptr<StaticTexture> vulkan_filter_chain_load_lut(VkCommandBuffer cmd,
const struct vulkan_filter_chain_create_info *info,
vulkan_filter_chain *chain,
const video_shader_lut *shader)
{
unsigned i;
texture_image image;
unique_ptr<Buffer> buffer;
VkMemoryRequirements mem_reqs;
VkImageCreateInfo image_info = { VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO };
VkImageViewCreateInfo view_info = { VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO };
VkMemoryAllocateInfo alloc = { VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO };
VkImage tex = VK_NULL_HANDLE;
VkDeviceMemory memory = VK_NULL_HANDLE;
VkImageView view = VK_NULL_HANDLE;
VkBufferImageCopy region = {};
void *ptr = nullptr;
image.width = 0;
image.height = 0;
image.pixels = NULL;
image.supports_rgba = video_driver_supports_rgba();
if (!image_texture_load(&image, shader->path))
return {};
image_info.imageType = VK_IMAGE_TYPE_2D;
image_info.format = VK_FORMAT_B8G8R8A8_UNORM;
image_info.extent.width = image.width;
image_info.extent.height = image.height;
image_info.extent.depth = 1;
image_info.mipLevels = shader->mipmap ? num_miplevels(image.width, image.height) : 1;
image_info.arrayLayers = 1;
image_info.samples = VK_SAMPLE_COUNT_1_BIT;
image_info.tiling = VK_IMAGE_TILING_OPTIMAL;
image_info.usage = VK_IMAGE_USAGE_SAMPLED_BIT |
VK_IMAGE_USAGE_TRANSFER_SRC_BIT |
VK_IMAGE_USAGE_TRANSFER_DST_BIT;
image_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
vkCreateImage(info->device, &image_info, nullptr, &tex);
vkGetImageMemoryRequirements(info->device, tex, &mem_reqs);
alloc.allocationSize = mem_reqs.size;
alloc.memoryTypeIndex = vulkan_find_memory_type(
&*info->memory_properties,
mem_reqs.memoryTypeBits,
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
if (vkAllocateMemory(info->device, &alloc, nullptr, &memory) != VK_SUCCESS)
goto error;
vkBindImageMemory(info->device, tex, memory, 0);
view_info.image = tex;
view_info.viewType = VK_IMAGE_VIEW_TYPE_2D;
view_info.format = VK_FORMAT_B8G8R8A8_UNORM;
view_info.components.r = VK_COMPONENT_SWIZZLE_R;
view_info.components.g = VK_COMPONENT_SWIZZLE_G;
view_info.components.b = VK_COMPONENT_SWIZZLE_B;
view_info.components.a = VK_COMPONENT_SWIZZLE_A;
view_info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
view_info.subresourceRange.levelCount = image_info.mipLevels;
view_info.subresourceRange.layerCount = 1;
vkCreateImageView(info->device, &view_info, nullptr, &view);
buffer = unique_ptr<Buffer>(new Buffer(info->device, *info->memory_properties,
image.width * image.height * sizeof(uint32_t), VK_BUFFER_USAGE_TRANSFER_SRC_BIT));
ptr = buffer->map();
memcpy(ptr, image.pixels, image.width * image.height * sizeof(uint32_t));
buffer->unmap();
vulkan_image_layout_transition_levels(cmd, tex,VK_REMAINING_MIP_LEVELS,
VK_IMAGE_LAYOUT_UNDEFINED,
shader->mipmap ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
0, VK_ACCESS_TRANSFER_WRITE_BIT,
VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT);
region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
region.imageSubresource.mipLevel = 0;
region.imageSubresource.baseArrayLayer = 0;
region.imageSubresource.layerCount = 1;
region.imageExtent.width = image.width;
region.imageExtent.height = image.height;
region.imageExtent.depth = 1;
vkCmdCopyBufferToImage(cmd, buffer->get_buffer(), tex,
shader->mipmap ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
1, &region);
for (i = 1; i < image_info.mipLevels; i++)
{
VkImageBlit blit_region = {};
unsigned src_width = MAX(image.width >> (i - 1), 1u);
unsigned src_height = MAX(image.height >> (i - 1), 1u);
unsigned target_width = MAX(image.width >> i, 1u);
unsigned target_height = MAX(image.height >> i, 1u);
blit_region.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
blit_region.srcSubresource.mipLevel = i - 1;
blit_region.srcSubresource.baseArrayLayer = 0;
blit_region.srcSubresource.layerCount = 1;
blit_region.dstSubresource = blit_region.srcSubresource;
blit_region.dstSubresource.mipLevel = i;
blit_region.srcOffsets[1].x = src_width;
blit_region.srcOffsets[1].y = src_height;
blit_region.srcOffsets[1].z = 1;
blit_region.dstOffsets[1].x = target_width;
blit_region.dstOffsets[1].y = target_height;
blit_region.dstOffsets[1].z = 1;
/* Only injects execution and memory barriers,
* not actual transition. */
vulkan_image_layout_transition_levels(cmd, tex, VK_REMAINING_MIP_LEVELS,
VK_IMAGE_LAYOUT_GENERAL,
VK_IMAGE_LAYOUT_GENERAL,
VK_ACCESS_TRANSFER_WRITE_BIT,
VK_ACCESS_TRANSFER_READ_BIT,
VK_PIPELINE_STAGE_TRANSFER_BIT,
VK_PIPELINE_STAGE_TRANSFER_BIT);
vkCmdBlitImage(cmd,
tex, VK_IMAGE_LAYOUT_GENERAL,
tex, VK_IMAGE_LAYOUT_GENERAL,
1, &blit_region, VK_FILTER_LINEAR);
}
vulkan_image_layout_transition_levels(cmd, tex,VK_REMAINING_MIP_LEVELS,
shader->mipmap ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT,
VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT);
image_texture_free(&image);
image.pixels = nullptr;
return unique_ptr<StaticTexture>(new StaticTexture(shader->id, info->device,
tex, view, memory, move(buffer), image.width, image.height,
shader->filter != RARCH_FILTER_NEAREST,
image_info.mipLevels > 1,
wrap_to_address(shader->wrap)));
error:
if (image.pixels)
image_texture_free(&image);
if (tex != VK_NULL_HANDLE)
vkDestroyImage(info->device, tex, nullptr);
if (view != VK_NULL_HANDLE)
vkDestroyImageView(info->device, view, nullptr);
if (memory != VK_NULL_HANDLE)
vkFreeMemory(info->device, memory, nullptr);
return {};
}
static bool vulkan_filter_chain_load_luts(
const struct vulkan_filter_chain_create_info *info,
vulkan_filter_chain *chain,
video_shader *shader)
{
VkCommandBufferBeginInfo begin_info = {
VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO };
VkSubmitInfo submit_info = {
VK_STRUCTURE_TYPE_SUBMIT_INFO };
VkCommandBuffer cmd = VK_NULL_HANDLE;
VkCommandBufferAllocateInfo cmd_info = { VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO };
bool recording = false;
cmd_info.commandPool = info->command_pool;
cmd_info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
cmd_info.commandBufferCount = 1;
vkAllocateCommandBuffers(info->device, &cmd_info, &cmd);
begin_info.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
vkBeginCommandBuffer(cmd, &begin_info);
recording = true;
for (unsigned i = 0; i < shader->luts; i++)
{
auto image = vulkan_filter_chain_load_lut(cmd, info, chain, &shader->lut[i]);
if (!image)
{
RARCH_ERR("[Vulkan]: Failed to load LUT \"%s\".\n", shader->lut[i].path);
goto error;
}
chain->add_static_texture(move(image));
}
vkEndCommandBuffer(cmd);
submit_info.commandBufferCount = 1;
submit_info.pCommandBuffers = &cmd;
vkQueueSubmit(info->queue, 1, &submit_info, VK_NULL_HANDLE);
vkQueueWaitIdle(info->queue);
vkFreeCommandBuffers(info->device, info->command_pool, 1, &cmd);
chain->release_staging_buffers();
return true;
error:
if (recording)
vkEndCommandBuffer(cmd);
if (cmd != VK_NULL_HANDLE)
vkFreeCommandBuffers(info->device, info->command_pool, 1, &cmd);
return false;
}
vulkan_filter_chain_t *vulkan_filter_chain_create_from_preset(
const struct vulkan_filter_chain_create_info *info,
const char *path, vulkan_filter_chain_filter filter)
{
unsigned i;
unique_ptr<video_shader> shader{ new video_shader() };
if (!shader)
return nullptr;
unique_ptr<config_file_t, ConfigDeleter> conf{ config_file_new(path) };
if (!conf)
return nullptr;
if (!video_shader_read_conf_cgp(conf.get(), shader.get()))
return nullptr;
video_shader_resolve_relative(shader.get(), path);
bool last_pass_is_fbo = shader->pass[shader->passes - 1].fbo.valid;
auto tmpinfo = *info;
tmpinfo.num_passes = shader->passes + (last_pass_is_fbo ? 1 : 0);
unique_ptr<vulkan_filter_chain> chain{ new vulkan_filter_chain(tmpinfo) };
if (!chain)
return nullptr;
if (shader->luts && !vulkan_filter_chain_load_luts(info, chain.get(), shader.get()))
return nullptr;
shader->num_parameters = 0;
for (i = 0; i < shader->passes; i++)
{
glslang_output output;
struct vulkan_filter_chain_pass_info pass_info;
const video_shader_pass *pass = &shader->pass[i];
const video_shader_pass *next_pass =
i + 1 < shader->passes ? &shader->pass[i + 1] : nullptr;
pass_info.scale_type_x = VULKAN_FILTER_CHAIN_SCALE_ORIGINAL;
pass_info.scale_type_y = VULKAN_FILTER_CHAIN_SCALE_ORIGINAL;
pass_info.scale_x = 0.0f;
pass_info.scale_y = 0.0f;
pass_info.rt_format = VK_FORMAT_UNDEFINED;
pass_info.source_filter = VULKAN_FILTER_CHAIN_LINEAR;
pass_info.mip_filter = VULKAN_FILTER_CHAIN_LINEAR;
pass_info.address = VULKAN_FILTER_CHAIN_ADDRESS_REPEAT;
pass_info.max_levels = 0;
if (!glslang_compile_shader(pass->source.path, &output))
{
RARCH_ERR("Failed to compile shader: \"%s\".\n",
pass->source.path);
return nullptr;
}
for (auto &meta_param : output.meta.parameters)
{
if (shader->num_parameters >= GFX_MAX_PARAMETERS)
{
RARCH_ERR("[Vulkan]: Exceeded maximum number of parameters.\n");
return nullptr;
}
auto itr = find_if(shader->parameters, shader->parameters + shader->num_parameters,
[&](const video_shader_parameter &param)
{
return meta_param.id == param.id;
});
if (itr != shader->parameters + shader->num_parameters)
{
// Allow duplicate #pragma parameter, but only if they are exactly the same.
if (meta_param.desc != itr->desc ||
meta_param.initial != itr->initial ||
meta_param.minimum != itr->minimum ||
meta_param.maximum != itr->maximum ||
meta_param.step != itr->step)
{
RARCH_ERR("[Vulkan]: Duplicate parameters found for \"%s\", but arguments do not match.\n",
itr->id);
return nullptr;
}
chain->add_parameter(i, itr - shader->parameters, meta_param.id);
}
else
{
auto &param = shader->parameters[shader->num_parameters];
strlcpy(param.id, meta_param.id.c_str(), sizeof(param.id));
strlcpy(param.desc, meta_param.desc.c_str(), sizeof(param.desc));
param.current = meta_param.initial;
param.initial = meta_param.initial;
param.minimum = meta_param.minimum;
param.maximum = meta_param.maximum;
param.step = meta_param.step;
chain->add_parameter(i, shader->num_parameters, meta_param.id);
shader->num_parameters++;
}
}
chain->set_shader(i,
VK_SHADER_STAGE_VERTEX_BIT,
output.vertex.data(),
output.vertex.size());
chain->set_shader(i,
VK_SHADER_STAGE_FRAGMENT_BIT,
output.fragment.data(),
output.fragment.size());
chain->set_frame_count_period(i, pass->frame_count_mod);
if (!output.meta.name.empty())
chain->set_pass_name(i, output.meta.name.c_str());
// Preset overrides.
if (*pass->alias)
chain->set_pass_name(i, pass->alias);
if (pass->filter == RARCH_FILTER_UNSPEC)
pass_info.source_filter = filter;
else
{
pass_info.source_filter =
pass->filter == RARCH_FILTER_LINEAR ? VULKAN_FILTER_CHAIN_LINEAR :
VULKAN_FILTER_CHAIN_NEAREST;
}
pass_info.address = wrap_to_address(pass->wrap);
pass_info.max_levels = 1;
// TODO: Expose max_levels in slangp.
// CGP format is a bit awkward in that it uses mipmap_input,
// so we much check if next pass needs the mipmapping.
if (next_pass && next_pass->mipmap)
pass_info.max_levels = ~0u;
pass_info.mip_filter = pass->filter != RARCH_FILTER_NEAREST && pass_info.max_levels > 1
? VULKAN_FILTER_CHAIN_LINEAR : VULKAN_FILTER_CHAIN_NEAREST;
bool explicit_format = output.meta.rt_format != SLANG_FORMAT_UNKNOWN;
// Set a reasonable default.
if (output.meta.rt_format == SLANG_FORMAT_UNKNOWN)
output.meta.rt_format = SLANG_FORMAT_R8G8B8A8_UNORM;
if (!pass->fbo.valid)
{
pass_info.scale_type_x = i + 1 == shader->passes
? VULKAN_FILTER_CHAIN_SCALE_VIEWPORT
: VULKAN_FILTER_CHAIN_SCALE_SOURCE;
pass_info.scale_type_y = i + 1 == shader->passes
? VULKAN_FILTER_CHAIN_SCALE_VIEWPORT
: VULKAN_FILTER_CHAIN_SCALE_SOURCE;
pass_info.scale_x = 1.0f;
pass_info.scale_y = 1.0f;
if (i + 1 == shader->passes)
{
pass_info.rt_format = tmpinfo.swapchain.format;
if (explicit_format)
RARCH_WARN("[slang]: Using explicit format for last pass in chain,"
" but it is not rendered to framebuffer, using swapchain format instead.\n");
}
else
{
pass_info.rt_format = glslang_format_to_vk(output.meta.rt_format);
RARCH_LOG("[slang]: Using render target format %s for pass output #%u.\n",
glslang_format_to_string(output.meta.rt_format), i);
}
}
else
{
// Preset overrides shader.
// Kinda ugly ...
if (pass->fbo.srgb_fbo)
output.meta.rt_format = SLANG_FORMAT_R8G8B8A8_SRGB;
else if (pass->fbo.fp_fbo)
output.meta.rt_format = SLANG_FORMAT_R16G16B16A16_SFLOAT;
///
pass_info.rt_format = glslang_format_to_vk(output.meta.rt_format);
RARCH_LOG("[slang]: Using render target format %s for pass output #%u.\n",
glslang_format_to_string(output.meta.rt_format), i);
switch (pass->fbo.type_x)
{
case RARCH_SCALE_INPUT:
pass_info.scale_x = pass->fbo.scale_x;
pass_info.scale_type_x = VULKAN_FILTER_CHAIN_SCALE_SOURCE;
break;
case RARCH_SCALE_ABSOLUTE:
pass_info.scale_x = float(pass->fbo.abs_x);
pass_info.scale_type_x = VULKAN_FILTER_CHAIN_SCALE_ABSOLUTE;
break;
case RARCH_SCALE_VIEWPORT:
pass_info.scale_x = pass->fbo.scale_x;
pass_info.scale_type_x = VULKAN_FILTER_CHAIN_SCALE_VIEWPORT;
break;
}
switch (pass->fbo.type_y)
{
case RARCH_SCALE_INPUT:
pass_info.scale_y = pass->fbo.scale_y;
pass_info.scale_type_y = VULKAN_FILTER_CHAIN_SCALE_SOURCE;
break;
case RARCH_SCALE_ABSOLUTE:
pass_info.scale_y = float(pass->fbo.abs_y);
pass_info.scale_type_y = VULKAN_FILTER_CHAIN_SCALE_ABSOLUTE;
break;
case RARCH_SCALE_VIEWPORT:
pass_info.scale_y = pass->fbo.scale_y;
pass_info.scale_type_y = VULKAN_FILTER_CHAIN_SCALE_VIEWPORT;
break;
}
}
chain->set_pass_info(i, pass_info);
}
if (last_pass_is_fbo)
{
struct vulkan_filter_chain_pass_info pass_info;
pass_info.scale_type_x = VULKAN_FILTER_CHAIN_SCALE_VIEWPORT;
pass_info.scale_type_y = VULKAN_FILTER_CHAIN_SCALE_VIEWPORT;
pass_info.scale_x = 1.0f;
pass_info.scale_y = 1.0f;
pass_info.rt_format = tmpinfo.swapchain.format;
pass_info.source_filter = filter;
pass_info.mip_filter = VULKAN_FILTER_CHAIN_NEAREST;
pass_info.address = VULKAN_FILTER_CHAIN_ADDRESS_CLAMP_TO_EDGE;
pass_info.max_levels = 0;
chain->set_pass_info(shader->passes, pass_info);
chain->set_shader(shader->passes,
VK_SHADER_STAGE_VERTEX_BIT,
opaque_vert,
sizeof(opaque_vert) / sizeof(uint32_t));
chain->set_shader(shader->passes,
VK_SHADER_STAGE_FRAGMENT_BIT,
opaque_frag,
sizeof(opaque_frag) / sizeof(uint32_t));
}
if (!video_shader_resolve_current_parameters(conf.get(), shader.get()))
return nullptr;
chain->set_shader_preset(move(shader));
if (!chain->init())
return nullptr;
return chain.release();
}
struct video_shader *vulkan_filter_chain_get_preset(
vulkan_filter_chain_t *chain)
{
return chain->get_shader_preset();
}
void vulkan_filter_chain_free(
vulkan_filter_chain_t *chain)
{
delete chain;
}
void vulkan_filter_chain_set_shader(
vulkan_filter_chain_t *chain,
unsigned pass,
VkShaderStageFlags stage,
const uint32_t *spirv,
size_t spirv_words)
{
chain->set_shader(pass, stage, spirv, spirv_words);
}
void vulkan_filter_chain_set_pass_info(
vulkan_filter_chain_t *chain,
unsigned pass,
const struct vulkan_filter_chain_pass_info *info)
{
chain->set_pass_info(pass, *info);
}
bool vulkan_filter_chain_update_swapchain_info(
vulkan_filter_chain_t *chain,
const vulkan_filter_chain_swapchain_info *info)
{
return chain->update_swapchain_info(*info);
}
void vulkan_filter_chain_notify_sync_index(
vulkan_filter_chain_t *chain,
unsigned index)
{
chain->notify_sync_index(index);
}
bool vulkan_filter_chain_init(vulkan_filter_chain_t *chain)
{
return chain->init();
}
void vulkan_filter_chain_set_input_texture(
vulkan_filter_chain_t *chain,
const struct vulkan_filter_chain_texture *texture)
{
chain->set_input_texture(*texture);
}
void vulkan_filter_chain_set_frame_count(
vulkan_filter_chain_t *chain,
uint64_t count)
{
chain->set_frame_count(count);
}
void vulkan_filter_chain_set_frame_count_period(
vulkan_filter_chain_t *chain,
unsigned pass,
unsigned period)
{
chain->set_frame_count_period(pass, period);
}
void vulkan_filter_chain_set_pass_name(
vulkan_filter_chain_t *chain,
unsigned pass,
const char *name)
{
chain->set_pass_name(pass, name);
}
void vulkan_filter_chain_build_offscreen_passes(
vulkan_filter_chain_t *chain,
VkCommandBuffer cmd, const VkViewport *vp)
{
chain->build_offscreen_passes(cmd, *vp);
}
void vulkan_filter_chain_build_viewport_pass(
vulkan_filter_chain_t *chain,
VkCommandBuffer cmd, const VkViewport *vp, const float *mvp)
{
chain->build_viewport_pass(cmd, *vp, mvp);
}
void vulkan_filter_chain_end_frame(
vulkan_filter_chain_t *chain,
VkCommandBuffer cmd)
{
chain->end_frame(cmd);
}