blob: 5ab922842bc9895599c35587f1a4ed80344bbd86 [file] [log] [blame]
// Copyright 2017 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "garnet/examples/ui/shadertoy/service/compiler.h"
#include <lib/async-loop/cpp/loop.h>
#include <lib/async/cpp/task.h>
#include "garnet/examples/ui/shadertoy/service/renderer.h"
#include "lib/escher/impl/glsl_compiler.h"
#include "lib/escher/impl/mesh_shader_binding.h"
namespace {
constexpr char kVertexShaderSrc[] = R"GLSL(
#version 450
#extension GL_ARB_separate_shader_objects : enable
layout(location = 0) in vec2 inPosition;
// TODO: generate mesh without UV coords, and remove this.
layout(location = 2) in vec2 inUV;
out gl_PerVertex {
vec4 gl_Position;
};
void main() {
// Halfway between min and max depth.
gl_Position = vec4(inPosition, 0, 1);
}
)GLSL";
constexpr char kFragmentShaderHeaderSrc[] = R"GLSL(
#version 450
#extension GL_ARB_separate_shader_objects : enable
layout(set = 0, binding = 0) uniform sampler2D iChannel0;
layout(set = 0, binding = 1) uniform sampler2D iChannel1;
layout(set = 0, binding = 2) uniform sampler2D iChannel2;
layout(set = 0, binding = 3) uniform sampler2D iChannel3;
// These correspond to the C++ struct ShadertoyRenderer::Params. In order to
// comply with the std430 layout used by Vulkan push constants, everything is
// represented here as 4-byte values, so that no additional padding is
// introduced.
layout(push_constant) uniform ShadertoyRendererParams {
uniform float iResolution[3];
uniform float iTime;
uniform float iTimeDelta;
uniform int iFrame;
uniform float iChannelTime[4];
// vec3[4] ShadertoyRenderer::Params.iChannelResolution
uniform float iCR[12];
uniform float iMouse[4];
uniform float iDate[4];
uniform float iSampleRate;
} __pushed;
// Copy uniforms into the variables expected by Shadertoy programs.
vec3 iResolution = vec3(__pushed.iResolution[0],
__pushed.iResolution[1],
__pushed.iResolution[2]);
float iTime = __pushed.iTime;
float iTimeDelta = __pushed.iTimeDelta;
int iFrame = __pushed.iFrame;
float iChannelTime[4] = __pushed.iChannelTime;
vec3 iChannelResolution[4] =
vec3[](vec3(__pushed.iCR[0], __pushed.iCR[1], __pushed.iCR[2]),
vec3(__pushed.iCR[3], __pushed.iCR[4], __pushed.iCR[5]),
vec3(__pushed.iCR[6], __pushed.iCR[7], __pushed.iCR[8]),
vec3(__pushed.iCR[9], __pushed.iCR[10], __pushed.iCR[11]));
vec4 iMouse = vec4(__pushed.iMouse[0],
__pushed.iMouse[1],
__pushed.iMouse[2],
__pushed.iMouse[3]);
vec4 iDate = vec4(__pushed.iDate[0],
__pushed.iDate[1],
__pushed.iDate[2],
__pushed.iDate[3]);
float iSampleRate = __pushed.iSampleRate;
// Backward compatibility? Some Shadertoy programs use this value, but it is
// not currently listed amongst those provided by the website.
float iGlobalTime = iTime;
layout(location = 0) out vec4 outColor;
void mainImage( out vec4 fragColor, in vec2 fragCoord);
void main() {
vec4 color = vec4(0.0,0.0,0.0,1.0);
vec2 swapped_y = {gl_FragCoord.x, iResolution.y-gl_FragCoord.y};
mainImage(color, swapped_y);
outColor = color;
}
// ******************* END of Compiler Fragment Shader header *********
)GLSL";
} // anonymous namespace
namespace shadertoy {
Compiler::Compiler(async::Loop* loop, escher::EscherWeakPtr weak_escher,
vk::RenderPass render_pass,
vk::DescriptorSetLayout descriptor_set_layout)
: loop_(loop),
escher_(std::move(weak_escher)),
model_data_(fxl::MakeRefCounted<escher::impl::ModelData>(escher_)),
render_pass_(render_pass),
descriptor_set_layout_(descriptor_set_layout) {
FXL_DCHECK(render_pass_);
FXL_DCHECK(descriptor_set_layout);
}
Compiler::~Compiler() {
std::lock_guard<std::mutex> lock(mutex_);
while (!requests_.empty()) {
requests_.front().callback({PipelinePtr()});
requests_.pop();
}
if (has_thread_) {
// TODO: This isn't a big deal, because it only happens when the process
// is shutting down, but it would be tidier to wait for the thread to
// finish.
FXL_LOG(WARNING) << "Destroying while compile thread is still active.";
}
}
const vk::DescriptorSetLayoutCreateInfo&
Compiler::GetDescriptorSetLayoutCreateInfo() {
constexpr uint32_t kNumBindings = 4;
static vk::DescriptorSetLayoutBinding bindings[kNumBindings];
static vk::DescriptorSetLayoutCreateInfo info;
static vk::DescriptorSetLayoutCreateInfo* ptr = nullptr;
if (!ptr) {
auto& texture_binding_0 = bindings[0];
auto& texture_binding_1 = bindings[1];
auto& texture_binding_2 = bindings[2];
auto& texture_binding_3 = bindings[3];
texture_binding_0.binding = 0;
texture_binding_0.descriptorType =
vk::DescriptorType::eCombinedImageSampler;
texture_binding_0.descriptorCount = 1;
texture_binding_0.stageFlags = vk::ShaderStageFlagBits::eFragment;
texture_binding_1.binding = 1;
texture_binding_1.descriptorType =
vk::DescriptorType::eCombinedImageSampler;
texture_binding_1.descriptorCount = 1;
texture_binding_1.stageFlags = vk::ShaderStageFlagBits::eFragment;
texture_binding_2.binding = 2;
texture_binding_2.descriptorType =
vk::DescriptorType::eCombinedImageSampler;
texture_binding_2.descriptorCount = 1;
texture_binding_2.stageFlags = vk::ShaderStageFlagBits::eFragment;
texture_binding_3.binding = 3;
texture_binding_3.descriptorType =
vk::DescriptorType::eCombinedImageSampler;
texture_binding_3.descriptorCount = 1;
texture_binding_3.stageFlags = vk::ShaderStageFlagBits::eFragment;
info.bindingCount = kNumBindings;
info.pBindings = bindings;
ptr = &info;
}
return *ptr;
}
void Compiler::Compile(std::string glsl, ResultCallback callback) {
std::lock_guard<std::mutex> lock(mutex_);
if (!has_thread_) {
thread_ = std::thread([this] { this->ProcessRequestQueue(); });
has_thread_ = true;
}
requests_.push({std::move(glsl), std::move(callback)});
}
void Compiler::ProcessRequestQueue() {
while (true) {
std::unique_lock<std::mutex> lock(mutex_);
if (requests_.empty()) {
thread_.detach();
has_thread_ = false;
return;
}
Request req(std::move(requests_.front()));
requests_.pop();
lock.unlock();
auto pipeline = CompileGlslToPipeline(req.glsl);
async::PostTask(loop_->dispatcher(), [result = Result{std::move(pipeline)},
callback = std::move(req.callback)] {
callback(std::move(result));
});
}
}
PipelinePtr Compiler::CompileGlslToPipeline(const std::string& glsl_code) {
auto vk_device = escher_->vulkan_context().device;
std::future<escher::impl::SpirvData> vertex_spirv_future =
glsl_compiler()->Compile(vk::ShaderStageFlagBits::eVertex,
{std::string(kVertexShaderSrc)}, std::string(),
"main");
std::future<escher::impl::SpirvData> fragment_spirv_future =
glsl_compiler()->Compile(
vk::ShaderStageFlagBits::eFragment,
{std::string(kFragmentShaderHeaderSrc) + glsl_code}, std::string(),
"main");
vk::ShaderModule vertex_module;
{
auto spirv = vertex_spirv_future.get();
vk::ShaderModuleCreateInfo module_info;
module_info.codeSize = spirv.size() * sizeof(uint32_t);
module_info.pCode = spirv.data();
auto result = vk_device.createShaderModule(module_info);
if (result.result != vk::Result::eSuccess) {
FXL_LOG(WARNING) << "Failed to compile vertex shader.";
return PipelinePtr();
}
vertex_module = result.value;
}
vk::ShaderModule fragment_module;
{
auto spirv = fragment_spirv_future.get();
vk::ShaderModuleCreateInfo module_info;
module_info.codeSize = spirv.size() * sizeof(uint32_t);
module_info.pCode = spirv.data();
auto result = vk_device.createShaderModule(module_info);
if (result.result != vk::Result::eSuccess) {
FXL_LOG(WARNING) << "Failed to compile fragment shader.";
vk_device.destroyShaderModule(vertex_module);
return PipelinePtr();
}
fragment_module = result.value;
}
escher::MeshSpec mesh_spec{escher::MeshAttribute::kPosition2D |
escher::MeshAttribute::kUV};
auto pipeline = ConstructPipeline(vertex_module, fragment_module, mesh_spec);
vk_device.destroyShaderModule(vertex_module);
vk_device.destroyShaderModule(fragment_module);
return pipeline;
}
PipelinePtr Compiler::ConstructPipeline(vk::ShaderModule vertex_module,
vk::ShaderModule fragment_module,
const escher::MeshSpec& mesh_spec) {
vk::Device device = escher_->vulkan_context().device;
// Depending on configuration, more dynamic states may be added later.
vk::PipelineDynamicStateCreateInfo dynamic_state_info;
std::vector<vk::DynamicState> dynamic_states{vk::DynamicState::eViewport,
vk::DynamicState::eScissor};
vk::PipelineShaderStageCreateInfo vertex_stage_info;
vertex_stage_info.stage = vk::ShaderStageFlagBits::eVertex;
vertex_stage_info.module = vertex_module;
vertex_stage_info.pName = "main";
vk::PipelineShaderStageCreateInfo fragment_stage_info;
fragment_stage_info.stage = vk::ShaderStageFlagBits::eFragment;
fragment_stage_info.module = fragment_module;
fragment_stage_info.pName = "main";
constexpr size_t kShaderStageCount = 2;
vk::PipelineShaderStageCreateInfo shader_stages[kShaderStageCount] = {
vertex_stage_info, fragment_stage_info};
vk::PipelineVertexInputStateCreateInfo vertex_input_info;
{
auto& mesh_shader_binding = model_data_->GetMeshShaderBinding(mesh_spec);
vertex_input_info.vertexBindingDescriptionCount = 1;
vertex_input_info.pVertexBindingDescriptions =
mesh_shader_binding.binding();
vertex_input_info.vertexAttributeDescriptionCount =
mesh_shader_binding.attributes().size();
vertex_input_info.pVertexAttributeDescriptions =
mesh_shader_binding.attributes().data();
}
vk::PipelineInputAssemblyStateCreateInfo input_assembly_info;
input_assembly_info.topology = vk::PrimitiveTopology::eTriangleList;
input_assembly_info.primitiveRestartEnable = false;
vk::PipelineDepthStencilStateCreateInfo depth_stencil_info;
depth_stencil_info.depthTestEnable = false;
depth_stencil_info.depthWriteEnable = false;
depth_stencil_info.depthBoundsTestEnable = false;
depth_stencil_info.stencilTestEnable = true;
// This is set dynamically during rendering.
vk::Viewport viewport;
viewport.x = 0.0f;
viewport.y = 0.0f;
viewport.width = 0.f;
viewport.height = 0.f;
viewport.minDepth = 0.0f;
viewport.maxDepth = 0.0f;
// This is set dynamically during rendering.
vk::Rect2D scissor;
scissor.offset = vk::Offset2D{0, 0};
scissor.extent = vk::Extent2D{0, 0};
vk::PipelineViewportStateCreateInfo viewport_state;
viewport_state.viewportCount = 1;
viewport_state.pViewports = &viewport;
viewport_state.scissorCount = 1;
viewport_state.pScissors = &scissor;
vk::PipelineRasterizationStateCreateInfo rasterizer;
rasterizer.depthClampEnable = false;
rasterizer.rasterizerDiscardEnable = false;
rasterizer.polygonMode = vk::PolygonMode::eFill;
rasterizer.lineWidth = 1.0f;
rasterizer.cullMode = vk::CullModeFlagBits::eBack;
rasterizer.frontFace = vk::FrontFace::eClockwise;
rasterizer.depthBiasEnable = false;
vk::PipelineMultisampleStateCreateInfo multisampling;
multisampling.sampleShadingEnable = false;
multisampling.rasterizationSamples = vk::SampleCountFlagBits::e1;
vk::PipelineColorBlendAttachmentState color_blend_attachment;
color_blend_attachment.colorWriteMask =
vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG |
vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA;
color_blend_attachment.blendEnable = false;
vk::PipelineColorBlendStateCreateInfo color_blending;
color_blending.logicOpEnable = false;
color_blending.logicOp = vk::LogicOp::eCopy;
color_blending.attachmentCount = 1;
color_blending.pAttachments = &color_blend_attachment;
color_blending.blendConstants[0] = 0.0f;
color_blending.blendConstants[1] = 0.0f;
color_blending.blendConstants[2] = 0.0f;
color_blending.blendConstants[3] = 0.0f;
vk::PushConstantRange push_constants;
push_constants.stageFlags = vk::ShaderStageFlagBits::eFragment;
push_constants.offset = 0;
push_constants.size = sizeof(Renderer::Params);
vk::PipelineLayoutCreateInfo pipeline_layout_info;
pipeline_layout_info.setLayoutCount = 1;
pipeline_layout_info.pSetLayouts = &descriptor_set_layout_;
pipeline_layout_info.pushConstantRangeCount = 1;
pipeline_layout_info.pPushConstantRanges = &push_constants;
vk::PipelineLayout pipeline_layout;
{
auto result = device.createPipelineLayout(pipeline_layout_info, nullptr);
FXL_DCHECK(result.result == vk::Result::eSuccess);
pipeline_layout = result.value;
}
// All dynamic states have been accumulated, so finalize them.
dynamic_state_info.dynamicStateCount =
static_cast<uint32_t>(dynamic_states.size());
dynamic_state_info.pDynamicStates = dynamic_states.data();
vk::GraphicsPipelineCreateInfo pipeline_info;
pipeline_info.stageCount = kShaderStageCount;
pipeline_info.pStages = shader_stages;
pipeline_info.pVertexInputState = &vertex_input_info;
pipeline_info.pInputAssemblyState = &input_assembly_info;
pipeline_info.pViewportState = &viewport_state;
pipeline_info.pRasterizationState = &rasterizer;
pipeline_info.pDepthStencilState = &depth_stencil_info;
pipeline_info.pMultisampleState = &multisampling;
pipeline_info.pColorBlendState = &color_blending;
pipeline_info.pDynamicState = &dynamic_state_info;
pipeline_info.layout = pipeline_layout;
pipeline_info.renderPass = render_pass_;
pipeline_info.subpass = 0;
pipeline_info.basePipelineHandle = vk::Pipeline();
vk::Pipeline pipeline;
{
auto result = device.createGraphicsPipeline(nullptr, pipeline_info);
FXL_DCHECK(result.result == vk::Result::eSuccess);
pipeline = result.value;
}
return fxl::MakeRefCounted<Pipeline>(device, pipeline, pipeline_layout);
}
escher::impl::GlslToSpirvCompiler* Compiler::glsl_compiler() {
return escher_->glsl_compiler();
}
} // namespace shadertoy