💿🐜 Antkeeper source code https://antkeeper.com
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

307 lines
8.3 KiB

1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
  1. /*
  2. * Copyright (C) 2023 Christopher J. Howard
  3. *
  4. * This file is part of Antkeeper source code.
  5. *
  6. * Antkeeper source code is free software: you can redistribute it and/or modify
  7. * it under the terms of the GNU General Public License as published by
  8. * the Free Software Foundation, either version 3 of the License, or
  9. * (at your option) any later version.
  10. *
  11. * Antkeeper source code is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License
  17. * along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
  18. */
  19. #include <engine/render/passes/bloom-pass.hpp>
  20. #include <engine/resources/resource-manager.hpp>
  21. #include <engine/gl/pipeline.hpp>
  22. #include <engine/gl/framebuffer.hpp>
  23. #include <engine/gl/shader-program.hpp>
  24. #include <engine/gl/shader-variable.hpp>
  25. #include <engine/gl/vertex-buffer.hpp>
  26. #include <engine/gl/vertex-array.hpp>
  27. #include <engine/gl/texture.hpp>
  28. #include <engine/render/vertex-attribute-location.hpp>
  29. #include <engine/render/context.hpp>
  30. #include <algorithm>
  31. #include <cmath>
  32. namespace render {
  33. bloom_pass::bloom_pass(gl::pipeline* pipeline, resource_manager* resource_manager):
  34. pass(pipeline, nullptr)
  35. {
  36. // Load downsample shader template
  37. auto downsample_shader_template = resource_manager->load<gl::shader_template>("bloom-downsample.glsl");
  38. // Build downsample shader program with Karis averaging
  39. m_downsample_karis_shader = downsample_shader_template->build
  40. (
  41. {
  42. {"KARIS_AVERAGE", std::string()}
  43. }
  44. );
  45. // Build downsample shader program without Karis averaging
  46. m_downsample_shader = downsample_shader_template->build();
  47. // Load upsample shader template
  48. auto upsample_shader_template = resource_manager->load<gl::shader_template>("bloom-upsample.glsl");
  49. // Build upsample shader program
  50. m_upsample_shader = upsample_shader_template->build();
  51. // Construct framebuffer texture sampler
  52. m_sampler = std::make_shared<gl::sampler>
  53. (
  54. gl::sampler_filter::linear,
  55. gl::sampler_filter::linear,
  56. gl::sampler_mipmap_mode::linear,
  57. gl::sampler_address_mode::clamp_to_edge,
  58. gl::sampler_address_mode::clamp_to_edge
  59. );
  60. // Allocate empty vertex array
  61. m_vertex_array = std::make_unique<gl::vertex_array>();
  62. }
  63. void bloom_pass::render(render::context& ctx)
  64. {
  65. // Execute command buffer
  66. for (const auto& command: m_command_buffer)
  67. {
  68. command();
  69. }
  70. }
  71. void bloom_pass::set_source_texture(std::shared_ptr<gl::texture_2d> texture)
  72. {
  73. if (m_source_texture != texture)
  74. {
  75. m_source_texture = texture;
  76. rebuild_mip_chain();
  77. correct_filter_radius();
  78. rebuild_command_buffer();
  79. }
  80. }
  81. void bloom_pass::set_mip_chain_length(unsigned int length)
  82. {
  83. m_mip_chain_length = length;
  84. rebuild_mip_chain();
  85. rebuild_command_buffer();
  86. }
  87. void bloom_pass::set_filter_radius(float radius)
  88. {
  89. m_filter_radius = radius;
  90. correct_filter_radius();
  91. }
  92. void bloom_pass::rebuild_mip_chain()
  93. {
  94. if (m_source_texture && m_mip_chain_length)
  95. {
  96. // Rebuild target image
  97. m_target_image = std::make_shared<gl::image_2d>
  98. (
  99. gl::format::r16g16b16_sfloat,
  100. m_source_texture->get_image_view()->get_image()->get_dimensions()[0],
  101. m_source_texture->get_image_view()->get_image()->get_dimensions()[1],
  102. m_mip_chain_length
  103. );
  104. m_target_textures.resize(m_mip_chain_length);
  105. m_target_framebuffers.resize(m_mip_chain_length);
  106. for (unsigned int i = 0; i < m_mip_chain_length; ++i)
  107. {
  108. // Rebuild mip texture
  109. m_target_textures[i] = std::make_shared<gl::texture_2d>
  110. (
  111. std::make_shared<gl::image_view_2d>
  112. (
  113. m_target_image,
  114. m_target_image->get_format(),
  115. i,
  116. 1
  117. ),
  118. m_sampler
  119. );
  120. // Rebuild mip framebuffer
  121. const gl::framebuffer_attachment attachments[1] =
  122. {{
  123. gl::color_attachment_bit,
  124. m_target_textures[i]->get_image_view(),
  125. 0
  126. }};
  127. m_target_framebuffers[i] = std::make_shared<gl::framebuffer>
  128. (
  129. attachments,
  130. m_target_image->get_dimensions()[0] >> i,
  131. m_target_image->get_dimensions()[1] >> i
  132. );
  133. }
  134. }
  135. else
  136. {
  137. m_target_image = nullptr;
  138. m_target_textures.clear();
  139. m_target_framebuffers.clear();
  140. }
  141. }
  142. void bloom_pass::correct_filter_radius()
  143. {
  144. // Get aspect ratio of target image
  145. float aspect_ratio = 1.0f;
  146. if (m_target_image)
  147. {
  148. aspect_ratio = static_cast<float>(m_target_image->get_dimensions()[1]) /
  149. static_cast<float>(m_target_image->get_dimensions()[0]);
  150. }
  151. // Correct filter radius according to target image aspect ratio
  152. m_corrected_filter_radius = {m_filter_radius * aspect_ratio, m_filter_radius};
  153. }
  154. void bloom_pass::rebuild_command_buffer()
  155. {
  156. m_command_buffer.clear();
  157. if (!m_source_texture ||
  158. !m_mip_chain_length ||
  159. !m_downsample_karis_shader ||
  160. !m_downsample_shader ||
  161. !m_upsample_shader)
  162. {
  163. return;
  164. }
  165. // Setup downsample state
  166. m_command_buffer.emplace_back
  167. (
  168. [&]()
  169. {
  170. m_pipeline->set_primitive_topology(gl::primitive_topology::triangle_list);
  171. m_pipeline->bind_vertex_array(m_vertex_array.get());
  172. m_pipeline->set_depth_test_enabled(false);
  173. m_pipeline->set_cull_mode(gl::cull_mode::back);
  174. m_pipeline->set_color_blend_enabled(false);
  175. }
  176. );
  177. // Downsample first mip with Karis average
  178. if (auto source_texture_var = m_downsample_karis_shader->variable("source_texture"))
  179. {
  180. m_command_buffer.emplace_back
  181. (
  182. [&, source_texture_var]()
  183. {
  184. m_pipeline->bind_shader_program(m_downsample_karis_shader.get());
  185. m_pipeline->bind_framebuffer(m_target_framebuffers[0].get());
  186. const auto& target_dimensions = m_target_image->get_dimensions();
  187. const gl::viewport viewport[1] = {{0.0f, 0.0f, static_cast<float>(target_dimensions[0]), static_cast<float>(target_dimensions[1])}};
  188. m_pipeline->set_viewport(0, viewport);
  189. source_texture_var->update(*m_source_texture);
  190. // Draw fullscreen triangle
  191. m_pipeline->draw(3, 1, 0, 0);
  192. }
  193. );
  194. }
  195. // Downsample remaining mips
  196. if (m_mip_chain_length > 1)
  197. {
  198. if (auto source_texture_var = m_downsample_shader->variable("source_texture"))
  199. {
  200. m_command_buffer.emplace_back([&](){m_pipeline->bind_shader_program(m_downsample_shader.get());});
  201. for (int i = 1; i < static_cast<int>(m_mip_chain_length); ++i)
  202. {
  203. m_command_buffer.emplace_back
  204. (
  205. [&, source_texture_var, i]()
  206. {
  207. m_pipeline->bind_framebuffer(m_target_framebuffers[i].get());
  208. const auto& target_dimensions = m_target_image->get_dimensions();
  209. const gl::viewport viewport[1] = {{0.0f, 0.0f, static_cast<float>(target_dimensions[0] >> i), static_cast<float>(target_dimensions[1] >> i)}};
  210. m_pipeline->set_viewport(0, viewport);
  211. // Use previous downsample texture as downsample source
  212. source_texture_var->update(*m_target_textures[i - 1]);
  213. // Draw fullscreen triangle
  214. m_pipeline->draw(3, 1, 0, 0);
  215. }
  216. );
  217. }
  218. }
  219. }
  220. // Setup upsample state
  221. m_command_buffer.emplace_back
  222. (
  223. [&]()
  224. {
  225. // Enable additive blending
  226. m_pipeline->set_color_blend_enabled(true);
  227. m_pipeline->set_color_blend_equation
  228. ({
  229. gl::blend_factor::one,
  230. gl::blend_factor::one,
  231. gl::blend_op::add,
  232. gl::blend_factor::one,
  233. gl::blend_factor::one,
  234. gl::blend_op::add
  235. });
  236. // Bind upsample shader
  237. m_pipeline->bind_shader_program(m_upsample_shader.get());
  238. }
  239. );
  240. // Update upsample filter radius
  241. if (auto filter_radius_var = m_upsample_shader->variable("filter_radius"))
  242. {
  243. m_command_buffer.emplace_back([&, filter_radius_var](){filter_radius_var->update(m_corrected_filter_radius);});
  244. }
  245. // Upsample
  246. if (auto source_texture_var = m_upsample_shader->variable("source_texture"))
  247. {
  248. for (int i = static_cast<int>(m_mip_chain_length) - 1; i > 0; --i)
  249. {
  250. const int j = i - 1;
  251. m_command_buffer.emplace_back
  252. (
  253. [&, source_texture_var, i, j]()
  254. {
  255. m_pipeline->bind_framebuffer(m_target_framebuffers[j].get());
  256. const auto& target_dimensions = m_target_image->get_dimensions();
  257. const gl::viewport viewport[1] = {{0.0f, 0.0f, static_cast<float>(target_dimensions[0] >> j), static_cast<float>(target_dimensions[1] >> j)}};
  258. m_pipeline->set_viewport(0, viewport);
  259. source_texture_var->update(*m_target_textures[i]);
  260. // Draw fullscreen triangle
  261. m_pipeline->draw(3, 1, 0, 0);
  262. }
  263. );
  264. }
  265. }
  266. }
  267. } // namespace render