#include "vulkan_shader_chain.hpp" #include "slang_helpers.hpp" #include "stb_image.h" #include "vulkan/vulkan_enums.hpp" namespace Vulkan { ShaderChain::ShaderChain(Context *context_) { context = context_; original_history_size = 3; original_width = 0; original_height = 0; viewport_width = 0; viewport_height = 0; vertex_buffer = nullptr; vertex_buffer_allocation = nullptr; last_frame_index = 2; current_frame_index = 0; } ShaderChain::~ShaderChain() { if (context && context->device) { if (vertex_buffer) context->allocator.destroyBuffer(vertex_buffer, vertex_buffer_allocation); vertex_buffer = nullptr; vertex_buffer_allocation = nullptr; } pipelines.clear(); } void ShaderChain::construct_buffer_objects() { for (size_t i = 0; i < pipelines.size(); i++) { auto &pipeline = *pipelines[i]; uint8_t *ubo_memory = nullptr; if (pipeline.shader->ubo_size > 0) ubo_memory = (uint8_t *)context->allocator.mapMemory(pipeline.uniform_buffer_allocation); for (auto &uniform : pipeline.shader->uniforms) { void *location = 0; const float MVP[16] = { 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f }; std::string block; switch (uniform.block) { case SlangShader::Uniform::UBO: location = &ubo_memory[uniform.offset]; block = "uniform"; break; case SlangShader::Uniform::PushConstant: location = &pipeline.push_constants[uniform.offset]; block = "push constant"; break; } auto write_size = [&location](int width, int height) { std::array size; size[0] = (float)width; size[1] = (float)height; size[2] = 1.0f / size[0]; size[3] = 1.0f / size[1]; memcpy(location, size.data(), sizeof(float) * 4); }; switch (uniform.type) { case SlangShader::Uniform::PassSize: case SlangShader::Uniform::PassFeedbackSize: // TODO: Does this need to differ? if (uniform.specifier == -1) { write_size(original_width, original_height); } else { write_size(pipelines[uniform.specifier]->destination_width, pipelines[uniform.specifier]->destination_height); } break; case SlangShader::Uniform::ViewportSize: write_size(viewport_width, viewport_height); break; case SlangShader::Uniform::PreviousFrameSize: if (original.size() > 1) write_size(original[1]->image_width, original[1]->image_height); else write_size(original_width, original_height); break; case SlangShader::Uniform::LutSize: if (uniform.specifier < (int)lookup_textures.size()) write_size(lookup_textures[uniform.specifier]->image_width, lookup_textures[uniform.specifier]->image_height); else write_size(1, 1); break; case SlangShader::Uniform::MVP: memcpy(location, MVP, sizeof(float) * 16); break; case SlangShader::Uniform::Parameter: if (uniform.specifier < (int)preset->parameters.size()) memcpy(location, &preset->parameters[uniform.specifier].val, sizeof(float)); break; case SlangShader::Uniform::FrameCount: memcpy(location, &frame_count, sizeof(uint32_t)); break; case SlangShader::Uniform::FrameDirection: const int32_t frame_direction = 1; memcpy(location, &frame_direction, sizeof(int32_t)); break; } } if (pipeline.shader->ubo_size > 0) { context->allocator.unmapMemory(pipeline.uniform_buffer_allocation); context->allocator.flushAllocation(pipeline.uniform_buffer_allocation, 0, pipeline.shader->ubo_size); } } } void ShaderChain::update_and_propagate_sizes(int original_width_new, int original_height_new, int viewport_width_new, int viewport_height_new) { if (pipelines.empty()) return; if (original_width == original_width_new && original_height == original_height_new && viewport_width == viewport_width_new && viewport_height == viewport_height_new) return; original_width = original_width_new; original_height = original_height_new; viewport_width = viewport_width_new; viewport_height = viewport_height_new; for (size_t i = 0; i < pipelines.size(); i++) { auto &p = pipelines[i]; if (i != 0) { p->source_width = pipelines[i - 1]->destination_width; p->source_height = pipelines[i - 1]->destination_height; } else { p->source_width = original_width_new; p->source_height = original_height_new; } if (p->shader->scale_type_x == "viewport") p->destination_width = viewport_width * p->shader->scale_x; else if (p->shader->scale_type_x == "absolute") p->destination_width = p->shader->scale_x; else p->destination_width = p->source_width * p->shader->scale_x; if (p->shader->scale_type_y == "viewport") p->destination_height = viewport_height * p->shader->scale_y; else if (p->shader->scale_type_y == "absolute") p->destination_height = p->shader->scale_y; else p->destination_height = p->source_height * p->shader->scale_y; if (i == pipelines.size() - 1) { p->destination_width = viewport_width; p->destination_height = viewport_height; } } } bool ShaderChain::load_shader_preset(std::string filename) { if (!ends_with(filename, ".slangp")) printf("Warning: loading preset without .slangp extension\n"); preset = std::make_unique(); if (!preset->load_preset_file(filename)) { printf("Couldn't load preset file: %s\n", filename.c_str()); return false; } if (!preset->introspect()) { printf("Failed introspection process in preset: %s\n", filename.c_str()); return false; } pipelines.clear(); pipelines.resize(preset->passes.size()); int num_ubos = 0; int num_samplers = 0; for (size_t i = 0; i < preset->passes.size(); i++) { auto &p = preset->passes[i]; pipelines[i] = std::make_unique(); pipelines[i]->init(context, &p); bool lastpass = (i == preset->passes.size() - 1); if (!pipelines[i]->generate_pipeline(lastpass)) { printf("Couldn't create pipeline for shader: %s\n", p.filename.c_str()); return false; } for (auto &u : p.samplers) if (u.type == SlangShader::Sampler::PreviousFrame) if (u.specifier > (int)original_history_size) original_history_size = u.specifier; if (p.ubo_size) num_ubos++; if (p.samplers.size() > 0) num_samplers += p.samplers.size(); } std::array descriptor_pool_sizes; descriptor_pool_sizes[0] .setType(vk::DescriptorType::eUniformBuffer) .setDescriptorCount(num_ubos * queue_size); descriptor_pool_sizes[1] .setType(vk::DescriptorType::eCombinedImageSampler) .setDescriptorCount(num_samplers * queue_size); auto descriptor_pool_create_info = vk::DescriptorPoolCreateInfo{} .setPoolSizes(descriptor_pool_sizes) .setMaxSets(pipelines.size() * queue_size) .setFlags(vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet); descriptor_pool = context->device.createDescriptorPoolUnique(descriptor_pool_create_info); for (auto &p : pipelines) p->generate_frame_resources(descriptor_pool.get()); load_lookup_textures(); float vertex_data[] = { -1.0f, -3.0f, 0.0f, 1.0f, /* texcoords */ 0.0, -1.0f, 3.0f, 1.0f, 0.0f, 1.0f, 2.0f, 1.0f, -1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f }; auto buffer_create_info = vk::BufferCreateInfo{} .setSize(sizeof(vertex_data)) .setUsage(vk::BufferUsageFlagBits::eVertexBuffer); auto allocation_create_info = vma::AllocationCreateInfo{} .setFlags(vma::AllocationCreateFlagBits::eHostAccessSequentialWrite) .setRequiredFlags(vk::MemoryPropertyFlagBits::eHostVisible); std::tie(vertex_buffer, vertex_buffer_allocation) = context->allocator.createBuffer(buffer_create_info, allocation_create_info); auto vertex_buffer_memory = context->allocator.mapMemory(vertex_buffer_allocation); memcpy(vertex_buffer_memory, vertex_data, sizeof(vertex_data)); context->allocator.unmapMemory(vertex_buffer_allocation); context->allocator.flushAllocation(vertex_buffer_allocation, 0, sizeof(vertex_data)); frame_count = 0; current_frame_index = 0; last_frame_index = 2; context->wait_idle(); return true; } void ShaderChain::update_framebuffers(vk::CommandBuffer cmd, int frame_num) { size_t pass_count = pipelines.size() - 1; if (preset->last_pass_uses_feedback) pass_count++; for (size_t i = 0; i < pass_count; i++) { bool mipmap = false; if (i < pipelines.size() - 1) mipmap = pipelines[i + 1]->shader->mipmap_input; pipelines[i]->update_framebuffer(cmd, frame_num, mipmap); } } void ShaderChain::update_descriptor_set(vk::CommandBuffer cmd, int pipe_num, int swapchain_index) { auto &pipe = *pipelines[pipe_num]; auto &frame = pipe.frame[swapchain_index]; if (pipe.shader->ubo_size > 0) { auto descriptor_buffer_info = vk::DescriptorBufferInfo{} .setBuffer(pipe.uniform_buffer) .setOffset(0) .setRange(pipe.shader->ubo_size); auto write_descriptor_set = vk::WriteDescriptorSet{} .setDescriptorType(vk::DescriptorType::eUniformBuffer) .setBufferInfo(descriptor_buffer_info) .setDstBinding(pipe.shader->ubo_binding) .setDstSet(frame.descriptor_set.get()); context->device.updateDescriptorSets(write_descriptor_set, {}); } auto descriptor_image_info = vk::DescriptorImageInfo{} .setImageLayout(vk::ImageLayout::eShaderReadOnlyOptimal); for (auto &sampler : pipe.shader->samplers) { if (sampler.type == SlangShader::Sampler::Lut) { descriptor_image_info .setImageView(lookup_textures[sampler.specifier]->image_view) .setSampler(lookup_textures[sampler.specifier]->sampler); } else if (sampler.type == SlangShader::Sampler::PassFeedback) { assert(sampler.specifier < (int)pipelines.size()); assert(sampler.specifier >= 0); if (!pipelines[sampler.specifier]->frame[last_frame_index].image.image) update_framebuffers(cmd, last_frame_index); auto &feedback_frame = pipelines[sampler.specifier]->frame[last_frame_index]; if (feedback_frame.image.current_layout == vk::ImageLayout::eUndefined) feedback_frame.image.clear(cmd); descriptor_image_info .setImageView(pipelines[sampler.specifier]->frame[last_frame_index].image.image_view); if (sampler.specifier == (int)pipelines.size() - 1) descriptor_image_info.setSampler(pipelines[sampler.specifier]->sampler.get()); else descriptor_image_info.setSampler(pipelines[sampler.specifier + 1]->sampler.get());; } else if (sampler.type == SlangShader::Sampler::Pass) { assert(sampler.specifier + 1 < (int)pipelines.size()); auto &sampler_to_use = pipelines[sampler.specifier + 1]->sampler.get(); if (sampler.specifier == -1) { descriptor_image_info .setSampler(sampler_to_use) .setImageView(original[0]->image_view); } else { descriptor_image_info .setSampler(sampler_to_use) .setImageView(pipelines[sampler.specifier]->frame[swapchain_index].image.image_view); } } else if (sampler.type == SlangShader::Sampler::PreviousFrame) { int which_original = sampler.specifier; if (which_original >= (int)original.size()) which_original = original.size() - 1; assert(which_original > -1); descriptor_image_info .setSampler(pipelines[0]->sampler.get()) .setImageView(original[which_original]->image_view); } auto write_descriptor_set = vk::WriteDescriptorSet{} .setDescriptorType(vk::DescriptorType::eCombinedImageSampler) .setDstSet(frame.descriptor_set.get()) .setDstBinding(sampler.binding) .setImageInfo(descriptor_image_info); context->device.updateDescriptorSets(write_descriptor_set, {}); } } bool ShaderChain::do_frame(uint8_t *data, int width, int height, int stride, vk::Format format, int viewport_x, int viewport_y, int viewport_width, int viewport_height) { if (!do_frame_without_swap(data, width, height, stride, format, viewport_x, viewport_y, viewport_width, viewport_height)) return false; context->swapchain->swap(); return true; } bool ShaderChain::do_frame_without_swap(uint8_t *data, int width, int height, int stride, vk::Format format, int viewport_x, int viewport_y, int viewport_width, int viewport_height) { if (!context->swapchain->begin_frame()) return false; auto cmd = context->swapchain->get_cmd(); update_and_propagate_sizes(width, height, viewport_width, viewport_height); update_framebuffers(cmd, current_frame_index); upload_original(cmd, data, width, height, stride, format); construct_buffer_objects(); for (size_t i = 0; i < pipelines.size(); i++) { auto &pipe = *pipelines[i]; auto &frame = pipe.frame[current_frame_index]; update_descriptor_set(cmd, i, current_frame_index); vk::ClearValue value{}; value.color = { 0.0f, 0.0f, 0.0f, 1.0f }; auto render_pass_begin_info = vk::RenderPassBeginInfo{} .setRenderPass(pipe.render_pass.get()) .setFramebuffer(frame.image.framebuffer.get()) .setRenderArea(vk::Rect2D({}, vk::Extent2D(frame.image.image_width, frame.image.image_height))) .setClearValues(value); if (i == pipelines.size() - 1) context->swapchain->begin_render_pass(); else cmd.beginRenderPass(render_pass_begin_info, vk::SubpassContents::eInline); cmd.bindPipeline(vk::PipelineBindPoint::eGraphics, pipe.pipeline.get()); cmd.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pipe.pipeline_layout.get(), 0, frame.descriptor_set.get(), {}); cmd.bindVertexBuffers(0, vertex_buffer, { 0 }); if (pipe.push_constants.size() > 0) cmd.pushConstants(pipe.pipeline_layout.get(), vk::ShaderStageFlagBits::eAllGraphics, 0, pipe.push_constants.size(), pipe.push_constants.data()); if (i < pipelines.size() - 1) { cmd.setViewport(0, vk::Viewport(0, 0, pipe.destination_width, pipe.destination_height, 0.0f, 1.0f)); cmd.setScissor(0, vk::Rect2D({}, vk::Extent2D(pipe.destination_width, pipe.destination_height))); } else { cmd.setViewport(0, vk::Viewport(viewport_x, viewport_y, viewport_width, viewport_height, 0.0f, 1.0f)); cmd.setScissor(0, vk::Rect2D(vk::Offset2D(viewport_x, viewport_y), vk::Extent2D(viewport_width, viewport_height))); } cmd.draw(3, 1, 0, 0); if (i < pipelines.size() - 1) { cmd.endRenderPass(); } else { context->swapchain->end_render_pass(); } frame.image.barrier(cmd); if (i < pipelines.size() - 1) frame.image.generate_mipmaps(cmd); if (preset->last_pass_uses_feedback && i == pipelines.size() - 1) { std::array image_memory_barrier{}; image_memory_barrier[0] .setImage(frame.image.image) .setOldLayout(vk::ImageLayout::eUndefined) .setNewLayout(vk::ImageLayout::eTransferDstOptimal) .setSrcAccessMask(vk::AccessFlagBits::eColorAttachmentWrite) .setDstAccessMask(vk::AccessFlagBits::eTransferWrite) .setSubresourceRange(vk::ImageSubresourceRange(vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1)); image_memory_barrier[1] .setImage(context->swapchain->get_image()) .setOldLayout(vk::ImageLayout::ePresentSrcKHR) .setNewLayout(vk::ImageLayout::eTransferSrcOptimal) .setSrcAccessMask(vk::AccessFlagBits::eColorAttachmentWrite) .setDstAccessMask(vk::AccessFlagBits::eTransferRead) .setSubresourceRange(vk::ImageSubresourceRange(vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1)); cmd.pipelineBarrier(vk::PipelineStageFlagBits::eColorAttachmentOutput, vk::PipelineStageFlagBits::eTransfer, {}, {}, {}, image_memory_barrier); auto image_blit = vk::ImageBlit{} .setSrcOffsets({ vk::Offset3D(viewport_x, viewport_y, 0), vk::Offset3D(viewport_x + viewport_width, viewport_y + viewport_height, 1) }) .setDstOffsets({ vk::Offset3D(0, 0, 0), vk::Offset3D(viewport_width, viewport_height, 1) }) .setSrcSubresource(vk::ImageSubresourceLayers(vk::ImageAspectFlagBits::eColor, 0, 0, 1)) .setDstSubresource(vk::ImageSubresourceLayers(vk::ImageAspectFlagBits::eColor, 0, 0, 1)); cmd.blitImage(context->swapchain->get_image(), vk::ImageLayout::eTransferSrcOptimal, frame.image.image, vk::ImageLayout::eTransferDstOptimal, image_blit, vk::Filter::eNearest); image_memory_barrier[0] .setOldLayout(vk::ImageLayout::eTransferDstOptimal) .setNewLayout(vk::ImageLayout::eShaderReadOnlyOptimal) .setSrcAccessMask(vk::AccessFlagBits::eTransferWrite) .setDstAccessMask(vk::AccessFlagBits::eShaderRead) .setSubresourceRange(vk::ImageSubresourceRange(vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1)); image_memory_barrier[1] .setOldLayout(vk::ImageLayout::eTransferSrcOptimal) .setNewLayout(vk::ImageLayout::ePresentSrcKHR) .setSrcAccessMask(vk::AccessFlagBits::eTransferWrite) .setDstAccessMask(vk::AccessFlagBits::eMemoryRead) .setSubresourceRange(vk::ImageSubresourceRange(vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1)); cmd.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eAllGraphics, {}, {}, {}, image_memory_barrier); frame.image.current_layout = vk::ImageLayout::eTransferDstOptimal; } } context->swapchain->end_frame_without_swap(); last_frame_index = current_frame_index; current_frame_index = (current_frame_index + 1) % queue_size; frame_count++; return true; } void ShaderChain::upload_original(vk::CommandBuffer cmd, uint8_t *data, int width, int height, int stride, vk::Format format) { std::unique_ptr texture; auto create_texture = [&]() { texture->create(width, height, format, wrap_mode_from_string(pipelines[0]->shader->wrap_mode), pipelines[0]->shader->filter_linear, pipelines[0]->shader->mipmap_input); }; if (original.size() > original_history_size) { texture = std::move(original.back()); original.pop_back(); if (texture->image_width != width || texture->image_height != height || texture->format != format) { texture->destroy(); create_texture(); } } else { texture = std::make_unique(); texture->init(context); create_texture(); } if (cmd) texture->from_buffer(cmd, data, width, height, stride); else texture->from_buffer(data, width, height, stride); original.push_front(std::move(texture)); } void ShaderChain::upload_original(uint8_t *data, int width, int height, int stride, vk::Format format) { upload_original(nullptr, data, width, height, stride, format); } bool ShaderChain::load_lookup_textures() { if (preset->textures.size() < 1) return true; lookup_textures.clear(); for (auto &l : preset->textures) { int width, height, channels; stbi_uc *bytes = stbi_load(l.filename.c_str(), &width, &height, &channels, 4); if (!bytes) { printf("Couldn't load look-up texture: %s\n", l.filename.c_str()); return false; } auto wrap_mode = wrap_mode_from_string(l.wrap_mode); lookup_textures.push_back(std::make_unique()); auto &t = lookup_textures.back(); t->init(context); t->create(width, height, vk::Format::eR8G8B8A8Unorm, wrap_mode, l.linear, l.mipmap); t->from_buffer(bytes, width, height); t->discard_staging_buffer(); free(bytes); } return true; } } // namespace Vulkan