RetroArch/gfx/drivers_shader/shader_vulkan.cpp
Themaister 124d9b57d6 Vulkan: Fix blue/red flip when using HW rendered cores with history.
Was using frontend format of BGRA8 when core was RGBA8, with blind
vkCmdCopyImage. Fix is to use the same format as core is pushing to
history buffer.
2018-12-06 11:38:30 +01:00

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 "../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)
: id(move(id)),
device(device),
image(image),
view(view),
memory(memory),
buffer(move(buffer))
{
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) :
device(device),
memory_properties(mem_props),
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);
}