70 WaterWindow(
const std::string &path,
const std::string &title,
int width,
int height,
bool fullscreen,
bool enable_vsync)
72 shader_root((path.empty() ? std::string(WATER_ASSET_DIR) : path) +
"/data") {
77 if (
device != VK_NULL_HANDLE) {
80 destroyPipeline(sky_pipeline);
81 destroyPipeline(water_pipeline);
82 destroyMesh(water_mesh);
85 void event(SDL_Event &e)
override {
86 if (e.type == SDL_EVENT_KEY_DOWN && e.key.key == SDLK_ESCAPE) {
91 if (e.type == SDL_EVENT_KEY_DOWN || e.type == SDL_EVENT_KEY_UP) {
92 const bool pressed = e.type == SDL_EVENT_KEY_DOWN;
95 rotate_left = pressed;
98 rotate_right = pressed;
113 if (e.type == SDL_EVENT_QUIT) {
119 destroyPipeline(sky_pipeline);
120 destroyPipeline(water_pipeline);
124 if (water_mesh_uploaded) {
128 uploadMesh(generateWaterMesh(), water_mesh);
129 water_mesh_uploaded =
true;
133 if (!water_mesh_uploaded || !ensurePipelines()) {
137 const auto now = std::chrono::steady_clock::now();
138 const float delta_seconds = std::chrono::duration<float>(now - last_frame_time).count();
139 last_frame_time = now;
140 updateCamera(delta_seconds);
142 const float elapsed_seconds = std::chrono::duration<float>(now - start_time).count();
144 const float aspect = (extent.height > 0U) ?
static_cast<float>(extent.width) /
static_cast<float>(extent.height) : 1.0f;
146 const glm::vec3 camera_target(0.0f, -0.15f, -18.0f);
147 const float yaw = glm::radians(camera_yaw_degrees);
148 const float pitch = glm::radians(camera_pitch_degrees);
149 const glm::vec3 camera_offset(
150 std::sin(yaw) * std::cos(pitch) * camera_distance,
151 std::sin(pitch) * camera_distance,
152 std::cos(yaw) * std::cos(pitch) * camera_distance);
153 const glm::vec3 camera_pos = camera_target + camera_offset;
154 const glm::mat4 view = glm::lookAt(camera_pos, camera_target, glm::vec3(0.0f, 1.0f, 0.0f));
155 glm::mat4 projection = glm::perspective(glm::radians(62.0f), aspect, 0.1f, 260.0f);
156 projection[1][1] *= -1.0f;
158 const PushConstants push_constants{
160 glm::vec4(camera_pos, elapsed_seconds),
161 glm::vec4(aspect, SCENE_REFERENCE_ASPECT, 0.0f, 0.0f),
164 drawSky(cmd, sky_pipeline, push_constants);
165 drawMesh(cmd, water_mesh, water_pipeline, push_constants);
169 std::string shader_root;
170 std::chrono::steady_clock::time_point start_time{std::chrono::steady_clock::now()};
171 std::chrono::steady_clock::time_point last_frame_time{start_time};
172 MeshResources water_mesh;
173 PipelineResources water_pipeline;
174 PipelineResources sky_pipeline;
175 bool water_mesh_uploaded =
false;
176 float camera_yaw_degrees = 0.0f;
177 float camera_pitch_degrees = 10.5f;
178 float camera_distance = 27.5f;
179 bool rotate_left =
false;
180 bool rotate_right =
false;
181 bool zoom_in =
false;
182 bool zoom_out =
false;
184 void updateCamera(
float delta_seconds) {
185 constexpr float ROTATION_SPEED_DEGREES = 42.0f;
189 camera_yaw_degrees -= ROTATION_SPEED_DEGREES * delta_seconds;
192 camera_yaw_degrees += ROTATION_SPEED_DEGREES * delta_seconds;
195 camera_distance -=
ZOOM_SPEED * delta_seconds;
198 camera_distance +=
ZOOM_SPEED * delta_seconds;
201 camera_pitch_degrees = std::clamp(camera_pitch_degrees, -4.0f, 46.0f);
202 camera_distance = std::clamp(camera_distance, 10.0f, 72.0f);
205 static MeshData generateWaterMesh() {
207 mesh.vertices.reserve(
static_cast<std::size_t
>(WATER_GRID_RESOLUTION + 1) *
static_cast<std::size_t
>(WATER_GRID_RESOLUTION + 1));
208 mesh.indices.reserve(
static_cast<std::size_t
>(WATER_GRID_RESOLUTION) *
static_cast<std::size_t
>(WATER_GRID_RESOLUTION) * 6U);
211 const float vz =
static_cast<float>(z) /
static_cast<float>(WATER_GRID_RESOLUTION);
213 const float vx =
static_cast<float>(x) /
static_cast<float>(WATER_GRID_RESOLUTION);
214 mesh.vertices.push_back({
215 glm::vec3((vx - 0.5f) * WATER_SIZE, 0.0f, (vz - 0.5f) * WATER_SIZE),
216 glm::vec2(vx * 72.0f, vz * 72.0f),
217 glm::vec3(0.0f, 1.0f, 0.0f),
218 glm::vec4(0.30f, 0.72f, 0.92f, 1.0f),
223 const auto vertex_index = [](
int x,
int z) {
228 const std::uint32_t a = vertex_index(x, z);
229 const std::uint32_t b = vertex_index(x + 1, z);
230 const std::uint32_t c = vertex_index(x, z + 1);
231 const std::uint32_t d = vertex_index(x + 1, z + 1);
232 mesh.indices.insert(mesh.indices.end(), {a, c, b, b, c, d});
239 void uploadMesh(
const MeshData &data, MeshResources &mesh)
const {
240 const mxvk::VulkanContext
context{
246 mesh.indexCount =
static_cast<uint32_t
>(data.indices.size());
248 const VkDeviceSize vertex_size =
sizeof(SceneVertex) * data.vertices.size();
251 data.vertices.data(),
253 VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
256 const VkDeviceSize index_size =
sizeof(std::uint32_t) * data.indices.size();
261 VK_BUFFER_USAGE_INDEX_BUFFER_BIT,
265 void drawMesh(VkCommandBuffer cmd,
const MeshResources &mesh,
const PipelineResources &pipeline,
const PushConstants &push_constants)
const {
266 if (mesh.vertexBuffer.buffer == VK_NULL_HANDLE || mesh.indexBuffer.buffer == VK_NULL_HANDLE || pipeline.pipeline == VK_NULL_HANDLE) {
270 const VkDeviceSize offsets[] = {0};
271 vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline.pipeline);
272 vkCmdBindVertexBuffers(cmd, 0, 1, &mesh.vertexBuffer.buffer, offsets);
273 vkCmdBindIndexBuffer(cmd, mesh.indexBuffer.buffer, 0, VK_INDEX_TYPE_UINT32);
277 VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT,
279 sizeof(PushConstants),
281 vkCmdDrawIndexed(cmd, mesh.indexCount, 1, 0, 0, 0);
284 void drawSky(VkCommandBuffer cmd,
const PipelineResources &pipeline,
const PushConstants &push_constants)
const {
285 if (pipeline.pipeline == VK_NULL_HANDLE) {
289 vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline.pipeline);
293 VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT,
295 sizeof(PushConstants),
297 vkCmdDraw(cmd, 3, 1, 0, 0);
300 void uploadDeviceBuffer(
const mxvk::VulkanContext &
context,
303 VkBufferUsageFlags usage,
304 mxvk::BufferResource &buffer)
const {
305 mxvk::BufferResource staging;
309 VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
310 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
315 std::memcpy(staging.
mapped, data,
static_cast<std::size_t
>(size));
321 usage | VK_BUFFER_USAGE_TRANSFER_DST_BIT,
322 VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
333 void destroyMesh(MeshResources &mesh)
const {
339 bool ensurePipelines() {
340 if (sky_pipeline.pipeline == VK_NULL_HANDLE) {
341 createPipeline(
"sky.vert.spv",
"sky.frag.spv",
false,
false,
false, sky_pipeline);
343 if (water_pipeline.pipeline == VK_NULL_HANDLE) {
344 createPipeline(
"water.vert.spv",
"water.frag.spv",
true,
false,
true, water_pipeline);
346 return sky_pipeline.pipeline != VK_NULL_HANDLE && water_pipeline.pipeline != VK_NULL_HANDLE;
349 void createPipeline(
const std::string &vertex_shader,
const std::string &fragment_shader,
bool useVertexInput,
bool alphaBlend,
bool depthTest, PipelineResources &resources) {
354 const std::vector<char> vert_bytes =
loadSpv(shader_root +
"/" + vertex_shader);
355 const std::vector<char> frag_bytes =
loadSpv(shader_root +
"/" + fragment_shader);
358 VkShaderModule frag_module = VK_NULL_HANDLE;
362 std::array<VkPipelineShaderStageCreateInfo, 2> shader_stages{};
363 shader_stages[0].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
364 shader_stages[0].stage = VK_SHADER_STAGE_VERTEX_BIT;
365 shader_stages[0].module = vert_module;
366 shader_stages[0].pName =
"main";
367 shader_stages[1].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
368 shader_stages[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT;
369 shader_stages[1].module = frag_module;
370 shader_stages[1].pName =
"main";
372 VkVertexInputBindingDescription binding_description{};
373 std::array<VkVertexInputAttributeDescription, 4> attribute_descriptions{};
374 if (useVertexInput) {
375 binding_description = vertexBindingDescription();
376 attribute_descriptions = vertexAttributeDescriptions();
379 VkPipelineVertexInputStateCreateInfo vertex_input{};
380 vertex_input.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
381 vertex_input.vertexBindingDescriptionCount = useVertexInput ? 1U : 0U;
382 vertex_input.pVertexBindingDescriptions = useVertexInput ? &binding_description :
nullptr;
383 vertex_input.vertexAttributeDescriptionCount = useVertexInput ?
static_cast<uint32_t
>(attribute_descriptions.size()) : 0U;
384 vertex_input.pVertexAttributeDescriptions = useVertexInput ? attribute_descriptions.data() :
nullptr;
386 VkPipelineInputAssemblyStateCreateInfo input_assembly{};
387 input_assembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
388 input_assembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
390 VkPipelineViewportStateCreateInfo viewport_state{};
391 viewport_state.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
392 viewport_state.viewportCount = 1;
393 viewport_state.scissorCount = 1;
395 const VkDynamicState dynamic_states[] = {
396 VK_DYNAMIC_STATE_VIEWPORT,
397 VK_DYNAMIC_STATE_SCISSOR,
399 VkPipelineDynamicStateCreateInfo dynamic_state{};
400 dynamic_state.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
401 dynamic_state.dynamicStateCount = 2;
402 dynamic_state.pDynamicStates = dynamic_states;
404 VkPipelineRasterizationStateCreateInfo rasterizer{};
405 rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
406 rasterizer.polygonMode = VK_POLYGON_MODE_FILL;
407 rasterizer.lineWidth = 1.0f;
408 rasterizer.cullMode = VK_CULL_MODE_NONE;
409 rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
411 VkPipelineMultisampleStateCreateInfo multisampling{};
412 multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
413 multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
415 VkPipelineDepthStencilStateCreateInfo depth_stencil{};
416 depth_stencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
417 depth_stencil.depthTestEnable = depthTest ? VK_TRUE : VK_FALSE;
418 depth_stencil.depthWriteEnable = (depthTest && !alphaBlend) ? VK_TRUE : VK_FALSE;
419 depth_stencil.depthCompareOp = VK_COMPARE_OP_LESS_OR_EQUAL;
421 VkPipelineColorBlendAttachmentState color_blend_attachment{};
422 color_blend_attachment.colorWriteMask =
423 VK_COLOR_COMPONENT_R_BIT |
424 VK_COLOR_COMPONENT_G_BIT |
425 VK_COLOR_COMPONENT_B_BIT |
426 VK_COLOR_COMPONENT_A_BIT;
427 color_blend_attachment.blendEnable = alphaBlend ? VK_TRUE : VK_FALSE;
428 color_blend_attachment.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
429 color_blend_attachment.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
430 color_blend_attachment.colorBlendOp = VK_BLEND_OP_ADD;
431 color_blend_attachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
432 color_blend_attachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
433 color_blend_attachment.alphaBlendOp = VK_BLEND_OP_ADD;
435 VkPipelineColorBlendStateCreateInfo color_blending{};
436 color_blending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
437 color_blending.attachmentCount = 1;
438 color_blending.pAttachments = &color_blend_attachment;
440 VkPushConstantRange push_constant_range{};
441 push_constant_range.stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT;
442 push_constant_range.size =
sizeof(PushConstants);
444 VkPipelineLayoutCreateInfo pipeline_layout_info{};
445 pipeline_layout_info.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
446 pipeline_layout_info.pushConstantRangeCount = 1;
447 pipeline_layout_info.pPushConstantRanges = &push_constant_range;
448 check_vk(vkCreatePipelineLayout(
device, &pipeline_layout_info,
nullptr, &resources.layout),
"water: failed to create pipeline layout");
450 VkPipelineRenderingCreateInfo pipeline_rendering_info{};
451 pipeline_rendering_info.sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO;
452 pipeline_rendering_info.colorAttachmentCount = 1;
455 pipeline_rendering_info.depthAttachmentFormat =
depth_format;
458 VkGraphicsPipelineCreateInfo pipeline_info{};
459 pipeline_info.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
460 pipeline_info.pNext = &pipeline_rendering_info;
461 pipeline_info.stageCount =
static_cast<uint32_t
>(shader_stages.size());
462 pipeline_info.pStages = shader_stages.data();
463 pipeline_info.pVertexInputState = &vertex_input;
464 pipeline_info.pInputAssemblyState = &input_assembly;
465 pipeline_info.pViewportState = &viewport_state;
466 pipeline_info.pRasterizationState = &rasterizer;
467 pipeline_info.pMultisampleState = &multisampling;
468 pipeline_info.pDepthStencilState = &depth_stencil;
469 pipeline_info.pColorBlendState = &color_blending;
470 pipeline_info.pDynamicState = &dynamic_state;
471 pipeline_info.layout = resources.layout;
472 pipeline_info.renderPass = VK_NULL_HANDLE;
474 check_vk(vkCreateGraphicsPipelines(
device,
pipeline_cache, 1, &pipeline_info,
nullptr, &resources.pipeline),
"water: failed to create graphics pipeline");
476 destroyPipeline(resources);
477 if (frag_module != VK_NULL_HANDLE) {
478 vkDestroyShaderModule(
device, frag_module,
nullptr);
480 vkDestroyShaderModule(
device, vert_module,
nullptr);
484 vkDestroyShaderModule(
device, frag_module,
nullptr);
485 vkDestroyShaderModule(
device, vert_module,
nullptr);
488 static VkVertexInputBindingDescription vertexBindingDescription() {
489 VkVertexInputBindingDescription binding{};
491 binding.stride =
sizeof(SceneVertex);
492 binding.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
496 static std::array<VkVertexInputAttributeDescription, 4> vertexAttributeDescriptions() {
497 std::array<VkVertexInputAttributeDescription, 4> attributes{};
498 attributes[0].binding = 0;
499 attributes[0].location = 0;
500 attributes[0].format = VK_FORMAT_R32G32B32_SFLOAT;
501 attributes[0].offset = offsetof(SceneVertex, position);
502 attributes[1].binding = 0;
503 attributes[1].location = 1;
504 attributes[1].format = VK_FORMAT_R32G32_SFLOAT;
505 attributes[1].offset = offsetof(SceneVertex, texCoord);
506 attributes[2].binding = 0;
507 attributes[2].location = 2;
508 attributes[2].format = VK_FORMAT_R32G32B32_SFLOAT;
509 attributes[2].offset = offsetof(SceneVertex, normal);
510 attributes[3].binding = 0;
511 attributes[3].location = 3;
512 attributes[3].format = VK_FORMAT_R32G32B32A32_SFLOAT;
513 attributes[3].offset = offsetof(SceneVertex, color);
517 void destroyPipeline(PipelineResources &resources)
const {
518 if (
device == VK_NULL_HANDLE) {
519 resources.pipeline = VK_NULL_HANDLE;
520 resources.layout = VK_NULL_HANDLE;
524 if (resources.pipeline != VK_NULL_HANDLE) {
525 vkDestroyPipeline(
device, resources.pipeline,
nullptr);
526 resources.pipeline = VK_NULL_HANDLE;
528 if (resources.layout != VK_NULL_HANDLE) {
529 vkDestroyPipelineLayout(
device, resources.layout,
nullptr);
530 resources.layout = VK_NULL_HANDLE;