1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
// The environment drawn as the background (see `concinnity_core::render::sky`):
// a fullscreen draw at the tail of each opaque scene pass (the main camera, a
// reflection-probe face, a planar mirror), inside that pass's own render pass so
// the sky costs no extra load or store of its targets, and one at the tail of
// the G-buffer pre-pass for the sky's motion (`post/gbuffer_sky.rs`).
//
// The sky reads set 0 of the pass it completes: the view block, the prefilter
// cube and the cube sampler of the global set the main pass, a probe face or a
// mirror already bound for its own viewpoint.
use ash::vk;
use concinnity_core::gfx::view_modes::ViewMode;
use concinnity_core::render::error::RenderResult;
use concinnity_core::render::pass_timing;
use concinnity_core::render::render_graph::PassId;
use concinnity_core::render::sky;
use super::builtin_shaders::CompileProgram;
use super::context::VkContext;
use super::owned::{OwnedPipeline, OwnedPipelineLayout, VkDevice};
use super::pipeline_desc::{Blend, Depth, GraphicsPipelineDesc};
pub(in crate::vulkan) struct VkSky {
// Set 0 only: the global set layout every scene pass binds.
layout: OwnedPipelineLayout,
pipeline: OwnedPipeline,
// The world draws its environment map as the background.
background: bool,
}
impl VkSky {
pub(in crate::vulkan) fn build(
device: &VkDevice,
global_set_layout: vk::DescriptorSetLayout,
main_render_pass: vk::RenderPass,
samples: vk::SampleCountFlags,
background: bool,
hot_reload: bool,
) -> RenderResult<Self> {
let layouts = [global_set_layout];
let layout = device
.create_pipeline_layout(&vk::PipelineLayoutCreateInfo::default().set_layouts(&layouts))
.map_err(|e| super::error::map_vk_result(e, "sky pipeline layout"))?;
let pipeline = build_sky_pipeline(
device,
layout.handle(),
main_render_pass,
samples,
hot_reload,
)?;
Ok(Self {
layout,
pipeline,
background,
})
}
pub(in crate::vulkan) fn layout(&self) -> vk::PipelineLayout {
self.layout.handle()
}
pub(in crate::vulkan) fn swap_pipeline(&mut self, pipeline: OwnedPipeline) {
self.pipeline = pipeline;
}
}
// The sky over the HDR color and the main depth, at the main pass's sample
// count: no blending, no vertex input, and the inclusive read-only depth test
// that passes only where the depth still holds the clear.
pub(in crate::vulkan) fn build_sky_pipeline(
device: &VkDevice,
layout: vk::PipelineLayout,
main_render_pass: vk::RenderPass,
samples: vk::SampleCountFlags,
hot_reload: bool,
) -> RenderResult<OwnedPipeline> {
let vs = super::builtin_shaders::SKY_VERT.compile(hot_reload)?;
let fs = super::builtin_shaders::SKY_FRAG.compile(hot_reload)?;
GraphicsPipelineDesc {
depth: Depth::read_only(),
samples,
..GraphicsPipelineDesc::fullscreen(&vs, &fs, layout, main_render_pass, &[Blend::Opaque])
}
.build(device, "sky")
}
impl VkContext {
// Whether a view rendered in `mode` draws the sky.
pub(in crate::vulkan) fn draws_sky(&self, mode: ViewMode) -> bool {
sky::draws_sky(
self.scene.prefilter_mip_count > 0,
self.sky.background,
mode,
)
}
// Draw the environment behind everything drawn so far in the open render
// pass, which must be `main_render_pass`-compatible, from the viewpoint
// `global_set` describes.
pub(in crate::vulkan) fn encode_sky(
&self,
cmd: vk::CommandBuffer,
global_set: vk::DescriptorSet,
) {
let device = &self.hw.device;
// SAFETY: `cmd` is a command buffer in the recording state inside a render pass the sky
// pipeline is compatible with, and every handle these commands name is live for the call.
unsafe {
device.cmd_bind_pipeline(
cmd,
vk::PipelineBindPoint::GRAPHICS,
self.sky.pipeline.handle(),
);
device.cmd_bind_descriptor_sets(
cmd,
vk::PipelineBindPoint::GRAPHICS,
self.sky.layout(),
0,
&[global_set],
&[],
);
device.cmd_draw(cmd, 3, 1, 0, 0);
}
self.inc_draw_calls(1);
}
// The main camera's sky, bracketed by its own timestamps inside the main
// pass. The draw is the last in the pass, so the bottom-of-pipe stamp
// before it lands when the opaque geometry finishes.
pub(in crate::vulkan) fn encode_main_sky(&self, cmd: vk::CommandBuffer, frame_idx: usize) {
let device = &self.hw.device;
let stamps = self
.hw
.timestamp_query_pool
.map(|pool| (pool, pass_timing::pass_pair(frame_idx, PassId::Sky)));
if let Some((pool, (start, _))) = stamps {
// SAFETY: `cmd` is a command buffer in the recording state and the query pool is live;
// the frame's query block was reset at the frame's start.
unsafe {
device.cmd_write_timestamp(cmd, vk::PipelineStageFlags::BOTTOM_OF_PIPE, pool, start)
};
}
self.encode_sky(cmd, self.descriptors.global_sets[frame_idx]);
if let Some((pool, (_, end))) = stamps {
// SAFETY: as above.
unsafe {
device.cmd_write_timestamp(cmd, vk::PipelineStageFlags::BOTTOM_OF_PIPE, pool, end)
};
}
}
}