diff options
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | Rakefile | 51 | ||||
-rw-r--r-- | glsl/shader.frag | 14 | ||||
-rw-r--r-- | glsl/shader.vert | 30 | ||||
-rw-r--r-- | lib/animation.rb | 58 | ||||
-rw-r--r-- | lib/menu.rb | 220 | ||||
-rw-r--r-- | src/camera.cpp | 143 | ||||
-rw-r--r-- | src/camera.hpp | 25 | ||||
-rw-r--r-- | src/candy_gear.cpp | 4 | ||||
-rw-r--r-- | src/candy_gear.hpp (renamed from src/candy_gear.h) | 10 | ||||
-rw-r--r-- | src/color.c | 61 | ||||
-rw-r--r-- | src/command.cpp | 78 | ||||
-rw-r--r-- | src/command.hpp | 92 | ||||
-rw-r--r-- | src/core.cpp | 490 | ||||
-rw-r--r-- | src/core.hpp (renamed from src/core.h) | 66 | ||||
-rw-r--r-- | src/font.c | 60 | ||||
-rw-r--r-- | src/graphic.c | 67 | ||||
-rw-r--r-- | src/key.cpp (renamed from src/key.c) | 2 | ||||
-rw-r--r-- | src/key.hpp (renamed from src/key.h) | 10 | ||||
-rw-r--r-- | src/loader.c | 162 | ||||
-rw-r--r-- | src/loader.h | 159 | ||||
-rw-r--r-- | src/log.cpp (renamed from src/log.c) | 2 | ||||
-rw-r--r-- | src/log.hpp (renamed from src/log.h) | 10 | ||||
-rw-r--r-- | src/main.cpp | 66 | ||||
-rw-r--r-- | src/model.cpp | 67 | ||||
-rw-r--r-- | src/model.hpp (renamed from src/graphic.h) | 19 | ||||
-rw-r--r-- | src/model/instance.cpp | 98 | ||||
-rw-r--r-- | src/model/instance.hpp | 25 | ||||
-rw-r--r-- | src/palette.c | 58 | ||||
-rw-r--r-- | src/palette_implementation.cpp | 35 | ||||
-rw-r--r-- | src/palette_implementation.h | 33 | ||||
-rw-r--r-- | src/pgm_image.cpp | 10 | ||||
-rw-r--r-- | src/pgm_image.hpp (renamed from src/pgm_image.h) | 10 | ||||
-rw-r--r-- | src/point.c | 122 | ||||
-rw-r--r-- | src/point.h | 40 | ||||
-rw-r--r-- | src/rect.c | 249 | ||||
-rw-r--r-- | src/rect.h | 40 | ||||
-rw-r--r-- | src/sound.cpp (renamed from src/sound.c) | 4 | ||||
-rw-r--r-- | src/sound.hpp (renamed from src/sound.h) | 10 | ||||
-rw-r--r-- | src/sprite.c | 198 | ||||
-rw-r--r-- | src/sprite.h | 40 | ||||
-rw-r--r-- | src/sprite_implementation.cpp | 6 | ||||
-rw-r--r-- | src/sprite_implementation.hpp (renamed from src/sprite_implementation.h) | 10 | ||||
-rw-r--r-- | src/texture.c | 223 | ||||
-rw-r--r-- | src/texture.cpp | 186 | ||||
-rw-r--r-- | src/texture.hpp (renamed from src/texture.h) | 17 | ||||
-rw-r--r-- | src/vk/base_buffer.cpp | 96 | ||||
-rw-r--r-- | src/vk/base_buffer.hpp | 56 | ||||
-rw-r--r-- | src/vk/camera.hpp | 33 | ||||
-rw-r--r-- | src/vk/command_pool.cpp | 87 | ||||
-rw-r--r-- | src/vk/command_pool.hpp | 59 | ||||
-rw-r--r-- | src/vk/core.hpp | 32 | ||||
-rw-r--r-- | src/vk/destination_buffer.cpp | 105 | ||||
-rw-r--r-- | src/vk/destination_buffer.hpp | 51 | ||||
-rw-r--r-- | src/vk/device.cpp | 254 | ||||
-rw-r--r-- | src/vk/device.hpp | 61 | ||||
-rw-r--r-- | src/vk/graphics_pipeline.cpp | 1009 | ||||
-rw-r--r-- | src/vk/graphics_pipeline.hpp | 80 | ||||
-rw-r--r-- | src/vk/image.cpp | 122 | ||||
-rw-r--r-- | src/vk/image.hpp | 61 | ||||
-rw-r--r-- | src/vk/model.cpp | 324 | ||||
-rw-r--r-- | src/vk/model.hpp | 55 | ||||
-rw-r--r-- | src/vk/model_instance.hpp | 32 | ||||
-rw-r--r-- | src/vk/queue.cpp | 61 | ||||
-rw-r--r-- | src/vk/queue.hpp | 82 | ||||
-rw-r--r-- | src/vk/queue_family.cpp | 80 | ||||
-rw-r--r-- | src/vk/queue_family.hpp | 58 | ||||
-rw-r--r-- | src/vk/source_buffer.cpp | 92 | ||||
-rw-r--r-- | src/vk/source_buffer.hpp (renamed from src/palette.h) | 37 | ||||
-rw-r--r-- | src/vk/swapchain.cpp | 151 | ||||
-rw-r--r-- | src/vk/swapchain.hpp (renamed from src/font.h) | 33 | ||||
-rw-r--r-- | src/vk/texture.cpp | 292 | ||||
-rw-r--r-- | src/vk/texture.hpp (renamed from src/color.h) | 37 | ||||
-rw-r--r-- | src/vk/uniform_buffer.cpp | 89 | ||||
-rw-r--r-- | src/vk/uniform_buffer.hpp | 60 | ||||
-rw-r--r-- | src/vk/vertex.hpp | 35 | ||||
-rw-r--r-- | test/models/cube.cgmodel | bin | 0 -> 952 bytes | |||
-rw-r--r-- | test/models/tetrahedron.cgmodel | bin | 0 -> 376 bytes | |||
-rw-r--r-- | test/src/main.rb | 64 | ||||
-rw-r--r-- | test/src/mode/collision.rb | 87 | ||||
-rw-r--r-- | test/textures/color_texture.png | bin | 0 -> 3594 bytes |
81 files changed, 4860 insertions, 2296 deletions
@@ -4,3 +4,4 @@ pkg *.mrb *.o +*.spv
\ No newline at end of file @@ -21,45 +21,45 @@ VERSION = '0.1.0' DATA_DIR = '/usr/local/share/candy_gear' -C_FILES = FileList[ - 'src/*.c' +CPP_H_FILES = FileList[ + 'src/**/*.hpp' ] -C_H_FILES = FileList[ - 'src/*.h' -] -C_OBJS = C_FILES.ext('.o') - CPP_FILES = FileList[ - 'src/*.cpp' + 'src/**/*.cpp' ] CPP_OBJS = CPP_FILES.ext('.o') -OBJ_FILES = C_OBJS + CPP_OBJS - RB_LIBS = FileList[ 'lib/*.rb' ] +SPV_FILES = [ + 'glsl/vert.spv', + 'glsl/frag.spv' +] + LIBRARIES = [ 'SDL2', 'SDL2_image', 'SDL2_mixer', - 'SDL2_ttf', 'm', 'mruby', + 'vulkan', 'yaml-cpp' ] +CLEAN.include( + FileList[ + 'src/**/*.o' + ] +) + task :doc do `doxygen Doxyfile` end -rule '.o' => ['.c'] do |t| - `gcc -c -std=c99 #{t.source} -o #{t.name}` -end - rule '.o' => ['.cpp'] do |t| - `g++ -c -std=c++17 #{t.source} -o #{t.name}` + `g++ -D DEBUG=1 -c -std=c++17 #{t.source} -o #{t.name}` end task :pkg do @@ -72,8 +72,7 @@ task :pkg do 'Rakefile', 'README.markdown' ] + - C_FILES + - C_H_FILES + + CPP_H_FILES + CPP_FILES + RB_LIBS @@ -81,17 +80,29 @@ task :pkg do `tar -czvf pkg/#{name}.tar.gz --transform 's,^,#{name}/,' #{files}` end -task build: OBJ_FILES do +task :shaders do + system('glslangValidator -V glsl/shader.vert -o glsl/vert.spv') and + system('glslangValidator -V glsl/shader.frag -o glsl/frag.spv') +end + +task build: CPP_OBJS do libs = LIBRARIES.inject('') {_1 + "-l#{_2} "} - `g++ -o #{OBJ} #{OBJ_FILES} #{libs}` + `g++ -o #{OBJ} #{CPP_OBJS} #{libs}` end task :install do destdir = ENV['DESTDIR'] || '' + # Install engine `install -d #{destdir}/usr/local/bin` `install #{OBJ} #{destdir}/usr/local/bin` + + # Install shaders + `install -d #{destdir}#{DATA_DIR}/glsl` + SPV_FILES.each {`install #{_1} #{destdir}#{DATA_DIR}/glsl`} + + # Install libs `install -d #{destdir}#{DATA_DIR}/lib` RB_LIBS.each {`install #{_1} #{destdir}#{DATA_DIR}/lib`} end diff --git a/glsl/shader.frag b/glsl/shader.frag new file mode 100644 index 0000000..39aa83c --- /dev/null +++ b/glsl/shader.frag @@ -0,0 +1,14 @@ +#version 450 +#extension GL_ARB_separate_shader_objects : enable + +layout(location = 0) in vec3 in_frag_color; +layout(location = 1) in vec2 in_frag_texture_coord; + +layout(location = 0) out vec4 out_color; + +layout(set = 0, binding = 1) uniform sampler2D texture_sampler; + +void main() +{ + out_color = texture(texture_sampler, in_frag_texture_coord); +} diff --git a/glsl/shader.vert b/glsl/shader.vert new file mode 100644 index 0000000..2407255 --- /dev/null +++ b/glsl/shader.vert @@ -0,0 +1,30 @@ +#version 450 +#extension GL_ARB_separate_shader_objects : enable + +layout(location = 0) in vec3 in_position; +layout(location = 1) in vec3 in_normal; +layout(location = 2) in vec3 in_color; +layout(location = 3) in vec2 in_texture_coord; + +layout(location = 0) out vec3 frag_color; +layout(location = 1) out vec2 frag_texture_coord; + +layout(set = 0, binding = 0) uniform UBOModelInstance +{ + mat4 model[128]; +} ubo_model_instance; + +layout(set = 1, binding = 0) uniform UBOViewProjection +{ + mat4 view; + mat4 proj; +} ubo_view_projection; + +void main() +{ + gl_Position = + ubo_view_projection.proj * ubo_view_projection.view * + ubo_model_instance.model[gl_InstanceIndex] * vec4(in_position, 1.0); + frag_color = in_color; + frag_texture_coord = in_texture_coord; +} diff --git a/lib/animation.rb b/lib/animation.rb deleted file mode 100644 index 7da2898..0000000 --- a/lib/animation.rb +++ /dev/null @@ -1,58 +0,0 @@ -# Copyright 2022 Frederico de Oliveira Linhares -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -module CandyGear - class Animation - Frame = Struct.new(:duration, :index); - - attr_reader(:index); - - def initialize(sequence, repeat = true) - @sequence = sequence; - @repeat = repeat; - - self.reset(); - end - - def tick() - return @index if @sequence_is_over; - - @current_time += 1; - frame = @sequence[@current_frame]; - - if @current_time >= frame.duration then - @current_time = 0; - @current_frame += 1; - - if @current_frame >= @sequence.size() then - if @repeat then - @current_frame = 0; - else - @current_frame -= 1; - @sequence_is_over = true; - end - end - end - - return @index = frame.index(); - end - - def reset() - @current_time = 0; - @current_frame = 0; - @index = @sequence[0].index; - @sequence_is_over = false; - end - end -end diff --git a/lib/menu.rb b/lib/menu.rb deleted file mode 100644 index 11c8715..0000000 --- a/lib/menu.rb +++ /dev/null @@ -1,220 +0,0 @@ -# Copyright 2022 Frederico de Oliveira Linhares -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -module CandyGear - class Menu - class Stack - def initialize(menu) - @stack = []; - @stack << menu; - end - - def current_menu() = @stack[-1]; - - def push(menu) = @stack << menu; - - def pop() - @stack.pop() if @stack.size > 1; - end - - def draw() = @stack.each {_1.draw();} - def size() = @stack.size; - end - - class BorderlessView - attr_reader(:texture, :font, :text_color, :bg_color, :sprites); - - def initialize( - texture, font, text_color, bg_color, sprite_width, sprite_height) - @texture = texture; - @font = font; - @text_color = text_color; - @bg_color = bg_color; - @sprite_width = sprite_width; - @sprite_height = sprite_height; - - @sprites = {}; - @sprites[:arrow_select] = Sprite.new( - @texture, 1 * @sprite_width, 1 * @sprite_height, - @sprite_width, @sprite_height); - end - - def draw(x, y, width, height) - # Nothing. - end - - def border_width() = 0; - def border_height() = 0; - end - - class BorderedView - attr_reader( - :texture, :font, :text_color, :bg_color, :sprites, :border_width, - :border_height); - - def initialize( - texture, font, text_color, bg_color, sprite_width, sprite_height) - @texture = texture; - @font = font; - @text_color = text_color; - @bg_color = bg_color; - @border_width = sprite_width; - @border_height = sprite_height; - - @sprites = {}; - @sprites[:arrow_select] = Sprite.new( - @texture, 1 * @border_width, 1 * @border_height, - @border_width, @border_height); - @sprites[:box_top_left] = Sprite.new( - @texture, 0, 0, @border_width, @border_height); - @sprites[:box_top] = Sprite.new( - @texture, 1 * @border_width, 0, - @border_width, @border_height); - @sprites[:box_top_right] = Sprite.new( - @texture, 2 * @border_height, 0, - @border_width, @border_height); - @sprites[:box_left] = Sprite.new( - @texture, 0, 1 * @border_height, - @border_width, @border_height); - @sprites[:box_right] = Sprite.new( - @texture, 2 * @border_width, 1 * @border_height, - @border_width, @border_height); - @sprites[:box_bottom_left] = Sprite.new( - @texture, 0, 2 * @border_height, - @border_width, @border_height); - @sprites[:box_bottom] = Sprite.new( - @texture, 1 * @border_width, 2 * @border_height, - @border_width, @border_height); - @sprites[:box_bottom_right] = Sprite.new( - @texture, 2 * @border_width, 2 * @border_height, - @border_width, @border_height); - end - - def draw(x, y, width, height) - num_horizontal_sprites = width / @border_width + 1; - num_horizontal_sprites += 1 if width % @border_width > 0; - num_vertical_sprites = height / @border_height; - num_vertical_sprites += 1 if height % @border_height > 0; - background = Rect.new( - x, y, - (num_horizontal_sprites + 2) * @border_width, - (num_vertical_sprites + 2) * @border_height); - - Graphic.set_color(@bg_color); - background.draw_fill(); - - # Draw the corners. - @sprites[:box_top_left].draw_xy(x, y); - @sprites[:box_top_right].draw_xy( - @border_width * (num_horizontal_sprites + 1) + x, y); - @sprites[:box_bottom_left].draw_xy( - x, @border_height * (num_vertical_sprites + 1) + y); - @sprites[:box_bottom_right].draw_xy( - @border_width * (num_horizontal_sprites + 1) + x, - @border_height * (num_vertical_sprites + 1) + y); - - # Draw the edges. - num_horizontal_sprites.times do |i| - # Top - @sprites[:box_top].draw_xy(@border_width * (i + 1) + x, y); - # Bottom - @sprites[:box_bottom].draw_xy( - @border_width * (i + 1) + x, - @border_height * (num_vertical_sprites + 1) + y); - end - num_vertical_sprites.times do |i| - # Left - @sprites[:box_left].draw_xy(x, @border_height * (i + 1) + y); - # Right - @sprites[:box_right].draw_xy( - @border_width * (num_horizontal_sprites + 1) + x, - @border_height * (i + 1) + y); - end - end - end - - class Option - attr_reader(:action, :text); - - def initialize(text, action); - @text = text; - @action = action; - end - end - - attr_reader(:width, :height); - - def initialize(view, pos_x, pos_y, options) - @view = view; - - @pos_x = pos_x; - @pos_y = pos_y; - - @options = options.map do |opt| - Option.new( - Texture.from_text( - opt[:text], view.font, view.text_color, view.bg_color), - opt[:action]); - end - @current_option = 0; - @option_max_width = 0; - @option_max_height = 0; - - @options.each do |i| - if @option_max_width < i.text.width then - @option_max_width = i.text.width; - end - if @option_max_height < i.text.height then - @option_max_height = i.text.height; - end - end - - @width = @option_max_width; - @height = @option_max_height * @options.size; - end - - def next_opt() - @current_option += 1; - @current_option = 0 if @current_option >= @options.size(); - end - - def pred_opt() - if @current_option <= 0 then - @current_option = @options.size - 1; - else - @current_option -= 1; - end - end - - def activate() - @options[@current_option].action.call(); - end - - def draw() - @view.draw(@pos_x, @pos_y, @width, @height); - - @options.each_with_index do |opt, i| - opt.text.draw_xy( - @pos_x + @view.border_width + @view.sprites[:arrow_select].width, - @pos_y + @view.border_height + - @option_max_height * i); - end - - @view.sprites[:arrow_select].draw_xy( - @pos_x + @view.border_width, - @pos_y + @view.border_height + - @option_max_height * @current_option); - end - end -end diff --git a/src/camera.cpp b/src/camera.cpp new file mode 100644 index 0000000..27805ac --- /dev/null +++ b/src/camera.cpp @@ -0,0 +1,143 @@ +/* + * Copyright 2022 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "camera.hpp" + +#include "vk/camera.hpp" + +void +cg_free_camera(mrb_state *mrb, void* obj) +{ + auto ptr = static_cast<std::shared_ptr<VK::Camera>*>(obj); + + ptr->~shared_ptr(); + mrb_free(mrb, ptr); +} + +const struct mrb_data_type cg_camera_type = {"CG_Camera", cg_free_camera}; + +static mrb_value +cg_cCamera_initialize(mrb_state *mrb, mrb_value self) +{ + mrb_float position_x, position_y, position_z, + rotation_x, rotation_y, rotation_z; + std::shared_ptr<VK::Camera> *ptr; + + mrb_get_args( + mrb, "ffffff", + &position_x, &position_y, &position_z, + &rotation_x, &rotation_y, &rotation_z); + ptr = (std::shared_ptr<VK::Camera>*)DATA_PTR(self); + if(ptr) mrb_free(mrb, ptr); + ptr = (std::shared_ptr<VK::Camera>*)mrb_malloc( + mrb, sizeof(std::shared_ptr<VK::Camera>)); + + new(ptr)std::shared_ptr<VK::Camera>(std::make_shared<VK::Camera>()); + (*ptr)->position = glm::vec3(position_x, position_y, position_z); + (*ptr)->rotation = glm::vec3(rotation_x, rotation_y, rotation_z); + + mrb_data_init(self, ptr, &cg_camera_type); + return self; +} + +static mrb_value +cg_cCamera_use(mrb_state *mrb, mrb_value self) +{ + std::shared_ptr<VK::Camera> *ptr; + + ptr = (std::shared_ptr<VK::Camera>*)DATA_PTR(self); + cg_core.vk_graphics_pipeline->camera = (*ptr); + + return self; +} + +static mrb_value +cg_cCamera_rotate(mrb_state *mrb, mrb_value self) +{ + mrb_float x, y, z; + std::shared_ptr<VK::Camera> *ptr; + + mrb_get_args(mrb, "fff", &x, &y, &z); + ptr = (std::shared_ptr<VK::Camera>*)DATA_PTR(self); + + (*ptr)->rotation.x += x; + (*ptr)->rotation.y += y; + (*ptr)->rotation.z += z; + + return self; +} + +static mrb_value +cg_cCamera_translate(mrb_state *mrb, mrb_value self) +{ + mrb_float x, y, z; + std::shared_ptr<VK::Camera> *ptr; + + mrb_get_args(mrb, "fff", &x, &y, &z); + ptr = (std::shared_ptr<VK::Camera>*)DATA_PTR(self); + + (*ptr)->position.x += x; + (*ptr)->position.y += y; + (*ptr)->position.z += z; + + return self; +} + +static mrb_value +cg_cCamera_translate_by_rotation(mrb_state *mrb, mrb_value self) +{ + mrb_float x, y, z; + std::shared_ptr<VK::Camera> *ptr; + + mrb_get_args(mrb, "fff", &x, &y, &z); + ptr = (std::shared_ptr<VK::Camera>*)DATA_PTR(self); + + glm::mat4 matrix{1.0f}; + matrix = glm::rotate( + matrix, (*ptr)->rotation.x, glm::vec3{1.0f, 0.0f, 0.0f}); + matrix = glm::rotate( + matrix, (*ptr)->rotation.y, glm::vec3{0.0f, 1.0f, 0.0f}); + matrix = glm::rotate( + matrix, (*ptr)->rotation.z, glm::vec3{0.0f, 0.0f, 1.0f}); + + glm::vec4 rotated_vector{matrix * glm::vec4{x, y, z, 1.0}}; + + (*ptr)->position.x += rotated_vector.x; + (*ptr)->position.y += rotated_vector.y; + (*ptr)->position.z += rotated_vector.z; + + return self; +} + +void +cg_camera_init(mrb_state *mrb) +{ + struct RClass *cg_m, *cg_cCamera; + + cg_m = mrb_module_get(mrb, "CandyGear"); + cg_cCamera = mrb_define_class_under(mrb, cg_m, "Camera", mrb->object_class); + MRB_SET_INSTANCE_TT(cg_cCamera, MRB_TT_DATA); + mrb_define_method( + mrb, cg_cCamera, "initialize", cg_cCamera_initialize, MRB_ARGS_REQ(6)); + mrb_define_method(mrb, cg_cCamera, "use", cg_cCamera_use, MRB_ARGS_NONE()); + mrb_define_method( + mrb, cg_cCamera, "rotate", cg_cCamera_rotate, MRB_ARGS_REQ(3)); + mrb_define_method( + mrb, cg_cCamera, "translate", cg_cCamera_translate, MRB_ARGS_REQ(3)); + mrb_define_method( + mrb, cg_cCamera, "translate_by_rotation", + cg_cCamera_translate_by_rotation, MRB_ARGS_REQ(3)); +} diff --git a/src/camera.hpp b/src/camera.hpp new file mode 100644 index 0000000..a3d9d1b --- /dev/null +++ b/src/camera.hpp @@ -0,0 +1,25 @@ +/* + * Copyright 2022 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CANDY_GEAR_CAMERA_H +#define CANDY_GEAR_CAMERA_H 1 + +#include "core.hpp" + +void +cg_camera_init(mrb_state *mrb); + +#endif /* CANDY_GEAR_CAMERA_H */ diff --git a/src/candy_gear.cpp b/src/candy_gear.cpp index 484ce6a..b10ed6e 100644 --- a/src/candy_gear.cpp +++ b/src/candy_gear.cpp @@ -14,7 +14,7 @@ * limitations under the License. */ -#include "candy_gear.h" +#include "candy_gear.hpp" #include <mruby/array.h> #include <mruby/hash.h> @@ -71,7 +71,7 @@ cg_mCandyGear_load_yaml(mrb_state *mrb, mrb_value self) static mrb_value cg_mCandyGear_quit(mrb_state *mrb, mrb_value self) { - cg_core.quit_game = SDL_TRUE; + cg_core.quit_game = true; return self; } diff --git a/src/candy_gear.h b/src/candy_gear.hpp index aca4740..487ab13 100644 --- a/src/candy_gear.h +++ b/src/candy_gear.hpp @@ -17,17 +17,9 @@ #ifndef CANDY_GEAR_GRAPHIC_H #define CANDY_GEAR_CANDY_GEAR_H 1 -#include "core.h" - -#ifdef __cplusplus -extern "C" { -#endif +#include "core.hpp" void cg_candy_gear_init(mrb_state *mrb); -#ifdef __cplusplus -}; -#endif - #endif /* CANDY_GEAR_CANDY_GEAR_H */ diff --git a/src/color.c b/src/color.c deleted file mode 100644 index 0ab9392..0000000 --- a/src/color.c +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright 2022 Frederico de Oliveira Linhares - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "color.h" - -void -cg_free_color(mrb_state *mrb, void* obj) -{ - struct cg_color *ptr = obj; - - mrb_free(mrb, ptr); -} - -const struct mrb_data_type cg_color_type = { "CG_Color", cg_free_color }; - -static mrb_value -cg_cColor_initialize(mrb_state *mrb, mrb_value self) -{ - mrb_int red, green, blue, alpha; - struct cg_color *ptr; - - alpha = 0xff; - mrb_get_args(mrb, "iii|i", &red, &green, &blue, &alpha); - ptr = (struct cg_color *)DATA_PTR(self); - if(ptr) mrb_free(mrb, ptr); - ptr = (struct cg_color *)mrb_malloc(mrb, sizeof(struct cg_color)); - - ptr->data.r = red; - ptr->data.g = green; - ptr->data.b = blue; - ptr->data.a = alpha; - - mrb_data_init(self, ptr, &cg_color_type); - return self; -} - -void -cg_color_init(mrb_state *mrb) -{ - struct RClass *cg_m, *cg_cColor; - - cg_m = mrb_module_get(mrb, "CandyGear"); - cg_cColor = mrb_define_class_under(mrb, cg_m, "Color", mrb->object_class); - MRB_SET_INSTANCE_TT(cg_cColor, MRB_TT_DATA); - mrb_define_method( - mrb, cg_cColor, "initialize", cg_cColor_initialize, - MRB_ARGS_REQ(3) | MRB_ARGS_OPT(1)); -} diff --git a/src/command.cpp b/src/command.cpp new file mode 100644 index 0000000..a1d3240 --- /dev/null +++ b/src/command.cpp @@ -0,0 +1,78 @@ +/* + * Copyright 2022 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <stdlib.h> + +#include "command.hpp" + +CommandError::CommandError(const std::string &m): + error(m) +{ +} + +CommandError::CommandError(const char &m): + CommandError{std::string{m}} +{ +} + +const char* CommandError::what() const noexcept +{ + return this->error.c_str(); +} + +CommandChain::CommandChain(std::initializer_list<Command> commands) +{ + for(auto c: commands) this->add(c); +} + +void +CommandChain::partial_revert(void *obj, int32_t step) const +{ + // Already unloaded, nothing to do. + if(step <= 0) return; + + for(; step > 0; step--) + { + auto command = this->_commands[step -1].undo_command; + if(command != nullptr) command(obj); + } +} + +void +CommandChain::add(const Command &c) +{ + this->_commands.push_back(c); +} + +void +CommandChain::execute(void *obj) const +{ + for(auto i{0}; i < this->_commands.size(); i++) + { + try { this->_commands[i].do_command(obj); } + catch(const CommandError &error) + { + this->partial_revert(obj, i); + throw; + } + } +} + +void +CommandChain::revert(void *obj) const +{ + this->partial_revert(obj, this->_commands.size()); +} diff --git a/src/command.hpp b/src/command.hpp new file mode 100644 index 0000000..47552a5 --- /dev/null +++ b/src/command.hpp @@ -0,0 +1,92 @@ +/* + * Copyright 2022 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CANDY_GEAR_COMMAND_CHAIN_H +#define CANDY_GEAR_COMMAND_CHAIN_H 1 + +#include <cstdint> +#include <initializer_list> +#include <stdexcept> +#include <string> +#include <vector> + +class CommandChain; + +struct CommandError: public std::exception +{ + CommandError(const std::string &m); + CommandError(const char &m); + + const char* what() const noexcept; + +private: + std::string error; +}; + +/** + * Stores a reversible action. + */ +struct Command +{ + void (*do_command)(void *obj); + void (*undo_command)(void *obj); +}; + +/** + * Stores a sequence of functions that must be executed and rolled back in + * order. + * + * For example, if the variable _commands contain A→B→C→D→E, it will load A, + * then B, then C, etc. If D fails, it unloads C, then B, then A. + */ +class CommandChain +{ + std::vector<Command> _commands; + + void + partial_revert(void *obj, int32_t step) const; + +public: + CommandChain(std::initializer_list<Command> commands); + +/** + * Adds a reversible action to the command. The first action added is the first + * to de executed the last rolled back, and so on. Those functions are stored + * inside _commands. + * + * @param[unload] the undo/rollback action, if the action do not need a + * rollback, this pointer can be set to null. + */ + void + add(const Command &c); + +/** + * Execute all of the load functions in the _commands. If one of them fails, + * roll back everything inside _commands. + * @return true on success and false on fail. + */ + void + execute(void *obj) const; + +/** + * Roll back all loaded function inside commands, if there are any. + */ + void + revert(void *obj) const; + +}; + +#endif /* CANDY_GEAR_COMMAND_CHAIN_H */ diff --git a/src/core.cpp b/src/core.cpp index 12e5b01..7e13f75 100644 --- a/src/core.cpp +++ b/src/core.cpp @@ -14,57 +14,45 @@ * limitations under the License. */ -#include "core.h" +#include "core.hpp" -#include <yaml-cpp/yaml.h> - -/* - Stretches the image to fill the screen vertically, horizontally, or both; - without changing the game aspect ratio. - */ -void -cg_Engine_calculate_full_scale() -{ - double screen_ratio, game_ratio, scale; +#include <iostream> - screen_ratio = - (double)cg_core.screen_width/(double)cg_core.screen_height; - game_ratio = (double)cg_core.game_width/(double)cg_core.game_height; - - // If screen is proportionally taller than game. - if(screen_ratio < game_ratio) - { - scale = (double)cg_core.screen_width/(double)cg_core.game_width; +#include <yaml-cpp/yaml.h> - cg_core.screen_rect.w = cg_core.game_width * scale; - cg_core.screen_rect.h = cg_core.game_height * scale; - cg_core.screen_rect.x = 0; - cg_core.screen_rect.y = cg_core.screen_height/2 - - cg_core.screen_rect.h/2; - } - // If screen is proportionally wider than game. - else if(screen_ratio > game_ratio) - { - scale = (double)cg_core.screen_height/(double)cg_core.game_height; +std::random_device random_seed; +std::mt19937 random_number_generator; - cg_core.screen_rect.w = cg_core.game_width * scale; - cg_core.screen_rect.h = cg_core.game_height * scale; - cg_core.screen_rect.x = cg_core.screen_width/2 - - cg_core.screen_rect.w/2; - cg_core.screen_rect.y = 0; - } - // If they have the same aspect ratio. - else - { - cg_core.screen_rect.x = 0; - cg_core.screen_rect.y = 0; - cg_core.screen_rect.w = cg_core.screen_width; - cg_core.screen_rect.h = cg_core.screen_height; - } +#ifdef DEBUG +static VKAPI_ATTR VkBool32 VKAPI_CALL +vk_debug_callback( + VkDebugUtilsMessageSeverityFlagBitsEXT message_severity, + VkDebugUtilsMessageTypeFlagsEXT message_type, + const VkDebugUtilsMessengerCallbackDataEXT* callback_data, + void* _obj) +{ + // Print message severy code. + std::cout << "[\e[1;0mVK\e[0;0m "; + if(message_severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT) + std::cout << "\e[1;32mV\e[0;0m"; + else if(message_severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT) + std::cout << "\e[1;34mI\e[0;0m"; + else if(message_severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT) + std::cout << "\e[1;33mW\e[0;0m"; + else if(message_severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) + std::cout << "\e[1;31mE\e[0;0m"; + + std::cout << "]"; + + // Log message. + std::cout << callback_data->pMessage << std::endl; + + return VK_FALSE; } +#endif -static SDL_bool -load_variables(void *obj, LoaderStack *ls) +static void +load_variables(void *obj) { YAML::Node root = YAML::LoadFile(cg_core.config_file); @@ -72,221 +60,421 @@ load_variables(void *obj, LoaderStack *ls) cg_core.game_name = new char[game_name.size() + 1]; strcpy(cg_core.game_name, game_name.c_str()); - // Based on NES. cg_core.game_width = root["game"]["width"].as<int>(); cg_core.game_height = root["game"]["height"].as<int>(); cg_core.screen_width = root["screen"]["width"].as<int>(); cg_core.screen_height = root["screen"]["height"].as<int>(); + cg_core.game_version_major = 0; + cg_core.game_version_minor = 1; + cg_core.game_version_patch = 0; + cg_core.fps = 30; cg_core.max_frame_duration_ms = 1000 / cg_core.fps; - - return SDL_TRUE; } static void -unload_variables(void *obj, LoaderStack *ls) +unload_variables(void *obj) { delete[] cg_core.game_name; } -static SDL_bool -load_sdl(void *obj, LoaderStack *ls) +static void +load_sdl(void *obj) { if(SDL_Init(SDL_INIT_EVERYTHING) < 0) { - const char* base_error = "SDL could not initialize! SDL Error → "; - LoaderStack_set_error(ls, base_error, SDL_GetError()); - return SDL_FALSE; + std::string error{"SDL could not initialize! SDL Error → "}; + error += SDL_GetError(); + throw error; } - return SDL_TRUE; + if(SDL_Vulkan_LoadLibrary(nullptr) != 0) + { + SDL_Quit(); + std::string error{"SDL could not initialize Vulkan! SDL_Error → "}; + error += SDL_GetError(); + throw CommandError{error}; + } } static void -unload_sdl(void *obj, LoaderStack *ls) +unload_sdl(void *obj) { + SDL_Vulkan_UnloadLibrary(); SDL_Quit(); } -static SDL_bool -load_sdl_image(void *obj, LoaderStack *ls) +static void +load_sdl_image(void *obj) { int flags = IMG_INIT_JPG|IMG_INIT_PNG|IMG_INIT_TIF; if(!(IMG_Init(flags) & flags)) { - const char* base_error = "Could not initialize SDL image → "; - LoaderStack_set_error(ls, base_error, IMG_GetError()); - return SDL_FALSE; + std::string error{"Could not initialize SDL image → "}; + error += IMG_GetError(); + throw CommandError{error}; } - - return SDL_TRUE; } static void -unload_sdl_image(void *obj, LoaderStack *ls) +unload_sdl_image(void *obj) { IMG_Quit(); } -static SDL_bool -load_sdl_mixer(void *obj, LoaderStack *ls) +static void +load_sdl_mixer(void *obj) { int flags = MIX_INIT_OGG|MIX_INIT_MOD; int initted = Mix_Init(flags); if(initted&flags != flags) { - const char* base_error = "Could not initialize SDL mixer → "; - LoaderStack_set_error(ls, base_error, Mix_GetError()); - return SDL_FALSE; + std::string error{"Could not initialize SDL mixer → "}; + error += Mix_GetError(); + throw CommandError{error}; } - - return SDL_TRUE; } static void -unload_sdl_mixer(void *obj, LoaderStack *ls) +unload_sdl_mixer(void *obj) { while(Mix_Init(0)) Mix_Quit(); } -static SDL_bool -load_sdl_open_audio(void *obj, LoaderStack *ls) +static void +load_sdl_open_audio(void *obj) { if(Mix_OpenAudio(22050, MIX_DEFAULT_FORMAT, 2, 1024) == -1) { - const char* base_error = "Could not open SDL mixer audio → "; - LoaderStack_set_error(ls, base_error, Mix_GetError()); - return SDL_FALSE; + std::string error{"Could not open SDL mixer audio → "}; + error += Mix_GetError(); + throw CommandError{error}; } - - return SDL_TRUE; } static void -unload_sdl_open_audio(void *obj, LoaderStack *ls) +unload_sdl_open_audio(void *obj) { Mix_CloseAudio(); } -static SDL_bool -load_sdl_ttf(void *obj, LoaderStack *ls) +static void +load_window(void *obj) { - if(TTF_Init()==-1) + cg_core.window = NULL; + cg_core.window = SDL_CreateWindow( + cg_core.game_name, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, + cg_core.screen_width, cg_core.screen_height, SDL_WINDOW_VULKAN); + if(cg_core.window == NULL) { - const char* base_error = "Could not initialize SDL ttf → "; - LoaderStack_set_error(ls, base_error, TTF_GetError()); - return SDL_FALSE; + std::string error{"Window could not be created! SDL_Error → "}; + error += SDL_GetError(); + throw CommandError{error}; } - - return SDL_TRUE; } static void -unload_sdl_ttf(void *obj, LoaderStack *ls) +unload_window(void *obj) { - TTF_Quit(); + SDL_DestroyWindow(cg_core.window); } -static SDL_bool -load_window(void *obj, LoaderStack *ls) +static void +load_vk_instance(void *obj) { - cg_core.window = NULL; - cg_core.window = SDL_CreateWindow( - cg_core.game_name, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, - cg_core.screen_width, cg_core.screen_height, SDL_WINDOW_SHOWN); - if(cg_core.window == NULL) + std::vector<const char*> vk_extensions; + std::vector<const char*> vk_required_layers_names; + + // Get extensions. { - const char* base_error = "Window could not be created! SDL_Error → "; - LoaderStack_set_error(ls, base_error, SDL_GetError()); - return SDL_FALSE; + uint32_t vk_extensions_count; + std::vector<const char*> vk_required_extensions; + + // Load debuging layers. +#ifdef DEBUG + vk_required_extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + vk_required_layers_names.push_back("VK_LAYER_KHRONOS_validation"); +#endif + + // Get extensions for SDL. + { + uint32_t vk_sdl_extension_count; + std::vector<const char*> vk_sdl_extensions; + + if(!SDL_Vulkan_GetInstanceExtensions( + cg_core.window, &vk_sdl_extension_count, nullptr)) + { + std::string error{ + "Vulkan extensions could not be loaded by SDL! SDL_Error: "}; + error += SDL_GetError(); + throw CommandError{error}; + } + + vk_sdl_extensions.resize(vk_sdl_extension_count); + SDL_Vulkan_GetInstanceExtensions( + cg_core.window, &vk_sdl_extension_count, vk_sdl_extensions.data()); + + // Combine all extensions. + vk_extensions_count = vk_sdl_extension_count + + vk_required_extensions.size(); + vk_extensions.resize(vk_extensions_count); + for(auto i{0}; i < vk_sdl_extension_count; i++) + vk_extensions[i] = vk_sdl_extensions[i]; + for(auto i{0}; i < vk_required_extensions.size(); i++) + vk_extensions[i + vk_sdl_extension_count] = vk_required_extensions[i]; + } + +#ifdef DEBUG + std::cout << "Enabled VK extensions." << std::endl; + for(auto vk_extension: vk_extensions) + std::cout << "Extension name: " << vk_extension << std::endl; +#endif } - return SDL_TRUE; + // Get available instance layers. + { + uint32_t vk_available_layers_count; + std::vector<VkLayerProperties> vk_available_layers; + std::vector<const char*> vk_available_layers_names; + + vkEnumerateInstanceLayerProperties(&vk_available_layers_count, nullptr); + vk_available_layers.resize(vk_available_layers_count); + vkEnumerateInstanceLayerProperties(&vk_available_layers_count, + vk_available_layers.data()); + vk_available_layers_names.resize(vk_available_layers_count); +#ifdef DEBUG + std::cout << "Available VK instance layers." << std::endl; +#endif + for(uint32_t i = 0; i < vk_available_layers_count; i++) + { +#ifdef DEBUG + std::cout << "\nname: " << vk_available_layers[i].layerName << + std::endl; + std::cout << "Description: " << vk_available_layers[i].description << + std::endl; + std::cout << "Spec version: " << vk_available_layers[i].specVersion << + std::endl; + std::cout << "Implementation version: " << + vk_available_layers[i].implementationVersion << std::endl; +#endif + + vk_available_layers_names[i] = vk_available_layers[i].layerName; + } + + // If required layers are not all available. + if(!std::includes( + vk_available_layers_names.begin(), vk_available_layers_names.end(), + vk_required_layers_names.begin(), vk_required_layers_names.begin())) + throw CommandError{"Some required Vulkan layers are not available."}; + } + + { + VkApplicationInfo app_info; + app_info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; + app_info.pNext = nullptr; + app_info.pApplicationName = cg_core.game_name; + app_info.applicationVersion = VK_MAKE_VERSION( + cg_core.game_version_major, + cg_core.game_version_minor, + cg_core.game_version_patch); + app_info.pEngineName = "CandyGear"; + app_info.engineVersion = VK_MAKE_VERSION( + CANDY_GEAR_VERSION_MAJOR, + CANDY_GEAR_VERSION_MINOR, + CANDY_GEAR_VERSION_PATCH); + app_info.apiVersion = VK_API_VERSION_1_0; + + VkInstanceCreateInfo create_info; + create_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + create_info.pNext = nullptr; + create_info.flags = 0; + create_info.pApplicationInfo = &app_info; + create_info.enabledExtensionCount = vk_extensions.size(); + create_info.ppEnabledExtensionNames = vk_extensions.data(); + create_info.enabledLayerCount = vk_required_layers_names.size(); + create_info.ppEnabledLayerNames = vk_required_layers_names.data(); + + VkResult result = + vkCreateInstance(&create_info, nullptr, &cg_core.vk_instance); + if(result != VK_SUCCESS) + { + std::string error{""}; + switch(result) + { + case VK_ERROR_LAYER_NOT_PRESENT: + error = " Layer not present."; + break; + case VK_ERROR_EXTENSION_NOT_PRESENT: + error = " Extension not present."; + } + error = "Failed to create Vulkan instance." + error; + throw CommandError{error}; + } + } } static void -unload_window(void *obj, LoaderStack *ls) +unload_vk_instance(void *obj) { - SDL_DestroyWindow(cg_core.window); + vkDestroyInstance(cg_core.vk_instance, nullptr); } -static SDL_bool -load_sdl_renderer(void *obj, LoaderStack *ls) +static void +load_window_surface(void *obj) { - cg_core.renderer = NULL; - cg_core.renderer = SDL_CreateRenderer( - cg_core.window, -1, SDL_RENDERER_ACCELERATED | - SDL_RENDERER_TARGETTEXTURE); - if(cg_core.renderer == NULL) + if(!SDL_Vulkan_CreateSurface( + cg_core.window, cg_core.vk_instance, &cg_core.window_surface)) { - const char* base_error = "Could not create SDL renderer → "; - LoaderStack_set_error(ls, base_error, SDL_GetError()); - return SDL_FALSE; + std::string error{"Failed to create a window surface → "}; + error += SDL_GetError(); + throw CommandError{error}; } +} - return SDL_TRUE; +static void +unload_window_surface(void *obj) +{ + vkDestroySurfaceKHR(cg_core.vk_instance, cg_core.window_surface, nullptr); } +#ifdef DEBUG static void -unload_sdl_renderer(void *obj, LoaderStack *ls) +load_vk_debug_callback(void *obj) { - SDL_DestroyRenderer(cg_core.renderer); + PFN_vkCreateDebugUtilsMessengerEXT debug_messenger; + + // A Vulkan instance extension named VK_EXT_debug_utils and a Vulkan instance + // layer named VK_LAYER_LUNARG_standard_validation are required to enable + // this callback. These instance extension and instance layer are loaded at + // Instance::load_vk_instance. + VkDebugUtilsMessengerCreateInfoEXT create_info; + create_info.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; + create_info.pNext = nullptr; + create_info.messageSeverity = + VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | + VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT | + VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | + VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + create_info.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | + VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | + VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; + create_info.pfnUserCallback = vk_debug_callback; + create_info.pUserData = nullptr; + create_info.flags = 0; + + debug_messenger = (PFN_vkCreateDebugUtilsMessengerEXT) + vkGetInstanceProcAddr(cg_core.vk_instance, + "vkCreateDebugUtilsMessengerEXT"); + + if(debug_messenger(cg_core.vk_instance, &create_info, nullptr, + &cg_core.vk_callback) != VK_SUCCESS) + CommandError{"Failed to setup debug callback for Vulkan."}; +} + +static void +unload_vk_debug_callback(void *obj) +{ + PFN_vkDestroyDebugUtilsMessengerEXT debug_messenger; + + debug_messenger = (PFN_vkDestroyDebugUtilsMessengerEXT) + vkGetInstanceProcAddr(cg_core.vk_instance, + "vkDestroyDebugUtilsMessengerEXT"); + + debug_messenger(cg_core.vk_instance, cg_core.vk_callback, nullptr); } +#endif -static SDL_bool -load_pre_screen(void *obj, LoaderStack *ls) +static void +load_vk_devices(void *obj) { - cg_core.pre_screen_buffer = SDL_CreateTexture( - cg_core.renderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, - cg_core.game_width, cg_core.game_height); + uint32_t devices_count; + std::vector<VkPhysicalDevice> vk_physical_devices; - if(cg_core.pre_screen_buffer == NULL) + // Enumerate physical devices { - const char* base_error = "Could not create renderering buffer → "; - LoaderStack_set_error(ls, base_error, SDL_GetError()); - return SDL_FALSE; + vkEnumeratePhysicalDevices(cg_core.vk_instance, &devices_count, nullptr); + if(devices_count < 1) + CommandError{"Failed to find GPUs with Vulkan support."}; + vk_physical_devices.resize(devices_count); + vkEnumeratePhysicalDevices( + cg_core.vk_instance, &devices_count, vk_physical_devices.data()); } - cg_Engine_calculate_full_scale(); - - SDL_SetRenderTarget(cg_core.renderer, cg_core.pre_screen_buffer); +#ifdef DEBUG + std::cout << "Physical devices properties" << std::endl; +#endif - return SDL_TRUE; + cg_core.vk_devices.reserve(devices_count); + for(auto i = 0; i < devices_count; i++) + { + // Use swapchain on first device. + if(i == 0) + { + cg_core.vk_devices.emplace_back(vk_physical_devices[i], true); + cg_core.vk_device_with_swapchain = &cg_core.vk_devices[i]; + } + else + cg_core.vk_devices.emplace_back(vk_physical_devices[i], false); + } } static void -unload_pre_screen(void *obj, LoaderStack *ls) +unload_vk_devices(void *obj) { - SDL_DestroyTexture(cg_core.pre_screen_buffer); + cg_core.vk_devices.clear(); } -void -cg_Core_init(LoaderStack *loader) +static void +load_vk_swapchain(void *obj) { - LoaderStack_constructor(loader, NULL); - - LoaderStack_add(loader, &load_variables, &unload_variables); - LoaderStack_add(loader, &load_sdl, &unload_sdl); - LoaderStack_add(loader, &load_sdl_image, &unload_sdl_image); - LoaderStack_add(loader, &load_sdl_mixer, &unload_sdl_mixer); - LoaderStack_add(loader, &load_sdl_open_audio, &unload_sdl_open_audio); - LoaderStack_add(loader, &load_sdl_ttf, &unload_sdl_ttf); - LoaderStack_add(loader, &load_window, &unload_window); - LoaderStack_add(loader, &load_sdl_renderer, &unload_sdl_renderer); - LoaderStack_add(loader, &load_pre_screen, &unload_pre_screen); + try { cg_core.vk_swapchain = new VK::Swapchain(); } + catch(const CommandError &error) + { + std::string error_message{"Failed to create swapchain → "}; + error_message += error.what(); + throw CommandError{error_message}; + } } -SDL_bool -cg_Core_load(LoaderStack *loader) +static void +unload_vk_swapchain(void *obj) { - if(!LoaderStack_load(loader)) return SDL_FALSE; + delete cg_core.vk_swapchain; +} - return SDL_TRUE; +static void +load_vk_graphics_pipeline(void *obj) +{ + try + { + cg_core.vk_graphics_pipeline = new VK::GraphicsPipeline(); + } + catch(const CommandError &e) + { + throw CommandError{"Failed to create graphics pipeline."}; + } } -void -cg_Core_unload(LoaderStack *loader) +static void +unload_vk_graphics_pipeline(void *obj) { - LoaderStack_destructor(loader); + delete cg_core.vk_graphics_pipeline; } + +const CommandChain cg_sCore::loader{ + {&load_variables, &unload_variables}, + {&load_sdl, &unload_sdl}, + {&load_sdl_image, &unload_sdl_image}, + {&load_sdl_mixer, &unload_sdl_mixer}, + {&load_sdl_open_audio, &unload_sdl_open_audio}, + {&load_window, &unload_window}, + {&load_vk_instance, &unload_vk_instance}, + {&load_window_surface, &unload_window_surface}, +#ifdef DEBUG + {&load_vk_debug_callback, &unload_vk_debug_callback}, +#endif + {&load_vk_devices, &unload_vk_devices}, + {&load_vk_swapchain, &unload_vk_swapchain}, + {&load_vk_graphics_pipeline, &unload_vk_graphics_pipeline} +}; diff --git a/src/core.h b/src/core.hpp index e5d651a..117a81f 100644 --- a/src/core.h +++ b/src/core.hpp @@ -17,6 +17,14 @@ #ifndef CANDY_GEAR_CORE_H #define CANDY_GEAR_CORE_H 1 +#define CANDY_GEAR_VERSION_MAJOR 0 +#define CANDY_GEAR_VERSION_MINOR 1 +#define CANDY_GEAR_VERSION_PATCH 0 + +#define DATA_DIR "/usr/local/share/candy_gear" + +#include <random> + #include <mruby.h> #include <mruby/class.h> #include <mruby/compile.h> @@ -25,22 +33,27 @@ #include <mruby/variable.h> #include <SDL2/SDL.h> +#include <SDL2/SDL_vulkan.h> #include <SDL2/SDL_image.h> #include <SDL2/SDL_mixer.h> -#include <SDL2/SDL_ttf.h> -#include "loader.h" +#include "command.hpp" -#ifdef __cplusplus -extern "C" { -#endif +#include "vk/device.hpp" +#include "vk/graphics_pipeline.hpp" +#include "vk/swapchain.hpp" + +extern std::random_device random_seed; +extern std::mt19937 random_number_generator; /** * The Core class stores all global states that the engine needs to work. * Global variables are not evil if you use them carefully. */ -typedef struct +struct cg_sCore { + static const CommandChain loader; + const char *config_file; /// Text displayed in the game window. @@ -51,7 +64,7 @@ typedef struct * This is the amount of pixel the games use to render a buffer. The image in * this buffer is then rendered to the screen. */ - int game_width, game_height; + uint32_t game_width, game_height; /// @} /** @@ -59,39 +72,32 @@ typedef struct * This is the ammount of pixel that the games uses when rendering to the * screen. */ - int screen_width, screen_height; + uint32_t screen_width, screen_height; /// @} + int game_version_major, game_version_minor, game_version_patch; + Uint32 fps; Uint32 max_frame_duration_ms; SDL_Window *window; - SDL_Renderer *renderer; - /// All rendering goes here before they are moved to the screen. - SDL_Texture *pre_screen_buffer; - SDL_Rect screen_rect; + VkSurfaceKHR window_surface; + VkInstance vk_instance; - SDL_bool quit_game; -} cg_sCore; - -extern cg_sCore cg_core; - -/// \memberof cg_sCore @{ - -void -cg_Core_init(LoaderStack *loader); - -SDL_bool -cg_Core_load(LoaderStack *loader); - -void -cg_Core_unload(LoaderStack *loader); +#ifdef DEBUG + VkDebugUtilsMessengerEXT vk_callback; +#endif -/// @} + // Vulkan devices. + std::vector<VK::Device> vk_devices; + VK::Device *vk_device_with_swapchain; + VK::Swapchain *vk_swapchain; + VK::GraphicsPipeline *vk_graphics_pipeline; -#ifdef __cplusplus + bool quit_game; }; -#endif + +extern cg_sCore cg_core; #endif /* CANDY_GEAR_CORE_H */ diff --git a/src/font.c b/src/font.c deleted file mode 100644 index a333a30..0000000 --- a/src/font.c +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright 2022 Frederico de Oliveira Linhares - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "font.h" -#include <stdio.h> - -void -cg_free_font(mrb_state *mrb, void* obj) -{ - struct cg_font *ptr = obj; - - TTF_CloseFont(ptr->data); - mrb_free(mrb, ptr); -} - -const struct mrb_data_type cg_font_type = {"CG_Font", cg_free_font }; - -static mrb_value -cg_cFont_initialize(mrb_state *mrb, mrb_value self) -{ - mrb_int font_size; - char *file_path; - struct cg_font *ptr; - - mrb_get_args(mrb, "zi", &file_path, &font_size); - ptr = (struct cg_font *)DATA_PTR(self); - if(ptr) mrb_free(mrb, ptr); - ptr = (struct cg_font *)mrb_malloc(mrb, sizeof(struct cg_font)); - - ptr->data = TTF_OpenFont(file_path, font_size); - if(!ptr->data) mrb_raise(mrb, E_ARGUMENT_ERROR, TTF_GetError()); - - mrb_data_init(self, ptr, &cg_font_type); - return self; -} - -void -cg_font_init(mrb_state *mrb) -{ - struct RClass *cg_m, *cg_cFont; - - cg_m = mrb_module_get(mrb, "CandyGear"); - cg_cFont = mrb_define_class_under(mrb, cg_m, "Font", mrb->object_class); - MRB_SET_INSTANCE_TT(cg_cFont, MRB_TT_DATA); - mrb_define_method( - mrb, cg_cFont, "initialize", cg_cFont_initialize, MRB_ARGS_REQ(2)); -} diff --git a/src/graphic.c b/src/graphic.c deleted file mode 100644 index b58c08c..0000000 --- a/src/graphic.c +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright 2022 Frederico de Oliveira Linhares - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "graphic.h" - -#include "color.h" - -static mrb_value -cg_mGraphic_set_color(mrb_state *mrb, mrb_value self) -{ - struct cg_color *color; - mrb_get_args(mrb, "d", &color, &cg_color_type); - - SDL_SetRenderDrawColor( - cg_core.renderer, color->data.r, color->data.g, color->data.b, - color->data.a); - - return self; -} - -static mrb_value -cg_mGraphic_draw_point(mrb_state *mrb, mrb_value self) -{ - mrb_int x, y; - mrb_get_args(mrb, "ii", &x, &y); - SDL_RenderDrawPoint(cg_core.renderer, x, y); - - return self; -} - -static mrb_value -cg_mGraphic_draw_line(mrb_state *mrb, mrb_value self) -{ - mrb_int x1, y1, x2, y2; - mrb_get_args(mrb, "iiii", &x1, &y1, &x2, &y2); - SDL_RenderDrawLine(cg_core.renderer, x1, y1, x2, y2); - - return self; -} - -void -cg_graphic_init(mrb_state *mrb) -{ - struct RClass *cg_m, *cg_mGraphic; - - cg_m = mrb_module_get(mrb, "CandyGear"); - cg_mGraphic = mrb_define_module_under(mrb, cg_m, "Graphic"); - mrb_define_class_method( - mrb, cg_mGraphic, "set_color", cg_mGraphic_set_color, MRB_ARGS_REQ(1)); - mrb_define_class_method( - mrb, cg_mGraphic, "draw_point", cg_mGraphic_draw_point, MRB_ARGS_REQ(2)); - mrb_define_class_method( - mrb, cg_mGraphic, "draw_line", cg_mGraphic_draw_line, MRB_ARGS_REQ(4)); -} @@ -14,7 +14,7 @@ * limitations under the License. */ -#include "key.h" +#include "key.hpp" void cg_key_init(mrb_state *mrb) @@ -17,17 +17,9 @@ #ifndef CANDY_GEAR_KEY_H #define CANDY_GEAR_KEY_H 1 -#include "core.h" - -#ifdef __cplusplus -extern "C" { -#endif +#include "core.hpp" void cg_key_init(mrb_state *mrb); -#ifdef __cplusplus -}; -#endif - #endif /* CANDY_GEAR_KEY_H */ diff --git a/src/loader.c b/src/loader.c deleted file mode 100644 index c24d47e..0000000 --- a/src/loader.c +++ /dev/null @@ -1,162 +0,0 @@ -/* - * Copyright 2022 Frederico de Oliveira Linhares - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include <stdlib.h> - -#include "loader.h" - -void -LoaderStack_constructor(LoaderStack *self, void *obj) -{ - self->_last_loaded = 0; - - self->_loader_capacity = 10; // Totally arbitrary value. - self->_loader_size = 0; - - self->_obj = obj; - - self->error_message = NULL; - - self->_loaders = malloc(sizeof(Loader) * self->_loader_capacity); -} - -void -LoaderStack_destructor(LoaderStack *self) -{ - if(self->error_message != NULL) - free(self->error_message); - - if(LoaderStack_is_loaded(self)) LoaderStack_unload(self); - free(self->_loaders); -} - -static void -LoaderStack_partial_unload(LoaderStack *self, int32_t step) -{ - // Already unloaded, nothing to do. - if(self->_last_loaded <= step) return; - - for(; self->_last_loaded >= step; self->_last_loaded--) - if(self->_loaders[self->_last_loaded].unload != NULL) - self->_loaders[self->_last_loaded].unload(self->_obj, self); - - // This number will be one before the last loaded after the unloading loop - // is over. - self->_last_loaded++; -} - -SDL_bool -LoaderStack_partial_load(LoaderStack *self, int32_t step) -{ - SDL_bool error = SDL_FALSE; - - // Already loaded, nothing to do. - if(self->_last_loaded >= step) return SDL_TRUE; - - for(;self->_last_loaded < step; self->_last_loaded++) - { - error = !self->_loaders[self->_last_loaded].load(self->_obj, self); - if(error) break; - } - - // Self number will be one after the last loeaded after the loading loop is - // over. - self->_last_loaded--; - - if(error) LoaderStack_partial_unload(self, 0); - - return !error; -} - -SDL_bool -LoaderStack_is_loaded(LoaderStack *self) -{ - return self->_last_loaded > 0; -} - -void -LoaderStack_add( - LoaderStack *self, - SDL_bool (*load)(void *obj, LoaderStack *ls), - void (*unload)(void *obj, LoaderStack *ls)) -{ - Loader *l; - uint32_t next_loader = self->_loader_size; - - // Expand if is full. - if(self->_loader_size == self->_loader_capacity) - { - self->_loader_capacity += 5; // Totally arbitrary value. - self->_loaders = realloc(self->_loaders, - sizeof(Loader) * self->_loader_capacity); - } - - l = &(self->_loaders[next_loader]); - l->load = load; - l->unload = unload; - - self->_loader_size++; -} - -void -LoaderStack_set_error( - LoaderStack *self, const char* base_error, const char* additional_error) -{ - if(self->error_message != NULL) free(self->error_message); - - if(additional_error != NULL) - { - self->error_message = - malloc(strlen(base_error) + strlen(additional_error) + 1); - - strcpy(self->error_message, base_error); - strcat(self->error_message, additional_error); - } - else - { - self->error_message = malloc(strlen(base_error)); - - strcpy(self->error_message, base_error); - } -} - -SDL_bool -LoaderStack_load(LoaderStack *self) -{ - if(LoaderStack_is_loaded(self)) return SDL_TRUE; - - if(LoaderStack_partial_load(self, self->_loader_size)) - return SDL_TRUE; - else - return SDL_FALSE; -} - -SDL_bool -LoaderStack_reload(LoaderStack *self) -{ - if(!LoaderStack_is_loaded(self)) return SDL_FALSE; - - LoaderStack_partial_unload(self, self->_loader_size); - return LoaderStack_partial_load(self, self->_loader_size); -} - -void -LoaderStack_unload(LoaderStack *self) -{ - if(!LoaderStack_is_loaded(self)) return; - - LoaderStack_partial_unload(self, 0); -} diff --git a/src/loader.h b/src/loader.h deleted file mode 100644 index 93ec42b..0000000 --- a/src/loader.h +++ /dev/null @@ -1,159 +0,0 @@ -/* - * Copyright 2022 Frederico de Oliveira Linhares - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef CANDY_GEAR_LOADER_STACK_H -#define CANDY_GEAR_LOADER_STACK_H 1 - -#include <SDL2/SDL.h> - -#ifdef __cplusplus -extern "C" { -#endif - -struct LoaderStack_s; - -/** - * Stores a reversible action. - */ -typedef struct -{ - SDL_bool (*load)(void *obj, struct LoaderStack_s *ls); - void (*unload)(void *obj, struct LoaderStack_s *ls); -} Loader; - -/** - * Stores a sequence of functions that must be executed and rolled back in - * order. - * - * For example, if the variable _loaders contain A→B→C→D→E, it will load A, - * then B, then C, etc. If D fails, it unloads C, then B, then A. - */ -struct LoaderStack_s -{ - void *_obj; - - int32_t _last_loaded; - - uint32_t _loader_capacity; - uint32_t _loader_size; - Loader *_loaders; - - char* error_message; -}; - -typedef struct LoaderStack_s LoaderStack; - -/// \memberof LoaderStack_s @{ - -/** - * Must be called to initialize the variables. - * - * @param[self] a pointer to the LoaderStack_s to be initalized. - * @param[obj] a pointer to an struct that will be send to every function - * inside the _loaders. - */ -void -LoaderStack_constructor(LoaderStack *self, void *obj); - -/** - * Must be called to finalize the variables and cleanup memory. - * - * @param[self] a pointer to the LoaderStack_s to be finalized. - */ -void -LoaderStack_destructor(LoaderStack *self); - -/** - * Adds a reversible action to the loader. The first action added is the first - * to de executed the last rolled back, and so on. Those functions are stored - * inside _loaders. - * - * @param[self] a pointer to the LoaderStack_s hat stores the action. - * @param[load] the do action. The function pointed by this variable must - * return SDL_TRUE on success and SDL_FALSE on fail. - * @param[unload] the undo/rollback action, if the action do not need a - * rollback, this pointer can be set to null. - */ -void -LoaderStack_add( - LoaderStack *self, - SDL_bool (*load)(void *obj, LoaderStack *ls), - void (*unload)(void *obj, LoaderStack *ls)); - -/** - * Can be used to set error before returning SDL_FALSE from a function used by - * _loaders - * - * @param[self] a pointer to the LoaderStack_s where the error happened. - * @param[base_error] the error message. - * @param[additional_error] if you need to concatenate two error messages, use - * this variable to a second message. Otherwise set to NULL. - */ -void -LoaderStack_set_error( - LoaderStack *self, const char* base_error, const char* additional_error); - -/** - * @return SDL_TRUE if you successfuly executed LoaderStack_load or - * LoaderStack_reload in the LoaderStack. SDL_FALSE after - * LoaderStack_constructor or LoaderStack_unload is executed. - */ -SDL_bool -LoaderStack_is_loaded(LoaderStack *self); - -/** - * Execute some of the load functions in the _loaders. If one of them fails, - * roll back everything inside _loaders. The LoaderStack knows which functions - * it already executed and will not rerun them unless they are rolled back - * first. - * - * @param[step] the number of functions to be executed. - * @return SDL_TRUE on success and SDL_FALSE on fail. - */ -SDL_bool -LoaderStack_partial_load(LoaderStack *self, int32_t step); - -/** - * Execute all of the load functions in the _loaders. If one of them fails, - * roll back everything inside _loaders. - * @return SDL_TRUE on success and SDL_FALSE on fail. - */ -SDL_bool -LoaderStack_load(LoaderStack *self); - -/** - * Roll back all loaded function inside loaders, if there are any. Then execute - * all of the load functions in the _loaders. If one of them fails, roll back - * everything inside _loaders. - * - * @return SDL_TRUE on success and SDL_FALSE on fail. - */ -SDL_bool -LoaderStack_reload(LoaderStack *self); - -/** - * Roll back all loaded function inside loaders, if there are any. - */ -void -LoaderStack_unload(LoaderStack *self); - -/// @} - -#ifdef __cplusplus -}; -#endif - -#endif /* CANDY_GEAR_LOADER_STACK_H */ @@ -14,7 +14,7 @@ * limitations under the License. */ -#include "log.h" +#include "log.hpp" #include <stdio.h> @@ -17,17 +17,9 @@ #ifndef CANDY_GEAR_LOG_H #define CANDY_GEAR_LOG_H 1 -#include "core.h" - -#ifdef __cplusplus -extern "C" { -#endif +#include "core.hpp" void cg_log_init(mrb_state *mrb); -#ifdef __cplusplus -}; -#endif - #endif /* CANDY_GEAR_LOG_H */ diff --git a/src/main.cpp b/src/main.cpp index 900bb11..3876db0 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -14,26 +14,26 @@ * limitations under the License. */ -#include "candy_gear.h" -#include "color.h" -#include "core.h" -#include "font.h" -#include "graphic.h" -#include "key.h" -#include "log.h" -#include "palette.h" -#include "point.h" -#include "rect.h" -#include "sound.h" -#include "sprite.h" -#include "texture.h" +#ifdef DEBUG +#include <iostream> +#endif + +#include "camera.hpp" +#include "candy_gear.hpp" +#include "core.hpp" +#include "key.hpp" +#include "log.hpp" +#include "model.hpp" +#include "model/instance.hpp" +#include "sound.hpp" +#include "texture.hpp" cg_sCore cg_core; static void handle_error(mrb_state *mrb) { mrb_print_error(mrb); - cg_core.quit_game = SDL_TRUE; + cg_core.quit_game = true; } int main(int argc, char *argv[]) @@ -41,31 +41,34 @@ int main(int argc, char *argv[]) SDL_Event event; Uint32 frame_start; + // Random numbers + random_number_generator.seed(random_seed()); + mrb_state *mrb = mrb_open(); mrb_value main_obj; mrb_sym sym_init, sym_key_down, sym_key_up, sym_quit, sym_tick; FILE *fp; - LoaderStack core_loader; - cg_core.config_file = argv[1]; - cg_Core_init(&core_loader); - if(!cg_Core_load(&core_loader)) return 1; + try{ cg_sCore::loader.execute(nullptr); } + catch(const CommandError &error) + { +#ifdef DEBUG + std::cout << error.what() << std::endl; +#endif + return 1; + } if (!mrb) { /* handle error */ } mrb_define_module(mrb, "CandyGear"); + cg_camera_init(mrb); cg_candy_gear_init(mrb); - cg_color_init(mrb); - cg_font_init(mrb); - cg_graphic_init(mrb); cg_key_init(mrb); cg_log_init(mrb); - cg_palette_init(mrb); - cg_point_init(mrb); - cg_rect_init(mrb); + cg_model_init(mrb); + cg_model_instance_init(mrb); cg_sound_init(mrb); - cg_sprite_init(mrb); cg_texture_init(mrb); main_obj = mrb_obj_iv_inspect(mrb, mrb->top_self); @@ -118,19 +121,10 @@ int main(int argc, char *argv[]) } } - // Clear buffer. - SDL_SetRenderDrawColor(cg_core.renderer, 0x00, 0x00, 0x00, 0xff); - SDL_RenderClear(cg_core.renderer); - mrb_funcall_id(mrb, main_obj, sym_tick, 0); if (mrb->exc) handle_error(mrb); - // Copy buffer to the screen and display it. - SDL_SetRenderTarget(cg_core.renderer, NULL); - SDL_RenderCopy( - cg_core.renderer, cg_core.pre_screen_buffer, NULL, &cg_core.screen_rect); - SDL_RenderPresent(cg_core.renderer); - SDL_SetRenderTarget(cg_core.renderer, cg_core.pre_screen_buffer); + cg_core.vk_graphics_pipeline->draw(); // Timer { @@ -148,7 +142,7 @@ int main(int argc, char *argv[]) } mrb_close(mrb); - cg_Core_unload(&core_loader); + cg_sCore::loader.revert(nullptr); return 0; } diff --git a/src/model.cpp b/src/model.cpp new file mode 100644 index 0000000..1a97c9b --- /dev/null +++ b/src/model.cpp @@ -0,0 +1,67 @@ +/* + * Copyright 2022 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "model.hpp" + +#include "texture.hpp" + +void +cg_free_model(mrb_state *mrb, void* obj) +{ + auto ptr = static_cast<std::shared_ptr<VK::Model>*>(obj); + + ptr->~shared_ptr(); + mrb_free(mrb, ptr); +} + +const struct mrb_data_type cg_model_type = { "CG_Model", cg_free_model }; + +static mrb_value +cg_cModel_initialize(mrb_state *mrb, mrb_value self) +{ + const char *file_path; + + mrb_sym id_at_texture; + mrb_value texture_v; + + std::shared_ptr<VK::Texture> *texture; + std::shared_ptr<VK::Model> *ptr; + + mrb_get_args(mrb, "zd", &file_path, &texture, &cg_texture_type); + ptr = (std::shared_ptr<VK::Model>*)DATA_PTR(self); + if(ptr) mrb_free(mrb, ptr); + ptr = (std::shared_ptr<VK::Model>*)mrb_malloc( + mrb, sizeof(std::shared_ptr<VK::Model>)); + + std::string path{file_path}; + new(ptr)std::shared_ptr<VK::Model>( + std::make_shared<VK::Model>(path, *texture)); + + mrb_data_init(self, ptr, &cg_model_type); + return self; +} + +void +cg_model_init(mrb_state *mrb) +{ + struct RClass *cg_m, *cg_cModel; + + cg_m = mrb_module_get(mrb, "CandyGear"); + cg_cModel = mrb_define_class_under(mrb, cg_m, "Model", mrb->object_class); + MRB_SET_INSTANCE_TT(cg_cModel, MRB_TT_DATA); + mrb_define_method( + mrb, cg_cModel, "initialize", cg_cModel_initialize, MRB_ARGS_REQ(2)); +} diff --git a/src/graphic.h b/src/model.hpp index 3fe9b1e..414fa30 100644 --- a/src/graphic.h +++ b/src/model.hpp @@ -14,20 +14,15 @@ * limitations under the License. */ -#ifndef CANDY_GEAR_GRAPHIC_H -#define CANDY_GEAR_GRAPHIC_H 1 +#ifndef CANDY_GEAR_MODEL_H +#define CANDY_GEAR_MODEL_H 1 -#include "core.h" +#include <memory> -#ifdef __cplusplus -extern "C" { -#endif +#include "core.hpp" +#include "vk/model.hpp" void -cg_graphic_init(mrb_state *mrb); +cg_model_init(mrb_state *mrb); -#ifdef __cplusplus -}; -#endif - -#endif /* CANDY_GEAR_GRAPHIC_H */ +#endif /* CANDY_GEAR_MODEL_H */ diff --git a/src/model/instance.cpp b/src/model/instance.cpp new file mode 100644 index 0000000..83a93c2 --- /dev/null +++ b/src/model/instance.cpp @@ -0,0 +1,98 @@ +/* + * Copyright 2022 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "instance.hpp" + +#include "../model.hpp" +#include "../vk/model_instance.hpp" + +// This variable works as a constant +static mrb_sym id_at_model; + +void +cg_free_model_instance(mrb_state *mrb, void* obj) +{ + auto ptr = static_cast<VK::ModelInstance*>(obj); + + mrb_free(mrb, ptr); +} + +const struct mrb_data_type cg_model_instance_type = { + "CG_Model_Instance", cg_free_model_instance }; + +static mrb_value +cg_cModel_cInstance_initialize(mrb_state *mrb, mrb_value self) +{ + mrb_float position_x, position_y, position_z, + rotation_x, rotation_y, rotation_z; + mrb_value model; + VK::ModelInstance *ptr; + + mrb_get_args( + mrb, "offffff", + &model, + &position_x, &position_y, &position_z, + &rotation_x, &rotation_y, &rotation_z); + ptr = (VK::ModelInstance*)DATA_PTR(self); + if(ptr) mrb_free(mrb, ptr); + ptr = (VK::ModelInstance*)mrb_malloc(mrb, sizeof(VK::ModelInstance)); + + mrb_iv_set(mrb, self, id_at_model, model); + ptr->position = glm::vec3(position_x, position_y, position_z); + ptr->rotation = glm::vec3(rotation_x, rotation_y, rotation_z); + + mrb_data_init(self, ptr, &cg_model_instance_type); + return self; +} + +static mrb_value +cg_cModel_cInstance_draw(mrb_state *mrb, mrb_value self) +{ + mrb_value model_v; + + VK::ModelInstance *ptr; + std::shared_ptr<VK::Model> *model_ptr; + + model_v = mrb_iv_get(mrb, self, id_at_model); + ptr = (VK::ModelInstance*)DATA_PTR(self); + model_ptr = (std::shared_ptr<VK::Model>*) DATA_PTR(model_v); + + auto &instances = + cg_core.vk_graphics_pipeline->models_to_draw[ + cg_core.vk_graphics_pipeline->current_frame][*model_ptr]; + instances.push_back(*ptr); + + return self; +} + +void +cg_model_instance_init(mrb_state *mrb) +{ + struct RClass *cg_m, *cg_cModel, *cg_cInstance; + + id_at_model = mrb_intern_cstr(mrb, "@model"); + + cg_m = mrb_module_get(mrb, "CandyGear"); + cg_cModel = mrb_class_get_under(mrb, cg_m, "Model"); + cg_cInstance = mrb_define_class_under( + mrb, cg_cModel, "Instance", mrb->object_class); + MRB_SET_INSTANCE_TT(cg_cInstance, MRB_TT_DATA); + mrb_define_method( + mrb, cg_cInstance, "initialize", cg_cModel_cInstance_initialize, + MRB_ARGS_REQ(7)); + mrb_define_method( + mrb, cg_cInstance, "draw", cg_cModel_cInstance_draw, MRB_ARGS_NONE()); +} diff --git a/src/model/instance.hpp b/src/model/instance.hpp new file mode 100644 index 0000000..f84003c --- /dev/null +++ b/src/model/instance.hpp @@ -0,0 +1,25 @@ +/* + * Copyright 2022 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CANDY_GEAR_MODEL_INSTANCE_H +#define CANDY_GEAR_MODEL_INSTANCE_H 1 + +#include "../core.hpp" + +void +cg_model_instance_init(mrb_state *mrb); + +#endif /* CANDY_GEAR_MODEL_INSTANCE_H */ diff --git a/src/palette.c b/src/palette.c deleted file mode 100644 index 9f98dc6..0000000 --- a/src/palette.c +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2022 Frederico de Oliveira Linhares - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "palette.h" -#include "palette_implementation.h" - -void -cg_free_palette(mrb_state *mrb, void* obj) -{ - struct cg_palette *ptr = obj; - - mrb_free(mrb, ptr); -} - -const struct mrb_data_type cg_palette_type = { - "CG_Palette", cg_free_palette }; - -static mrb_value -cg_cPalette_initialize(mrb_state *mrb, mrb_value self) -{ - const char *file_path; - struct cg_palette *ptr; - - mrb_get_args(mrb, "z", &file_path); - ptr = (struct cg_palette *)DATA_PTR(self); - if(ptr) mrb_free(mrb, ptr); - ptr = (struct cg_palette *)mrb_malloc(mrb, sizeof(struct cg_palette)); - - cg_pallet_load_yaml(ptr, file_path); - - mrb_data_init(self, ptr, &cg_palette_type); - return self; -} - -void -cg_palette_init(mrb_state *mrb) -{ - struct RClass *cg_m, *cg_cPalette; - - cg_m = mrb_module_get(mrb, "CandyGear"); - cg_cPalette = mrb_define_class_under(mrb, cg_m, "Palette", mrb->object_class); - MRB_SET_INSTANCE_TT(cg_cPalette, MRB_TT_DATA); - mrb_define_method( - mrb, cg_cPalette, "initialize", cg_cPalette_initialize, MRB_ARGS_REQ(1)); -} diff --git a/src/palette_implementation.cpp b/src/palette_implementation.cpp deleted file mode 100644 index 7f85c6a..0000000 --- a/src/palette_implementation.cpp +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2022 Frederico de Oliveira Linhares - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "palette_implementation.h" - -#include <string> - -#include <yaml-cpp/yaml.h> - -void cg_pallet_load_yaml(struct cg_palette *palette, const char *file_path) -{ - YAML::Node root = YAML::LoadFile(file_path); - for(std::size_t i = 0; i < root.size(); i++) - { - std::string color = root[i].as<std::string>(); - - palette->colors[i].r = std::stoi(color.substr(0, 2), nullptr, 16); - palette->colors[i].g = std::stoi(color.substr(2, 2), nullptr, 16); - palette->colors[i].b = std::stoi(color.substr(4, 2), nullptr, 16); - palette->colors[i].a = std::stoi(color.substr(6, 2), nullptr, 16); - } -} diff --git a/src/palette_implementation.h b/src/palette_implementation.h deleted file mode 100644 index 50c9ef3..0000000 --- a/src/palette_implementation.h +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2022 Frederico de Oliveira Linhares - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef CANDY_GEAR_PALETTE_IMPLEMENTATION_H -#define CANDY_GEAR_PALETTE_IMPLEMENTATION_H 1 - -#include "palette.h" - -#ifdef __cplusplus -extern "C" { -#endif - -void -cg_pallet_load_yaml(struct cg_palette *palette, const char *file_path); - -#ifdef __cplusplus -}; -#endif - -#endif /* CANDY_GEAR_PALETTE_IMPLEMENTATION_H */ diff --git a/src/pgm_image.cpp b/src/pgm_image.cpp index a88455c..04ff9b9 100644 --- a/src/pgm_image.cpp +++ b/src/pgm_image.cpp @@ -14,14 +14,14 @@ * limitations under the License. */ -#include "pgm_image.h" +#include "pgm_image.hpp" #include <cstring> #include <charconv> #include <fstream> #include <string> -SDL_bool PGMImage_constructor(PGMImage *self, const char *file_path) +bool PGMImage_constructor(PGMImage *self, const char *file_path) { self->data = nullptr; int wh_pos; @@ -31,7 +31,7 @@ SDL_bool PGMImage_constructor(PGMImage *self, const char *file_path) if(!file) { self->error = "Failed to open PGM file"; - return SDL_FALSE; + return false; } // Read file magic. @@ -39,7 +39,7 @@ SDL_bool PGMImage_constructor(PGMImage *self, const char *file_path) if(line != "P5") { self->error = "PGM file contains a invalid magic"; - return SDL_FALSE; + return false; } // Read file comment. @@ -61,7 +61,7 @@ SDL_bool PGMImage_constructor(PGMImage *self, const char *file_path) self->data = new char[self->data_size]; file.read(self->data, self->data_size); - return SDL_TRUE; + return true; } void PGMImage_destructor(PGMImage *self) diff --git a/src/pgm_image.h b/src/pgm_image.hpp index 5a271d0..ef9607d 100644 --- a/src/pgm_image.h +++ b/src/pgm_image.hpp @@ -19,10 +19,6 @@ #include <SDL2/SDL.h> -#ifdef __cplusplus -extern "C" { -#endif - struct PGMImage_s { int width, height, max_value, data_size; @@ -32,11 +28,7 @@ struct PGMImage_s typedef struct PGMImage_s PGMImage; -SDL_bool PGMImage_constructor(PGMImage *self, const char *file_path); +bool PGMImage_constructor(PGMImage *self, const char *file_path); void PGMImage_destructor(PGMImage *self); -#ifdef __cplusplus -}; -#endif - #endif /* CANDY_GEAR_PGM_IMAGE_H */ diff --git a/src/point.c b/src/point.c deleted file mode 100644 index 4d11480..0000000 --- a/src/point.c +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright 2022 Frederico de Oliveira Linhares - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "point.h" - -void -cg_free_point(mrb_state *mrb, void* obj) -{ - struct cg_point *ptr = obj; - - mrb_free(mrb, ptr); -} - -const struct mrb_data_type cg_point_type = { - "CG_Point", cg_free_point }; - -static mrb_value -cg_cPoint_initialize(mrb_state *mrb, mrb_value self) -{ - mrb_int x, y; - struct cg_point *ptr; - - mrb_get_args(mrb, "ii", &x, &y); - ptr = (struct cg_point *)DATA_PTR(self); - if(ptr) mrb_free(mrb, ptr); - ptr = (struct cg_point *)mrb_malloc(mrb, sizeof(struct cg_point)); - - ptr->data.x = x; - ptr->data.y = y; - - mrb_data_init(self, ptr, &cg_point_type); - return self; -} - -static mrb_value -cg_cPoint_get_x(mrb_state *mrb, mrb_value self) -{ - struct cg_point *ptr; - - ptr = (struct cg_point *)DATA_PTR(self); - - return mrb_int_value(mrb, ptr->data.x); -} - -static mrb_value -cg_cPoint_set_x(mrb_state *mrb, mrb_value self) -{ - mrb_int x; - struct cg_point *ptr; - - mrb_get_args(mrb, "i", &x); - ptr = (struct cg_point *)DATA_PTR(self); - - ptr->data.x = x; - - return mrb_int_value(mrb, ptr->data.x); -} - -static mrb_value -cg_cPoint_get_y(mrb_state *mrb, mrb_value self) -{ - struct cg_point *ptr; - - ptr = (struct cg_point *)DATA_PTR(self); - - return mrb_int_value(mrb, ptr->data.y); -} - -static mrb_value -cg_cPoint_set_y(mrb_state *mrb, mrb_value self) -{ - mrb_int y; - struct cg_point *ptr; - - mrb_get_args(mrb, "i", &y); - ptr = (struct cg_point *)DATA_PTR(self); - - ptr->data.y = y; - - return mrb_int_value(mrb, ptr->data.y); -} - -static mrb_value -cg_cPoint_draw(mrb_state *mrb, mrb_value self) -{ - struct cg_point *ptr; - ptr = (struct cg_point *)DATA_PTR(self); - SDL_RenderDrawPoint(cg_core.renderer, ptr->data.x, ptr->data.y); - - return self; -} - -void -cg_point_init(mrb_state *mrb) -{ - struct RClass *cg_m, *cg_cPoint; - - cg_m = mrb_module_get(mrb, "CandyGear"); - cg_cPoint = mrb_define_class_under( - mrb, cg_m, "Point", mrb->object_class); - MRB_SET_INSTANCE_TT(cg_cPoint, MRB_TT_DATA); - mrb_define_method( - mrb, cg_cPoint, "initialize", cg_cPoint_initialize, MRB_ARGS_REQ(2)); - mrb_define_method(mrb, cg_cPoint, "x", cg_cPoint_get_x, MRB_ARGS_NONE()); - mrb_define_method(mrb, cg_cPoint, "x=", cg_cPoint_set_x, MRB_ARGS_REQ(1)); - mrb_define_method(mrb, cg_cPoint, "y", cg_cPoint_get_y, MRB_ARGS_NONE()); - mrb_define_method(mrb, cg_cPoint, "y=", cg_cPoint_set_y, MRB_ARGS_REQ(1)); - mrb_define_method(mrb, cg_cPoint, "draw", cg_cPoint_draw, MRB_ARGS_NONE()); -} diff --git a/src/point.h b/src/point.h deleted file mode 100644 index 7507d62..0000000 --- a/src/point.h +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2022 Frederico de Oliveira Linhares - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef CANDY_GEAR_POINT_H -#define CANDY_GEAR_POINT_H 1 - -#include "core.h" - -#ifdef __cplusplus -extern "C" { -#endif - -struct cg_point -{ - SDL_Point data; -}; - -extern const struct mrb_data_type cg_point_type; - -void -cg_point_init(mrb_state *mrb); - -#ifdef __cplusplus -}; -#endif - -#endif /* CANDY_GEAR_POINT_H */ diff --git a/src/rect.c b/src/rect.c deleted file mode 100644 index fa02ceb..0000000 --- a/src/rect.c +++ /dev/null @@ -1,249 +0,0 @@ -/* - * Copyright 2022 Frederico de Oliveira Linhares - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "rect.h" - -void -cg_free_rect(mrb_state *mrb, void* obj) -{ - struct cg_rect *ptr = obj; - - mrb_free(mrb, ptr); -} - -const struct mrb_data_type cg_rect_type = { - "CG_Rect", cg_free_rect }; - -static inline SDL_bool -align_vertically(int a_x, int a_width, int b_x, int b_width) -{ - return a_x <= b_x + b_width && a_x + a_width >= b_x; -} - -static inline SDL_bool -align_horizontally(int a_y, int a_height, int b_y, int b_height) -{ - return a_y <= b_y + b_height && a_y + a_height >= b_y; -} - -static mrb_value -cg_cRect_initialize(mrb_state *mrb, mrb_value self) -{ - mrb_int x, y, width, height; - struct cg_rect *ptr; - - mrb_get_args(mrb, "iiii", &x, &y, &width, &height); - ptr = (struct cg_rect *)DATA_PTR(self); - if(ptr) mrb_free(mrb, ptr); - ptr = (struct cg_rect *)mrb_malloc(mrb, sizeof(struct cg_rect)); - - ptr->rect.x = x; - ptr->rect.y = y; - ptr->rect.w = width; - ptr->rect.h = height; - - mrb_data_init(self, ptr, &cg_rect_type); - return self; -} - -static mrb_value -cg_cRect_get_x(mrb_state *mrb, mrb_value self) -{ - struct cg_rect *ptr; - - ptr = (struct cg_rect *)DATA_PTR(self); - - return mrb_int_value(mrb, ptr->rect.x); -} - -static mrb_value -cg_cRect_set_x(mrb_state *mrb, mrb_value self) -{ - mrb_int x; - struct cg_rect *ptr; - - mrb_get_args(mrb, "i", &x); - ptr = (struct cg_rect *)DATA_PTR(self); - - ptr->rect.x = x; - - return mrb_int_value(mrb, ptr->rect.x); -} - -static mrb_value -cg_cRect_get_y(mrb_state *mrb, mrb_value self) -{ - struct cg_rect *ptr; - - ptr = (struct cg_rect *)DATA_PTR(self); - - return mrb_int_value(mrb, ptr->rect.y); -} - -static mrb_value -cg_cRect_set_y(mrb_state *mrb, mrb_value self) -{ - mrb_int y; - struct cg_rect *ptr; - - mrb_get_args(mrb, "i", &y); - ptr = (struct cg_rect *)DATA_PTR(self); - - ptr->rect.y = y; - - return mrb_int_value(mrb, ptr->rect.y); -} - -static mrb_value -cg_cRect_get_width(mrb_state *mrb, mrb_value self) -{ - struct cg_rect *ptr; - - ptr = (struct cg_rect *)DATA_PTR(self); - - return mrb_int_value(mrb, ptr->rect.w); -} - -static mrb_value -cg_cRect_set_width(mrb_state *mrb, mrb_value self) -{ - mrb_int width; - struct cg_rect *ptr; - - mrb_get_args(mrb, "i", &width); - ptr = (struct cg_rect *)DATA_PTR(self); - - ptr->rect.w = width; - - return mrb_int_value(mrb, ptr->rect.w); -} - -static mrb_value -cg_cRect_get_height(mrb_state *mrb, mrb_value self) -{ - struct cg_rect *ptr; - - ptr = (struct cg_rect *)DATA_PTR(self); - - return mrb_int_value(mrb, ptr->rect.h); -} - -static mrb_value -cg_cRect_set_height(mrb_state *mrb, mrb_value self) -{ - mrb_int height; - struct cg_rect *ptr; - - mrb_get_args(mrb, "i", &height); - ptr = (struct cg_rect *)DATA_PTR(self); - - ptr->rect.h = height; - - return mrb_int_value(mrb, ptr->rect.h); -} - -static mrb_value -cg_cRect_draw_lines(mrb_state *mrb, mrb_value self) -{ - struct cg_rect *ptr; - ptr = (struct cg_rect *)DATA_PTR(self); - SDL_RenderDrawRect(cg_core.renderer, &ptr->rect); - - return self; -} - -static mrb_value -cg_cRect_draw_fill(mrb_state *mrb, mrb_value self) -{ - struct cg_rect *ptr; - ptr = (struct cg_rect *)DATA_PTR(self); - SDL_RenderFillRect(cg_core.renderer, &ptr->rect); - - return self; -} - -static mrb_value -cg_cRect_align_vertically(mrb_state *mrb, mrb_value self) -{ - struct cg_rect *ptr, *that; - - mrb_get_args(mrb, "d", &that, &cg_rect_type); - ptr = (struct cg_rect *)DATA_PTR(self); - - return mrb_bool_value( - align_vertically(ptr->rect.x, ptr->rect.w, that->rect.x, that->rect.w)); -} - -static mrb_value -cg_cRect_align_horizontally(mrb_state *mrb, mrb_value self) -{ - struct cg_rect *ptr, *that; - - mrb_get_args(mrb, "d", &that, &cg_rect_type); - ptr = (struct cg_rect *)DATA_PTR(self); - - return mrb_bool_value( - align_horizontally(ptr->rect.y, ptr->rect.h, that->rect.y, that->rect.h)); -} - -static mrb_value -cg_cRect_collide(mrb_state *mrb, mrb_value self) -{ - struct cg_rect *ptr, *that; - - mrb_get_args(mrb, "d", &that, &cg_rect_type); - ptr = (struct cg_rect *)DATA_PTR(self); - - return mrb_bool_value( - align_vertically(ptr->rect.x, ptr->rect.w, that->rect.x, that->rect.w) && - align_horizontally(ptr->rect.y, ptr->rect.h, that->rect.y, that->rect.h)); -} - -void -cg_rect_init(mrb_state *mrb) -{ - struct RClass *cg_m, *cg_cRect; - - cg_m = mrb_module_get(mrb, "CandyGear"); - cg_cRect = mrb_define_class_under(mrb, cg_m, "Rect", mrb->object_class); - MRB_SET_INSTANCE_TT(cg_cRect, MRB_TT_DATA); - mrb_define_method( - mrb, cg_cRect, "initialize", cg_cRect_initialize, MRB_ARGS_REQ(4)); - mrb_define_method(mrb, cg_cRect, "x", cg_cRect_get_x, MRB_ARGS_NONE()); - mrb_define_method(mrb, cg_cRect, "x=", cg_cRect_set_x, MRB_ARGS_REQ(1)); - mrb_define_method(mrb, cg_cRect, "y", cg_cRect_get_y, MRB_ARGS_NONE()); - mrb_define_method(mrb, cg_cRect, "y=", cg_cRect_set_y, MRB_ARGS_REQ(1)); - mrb_define_method( - mrb, cg_cRect, "width", cg_cRect_get_width, MRB_ARGS_NONE()); - mrb_define_method( - mrb, cg_cRect, "width=", cg_cRect_set_width, MRB_ARGS_REQ(1)); - mrb_define_method( - mrb, cg_cRect, "height", cg_cRect_get_height, MRB_ARGS_NONE()); - mrb_define_method( - mrb, cg_cRect, "height=", cg_cRect_set_height, MRB_ARGS_REQ(1)); - mrb_define_method( - mrb, cg_cRect, "draw_lines", cg_cRect_draw_lines, MRB_ARGS_NONE()); - mrb_define_method( - mrb, cg_cRect, "draw_fill", cg_cRect_draw_fill, MRB_ARGS_NONE()); - mrb_define_method( - mrb, cg_cRect, "align_vertically?", cg_cRect_align_vertically, - MRB_ARGS_REQ(1)); - mrb_define_method( - mrb, cg_cRect, "align_horizontally?", cg_cRect_align_horizontally, - MRB_ARGS_REQ(1)); - mrb_define_method( - mrb, cg_cRect, "collide?", cg_cRect_collide, MRB_ARGS_REQ(1)); -} diff --git a/src/rect.h b/src/rect.h deleted file mode 100644 index 64a7ba0..0000000 --- a/src/rect.h +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2022 Frederico de Oliveira Linhares - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef CANDY_GEAR_RECT_H -#define CANDY_GEAR_RECT_H 1 - -#include "core.h" - -#ifdef __cplusplus -extern "C" { -#endif - -struct cg_rect -{ - SDL_Rect rect; -}; - -extern const struct mrb_data_type cg_rect_type; - -void -cg_rect_init(mrb_state *mrb); - -#ifdef __cplusplus -}; -#endif - -#endif /* CANDY_GEAR_RECT_H */ diff --git a/src/sound.c b/src/sound.cpp index 8ca009c..0b3518f 100644 --- a/src/sound.c +++ b/src/sound.cpp @@ -14,12 +14,12 @@ * limitations under the License. */ -#include "sound.h" +#include "sound.hpp" void cg_free_sound(mrb_state *mrb, void* obj) { - struct cg_sound *ptr = obj; + struct cg_sound *ptr = static_cast<cg_sound*>(obj); Mix_FreeChunk(ptr->chunk); mrb_free(mrb, ptr); diff --git a/src/sound.h b/src/sound.hpp index f2cd2e7..2f483e3 100644 --- a/src/sound.h +++ b/src/sound.hpp @@ -17,11 +17,7 @@ #ifndef CANDY_GEAR_SOUND_H #define CANDY_GEAR_SOUND_H 1 -#include "core.h" - -#ifdef __cplusplus -extern "C" { -#endif +#include "core.hpp" struct cg_sound { @@ -33,8 +29,4 @@ extern const struct mrb_data_type cg_sound_type; void cg_sound_init(mrb_state *mrb); -#ifdef __cplusplus -}; -#endif - #endif /* CANDY_GEAR_SOUND_H */ diff --git a/src/sprite.c b/src/sprite.c deleted file mode 100644 index b8495d4..0000000 --- a/src/sprite.c +++ /dev/null @@ -1,198 +0,0 @@ -/* - * Copyright 2022 Frederico de Oliveira Linhares - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "sprite.h" -#include "sprite_implementation.h" - -#include <mruby/hash.h> - -#include "point.h" -#include "texture.h" - -// This variable works as a constant -static mrb_sym id_at_texture; - -void -cg_free_sprite(mrb_state *mrb, void* obj) -{ - struct cg_sprite *ptr = obj; - - mrb_free(mrb, ptr); -} - -const struct mrb_data_type cg_sprite_type = { - "CG_Sprite", cg_free_sprite }; - -static mrb_value -cg_cSrpite_load_yaml(mrb_state *mrb, mrb_value klass) -{ - const char *file_path; - struct cg_SpriteArray_s sprite_ary; - mrb_value hash, texture; - - mrb_get_args(mrb, "zo", &file_path, &texture); - - if(!cg_SpriteArray_constructor(&sprite_ary, file_path)) - mrb_raise(mrb, E_RUNTIME_ERROR, "failed to open sprites"); - - hash = mrb_hash_new_capa(mrb, sprite_ary.len); - - for(int i = 0; i < sprite_ary.len; i++) - { - mrb_hash_set( - mrb, hash, - mrb_symbol_value(mrb_intern_cstr(mrb, sprite_ary.sprites[i].name)), - mrb_funcall( - mrb, klass, "new", 5, - texture, - mrb_int_value(mrb, sprite_ary.sprites[i].x), - mrb_int_value(mrb, sprite_ary.sprites[i].y), - mrb_int_value(mrb, sprite_ary.sprites[i].width), - mrb_int_value(mrb, sprite_ary.sprites[i].height))); - } - - cg_SpriteArray_destructor(&sprite_ary); - - return hash; -} - -static mrb_value -cg_cSprite_initialize(mrb_state *mrb, mrb_value self) -{ - mrb_value texture_v; - mrb_int x, y, width, height; - struct cg_sprite *ptr; - - mrb_get_args(mrb, "oiiii", &texture_v, &x, &y, &width, &height); - ptr = (struct cg_sprite *)DATA_PTR(self); - if(ptr) mrb_free(mrb, ptr); - ptr = (struct cg_sprite *)mrb_malloc(mrb, sizeof(struct cg_sprite)); - - mrb_iv_set(mrb, self, id_at_texture, texture_v); - ptr->rect.x = x; - ptr->rect.y = y; - ptr->rect.w = width; - ptr->rect.h = height; - - mrb_data_init(self, ptr, &cg_sprite_type); - return self; -} - -static mrb_value -cg_cSprite_texture(mrb_state *mrb, mrb_value self) -{ - return mrb_iv_get(mrb, self, id_at_texture); -} - -static mrb_value -cg_cSprite_width(mrb_state *mrb, mrb_value self) -{ - struct cg_sprite *ptr; - - ptr = (struct cg_sprite *)DATA_PTR(self); - - return mrb_int_value(mrb, ptr->rect.w); -} - -static mrb_value -cg_cSprite_height(mrb_state *mrb, mrb_value self) -{ - struct cg_sprite *ptr; - - ptr = (struct cg_sprite *)DATA_PTR(self); - - return mrb_int_value(mrb, ptr->rect.h); -} - -static mrb_value -cg_cSprite_draw(mrb_state *mrb, mrb_value self) -{ - SDL_Rect dst_rect; - - mrb_value texture_v; - - struct cg_sprite *ptr; - struct cg_texture *texture_ptr; - struct cg_point *point; - - mrb_get_args(mrb, "d", &point, &cg_point_type); - - ptr = (struct cg_sprite *)DATA_PTR(self); - texture_v = mrb_iv_get(mrb, self, id_at_texture); - texture_ptr = (struct cg_texture *)DATA_PTR(texture_v); - - dst_rect.x = point->data.x; - dst_rect.y = point->data.y; - dst_rect.w = ptr->rect.w; - dst_rect.h = ptr->rect.h; - - SDL_RenderCopy(cg_core.renderer, texture_ptr->data, &ptr->rect, &dst_rect); - - return self; -} - -static mrb_value -cg_cSprite_draw_xy(mrb_state *mrb, mrb_value self) -{ - SDL_Rect dst_rect; - - mrb_value texture_v; - - mrb_int x, y; - struct cg_sprite *ptr; - struct cg_texture *texture_ptr; - - mrb_get_args(mrb, "ii", &x, &y); - - ptr = (struct cg_sprite *)DATA_PTR(self); - texture_v = mrb_iv_get(mrb, self, id_at_texture); - texture_ptr = (struct cg_texture *)DATA_PTR(texture_v); - - dst_rect.x = x; - dst_rect.y = y; - dst_rect.w = ptr->rect.w; - dst_rect.h = ptr->rect.h; - - SDL_RenderCopy(cg_core.renderer, texture_ptr->data, &ptr->rect, &dst_rect); - - return self; -} - -void -cg_sprite_init(mrb_state *mrb) -{ - struct RClass *cg_m, *cg_cSprite; - - id_at_texture = mrb_intern_lit(mrb, "@texture"); - - cg_m = mrb_module_get(mrb, "CandyGear"); - cg_cSprite = mrb_define_class_under(mrb, cg_m, "Sprite", mrb->object_class); - MRB_SET_INSTANCE_TT(cg_cSprite, MRB_TT_DATA); - mrb_define_class_method( - mrb, cg_cSprite, "load_yaml", cg_cSrpite_load_yaml, MRB_ARGS_REQ(2)); - mrb_define_method( - mrb, cg_cSprite, "initialize", cg_cSprite_initialize, MRB_ARGS_REQ(5)); - mrb_define_method( - mrb, cg_cSprite, "texture", cg_cSprite_texture, MRB_ARGS_NONE()); - mrb_define_method( - mrb, cg_cSprite, "width", cg_cSprite_width, MRB_ARGS_NONE()); - mrb_define_method( - mrb, cg_cSprite, "height", cg_cSprite_height, MRB_ARGS_NONE()); - mrb_define_method( - mrb, cg_cSprite, "draw", cg_cSprite_draw, MRB_ARGS_REQ(1)); - mrb_define_method( - mrb, cg_cSprite, "draw_xy", cg_cSprite_draw_xy, MRB_ARGS_REQ(2)); -} diff --git a/src/sprite.h b/src/sprite.h deleted file mode 100644 index 7a85418..0000000 --- a/src/sprite.h +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2022 Frederico de Oliveira Linhares - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef CANDY_GEAR_SPRITE_H -#define CANDY_GEAR_SPRITE_H 1 - -#include "core.h" - -#ifdef __cplusplus -extern "C" { -#endif - -struct cg_sprite -{ - SDL_Rect rect; -}; - -extern const struct mrb_data_type cg_sprite_type; - -void -cg_sprite_init(mrb_state *mrb); - -#ifdef __cplusplus -}; -#endif - -#endif /* CANDY_GEAR_SPRITE_H */ diff --git a/src/sprite_implementation.cpp b/src/sprite_implementation.cpp index 4ba0edf..099fa35 100644 --- a/src/sprite_implementation.cpp +++ b/src/sprite_implementation.cpp @@ -14,13 +14,13 @@ * limitations under the License. */ -#include "sprite_implementation.h" +#include "sprite_implementation.hpp" #include <fstream> #include <yaml-cpp/yaml.h> -SDL_bool +bool cg_SpriteArray_constructor( struct cg_SpriteArray_s *self, const char *file_path) { @@ -47,7 +47,7 @@ cg_SpriteArray_constructor( index++; } - return SDL_TRUE; + return true; } void diff --git a/src/sprite_implementation.h b/src/sprite_implementation.hpp index c421188..bd3e954 100644 --- a/src/sprite_implementation.h +++ b/src/sprite_implementation.hpp @@ -19,10 +19,6 @@ #include <SDL2/SDL.h> -#ifdef __cplusplus -extern "C" { -#endif - struct cg_Sprite_s { char *name; @@ -35,15 +31,11 @@ struct cg_SpriteArray_s struct cg_Sprite_s *sprites; }; -SDL_bool +bool cg_SpriteArray_constructor( struct cg_SpriteArray_s *self, const char *file_path); void cg_SpriteArray_destructor(struct cg_SpriteArray_s *self); -#ifdef __cplusplus -}; -#endif - #endif /* CANDY_GEAR_SPRITE_IMPLEMENTATION_H */ diff --git a/src/texture.c b/src/texture.c deleted file mode 100644 index f363c1f..0000000 --- a/src/texture.c +++ /dev/null @@ -1,223 +0,0 @@ -/* - * Copyright 2022 Frederico de Oliveira Linhares - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "texture.h" - -#include "color.h" -#include "core.h" -#include "font.h" -#include "palette.h" -#include "pgm_image.h" -#include "point.h" - -void -cg_free_texture(mrb_state *mrb, void* obj) -{ - struct cg_texture *ptr = obj; - - SDL_DestroyTexture(ptr->data); - mrb_free(mrb, ptr); -} - -const struct mrb_data_type cg_texture_type = { - "CG_Texture", cg_free_texture }; - -static mrb_value -texture_alloc(mrb_state *mrb, mrb_value class) -{ - struct RClass *cg_cTexture = mrb_class_ptr(class); - enum mrb_vtype ttype = MRB_INSTANCE_TT(cg_cTexture); - - // I do not know if I need this. If things break, try uncomment this line. - // if (ttype == 0) ttype = MRB_TT_OBJECT; - return mrb_obj_value( - (struct RObject*)mrb_obj_alloc(mrb, ttype, cg_cTexture)); -} - -static mrb_value -cg_cTexture_from_image(mrb_state *mrb, mrb_value self) -{ - PGMImage pgm_img; - - struct mrb_value texture = texture_alloc(mrb, self); - const char *file_path; - struct cg_palette *palette; - struct cg_texture *ptr; - mrb_bool palette_given; - SDL_Surface *surface = NULL; - - mrb_get_args( - mrb, "z|d?", &file_path, &palette, &cg_palette_type, &palette_given); - ptr = (struct cg_texture *)DATA_PTR(texture); - if(ptr) mrb_free(mrb, ptr); - ptr = (struct cg_texture *)mrb_malloc(mrb, sizeof(struct cg_texture)); - if(palette_given) - { - if(!PGMImage_constructor(&pgm_img, file_path)) - mrb_raise(mrb, E_RUNTIME_ERROR, pgm_img.error); - - // Depth = 8 bits image. - // Pitch = 1 byte (8 bits) * image width. - surface = SDL_CreateRGBSurfaceWithFormatFrom( - pgm_img.data, pgm_img.width, pgm_img.height, 8, pgm_img.width, - SDL_PIXELFORMAT_INDEX8); - if(!surface) mrb_raise(mrb, E_RUNTIME_ERROR, SDL_GetError()); - SDL_SetPaletteColors(surface->format->palette, palette->colors, 0, 256); - } - else - { - surface = IMG_Load(file_path); - if(!surface) mrb_raise(mrb, E_RUNTIME_ERROR, IMG_GetError()); - } - - ptr->data = SDL_CreateTextureFromSurface(cg_core.renderer, surface); - SDL_SetTextureBlendMode(ptr->data, SDL_BLENDMODE_BLEND); - SDL_FreeSurface(surface); - if(palette_given) PGMImage_destructor(&pgm_img); - if(ptr->data == NULL) mrb_raise(mrb, E_RUNTIME_ERROR, SDL_GetError()); - - SDL_QueryTexture(ptr->data, NULL, NULL, &ptr->width, &ptr->height); - - mrb_data_init(texture, ptr, &cg_texture_type); - return texture; -} - -mrb_value -cg_cText_from_text(mrb_state *mrb, mrb_value self) -{ - char *text; - SDL_Surface *surface = NULL; - mrb_bool background; - struct mrb_value texture = texture_alloc(mrb, self); - struct cg_color *tx_color_ptr, *bg_color_ptr; - struct cg_font *font_ptr; - struct cg_texture *ptr; - - mrb_get_args( - mrb, "zdd|d?", &text, &font_ptr, &cg_font_type, - &tx_color_ptr, &cg_color_type, &bg_color_ptr, &cg_color_type, - &background); - - ptr = (struct cg_texture *)DATA_PTR(texture); - if(ptr) mrb_free(mrb, ptr); - ptr = (struct cg_texture *)mrb_malloc(mrb, sizeof(struct cg_texture)); - - // Create surface. - if(background) - surface = TTF_RenderUTF8_Shaded( - font_ptr->data, text, tx_color_ptr->data, bg_color_ptr->data); - else - surface = TTF_RenderUTF8_Solid(font_ptr->data, text, tx_color_ptr->data); - if(surface == NULL) mrb_raise(mrb, E_RUNTIME_ERROR, TTF_GetError()); - - // Convert the surface to a texture. - ptr->data = SDL_CreateTextureFromSurface(cg_core.renderer, surface); - SDL_FreeSurface(surface); - if(ptr->data == NULL) mrb_raise(mrb, E_RUNTIME_ERROR, SDL_GetError()); - - SDL_QueryTexture(ptr->data, NULL, NULL, &ptr->width, &ptr->height); - mrb_data_init(texture, ptr, &cg_texture_type); - return texture; -} - -static mrb_value -cg_cTexture_width(mrb_state *mrb, mrb_value self) -{ - struct cg_texture *ptr; - - ptr = (struct cg_texture *)DATA_PTR(self); - - return mrb_int_value(mrb, ptr->width); -} - -static mrb_value -cg_cTexture_height(mrb_state *mrb, mrb_value self) -{ - struct cg_texture *ptr; - - ptr = (struct cg_texture *)DATA_PTR(self); - - return mrb_int_value(mrb, ptr->height); -} - -static mrb_value -cg_cTexture_draw(mrb_state *mrb, mrb_value self) -{ - SDL_Rect dst_rect; - - struct cg_texture *ptr; - struct cg_point *point; - - mrb_get_args(mrb, "d", &point, &cg_point_type); - ptr = (struct cg_texture *)DATA_PTR(self); - - dst_rect.x = point->data.x; - dst_rect.y = point->data.y; - dst_rect.w = ptr->width; - dst_rect.h = ptr->height; - - SDL_RenderCopy(cg_core.renderer, ptr->data, NULL, &dst_rect); - - return self; -} - -static mrb_value -cg_cTexture_draw_xy(mrb_state *mrb, mrb_value self) -{ - SDL_Rect dst_rect; - - struct cg_texture *ptr; - mrb_int x, y; - - mrb_get_args(mrb, "ii", &x, &y); - ptr = (struct cg_texture *)DATA_PTR(self); - - dst_rect.x = x; - dst_rect.y = y; - dst_rect.w = ptr->width; - dst_rect.h = ptr->height; - - SDL_RenderCopy(cg_core.renderer, ptr->data, NULL, &dst_rect); - - return self; -} - -void -cg_texture_init(mrb_state *mrb) -{ - struct RClass *cg_m, *cg_cTexture; - - cg_m = mrb_module_get(mrb, "CandyGear"); - cg_cTexture = mrb_define_class_under( - mrb, cg_m, "Texture", mrb->object_class); - MRB_SET_INSTANCE_TT(cg_cTexture, MRB_TT_DATA); - - mrb_undef_class_method(mrb, cg_cTexture, "new"); - mrb_define_class_method( - mrb, cg_cTexture, "from_image", cg_cTexture_from_image, - MRB_ARGS_REQ(1) | MRB_ARGS_OPT(1)); - mrb_define_class_method( - mrb, cg_cTexture, "from_text", cg_cText_from_text, - MRB_ARGS_REQ(3)|MRB_ARGS_OPT(1)); - mrb_define_method( - mrb, cg_cTexture, "width", cg_cTexture_width, MRB_ARGS_NONE()); - mrb_define_method( - mrb, cg_cTexture, "height", cg_cTexture_height, MRB_ARGS_NONE()); - mrb_define_method( - mrb, cg_cTexture, "draw", cg_cTexture_draw, MRB_ARGS_REQ(1)); - mrb_define_method( - mrb, cg_cTexture, "draw_xy", cg_cTexture_draw_xy, MRB_ARGS_REQ(2)); -} diff --git a/src/texture.cpp b/src/texture.cpp new file mode 100644 index 0000000..28cdeff --- /dev/null +++ b/src/texture.cpp @@ -0,0 +1,186 @@ +/* + * Copyright 2022 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "texture.hpp" + +#include "core.hpp" +#include "pgm_image.hpp" +#include "vk/texture.hpp" + +void +cg_free_texture(mrb_state *mrb, void* obj) +{ + auto *ptr = static_cast<std::shared_ptr<VK::Texture>*>(obj); + + ptr->~shared_ptr(); + mrb_free(mrb, ptr); +} + +const struct mrb_data_type cg_texture_type = { "CG_Texture", cg_free_texture }; + +static mrb_value +texture_alloc(mrb_state *mrb, mrb_value klass) +{ + struct RClass *cg_cTexture = mrb_class_ptr(klass); + enum mrb_vtype ttype = MRB_INSTANCE_TT(cg_cTexture); + + // I do not know if I need this. If things break, try uncomment this line. + // if (ttype == 0) ttype = MRB_TT_OBJECT; + return mrb_obj_value( + (struct RObject*)mrb_obj_alloc(mrb, ttype, cg_cTexture)); +} + +static mrb_value +cg_cTexture_from_image(mrb_state *mrb, mrb_value self) +{ + PGMImage pgm_img; + + struct mrb_value texture = texture_alloc(mrb, self); + const char *file_path; + std::shared_ptr<VK::Texture> *ptr; + + mrb_get_args(mrb, "z", &file_path); + ptr = (std::shared_ptr<VK::Texture>*)DATA_PTR(texture); + if(ptr) mrb_free(mrb, ptr); + ptr = (std::shared_ptr<VK::Texture>*)mrb_malloc( + mrb, sizeof(std::shared_ptr<VK::Texture>)); + new(ptr)std::shared_ptr<VK::Texture>( + std::make_shared<VK::Texture>(file_path)); + + mrb_data_init(texture, ptr, &cg_texture_type); + return texture; +} + +// mrb_value +// cg_cText_from_text(mrb_state *mrb, mrb_value self) +// { +// char *text; +// SDL_Surface *surface = nullptr; +// mrb_bool background; +// struct mrb_value texture = texture_alloc(mrb, self); +// struct cg_color *tx_color_ptr, *bg_color_ptr; +// struct cg_font *font_ptr; +// struct cg_texture *ptr; + +// mrb_get_args( +// mrb, "zdd|d?", &text, &font_ptr, &cg_font_type, +// &tx_color_ptr, &cg_color_type, &bg_color_ptr, &cg_color_type, +// &background); + +// ptr = (struct cg_texture *)DATA_PTR(texture); +// if(ptr) mrb_free(mrb, ptr); +// ptr = (struct cg_texture *)mrb_malloc(mrb, sizeof(struct cg_texture)); + +// // Create surface. +// if(background) +// surface = TTF_RenderUTF8_Shaded( +// font_ptr->data, text, tx_color_ptr->data, bg_color_ptr->data); +// else +// surface = TTF_RenderUTF8_Solid(font_ptr->data, text, tx_color_ptr->data); +// if(surface == nullptr) mrb_raise(mrb, E_RUNTIME_ERROR, TTF_GetError()); + +// // Convert the surface to a texture. +// ptr->data = SDL_CreateTextureFromSurface(cg_core.renderer, surface); +// SDL_FreeSurface(surface); +// if(ptr->data == nullptr) mrb_raise(mrb, E_RUNTIME_ERROR, SDL_GetError()); + +// SDL_QueryTexture(ptr->data, nullptr, nullptr, &ptr->width, &ptr->height); +// mrb_data_init(texture, ptr, &cg_texture_type); +// return texture; +// } + +static mrb_value +cg_cTexture_width(mrb_state *mrb, mrb_value self) +{ + auto ptr = (std::shared_ptr<VK::Texture>*)DATA_PTR(self); + return mrb_int_value(mrb, (*ptr)->width); +} + +static mrb_value +cg_cTexture_height(mrb_state *mrb, mrb_value self) +{ + auto ptr = (std::shared_ptr<VK::Texture>*)DATA_PTR(self); + return mrb_int_value(mrb, (*ptr)->height); +} + +// static mrb_value +// cg_cTexture_draw(mrb_state *mrb, mrb_value self) +// { +// SDL_Rect dst_rect; + +// struct cg_texture *ptr; +// struct cg_point *point; + +// mrb_get_args(mrb, "d", &point, &cg_point_type); +// ptr = (struct cg_texture *)DATA_PTR(self); + +// dst_rect.x = point->data.x; +// dst_rect.y = point->data.y; +// dst_rect.w = ptr->width; +// dst_rect.h = ptr->height; + +// SDL_RenderCopy(cg_core.renderer, ptr->data, nullptr, &dst_rect); + +// return self; +// } + +// static mrb_value +// cg_cTexture_draw_xy(mrb_state *mrb, mrb_value self) +// { +// SDL_Rect dst_rect; + +// struct cg_texture *ptr; +// mrb_int x, y; + +// mrb_get_args(mrb, "ii", &x, &y); +// ptr = (struct cg_texture *)DATA_PTR(self); + +// dst_rect.x = x; +// dst_rect.y = y; +// dst_rect.w = ptr->width; +// dst_rect.h = ptr->height; + +// SDL_RenderCopy(cg_core.renderer, ptr->data, nullptr, &dst_rect); + +// return self; +// } + +void +cg_texture_init(mrb_state *mrb) +{ + struct RClass *cg_m, *cg_cTexture; + + cg_m = mrb_module_get(mrb, "CandyGear"); + cg_cTexture = mrb_define_class_under( + mrb, cg_m, "Texture", mrb->object_class); + MRB_SET_INSTANCE_TT(cg_cTexture, MRB_TT_DATA); + + mrb_undef_class_method(mrb, cg_cTexture, "new"); + mrb_define_class_method( + mrb, cg_cTexture, "from_image", cg_cTexture_from_image, + MRB_ARGS_REQ(1) | MRB_ARGS_OPT(1)); + // mrb_define_class_method( + // mrb, cg_cTexture, "from_text", cg_cText_from_text, + // MRB_ARGS_REQ(3)|MRB_ARGS_OPT(1)); + mrb_define_method( + mrb, cg_cTexture, "width", cg_cTexture_width, MRB_ARGS_NONE()); + mrb_define_method( + mrb, cg_cTexture, "height", cg_cTexture_height, MRB_ARGS_NONE()); + // mrb_define_method( + // mrb, cg_cTexture, "draw", cg_cTexture_draw, MRB_ARGS_REQ(1)); + // mrb_define_method( + // mrb, cg_cTexture, "draw_xy", cg_cTexture_draw_xy, MRB_ARGS_REQ(2)); +} diff --git a/src/texture.h b/src/texture.hpp index f89542f..b20244d 100644 --- a/src/texture.h +++ b/src/texture.hpp @@ -17,25 +17,12 @@ #ifndef CANDY_GEAR_TEXTURE_H #define CANDY_GEAR_TEXTURE_H 1 -#include "core.h" - -#ifdef __cplusplus -extern "C" { -#endif - -struct cg_texture -{ - SDL_Texture *data; - int width, height; -}; +#include "core.hpp" +#include "command.hpp" extern const struct mrb_data_type cg_texture_type; void cg_texture_init(mrb_state *mrb); -#ifdef __cplusplus -}; -#endif - #endif /* CANDY_GEAR_TEXTURE_H */ diff --git a/src/vk/base_buffer.cpp b/src/vk/base_buffer.cpp new file mode 100644 index 0000000..0846b20 --- /dev/null +++ b/src/vk/base_buffer.cpp @@ -0,0 +1,96 @@ +/* + * Copyright 2022 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "base_buffer.hpp" + +namespace VK +{ + +const CommandChain BaseBuffer::loader{ + {&BaseBuffer::load_buffer, &BaseBuffer::unload_buffer}, + {&BaseBuffer::load_memory, &BaseBuffer::unload_memory} +}; + +void +BaseBuffer::load_buffer(void *obj) +{ + auto self = static_cast<BaseBuffer*>(obj); + + VkBufferCreateInfo buffer_info = {}; + buffer_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + buffer_info.pNext = nullptr; + buffer_info.flags = 0; + buffer_info.size = self->device_size; + buffer_info.usage = self->buffer_usage; + buffer_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + buffer_info.queueFamilyIndexCount = 0; + buffer_info.pQueueFamilyIndices = nullptr; + + if(vkCreateBuffer( + self->device->device, &buffer_info, nullptr, &self->buffer) + != VK_SUCCESS) + throw CommandError{"Failed to create vertex buffer."}; +} + +void +BaseBuffer::unload_buffer(void *obj) +{ + auto self = static_cast<BaseBuffer*>(obj); + + if(self->buffer != VK_NULL_HANDLE) + vkDestroyBuffer(self->device->device, self->buffer, nullptr); +} + +void +BaseBuffer::load_memory(void *obj) +{ + auto self = static_cast<BaseBuffer*>(obj); + + VkMemoryRequirements memory_requirements; + vkGetBufferMemoryRequirements( + self->device->device, self->buffer, &memory_requirements); + + VkPhysicalDeviceMemoryProperties memory_properties; + vkGetPhysicalDeviceMemoryProperties( + self->device->physical_device, &memory_properties); + + VkMemoryAllocateInfo alloc_info = {}; + alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + alloc_info.pNext = nullptr; + alloc_info.allocationSize = memory_requirements.size; + if(!self->device->select_memory_type( + &alloc_info.memoryTypeIndex, &memory_requirements, + self->memory_properties)) + throw CommandError{"Could not allocate memory for Vulkan vertex buffer."}; + + if(vkAllocateMemory(self->device->device, &alloc_info, nullptr, + &self->device_memory) != VK_SUCCESS) + throw CommandError{"Could not allocate memory for Vulkan vertex buffer."}; + + vkBindBufferMemory( + self->device->device, self->buffer, self->device_memory, 0); +} + +void +BaseBuffer::unload_memory(void *obj) +{ + auto self = static_cast<BaseBuffer*>(obj); + + if(self->device_memory != VK_NULL_HANDLE) + vkFreeMemory(self->device->device, self->device_memory, nullptr); +} + +} diff --git a/src/vk/base_buffer.hpp b/src/vk/base_buffer.hpp new file mode 100644 index 0000000..0f3e513 --- /dev/null +++ b/src/vk/base_buffer.hpp @@ -0,0 +1,56 @@ +/* + * Copyright 2022 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CANDY_GEAR_VK_BASE_BUFFER_H +#define CANDY_GEAR_VK_BASE_BUFFER_H 1 + +#include "../command.hpp" +#include "core.hpp" +#include "device.hpp" + +namespace VK +{ + +class BaseBuffer +{ + public: + virtual ~BaseBuffer(){}; + + VkBuffer buffer; + VkDeviceMemory device_memory; + VkDeviceSize device_size; + VkBufferUsageFlags buffer_usage; + VkMemoryPropertyFlags memory_properties; + + protected: + static const CommandChain loader; + + Device *device; + + static void + load_buffer(void *obj); + static void + unload_buffer(void *obj); + + static void + load_memory(void *obj); + static void + unload_memory(void *obj); +}; + +} + +#endif /* CANDY_GEAR_VK_BASE_BUFFER_H */ diff --git a/src/vk/camera.hpp b/src/vk/camera.hpp new file mode 100644 index 0000000..5f09390 --- /dev/null +++ b/src/vk/camera.hpp @@ -0,0 +1,33 @@ +/* + * Copyright 2022 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CANDY_GEAR_VK_CAMERA_H +#define CANDY_GEAR_VK_CAMERA_H 1 + +#include "core.hpp" + +namespace VK +{ + +struct Camera +{ + glm::vec3 position; + glm::vec3 rotation; // Radians +}; + +} + +#endif /* CANDY_GEAR_VK_CAMERA_H */ diff --git a/src/vk/command_pool.cpp b/src/vk/command_pool.cpp new file mode 100644 index 0000000..3af62c1 --- /dev/null +++ b/src/vk/command_pool.cpp @@ -0,0 +1,87 @@ +/* + * Copyright 2022 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "command_pool.hpp" + +namespace VK +{ + +const CommandChain CommandPool::loader{ + {&CommandPool::load_command_pool, &CommandPool::unload_command_pool}, + {&CommandPool::load_command_buffers, nullptr} +}; + +CommandPool::CommandPool(QueueFamily *queue_family, uint32_t buffers_quantity): + queue_family{queue_family} +{ + this->command_buffers.resize(buffers_quantity); + + CommandPool::loader.execute(this); +} + +CommandPool::~CommandPool() +{ + CommandPool::loader.revert(this); +} + +void +CommandPool::load_command_pool(void *obj) +{ + auto *self = static_cast<CommandPool*>(obj); + + VkCommandPoolCreateInfo command_pool_create_info; + + command_pool_create_info.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; + command_pool_create_info.pNext = nullptr; + command_pool_create_info.flags = + VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; + command_pool_create_info.queueFamilyIndex = self->queue_family->family_index; + + if(vkCreateCommandPool( + self->queue_family->device->device, &command_pool_create_info, + nullptr, &self->command_pool) != VK_SUCCESS) + throw CommandError{"Vulkan command pool could not be created."}; +} + +void +CommandPool::unload_command_pool(void *obj) +{ + auto *self = static_cast<CommandPool*>(obj); + + vkDestroyCommandPool( + self->queue_family->device->device, self->command_pool, nullptr); +} + +void +CommandPool::load_command_buffers(void *obj) +{ + auto *self = static_cast<CommandPool*>(obj); + + VkCommandBufferAllocateInfo command_buffer_info; + command_buffer_info.sType = + VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + command_buffer_info.pNext = nullptr; + command_buffer_info.commandPool = self->command_pool; + command_buffer_info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + command_buffer_info.commandBufferCount = self->command_buffers.size(); + + if(vkAllocateCommandBuffers( + self->queue_family->device->device, + &command_buffer_info, self->command_buffers.data()) != VK_SUCCESS) + throw CommandError{"Vulkan command buffers could not be allocated."}; +} + +} diff --git a/src/vk/command_pool.hpp b/src/vk/command_pool.hpp new file mode 100644 index 0000000..68ab7a6 --- /dev/null +++ b/src/vk/command_pool.hpp @@ -0,0 +1,59 @@ +/* + * Copyright 2022 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CANDY_GEAR_VK_COMMAND_POOL_H +#define CANDY_GEAR_VK_COMMAND_POOL_H 1 + +#include <vector> + +#include "../command.hpp" +#include "core.hpp" +#include "device.hpp" + +namespace VK +{ + +class CommandPool +{ + CommandPool(const CommandPool &t) = delete; + CommandPool& operator=(const CommandPool &t) = delete; + CommandPool(const CommandPool &&t) = delete; + CommandPool& operator=(const CommandPool &&t) = delete; + +public: + std::vector<VkCommandBuffer> command_buffers; + + CommandPool(QueueFamily *queue_family, uint32_t buffers_quantity); + ~CommandPool(); + +private: + static const CommandChain loader; + + QueueFamily *queue_family; + VkCommandPool command_pool; + + static void + load_command_pool(void *obj); + static void + unload_command_pool(void *obj); + + static void + load_command_buffers(void *obj); +}; + +} + +#endif /* CANDY_GEAR_VK_COMMAND_POOL_H */ diff --git a/src/vk/core.hpp b/src/vk/core.hpp new file mode 100644 index 0000000..6d42bde --- /dev/null +++ b/src/vk/core.hpp @@ -0,0 +1,32 @@ +/* + * Copyright 2022 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CANDY_GEAR_VK_CORE_H +#define CANDY_GEAR_VK_CORE_H 1 + +// GLM uses some definitions to control their behavior, so you should not +// include it directly. Instead, use this header. +#define GLM_FORCE_RADIANS +#define GLM_FORCE_DEPTH_ZERO_TO_ONE + +#include <glm/ext/vector_float3.hpp> +#include <glm/glm.hpp> +#include <glm/gtc/matrix_transform.hpp> +#include <glm/vec3.hpp> + +#include <vulkan/vulkan.h> + +#endif /* CANDY_GEAR_VK_CORE_H */ diff --git a/src/vk/destination_buffer.cpp b/src/vk/destination_buffer.cpp new file mode 100644 index 0000000..872f8a8 --- /dev/null +++ b/src/vk/destination_buffer.cpp @@ -0,0 +1,105 @@ +/* + * Copyright 2022 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "destination_buffer.hpp" + +#include "command_pool.hpp" + +namespace VK +{ + +DestinationBuffer::DestinationBuffer( + QueueFamily *queue_family, SourceBuffer *source_buffer, + VkBufferUsageFlags buffer_usage) +{ + this->device = queue_family->device; + this->device_size = source_buffer->device_size; + this->buffer_usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | buffer_usage; + this->memory_properties = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; + this->queue_family = queue_family; + this->source_buffer = source_buffer; + + BaseBuffer::loader.execute(dynamic_cast<BaseBuffer*>(this)); + + // Load command + { + CommandPool command_pool(this->queue_family, 1); + Queue transfer_queue{this->queue_family->get_queue()}; + + this->vk_command_buffer = command_pool.command_buffers[0]; + + VkCommandBufferBeginInfo begin_info = {}; + begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + begin_info.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + + VkBufferCopy copy_region = {}; + copy_region.srcOffset = 0; + copy_region.dstOffset = 0; + copy_region.size = this->device_size; + + vkBeginCommandBuffer(this->vk_command_buffer, &begin_info); + + vkCmdCopyBuffer( + this->vk_command_buffer, this->source_buffer->buffer, this->buffer, 1, + ©_region); + + vkEndCommandBuffer(this->vk_command_buffer); + + VkSubmitInfo submit_info = {}; + submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + submit_info.commandBufferCount = 1; + submit_info.pCommandBuffers = &this->vk_command_buffer; + + vkQueueSubmit(transfer_queue.queue, 1, &submit_info, VK_NULL_HANDLE); + + vkQueueWaitIdle(transfer_queue.queue); + } +} + +DestinationBuffer::DestinationBuffer( + DestinationBuffer &&that) +{ + this->buffer = that.buffer; + this->device_memory = that.device_memory; + this->device_size = that.device_size; + this->buffer_usage = that.buffer_usage; + this->memory_properties = that.memory_properties; + + that.buffer = VK_NULL_HANDLE; + that.device_memory = VK_NULL_HANDLE; +} + +DestinationBuffer& +DestinationBuffer::operator=(DestinationBuffer &&that) +{ + this->buffer = that.buffer; + this->device_memory = that.device_memory; + this->device_size = that.device_size; + this->buffer_usage = that.buffer_usage; + this->memory_properties = that.memory_properties; + + that.buffer = VK_NULL_HANDLE; + that.device_memory = VK_NULL_HANDLE; + + return *this; +} + +DestinationBuffer::~DestinationBuffer() +{ + BaseBuffer::loader.revert(dynamic_cast<BaseBuffer*>(this)); +} + +} diff --git a/src/vk/destination_buffer.hpp b/src/vk/destination_buffer.hpp new file mode 100644 index 0000000..e856f4d --- /dev/null +++ b/src/vk/destination_buffer.hpp @@ -0,0 +1,51 @@ +/* + * Copyright 2022 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CANDY_GEAR_VK_DESTINATION_BUFFER_H +#define CANDY_GEAR_VK_DESTINATION_BUFFER_H 1 + +#include "base_buffer.hpp" +#include "core.hpp" +#include "source_buffer.hpp" +#include "queue_family.hpp" + +namespace VK +{ + +struct DestinationBuffer: public BaseBuffer +{ + DestinationBuffer(const DestinationBuffer &t) = delete; + DestinationBuffer& + operator=(const DestinationBuffer &t) = delete; + + QueueFamily *queue_family; + SourceBuffer *source_buffer; + VkCommandBuffer vk_command_buffer; + + DestinationBuffer( + QueueFamily *queue_family, SourceBuffer *source_buffer, + VkBufferUsageFlags buffer_usage); + + DestinationBuffer(DestinationBuffer &&that); + DestinationBuffer& + operator=(DestinationBuffer &&that); + + ~DestinationBuffer(); +}; + +} + +#endif /* CANDY_GEAR_VK_DESTINATION_BUFFER_H */ diff --git a/src/vk/device.cpp b/src/vk/device.cpp new file mode 100644 index 0000000..2434283 --- /dev/null +++ b/src/vk/device.cpp @@ -0,0 +1,254 @@ +/* + * Copyright 2022 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "device.hpp" + +#include <fstream> +#include <iostream> +#include <new> +#include <vector> + +#include "../core.hpp" + +namespace +{ +VkShaderModule +create_shader_module(VkDevice vk_device, const char *filename) +{ + std::ifstream file(filename, std::ios::ate | std::ios::binary); + + if (!file.is_open()) + { + throw std::runtime_error("Failed to open shader module file."); + } + + size_t file_size = (size_t) file.tellg(); + std::vector<char> code(file_size); + + file.seekg(0); + file.read(code.data(), file_size); + + file.close(); + + VkShaderModuleCreateInfo create_info = {}; + create_info.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; + create_info.codeSize = code.size(); + create_info.pCode = reinterpret_cast<const uint32_t*>(code.data()); + + VkShaderModule shader_module; + if (vkCreateShaderModule(vk_device, &create_info, nullptr, + &shader_module) != VK_SUCCESS) + { + throw std::runtime_error("Failed to create shader module."); + } + + return shader_module; +} +} + +namespace VK +{ + +Device::Device(VkPhysicalDevice vk_physical_device, bool with_swapchain) +{ + this->physical_device = vk_physical_device; + + std::vector<VkQueueFamilyProperties> queue_family_properties; + + // Get queue families. + { + vkGetPhysicalDeviceQueueFamilyProperties( + vk_physical_device, &this->queue_families_count, nullptr); + queue_family_properties.resize(this->queue_families_count); + vkGetPhysicalDeviceQueueFamilyProperties( + vk_physical_device, &this->queue_families_count, + queue_family_properties.data()); + } + + // Get information from physical device. + { + VkPhysicalDeviceProperties physical_properties = {}; + vkGetPhysicalDeviceProperties(vk_physical_device, &physical_properties); + VkPhysicalDeviceFeatures supported_features = {}; + vkGetPhysicalDeviceFeatures(vk_physical_device, &supported_features); + +#ifdef DEBUG + std::cout << "Name: " << physical_properties.deviceName << std::endl; + std::cout << "API version: " << physical_properties.apiVersion << + std::endl; + std::cout << "Driver version: " << physical_properties.driverVersion << + std::endl; + std::cout << "Vendor ID: " << physical_properties.vendorID << std::endl; + std::cout << "Device ID: " << physical_properties.deviceID << std::endl; + std::cout << "Device type: " << physical_properties.deviceType << + std::endl; +#endif + + std::vector<VkDeviceQueueCreateInfo> device_queue_create_infos; + std::vector<std::vector<float>> queue_priorities( + queue_family_properties.size()); + device_queue_create_infos.resize(queue_family_properties.size()); + for(auto i{0}; i < queue_family_properties.size(); i++) + { + // Give different priorities to queues. + int queue_count = queue_family_properties[i].queueCount; + queue_priorities[i].resize(queue_count); + float priority_unity = 1.0f/queue_count; + for(auto ii{0}; ii < queue_count; ii++) + queue_priorities[i][ii] = priority_unity * (queue_count - ii); + + device_queue_create_infos[i].sType = + VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + device_queue_create_infos[i].pNext = nullptr; + device_queue_create_infos[i].flags = 0; + device_queue_create_infos[i].queueFamilyIndex = i; + device_queue_create_infos[i].queueCount = queue_priorities[i].size(); + device_queue_create_infos[i].pQueuePriorities = + queue_priorities[i].data(); + } + + VkPhysicalDeviceFeatures required_features = {}; + required_features.multiDrawIndirect = supported_features.multiDrawIndirect; + required_features.geometryShader = VK_TRUE; + required_features.tessellationShader = VK_TRUE; + required_features.samplerAnisotropy = VK_TRUE; + + std::vector<const char*> device_extensions; + if(with_swapchain) + device_extensions.emplace_back(VK_KHR_SWAPCHAIN_EXTENSION_NAME); + + VkDeviceCreateInfo device_create_info = {}; + device_create_info.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + device_create_info.pNext = nullptr; + device_create_info.flags = 0; + device_create_info.queueCreateInfoCount = device_queue_create_infos.size(); + device_create_info.pQueueCreateInfos = device_queue_create_infos.data(); + device_create_info.enabledLayerCount = 0; + device_create_info.ppEnabledLayerNames = nullptr; + device_create_info.enabledExtensionCount = device_extensions.size(); + if(device_extensions.size() == 0) + device_create_info.ppEnabledExtensionNames = nullptr; + else + device_create_info.ppEnabledExtensionNames = device_extensions.data(); + + device_create_info.pEnabledFeatures = &required_features; + + if(vkCreateDevice(this->physical_device, &device_create_info, nullptr, + &this->device) != VK_SUCCESS) + throw std::runtime_error("Failed to create Vulkan physical device."); + } + + // Load Shaders + { + this->vert_shader_module = create_shader_module( + this->device, DATA_DIR "/glsl/vert.spv"); + this->frag_shader_module = create_shader_module( + this->device, DATA_DIR "/glsl/frag.spv"); + } + + this->queue_families = static_cast<QueueFamily*>( + std::malloc(this->queue_families_count * sizeof(QueueFamily))); + for(auto i{0}; i < this->queue_families_count; i++) + { + new(&this->queue_families[i])QueueFamily( + this, i, queue_family_properties[i]); + + // Select families with graphics support. + auto &family_properties = this->queue_families[i].family_properties; + if(family_properties.queueCount > 0 && + family_properties.queueFlags & VK_QUEUE_GRAPHICS_BIT) + this->queue_families_with_graphics.push_back( + &this->queue_families[i]); + + // Select families with presentation support. + VkBool32 present_supported; + vkGetPhysicalDeviceSurfaceSupportKHR( + vk_physical_device, i, cg_core.window_surface, &present_supported); + if(present_supported) + this->queue_families_with_presentation.push_back( + &this->queue_families[i]); + } +} + +Device::~Device() +{ + for(auto i{0}; i < this->queue_families_count; i++) + this->queue_families[i].~QueueFamily(); + std::free(this->queue_families); + + // Destroy shaders + vkDestroyShaderModule(this->device, this->vert_shader_module, nullptr); + vkDestroyShaderModule(this->device, this->frag_shader_module, nullptr); + + vkDeviceWaitIdle(this->device); + vkDestroyDevice(this->device, nullptr); +} + +bool +Device::select_memory_type( + uint32_t *memory_type_index, VkMemoryRequirements *vk_memory_requirements, + VkMemoryPropertyFlags vk_property_flags) +{ + VkPhysicalDeviceMemoryProperties vk_memory_properties; + vkGetPhysicalDeviceMemoryProperties( + this->physical_device, &vk_memory_properties); + + for (auto i{0}; i < vk_memory_properties.memoryTypeCount; i++) + { + if(vk_memory_requirements->memoryTypeBits & (1 << i)) + { + const VkMemoryType& type = vk_memory_properties.memoryTypes[i]; + + if ((type.propertyFlags & vk_property_flags) == vk_property_flags) + { + *memory_type_index = i; + return true; + } + } + } + + return false; +} + +QueueFamily* +Device::get_queue_family_with_graphics() const +{ + /* + Returns a random queue family, so not all commands in the engine use the + same queue. + TODO: There must be a better way of doing this. + */ + std::uniform_int_distribution<std::size_t> random_distribution{ + 0, this->queue_families_with_graphics.size() -1}; + auto random = random_distribution(random_number_generator); + return this->queue_families_with_graphics[0]; +} + +QueueFamily* +Device::get_queue_family_with_presentation() const +{ + /* + Returns a random queue family, so not all commands in the engine use the + same queue. + TODO: There must be a better way of doing this. + */ + std::uniform_int_distribution<std::size_t> random_distribution{ + 0, this->queue_families_with_presentation.size() -1}; + auto random = random_distribution(random_number_generator); + return this->queue_families_with_presentation[0]; +} + +} diff --git a/src/vk/device.hpp b/src/vk/device.hpp new file mode 100644 index 0000000..56bd6e4 --- /dev/null +++ b/src/vk/device.hpp @@ -0,0 +1,61 @@ +/* + * Copyright 2022 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CANDY_GEAR_VK_DEVICE_H +#define CANDY_GEAR_VK_DEVICE_H 1 + +#include <cstdlib> +#include <vector> + +#include "core.hpp" +#include "queue_family.hpp" + +namespace VK +{ + +struct Device +{ + uint32_t queue_families_count; + QueueFamily *queue_families; + std::vector<QueueFamily*> queue_families_with_graphics; + std::vector<QueueFamily*> queue_families_with_presentation; + +public: + VkDevice device; + VkPhysicalDevice physical_device; + VkShaderModule vert_shader_module; + VkShaderModule frag_shader_module; + + bool with_swapchain; + + Device(VkPhysicalDevice vk_physical_device, bool with_swapchain); + ~Device(); + + bool + select_memory_type( + uint32_t *memoryTypeIndex, VkMemoryRequirements *vk_memory_requirements, + VkMemoryPropertyFlags vk_property_flags); + + QueueFamily* + get_queue_family_with_graphics() const; + + QueueFamily* + get_queue_family_with_presentation() const; +}; + +} + +#endif /* CANDY_GEAR_VK_DEVICE_H */ diff --git a/src/vk/graphics_pipeline.cpp b/src/vk/graphics_pipeline.cpp new file mode 100644 index 0000000..1c13425 --- /dev/null +++ b/src/vk/graphics_pipeline.cpp @@ -0,0 +1,1009 @@ +/* + * Copyright 2022 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "graphics_pipeline.hpp" + +#include <array> +#include <stdexcept> + +#include "../core.hpp" +#include "core.hpp" +#include "image.hpp" +#include "uniform_buffer.hpp" +#include "vertex.hpp" + +namespace +{ + +void +load_descriptor_set_layout_model_instance(void *obj) +{ + auto self = static_cast<VK::GraphicsPipeline*>(obj); + + std::array<VkDescriptorSetLayoutBinding, 2> layout_bindings{}; + + layout_bindings[0].binding = 0; + layout_bindings[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + layout_bindings[0].descriptorCount = 1; + layout_bindings[0].stageFlags = VK_SHADER_STAGE_VERTEX_BIT; + layout_bindings[0].pImmutableSamplers = nullptr; + + layout_bindings[1].binding = 1; + layout_bindings[1].descriptorType = + VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + layout_bindings[1].descriptorCount = 1; + layout_bindings[1].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT; + layout_bindings[1].pImmutableSamplers = nullptr; + + VkDescriptorSetLayoutCreateInfo layout_info{}; + layout_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; + layout_info.pNext = nullptr; + layout_info.flags = 0; + layout_info.bindingCount = static_cast<uint32_t>(layout_bindings.size()); + layout_info.pBindings = layout_bindings.data(); + + if(vkCreateDescriptorSetLayout( + cg_core.vk_device_with_swapchain->device, &layout_info, nullptr, + &self->descriptor_set_layout_model_instance) != VK_SUCCESS) + throw CommandError{ + "Failed to create Vulkan descriptor set layout for model instance."}; +} + +void +unload_descriptor_set_layout_model_instance(void *obj) +{ + auto self = static_cast<VK::GraphicsPipeline*>(obj); + + vkDestroyDescriptorSetLayout( + cg_core.vk_device_with_swapchain->device, + self->descriptor_set_layout_model_instance, nullptr); +} + +void +load_descriptor_set_layout_view_projection(void *obj) +{ + auto self = static_cast<VK::GraphicsPipeline*>(obj); + + VkDescriptorSetLayoutBinding layout_binding{}; + layout_binding.binding = 0; + layout_binding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + layout_binding.descriptorCount = 1; + layout_binding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT; + layout_binding.pImmutableSamplers = nullptr; + + VkDescriptorSetLayoutCreateInfo layout_info{}; + layout_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; + layout_info.pNext = nullptr; + layout_info.flags = 0; + layout_info.bindingCount = 1; + layout_info.pBindings = &layout_binding; + + if(vkCreateDescriptorSetLayout( + cg_core.vk_device_with_swapchain->device, &layout_info, nullptr, + &self->descriptor_set_layout_view_projection) != VK_SUCCESS) + throw CommandError{ + "Failed to create Vulkan descriptor set layout for view projection."}; +} + +void +unload_descriptor_set_layout_view_projection(void *obj) +{ + auto self = static_cast<VK::GraphicsPipeline*>(obj); + + vkDestroyDescriptorSetLayout( + cg_core.vk_device_with_swapchain->device, + self->descriptor_set_layout_view_projection, nullptr); +} + +void +load_pipeline_layout(void *obj) +{ + auto self = static_cast<VK::GraphicsPipeline*>(obj); + + std::array<VkDescriptorSetLayout, 2> set_layouts{ + self->descriptor_set_layout_model_instance, + self->descriptor_set_layout_view_projection}; + + VkPipelineLayoutCreateInfo pipeline_layout_info{}; + pipeline_layout_info.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; + pipeline_layout_info.setLayoutCount = set_layouts.size(); + pipeline_layout_info.pSetLayouts = set_layouts.data(); + pipeline_layout_info.pushConstantRangeCount = 0; + pipeline_layout_info.pPushConstantRanges = nullptr; + + if(vkCreatePipelineLayout( + cg_core.vk_device_with_swapchain->device, + &pipeline_layout_info, nullptr, &self->pipeline_layout) != VK_SUCCESS) + throw CommandError{ + "Failed to create Vulkan descriptor set layout for view projection."}; +} + +void +unload_pipeline_layout(void *obj) +{ + auto self = static_cast<VK::GraphicsPipeline*>(obj); + + vkDestroyPipelineLayout( + cg_core.vk_device_with_swapchain->device, self->pipeline_layout, nullptr); +} + +void +load_uniform_buffer(void *obj) +{ + auto self = static_cast<VK::GraphicsPipeline*>(obj); + + try + { + self->ub_view_projection.reserve(cg_core.vk_swapchain->images_count); + for(auto i{0}; i < cg_core.vk_swapchain->images_count; i++) + self->ub_view_projection.emplace_back( + cg_core.vk_device_with_swapchain, sizeof(VK::UBOViewProjection)); + } + catch(const std::exception& e) + { + throw CommandError{e.what()}; + } +} + +void +unload_uniform_buffer(void *obj) +{ + auto self = static_cast<VK::GraphicsPipeline*>(obj); + + self->ub_view_projection.clear(); +} + +void +load_descriptor_pool(void *obj) +{ + auto self = static_cast<VK::GraphicsPipeline*>(obj); + + VkDescriptorPoolSize descriptor_pool_size{}; + descriptor_pool_size.type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + descriptor_pool_size.descriptorCount = self->ub_view_projection.size(); + + VkDescriptorPoolCreateInfo pool_info{}; + pool_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; + pool_info.pNext = nullptr; + pool_info.flags = 0; + pool_info.maxSets = self->ub_view_projection.size(); + pool_info.poolSizeCount = 1; + pool_info.pPoolSizes = &descriptor_pool_size; + + if(vkCreateDescriptorPool( + cg_core.vk_device_with_swapchain->device, + &pool_info, nullptr, &self->descriptor_pool) != VK_SUCCESS) + throw CommandError{"Failed to create a Vulkan descriptor pool."}; +} + +void +unload_descriptor_pool(void *obj) +{ + auto self = static_cast<VK::GraphicsPipeline*>(obj); + + vkDestroyDescriptorPool( + cg_core.vk_device_with_swapchain->device, self->descriptor_pool, nullptr); +} + +void +load_descriptor_sets(void *obj) +{ + auto self = static_cast<VK::GraphicsPipeline*>(obj); + + std::vector<VkDescriptorSetLayout> layouts( + cg_core.vk_swapchain->images_count, + self->descriptor_set_layout_view_projection); + + VkDescriptorSetAllocateInfo alloc_info{}; + alloc_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; + alloc_info.descriptorPool = self->descriptor_pool; + alloc_info.descriptorSetCount = self->ub_view_projection.size(); + alloc_info.pSetLayouts = layouts.data(); + + self->view_projection_descriptor_sets.resize( + cg_core.vk_swapchain->images_count); + if(vkAllocateDescriptorSets( + cg_core.vk_device_with_swapchain->device, &alloc_info, + self->view_projection_descriptor_sets.data()) != VK_SUCCESS) + throw CommandError{"Failed to create Vulkan descriptor set."}; +} + +void +load_descriptor_sets_to_buffers(void *obj) +{ + auto self = static_cast<VK::GraphicsPipeline*>(obj); + + for(auto i{0}; i < self->ub_view_projection.size(); i++) + { + VkDescriptorBufferInfo buffer_info{}; + buffer_info.buffer = self->ub_view_projection[i].buffer; + buffer_info.offset = 0; + buffer_info.range = sizeof(VK::UBOViewProjection); + + VkWriteDescriptorSet write_descriptor{}; + write_descriptor.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + write_descriptor.dstSet = self->view_projection_descriptor_sets[i]; + write_descriptor.dstBinding = 0; + write_descriptor.dstArrayElement = 0; + write_descriptor.descriptorCount = 1; + write_descriptor.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + write_descriptor.pBufferInfo = &buffer_info; + write_descriptor.pImageInfo = nullptr; + write_descriptor.pTexelBufferView = nullptr; + + vkUpdateDescriptorSets( + cg_core.vk_device_with_swapchain->device, 1, &write_descriptor, 0, + nullptr); + } +} + +void +load_depth_image(void *obj) +{ + auto self = static_cast<VK::GraphicsPipeline*>(obj); + + VkExtent3D extent3d{}; + extent3d.width = cg_core.screen_width; + extent3d.height = cg_core.screen_height; + extent3d.depth = 1; + + try + { + VK::Image::create( + cg_core.vk_device_with_swapchain, + &self->depth_image, + &self->depth_image_memory, + VK_FORMAT_D32_SFLOAT, + extent3d, + 1, + VK_IMAGE_TILING_OPTIMAL, + VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT + ); + } + catch(VK::Image::Error error) + { + std::string error_message{"Failed to create depth image → "}; + error_message += error.what(); + throw CommandError{error_message}; + } +} + +void +unload_depth_image(void *obj) +{ + auto self = static_cast<VK::GraphicsPipeline*>(obj); + + vkDestroyImage( + cg_core.vk_device_with_swapchain->device, self->depth_image, nullptr); + vkFreeMemory( + cg_core.vk_device_with_swapchain->device, self->depth_image_memory, + nullptr); +} + +void +load_depth_image_view(void *obj) +{ + auto self = static_cast<VK::GraphicsPipeline*>(obj); + + try + { + VK::Image::create_view( + cg_core.vk_device_with_swapchain, &self->depth_image_view, + self->depth_image, + VK_FORMAT_D32_SFLOAT, VK_IMAGE_ASPECT_DEPTH_BIT); + } + catch(VK::Image::Error error) + { + std::string error_message{"Failed to create depth image view → "}; + error_message += error.what(); + throw CommandError{error_message}; + } +} + +void +unload_depth_image_view(void *obj) +{ + auto self = static_cast<VK::GraphicsPipeline*>(obj); + + vkDestroyImageView( + cg_core.vk_device_with_swapchain->device, self->depth_image_view, nullptr); +} + +void +load_render_pass(void *obj) +{ + auto self = static_cast<VK::GraphicsPipeline*>(obj); + + std::array<VkAttachmentDescription, 2> attachments{}; + // Color attachment. + attachments[0].flags = 0; + attachments[0].format = cg_core.vk_swapchain->image_format; + attachments[0].samples = VK_SAMPLE_COUNT_1_BIT; + attachments[0].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + attachments[0].storeOp = VK_ATTACHMENT_STORE_OP_STORE; + attachments[0].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + attachments[0].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + attachments[0].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + attachments[0].finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + // Depth attachment. + attachments[1].flags = 0; + attachments[1].format = VK_FORMAT_D32_SFLOAT; + attachments[1].samples = VK_SAMPLE_COUNT_1_BIT; + attachments[1].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + attachments[1].storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + attachments[1].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + attachments[1].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + attachments[1].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + attachments[1].finalLayout = + VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + + VkAttachmentReference color_attachment_ref{}; + color_attachment_ref.attachment = 0; + color_attachment_ref.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + VkAttachmentReference depth_attachment_ref{}; + depth_attachment_ref.attachment = 1; + depth_attachment_ref.layout = + VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + + VkSubpassDescription subpass = {}; + subpass.flags = 0; + subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + subpass.inputAttachmentCount = 0; + subpass.pInputAttachments = nullptr; + subpass.colorAttachmentCount = 1; + subpass.pColorAttachments = &color_attachment_ref; + subpass.pResolveAttachments = nullptr; + subpass.pDepthStencilAttachment = &depth_attachment_ref; + subpass.preserveAttachmentCount = 0; + subpass.pPreserveAttachments = nullptr; + + VkSubpassDependency dependency = {}; + dependency.srcSubpass = VK_SUBPASS_EXTERNAL; + dependency.dstSubpass = 0; + dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | + VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT; + dependency.srcAccessMask = 0; + dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | + VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT; + dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | + VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | + VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + + VkRenderPassCreateInfo render_pass_info{}; + render_pass_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; + render_pass_info.pNext = nullptr; + render_pass_info.flags = 0; + render_pass_info.attachmentCount = attachments.size(); + render_pass_info.pAttachments = attachments.data(); + render_pass_info.subpassCount = 1; + render_pass_info.pSubpasses = &subpass; + render_pass_info.dependencyCount = 1; + render_pass_info.pDependencies = &dependency; + + if(vkCreateRenderPass( + cg_core.vk_device_with_swapchain->device, &render_pass_info, + nullptr, &self->render_pass) != VK_SUCCESS) + throw CommandError{"Failed to create Vulkan Render Pass."}; +} + +void +unload_render_pass(void *obj) +{ + auto self = static_cast<VK::GraphicsPipeline*>(obj); + + vkDestroyRenderPass( + cg_core.vk_device_with_swapchain->device, self->render_pass, nullptr); +} + +void +load_framebuffer(void *obj) +{ + auto self = static_cast<VK::GraphicsPipeline*>(obj); + + self->swapchain_framebuffers.resize(cg_core.vk_swapchain->images_count); + for (auto i{0}; i < cg_core.vk_swapchain->images_count; i++) + { + std::array<VkImageView, 2> attachments = { + cg_core.vk_swapchain->image_views[i], + self->depth_image_view + }; + + VkFramebufferCreateInfo framebuffer_info{}; + framebuffer_info.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; + framebuffer_info.renderPass = self->render_pass; + framebuffer_info.attachmentCount = attachments.size(); + framebuffer_info.pAttachments = attachments.data(); + framebuffer_info.width = cg_core.screen_width; + framebuffer_info.height = cg_core.screen_height; + + framebuffer_info.layers = 1; + + if(vkCreateFramebuffer( + cg_core.vk_device_with_swapchain->device, &framebuffer_info, nullptr, + &self->swapchain_framebuffers[i]) != VK_SUCCESS) + throw CommandError{"Failed to create Vulkan Framebuffer."}; + } +} + +void +unload_framebuffer(void *obj) +{ + auto self = static_cast<VK::GraphicsPipeline*>(obj); + + for(auto framebuffer: self->swapchain_framebuffers) + vkDestroyFramebuffer( + cg_core.vk_device_with_swapchain->device, framebuffer, nullptr); +} + +void +load_pipeline(void *obj) +{ + auto self = static_cast<VK::GraphicsPipeline*>(obj); + + VkPipelineShaderStageCreateInfo vert_shader_stage_info = {}; + vert_shader_stage_info.sType = + VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + vert_shader_stage_info.pNext = nullptr; + vert_shader_stage_info.flags = 0; + vert_shader_stage_info.stage = VK_SHADER_STAGE_VERTEX_BIT; + vert_shader_stage_info.module = + cg_core.vk_device_with_swapchain->vert_shader_module; + vert_shader_stage_info.pName = "main"; + vert_shader_stage_info.pSpecializationInfo = nullptr; + + VkPipelineShaderStageCreateInfo frag_shader_stage_info = {}; + frag_shader_stage_info.sType = + VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + frag_shader_stage_info.pNext = nullptr; + frag_shader_stage_info.flags = 0; + frag_shader_stage_info.stage = VK_SHADER_STAGE_FRAGMENT_BIT; + frag_shader_stage_info.module = + cg_core.vk_device_with_swapchain->frag_shader_module; + frag_shader_stage_info.pName = "main"; + frag_shader_stage_info.pSpecializationInfo = nullptr; + + VkPipelineShaderStageCreateInfo shader_stages[] = { + vert_shader_stage_info, + frag_shader_stage_info + }; + + VkVertexInputBindingDescription vertex_input_binding{}; + vertex_input_binding.binding = 0; + vertex_input_binding.stride = sizeof(VK::Vertex); + vertex_input_binding.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; + + std::array<VkVertexInputAttributeDescription, 4> vertex_attribute{}; + // Position. + vertex_attribute[0].location = 0; + vertex_attribute[0].binding = 0; + vertex_attribute[0].format = VK_FORMAT_R32G32B32_SFLOAT; + vertex_attribute[0].offset = offsetof(VK::Vertex, position); + // Normal. + vertex_attribute[1].location = 1; + vertex_attribute[1].binding = 0; + vertex_attribute[1].format = VK_FORMAT_R32G32B32_SFLOAT; + vertex_attribute[1].offset = offsetof(VK::Vertex, normal); + // Color. + vertex_attribute[2].location = 2; + vertex_attribute[2].binding = 0; + vertex_attribute[2].format = VK_FORMAT_R32G32B32_SFLOAT; + vertex_attribute[2].offset = offsetof(VK::Vertex, color); + // Texture coordinate. + vertex_attribute[3].location = 3; + vertex_attribute[3].binding = 0; + vertex_attribute[3].format = VK_FORMAT_R32G32_SFLOAT; + vertex_attribute[3].offset = offsetof(VK::Vertex, texture_coord); + + VkPipelineVertexInputStateCreateInfo vertex_input_info = {}; + vertex_input_info.sType = + VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; + vertex_input_info.pNext = nullptr; + vertex_input_info.flags = 0; + vertex_input_info.vertexBindingDescriptionCount = 1; + vertex_input_info.pVertexBindingDescriptions = &vertex_input_binding; + vertex_input_info.vertexAttributeDescriptionCount = + static_cast<uint32_t>(vertex_attribute.size()); + vertex_input_info.pVertexAttributeDescriptions = vertex_attribute.data(); + + VkPipelineInputAssemblyStateCreateInfo input_assembly = {}; + input_assembly.sType = + VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; + input_assembly.pNext = nullptr; + input_assembly.flags = 0; + input_assembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + input_assembly.primitiveRestartEnable = VK_FALSE; + + VkViewport viewport = {}; + viewport.x = 0.0f; + viewport.y = 0.0f; + viewport.width = static_cast<float>(cg_core.screen_width); + viewport.height = static_cast<float>(cg_core.screen_height); + viewport.minDepth = 0.0f; + viewport.maxDepth = 1.0f; + + VkRect2D scissor = {}; + scissor.offset = {0, 0}; + scissor.extent = {cg_core.screen_width, cg_core.screen_height}; + + VkPipelineViewportStateCreateInfo viewport_state = {}; + viewport_state.sType = + VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; + viewport_state.pNext = nullptr; + viewport_state.flags = 0; + viewport_state.viewportCount = 1; + viewport_state.pViewports = &viewport; + viewport_state.scissorCount = 1; + viewport_state.pScissors = &scissor; + + VkPipelineRasterizationStateCreateInfo rasterizer = {}; + rasterizer.sType = + VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; + rasterizer.pNext = nullptr; + rasterizer.flags = 0; + rasterizer.depthClampEnable = VK_FALSE; + rasterizer.rasterizerDiscardEnable = VK_FALSE; + rasterizer.polygonMode = VK_POLYGON_MODE_FILL; + rasterizer.cullMode = VK_CULL_MODE_NONE; + rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; + rasterizer.depthBiasEnable = VK_FALSE; + rasterizer.depthBiasConstantFactor = 0.0f; + rasterizer.depthBiasClamp = 0.0f; + rasterizer.depthBiasSlopeFactor = 0.0f; + rasterizer.lineWidth = 1.0f; + + VkPipelineMultisampleStateCreateInfo multisampling = {}; + multisampling.sType = + VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; + multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; + multisampling.sampleShadingEnable = VK_FALSE; + multisampling.minSampleShading = 1.0f; + multisampling.pSampleMask = nullptr; + multisampling.alphaToCoverageEnable = VK_FALSE; + multisampling.alphaToOneEnable = VK_FALSE; + + VkPipelineDepthStencilStateCreateInfo depth_stencil = {}; + depth_stencil.sType = + VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO; + depth_stencil.depthTestEnable = VK_TRUE; + depth_stencil.depthWriteEnable = VK_TRUE; + depth_stencil.depthCompareOp = VK_COMPARE_OP_LESS; + depth_stencil.depthBoundsTestEnable = VK_FALSE; + depth_stencil.minDepthBounds = 0.0f; + depth_stencil.maxDepthBounds = 1.0f; + depth_stencil.stencilTestEnable = VK_FALSE; + depth_stencil.front = {}; + depth_stencil.back = {}; + + VkPipelineColorBlendAttachmentState color_blend_attachment = {}; + color_blend_attachment.blendEnable = VK_FALSE; + color_blend_attachment.srcColorBlendFactor = VK_BLEND_FACTOR_ONE; + color_blend_attachment.dstColorBlendFactor = VK_BLEND_FACTOR_ZERO; + color_blend_attachment.colorBlendOp = VK_BLEND_OP_ADD; + color_blend_attachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE; + color_blend_attachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO; + color_blend_attachment.alphaBlendOp = VK_BLEND_OP_ADD; + color_blend_attachment.colorWriteMask = + VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | + VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; + + VkPipelineColorBlendStateCreateInfo color_blending = {}; + color_blending.sType = + VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; + color_blending.pNext = nullptr; + color_blending.flags = 0; + color_blending.logicOpEnable = VK_FALSE; + color_blending.logicOp = VK_LOGIC_OP_COPY; + 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; + + VkDynamicState dynamic_states[] = { + VK_DYNAMIC_STATE_VIEWPORT, + VK_DYNAMIC_STATE_LINE_WIDTH + }; + + VkPipelineDynamicStateCreateInfo dynamic_state_info = {}; + dynamic_state_info.sType = + VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; + dynamic_state_info.dynamicStateCount = 2; + dynamic_state_info.pDynamicStates = dynamic_states; + + VkGraphicsPipelineCreateInfo pipeline_info{}; + pipeline_info.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; + pipeline_info.pNext = nullptr; + pipeline_info.flags = 0; + pipeline_info.stageCount = 2; + pipeline_info.pStages = shader_stages; + pipeline_info.pVertexInputState = &vertex_input_info; + pipeline_info.pInputAssemblyState = &input_assembly; + pipeline_info.pTessellationState = nullptr; + pipeline_info.pViewportState = &viewport_state; + pipeline_info.pRasterizationState = &rasterizer; + pipeline_info.pMultisampleState = &multisampling; + pipeline_info.pDepthStencilState = &depth_stencil; + pipeline_info.pColorBlendState = &color_blending; + pipeline_info.pDynamicState = &dynamic_state_info; + pipeline_info.layout = self->pipeline_layout; + pipeline_info.renderPass = self->render_pass; + pipeline_info.subpass = 0; + pipeline_info.basePipelineHandle = VK_NULL_HANDLE; + pipeline_info.basePipelineIndex = -1; + + if(vkCreateGraphicsPipelines( + cg_core.vk_device_with_swapchain->device, VK_NULL_HANDLE, 1, + &pipeline_info, nullptr, &self->graphic_pipeline) != VK_SUCCESS) + throw CommandError{"Failed to create graphics pipeline."}; +} + +void +unload_pipeline(void *obj) +{ + auto self = static_cast<VK::GraphicsPipeline*>(obj); + + vkDestroyPipeline( + cg_core.vk_device_with_swapchain->device, self->graphic_pipeline, nullptr); +} + +void +load_frame_sync(void *obj) +{ + auto self = static_cast<VK::GraphicsPipeline*>(obj); + + self->image_available_semaphores.resize(self->max_frames_in_flight); + self->render_finished_semaphores.resize(self->max_frames_in_flight); + self->in_flight_fences.resize(self->max_frames_in_flight); + + VkSemaphoreCreateInfo semaphore_info = {}; + semaphore_info.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + semaphore_info.pNext = nullptr; + semaphore_info.flags = 0; + + VkFenceCreateInfo fence_info = {}; + fence_info.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; + fence_info.pNext = nullptr; + fence_info.flags = VK_FENCE_CREATE_SIGNALED_BIT; + + // FIXME: if this loop fails, it will not destroy the semaphores. + for(auto i{0}; i < self->max_frames_in_flight; i++) + { + if(vkCreateSemaphore( + cg_core.vk_device_with_swapchain->device, &semaphore_info, + nullptr, &self->image_available_semaphores[i]) != VK_SUCCESS || + vkCreateSemaphore( + cg_core.vk_device_with_swapchain->device, &semaphore_info, + nullptr, &self->render_finished_semaphores[i]) != VK_SUCCESS || + vkCreateFence(cg_core.vk_device_with_swapchain->device, &fence_info, + nullptr, &self->in_flight_fences[i]) != VK_SUCCESS) + throw CommandError{"Failed to create semaphores."}; + } +} + +void +unload_frame_sync(void *obj) +{ + auto self = static_cast<VK::GraphicsPipeline*>(obj); + + vkDeviceWaitIdle(cg_core.vk_device_with_swapchain->device); + + for(auto i{0}; i < self->max_frames_in_flight; i++) + { + vkDestroySemaphore(cg_core.vk_device_with_swapchain->device, + self->render_finished_semaphores[i], nullptr); + vkDestroySemaphore(cg_core.vk_device_with_swapchain->device, + self->image_available_semaphores[i], nullptr); + vkDestroyFence(cg_core.vk_device_with_swapchain->device, + self->in_flight_fences[i], nullptr); + } +} + +void +load_queue_family(void *obj) +{ + auto self = static_cast<VK::GraphicsPipeline*>(obj); + + self->queue_family = + cg_core.vk_device_with_swapchain->get_queue_family_with_presentation(); +} + +void +load_command_pool(void *obj) +{ + auto self = static_cast<VK::GraphicsPipeline*>(obj); + + VkCommandPoolCreateInfo create_info{}; + create_info.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; + create_info.pNext = nullptr; + create_info.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; + create_info.queueFamilyIndex = self->queue_family->family_index; + + vkCreateCommandPool( + self->queue_family->device->device, &create_info, nullptr, + &self->command_pool); +} + +void +unload_command_pool(void *obj) +{ + auto self = static_cast<VK::GraphicsPipeline*>(obj); + + vkWaitForFences(cg_core.vk_device_with_swapchain->device, 2, + self->in_flight_fences.data(), VK_TRUE, + std::numeric_limits<uint64_t>::max()); + vkDestroyCommandPool( + self->queue_family->device->device, self->command_pool, nullptr); +} + +void +load_draw_command_buffer(void *obj) +{ + auto self = static_cast<VK::GraphicsPipeline*>(obj); + + // FIXME: 3 is a magical number, triple buffering. + self->draw_command_buffers.resize(3); + + VkCommandBufferAllocateInfo allocate_info{}; + allocate_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocate_info.pNext = nullptr; + allocate_info.commandPool = self->command_pool; + allocate_info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocate_info.commandBufferCount = self->draw_command_buffers.size(); + + if(vkAllocateCommandBuffers( + self->queue_family->device->device, &allocate_info, + self->draw_command_buffers.data()) != VK_SUCCESS) + throw CommandError{"Vulkan draw command buffers could not be allocated."}; +} + +const CommandChain loader{ + {&load_descriptor_set_layout_model_instance, + &unload_descriptor_set_layout_model_instance}, + {&load_descriptor_set_layout_view_projection, + &unload_descriptor_set_layout_view_projection}, + {&load_pipeline_layout, &unload_pipeline_layout}, + {&load_uniform_buffer, &unload_uniform_buffer}, + {&load_descriptor_pool, &unload_descriptor_pool}, + // By destroying the pool the sets are also destroyed. + {&load_descriptor_sets, nullptr}, + {&load_descriptor_sets_to_buffers, nullptr}, + {&load_depth_image, &unload_depth_image}, + {&load_depth_image_view, &unload_depth_image_view}, + {&load_render_pass, &unload_render_pass}, + {&load_framebuffer, &unload_framebuffer}, + {&load_pipeline, &unload_pipeline}, + {&load_frame_sync, &unload_frame_sync}, + {&load_queue_family, nullptr}, + {&load_command_pool, &unload_command_pool}, + {&load_draw_command_buffer, nullptr} +}; + +} + +namespace VK +{ + +GraphicsPipeline::GraphicsPipeline(): + current_frame{0}, + camera{std::make_shared<VK::Camera>()}, + models_to_draw{cg_core.vk_swapchain->images_count} +{ + loader.execute(this); +} + +GraphicsPipeline::~GraphicsPipeline() +{ + loader.revert(this); +} + +void +GraphicsPipeline::draw() +{ + vkWaitForFences(cg_core.vk_device_with_swapchain->device, 1, + &this->in_flight_fences[this->current_frame], VK_TRUE, + std::numeric_limits<uint64_t>::max()); + vkResetFences(cg_core.vk_device_with_swapchain->device, 1, + &this->in_flight_fences[this->current_frame]); + + uint32_t image_index; + vkAcquireNextImageKHR( + cg_core.vk_device_with_swapchain->device, cg_core.vk_swapchain->swapchain, + std::numeric_limits<uint64_t>::max(), + this->image_available_semaphores[this->current_frame], + VK_NULL_HANDLE, &image_index); + + auto &draw_command_buffer = this->draw_command_buffers[this->current_frame]; + vkResetCommandBuffer(draw_command_buffer, 0); + + // Load command. + { + VkCommandBufferBeginInfo begin_info{}; + begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + begin_info.flags = 0; + begin_info.pInheritanceInfo = nullptr; + if (vkBeginCommandBuffer(draw_command_buffer, &begin_info) != VK_SUCCESS) + { + throw std::runtime_error{"Failed to beggin draw command buffer."}; + } + + // Dark gray blue. + std::array<VkClearValue, 2> clear_values{}; + clear_values[0].color = {0.12f, 0.12f, 0.18f, 1.0f}; + clear_values[1].depthStencil = {1.0f, 0}; + + VkRenderPassBeginInfo render_pass_begin{}; + render_pass_begin.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; + render_pass_begin.pNext = nullptr; + render_pass_begin.renderPass = this->render_pass; + render_pass_begin.framebuffer = this->swapchain_framebuffers[image_index]; + render_pass_begin.renderArea.offset = {0, 0}; + render_pass_begin.renderArea.extent = { + cg_core.screen_width, cg_core.screen_height}; + render_pass_begin.clearValueCount = clear_values.size(); + render_pass_begin.pClearValues = clear_values.data(); + + vkCmdBeginRenderPass( + draw_command_buffer, &render_pass_begin,VK_SUBPASS_CONTENTS_INLINE); + + VkViewport vk_viewport{}; + vk_viewport.width = static_cast<float>(cg_core.screen_width); + vk_viewport.height = static_cast<float>(cg_core.screen_height); + vk_viewport.minDepth = 0.0f; + vk_viewport.maxDepth = 1.0f; + vkCmdSetViewport(draw_command_buffer, 0, 1, &vk_viewport); + + VkRect2D vk_scissor{}; + vk_scissor.extent.width = cg_core.screen_width; + vk_scissor.extent.height = cg_core.screen_height; + vk_scissor.offset.x = 0; + vk_scissor.offset.y = 0; + vkCmdSetScissor(draw_command_buffer, 0, 1, &vk_scissor); + + // Draw models + for(auto& [model, instances] : this->models_to_draw[this->current_frame]) + { + // Commands + { + std::array<VkDescriptorSet, 2> vk_descriptor_sets{ + model->descriptor_sets[image_index], + this->view_projection_descriptor_sets[image_index]}; + VkBuffer vertex_buffers[]{model->vertex_buffer->buffer}; + VkDeviceSize offsets[]{0}; + + vkCmdBindDescriptorSets( + draw_command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, + this->pipeline_layout, 0, vk_descriptor_sets.size(), + vk_descriptor_sets.data(), 0, nullptr); + vkCmdBindPipeline( + draw_command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, + this->graphic_pipeline); + vkCmdBindVertexBuffers( + draw_command_buffer, 0, 1, vertex_buffers, offsets); + vkCmdBindIndexBuffer( + draw_command_buffer, model->index_buffer->buffer, 0, + VK_INDEX_TYPE_UINT32); + vkCmdDrawIndexed( + draw_command_buffer, model->index_count, instances.size(), 0, 0, 0); + } + + VK::UBOModelInstance ubo_model_instance; + + for(int i{0}; i < instances.size(); i++) + { + // Object matrix. + glm::mat4 instance_matrix{1.0f}; + instance_matrix = glm::rotate( + instance_matrix, glm::radians(instances[i].rotation.x), + glm::vec3{1.0, 0.0, 0.0}); + instance_matrix = glm::rotate( + instance_matrix, glm::radians(instances[i].rotation.y), + glm::vec3{0.0, 1.0, 0.0}); + instance_matrix = glm::rotate( + instance_matrix, glm::radians(instances[i].rotation.z), + glm::vec3{0.0, 0.0, 1.0}); + instance_matrix = glm::translate( + instance_matrix, instances[i].position); + + ubo_model_instance.model[i] = instance_matrix; + } + + model->ub_model_instance[image_index].copy_data(&ubo_model_instance); + } + vkCmdEndRenderPass(draw_command_buffer); + + if(vkEndCommandBuffer(draw_command_buffer) != VK_SUCCESS) + throw std::runtime_error{"Failed to end draw command buffer."}; + } + + // Update uniform buffers + { + VK::UBOViewProjection ubo_view_projection{}; + + // View matrix. + ubo_view_projection.view = glm::mat4{1.0f}; + ubo_view_projection.view = glm::translate( + ubo_view_projection.view, this->camera->position); + ubo_view_projection.view = glm::rotate( + ubo_view_projection.view, this->camera->rotation.x, + glm::vec3{1.0, 0.0, 0.0}); + ubo_view_projection.view = glm::rotate( + ubo_view_projection.view, this->camera->rotation.y, + glm::vec3{0.0, 1.0, 0.0}); + ubo_view_projection.view = glm::rotate( + ubo_view_projection.view, this->camera->rotation.z, + glm::vec3{0.0, 0.0, 1.0}); + ubo_view_projection.view = glm::inverse(ubo_view_projection.view); + + // Projection matrix. + ubo_view_projection.proj = glm::perspective( + glm::radians(45.0f), + cg_core.screen_width / static_cast<float>(cg_core.screen_height), + 0.1f, 10.0f); + ubo_view_projection.proj[1][1] *= -1; + + this->ub_view_projection[image_index].copy_data(&ubo_view_projection); + } + + // Submit drawing command. + { + auto queue{this->queue_family->get_queue()}; + + VkSemaphore wait_semaphores[]{ + this->image_available_semaphores[this->current_frame]}; + VkPipelineStageFlags wait_stages[] = + {VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT}; + VkSemaphore signal_semaphores[]{ + this->render_finished_semaphores[this->current_frame]}; + + VkSubmitInfo submit_info{}; + submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + submit_info.pNext = nullptr; + submit_info.waitSemaphoreCount = 1; + submit_info.pWaitSemaphores = wait_semaphores; + submit_info.pWaitDstStageMask = wait_stages; + submit_info.commandBufferCount = 1; + submit_info.pCommandBuffers = &draw_command_buffer; + submit_info.signalSemaphoreCount = 1; + submit_info.pSignalSemaphores = signal_semaphores; + + if(vkQueueSubmit( + queue.queue, 1, &submit_info, + this->in_flight_fences[this->current_frame]) != VK_SUCCESS) + throw std::runtime_error{"Failed to submit draw command buffer."}; + + VkSwapchainKHR swap_chains[]{cg_core.vk_swapchain->swapchain}; + + VkPresentInfoKHR present_info{}; + present_info.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; + present_info.pNext = nullptr; + present_info.waitSemaphoreCount = 1; + present_info.pWaitSemaphores = signal_semaphores; + present_info.swapchainCount = 1; + present_info.pSwapchains = swap_chains; + present_info.pImageIndices = &image_index; + present_info.pResults = nullptr; + + vkQueuePresentKHR(queue.queue, &present_info); + + // Prepare for the next frame. + this->current_frame = + (this->current_frame + 1) % this->max_frames_in_flight; + this->models_to_draw[this->current_frame].clear(); + } +} + +} diff --git a/src/vk/graphics_pipeline.hpp b/src/vk/graphics_pipeline.hpp new file mode 100644 index 0000000..c7cc0a0 --- /dev/null +++ b/src/vk/graphics_pipeline.hpp @@ -0,0 +1,80 @@ +/* + * Copyright 2022 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CANDY_GEAR_VK_GRAPHICS_PIPELINE_H +#define CANDY_GEAR_VK_GRAPHICS_PIPELINE_H 1 + +#include <memory> +#include <unordered_map> + +#include "../command.hpp" +#include "core.hpp" +#include "camera.hpp" +#include "command_pool.hpp" +#include "model.hpp" +#include "model_instance.hpp" +#include "uniform_buffer.hpp" + +namespace VK +{ + +struct GraphicsPipeline +{ + VkDescriptorSetLayout descriptor_set_layout_model_instance; + VkDescriptorSetLayout descriptor_set_layout_view_projection; + VkPipelineLayout pipeline_layout; + + // Depth image. + VkImage depth_image; + VkDeviceMemory depth_image_memory; + VkImageView depth_image_view; + + // FIXME: if this vector get resized, it will cause a segmentation fault! + std::vector<UniformBuffer> ub_view_projection; + + VkDescriptorPool descriptor_pool; + std::vector<VkDescriptorSet> view_projection_descriptor_sets; + + VkRenderPass render_pass; + std::vector<VkFramebuffer> swapchain_framebuffers; + VkPipeline graphic_pipeline; + + QueueFamily *queue_family; + VkCommandPool command_pool; + std::vector<VkCommandBuffer> draw_command_buffers; + + // Buffering control. + static const int max_frames_in_flight{2}; + size_t current_frame; + std::vector<VkSemaphore> image_available_semaphores; + std::vector<VkSemaphore> render_finished_semaphores; + std::vector<VkFence> in_flight_fences; + + std::shared_ptr<Camera> camera; + std::vector<std::unordered_map< + std::shared_ptr<Model>, std::vector<ModelInstance>>> + models_to_draw; + + GraphicsPipeline(); + ~GraphicsPipeline(); + + void + draw(); +}; + +} + +#endif /* CANDY_GEAR_VK_GRAPHICS_PIPELINE_H */ diff --git a/src/vk/image.cpp b/src/vk/image.cpp new file mode 100644 index 0000000..11dc9a5 --- /dev/null +++ b/src/vk/image.cpp @@ -0,0 +1,122 @@ +/* + * Copyright 2022 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "image.hpp" + +#include "../core.hpp" + +namespace VK::Image +{ + +Error::Error(const std::string &m): + error(m) +{ +} + +Error::Error(const char &m): + Error{std::string{m}} +{ +} + +const char* +Error::what() const noexcept +{ + return this->error.c_str(); +} + +void +create( + VK::Device *device, + VkImage *image, + VkDeviceMemory *image_memory, + VkFormat format, + const VkExtent3D &extent3d, + uint32_t mip_levels, + VkImageTiling image_tiling, + VkImageUsageFlags usage) +{ + VkImageCreateInfo image_info{}; + image_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; + image_info.pNext = nullptr; + image_info.flags = 0; + image_info.imageType = VK_IMAGE_TYPE_2D; + image_info.format = format; + image_info.extent = extent3d; + image_info.mipLevels = mip_levels; + image_info.arrayLayers = 1; + image_info.samples = VK_SAMPLE_COUNT_1_BIT; + image_info.tiling = image_tiling; + image_info.usage = usage; + image_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + image_info.queueFamilyIndexCount = 0; + image_info.pQueueFamilyIndices = nullptr; + image_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + + if(vkCreateImage( + device->device, &image_info, nullptr, image) != VK_SUCCESS) + throw Error{"Failed to create Vulkan image."}; + + VkMemoryRequirements memory_requirements; + vkGetImageMemoryRequirements(device->device, *image, &memory_requirements); + + VkMemoryAllocateInfo memory_alloc_info{}; + memory_alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + memory_alloc_info.allocationSize = memory_requirements.size; + device->select_memory_type(&memory_alloc_info.memoryTypeIndex, + &memory_requirements, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); + + if(vkAllocateMemory( + device->device, &memory_alloc_info, nullptr, image_memory) + != VK_SUCCESS) + { + vkDestroyImage(device->device, *image, nullptr); + throw Error{"Failed to allocate memory for Vulkan image."}; + } + + vkBindImageMemory(device->device, *image, *image_memory, 0); +} + +void create_view( + VK::Device *device, + VkImageView *image_view, + const VkImage &image, + VkFormat format, + VkImageAspectFlags image_aspect_flags) +{ + VkImageViewCreateInfo image_view_info{}; + image_view_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + image_view_info.pNext = nullptr; + image_view_info.flags = 0; + image_view_info.image = image; + image_view_info.viewType = VK_IMAGE_VIEW_TYPE_2D; + image_view_info.format = format; + image_view_info.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; + image_view_info.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; + image_view_info.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; + image_view_info.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; + image_view_info.subresourceRange.aspectMask = image_aspect_flags; + image_view_info.subresourceRange.baseMipLevel = 0; + image_view_info.subresourceRange.levelCount = 1; + image_view_info.subresourceRange.baseArrayLayer = 0; + image_view_info.subresourceRange.layerCount = 1; + + if(vkCreateImageView(device->device, &image_view_info, + nullptr, image_view) != VK_SUCCESS) + throw Error{"Failed to create texture view."}; + +} + +} diff --git a/src/vk/image.hpp b/src/vk/image.hpp new file mode 100644 index 0000000..63ebab6 --- /dev/null +++ b/src/vk/image.hpp @@ -0,0 +1,61 @@ +/* + * Copyright 2022 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef BLUE_KITTY_VK_IMAGE_H +#define BLUE_KITTY_VK_IMAGE_H 1 + +#include <memory> +#include <string> + +#include "core.hpp" +#include "device.hpp" + +namespace VK::Image +{ + +struct Error: public std::exception +{ + Error(const std::string &m); + Error(const char &m); + + const char* + what() const noexcept; + +private: + std::string error; +}; + +void +create( + VK::Device *device, + VkImage *image, + VkDeviceMemory *image_memory, + VkFormat format, + const VkExtent3D &extent3d, + uint32_t mip_levels, + VkImageTiling image_tiling, + VkImageUsageFlags usage); + +void +create_view( + VK::Device *device, + VkImageView *image_view, + const VkImage &image, + VkFormat format, + VkImageAspectFlags image_aspect_flags); +} + +#endif /* CANDY_GEAR_VK_IMAGE_H */ diff --git a/src/vk/model.cpp b/src/vk/model.cpp new file mode 100644 index 0000000..d47db8a --- /dev/null +++ b/src/vk/model.cpp @@ -0,0 +1,324 @@ +/* + * Copyright 2022 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "model.hpp" + +#include <array> +#include <fstream> + +#include "../command.hpp" +#include "../core.hpp" +#include "vertex.hpp" + +namespace +{ + +// Data that is only needed for the command chain but not for the Model goes +// here. +struct ModelBuilder +{ + std::string model_path; + VK::Model *model; + + ModelBuilder(VK::Model *m, std::string mp); + ModelBuilder(VK::Model *m, const char* mp); +}; + +ModelBuilder::ModelBuilder(VK::Model *m, std::string mp): + model{m}, + model_path{mp} +{ +} + +ModelBuilder::ModelBuilder(VK::Model *m, const char *mp): + ModelBuilder{m, std::string(mp)} +{ +} + +struct MeshData +{ + glm::vec3 color; + + uint32_t vertex_base; + uint32_t vertex_count; + uint32_t index_base; + uint32_t index_count; +}; + +uint32_t read_uint32_from_file(std::ifstream &input_file) +{ + uint32_t data{}; + input_file.read((char*)&data, sizeof(uint32_t)); + return data; +} + +glm::vec2 read_vec2_from_file(std::ifstream &input_file) +{ + glm::vec2 data{}; + input_file.read((char*)&data.x, sizeof(glm::vec2::value_type)); + input_file.read((char*)&data.y, sizeof(glm::vec2::value_type)); + return data; +} + +glm::vec3 read_vec3_from_file(std::ifstream &input_file) +{ + glm::vec3 data{}; + input_file.read((char*)&data.x, sizeof(glm::vec3::value_type)); + input_file.read((char*)&data.y, sizeof(glm::vec3::value_type)); + input_file.read((char*)&data.z, sizeof(glm::vec3::value_type)); + return data; +} + +void +load_mesh(void *obj) +{ + auto self = static_cast<ModelBuilder*>(obj); + + std::ifstream input_file{self->model_path}; + if(!input_file.is_open()) throw CommandError{"Failed to open file."}; + + std::vector<MeshData> meshes{}; + + self->model->queue_family = + cg_core.vk_device_with_swapchain->get_queue_family_with_graphics(); + VK::Queue transfer_queue{self->model->queue_family->get_queue()}; + + // Load meshes. + { + uint32_t meshes_count{read_uint32_from_file(input_file)}; + meshes.resize(meshes_count); + + for(uint32_t i{0}; i < meshes_count; i++) + { + meshes[i].color = read_vec3_from_file(input_file); + + meshes[i].vertex_base = read_uint32_from_file(input_file); + meshes[i].vertex_count = read_uint32_from_file(input_file); + meshes[i].index_base = read_uint32_from_file(input_file); + meshes[i].index_count = read_uint32_from_file(input_file); + } + } + + // Load vertexes. + { + uint32_t vertex_count{read_uint32_from_file(input_file)}; + std::vector<VK::Vertex> vertexes{vertex_count}; + + for(auto mesh: meshes) + { + for(uint32_t i{mesh.vertex_base}; i < mesh.vertex_count; i++) + { + vertexes[i].position = read_vec3_from_file(input_file); + vertexes[i].normal = read_vec3_from_file(input_file); + vertexes[i].texture_coord = read_vec2_from_file(input_file); + vertexes[i].color = mesh.color; + } + } + + void *vertexes_data{vertexes.data()}; + size_t vertexes_size = sizeof(vertexes[0]) * vertexes.size(); + self->model->source_vertex_buffer = new VK::SourceBuffer{ + self->model->queue_family->device, vertexes_data, vertexes_size}; + self->model->vertex_buffer = new VK::DestinationBuffer{ + self->model->queue_family, self->model->source_vertex_buffer, + VK_BUFFER_USAGE_VERTEX_BUFFER_BIT}; + } + + // Load indexes. + { + self->model->index_count = read_uint32_from_file(input_file); + std::vector<uint32_t> indexes(self->model->index_count); + + for(uint32_t i{0}; i < self->model->index_count; i++) + { + indexes[i] = read_uint32_from_file(input_file); + } + + void *indexes_data{indexes.data()}; + size_t indexes_size{sizeof(indexes[0]) * indexes.size()}; + VK::SourceBuffer source_index_buffer{ + self->model->queue_family->device, indexes_data, indexes_size}; + self->model->index_buffer = new VK::DestinationBuffer{ + self->model->queue_family, &source_index_buffer, + VK_BUFFER_USAGE_INDEX_BUFFER_BIT}; + } +} + +void +unload_mesh(void *obj) +{ + auto self = static_cast<ModelBuilder*>(obj); + + delete self->model->index_buffer; + delete self->model->vertex_buffer; + delete self->model->source_vertex_buffer; +} + +auto +load_uniform_buffers(void *obj) +{ + auto self = static_cast<ModelBuilder*>(obj); + + self->model->ub_model_instance.reserve(cg_core.vk_swapchain->images_count); + for(int i{0}; i < cg_core.vk_swapchain->images_count; i++) + self->model->ub_model_instance.emplace_back( + cg_core.vk_device_with_swapchain, sizeof(VK::UBOModelInstance)); +} + +void +unload_uniform_buffers(void *obj) +{ + auto self = static_cast<ModelBuilder*>(obj); + + self->model->ub_model_instance.clear(); +} + +void +load_descriptor_set_pool(void *obj) +{ + auto self = static_cast<ModelBuilder*>(obj); + + std::array<VkDescriptorPoolSize, 2> descriptor_pool_sizes{}; + descriptor_pool_sizes[0].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + descriptor_pool_sizes[0].descriptorCount = + self->model->ub_model_instance.size(); + descriptor_pool_sizes[1].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + descriptor_pool_sizes[1].descriptorCount = + self->model->ub_model_instance.size(); + + VkDescriptorPoolCreateInfo pool_info{}; + pool_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; + pool_info.pNext = nullptr; + pool_info.flags = 0; + pool_info.maxSets = self->model->ub_model_instance.size(); + pool_info.poolSizeCount = descriptor_pool_sizes.size(); + pool_info.pPoolSizes = descriptor_pool_sizes.data(); + + if(vkCreateDescriptorPool( + self->model->queue_family->device->device, &pool_info, nullptr, + &self->model->descriptor_pool) != VK_SUCCESS) + throw CommandError{"Failed to create a Vulkan descriptor pool."}; +} + +void +unload_descriptor_set_pool(void *obj) +{ + auto self = static_cast<ModelBuilder*>(obj); + + vkDestroyDescriptorPool( + self->model->queue_family->device->device, self->model->descriptor_pool, + nullptr); +} + +void +load_descriptor_sets(void *obj) +{ + auto self = static_cast<ModelBuilder*>(obj); + + std::vector<VkDescriptorSetLayout> layouts( + cg_core.vk_swapchain->images_count, + cg_core.vk_graphics_pipeline->descriptor_set_layout_model_instance); + + VkDescriptorSetAllocateInfo alloc_info{}; + alloc_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; + alloc_info.descriptorPool = self->model->descriptor_pool; + alloc_info.descriptorSetCount = layouts.size(); + alloc_info.pSetLayouts = layouts.data(); + + self->model->descriptor_sets.resize(layouts.size()); + if(vkAllocateDescriptorSets( + self->model->queue_family->device->device, &alloc_info, + self->model->descriptor_sets.data()) != VK_SUCCESS) + CommandError{"Failed to create Vulkan descriptor set."}; +} + +void +load_buffers_to_descriptor_sets(void *obj) +{ + auto self = static_cast<ModelBuilder*>(obj); + + for(auto i{0}; i < self->model->ub_model_instance.size(); i++) + { + VkDescriptorBufferInfo buffer_info{}; + buffer_info.buffer = self->model->ub_model_instance[i].buffer; + buffer_info.offset = 0; + buffer_info.range = sizeof(VK::UBOModelInstance); + + VkDescriptorImageInfo image_info{}; + image_info.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + image_info.imageView = self->model->texture->view; + image_info.sampler = self->model->texture->sampler; + + std::array<VkWriteDescriptorSet, 2> write_descriptors{}; + write_descriptors[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + write_descriptors[0].dstSet = self->model->descriptor_sets[i]; + write_descriptors[0].dstBinding = 0; + write_descriptors[0].dstArrayElement = 0; + write_descriptors[0].descriptorCount = 1; + write_descriptors[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + write_descriptors[0].pBufferInfo = &buffer_info; + write_descriptors[0].pImageInfo = nullptr; + write_descriptors[0].pTexelBufferView = nullptr; + + write_descriptors[1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + write_descriptors[1].dstSet = self->model->descriptor_sets[i]; + write_descriptors[1].dstBinding = 1; + write_descriptors[1].dstArrayElement = 0; + write_descriptors[1].descriptorCount = 1; + write_descriptors[1].descriptorType = + VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + write_descriptors[1].pBufferInfo = nullptr; + write_descriptors[1].pImageInfo = &image_info; + write_descriptors[1].pTexelBufferView = nullptr; + + vkUpdateDescriptorSets( + cg_core.vk_device_with_swapchain->device, write_descriptors.size(), + write_descriptors.data(), 0, nullptr); + } +} + +static const CommandChain loader{ + {&load_mesh, &unload_mesh}, + {&load_uniform_buffers, &unload_uniform_buffers}, + {&load_descriptor_set_pool, &unload_descriptor_set_pool}, + {&load_descriptor_sets, nullptr}, + {&load_buffers_to_descriptor_sets, nullptr}, +}; + +} + +namespace VK +{ + +Model::Model(std::string model_path, std::shared_ptr<Texture> texture): + texture{texture} +{ + ModelBuilder model_builder(this, model_path); + loader.execute(&model_builder); +} + +Model::Model(const char* model_path, std::shared_ptr<Texture> texture): + Model{std::string(model_path), texture} +{ +} + +Model::~Model() +{ + ModelBuilder model_builder(this, ""); + loader.revert(&model_builder); +} + +} diff --git a/src/vk/model.hpp b/src/vk/model.hpp new file mode 100644 index 0000000..d6f8b69 --- /dev/null +++ b/src/vk/model.hpp @@ -0,0 +1,55 @@ +/* + * Copyright 2022 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CANDY_GEAR_VK_MODEL_H +#define CANDY_GEAR_VK_MODEL_H 1 + +#include <string> +#include <vector> + +#include "core.hpp" +#include "destination_buffer.hpp" +#include "queue_family.hpp" +#include "uniform_buffer.hpp" +#include "texture.hpp" + +namespace VK +{ + +struct Model +{ + QueueFamily *queue_family; + + uint32_t index_count; + SourceBuffer *source_vertex_buffer; + DestinationBuffer *index_buffer; + DestinationBuffer *vertex_buffer; + + std::vector<UniformBuffer> ub_model_instance; + + VkDescriptorPool descriptor_pool; + std::vector<VkDescriptorSet> descriptor_sets; + + std::shared_ptr<Texture> texture; + + Model(std::string model_path, std::shared_ptr<Texture> texture); + Model(const char* model_path, std::shared_ptr<Texture> texture); + ~Model(); +}; + +} + +#endif /* CANDY_GEAR_VK_MODEL_H */ diff --git a/src/vk/model_instance.hpp b/src/vk/model_instance.hpp new file mode 100644 index 0000000..1383737 --- /dev/null +++ b/src/vk/model_instance.hpp @@ -0,0 +1,32 @@ +/* + * Copyright 2022 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CANDY_GEAR_VK_MODEL_INSTANCE_H +#define CANDY_GEAR_VK_MODEL_INSTANCE_H 1 + +#include "core.hpp" + +namespace VK +{ + +struct ModelInstance +{ + glm::vec3 position, rotation; +}; + +} + +#endif /* CANDY_GEAR_VK_MODEL_INSTANCE_H */ diff --git a/src/vk/queue.cpp b/src/vk/queue.cpp new file mode 100644 index 0000000..deb59ba --- /dev/null +++ b/src/vk/queue.cpp @@ -0,0 +1,61 @@ +/* + * Copyright 2022 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "queue.hpp" + +#include "queue_family.hpp" + +namespace VK +{ + +Queue::Queue( + VK::QueueFamily *queue_family, VkQueue queue, int queue_index): + queue_family{queue_family}, + queue{queue}, + queue_index{queue_index} +{ + this->queue_family->queue_states[this->queue_index].busy = true; +} + +Queue::Queue(Queue &&that): + queue{that.queue}, + queue_family{that.queue_family}, + queue_index{that.queue_index} +{ + that.queue_family = nullptr; +} + +Queue& Queue::operator=(Queue &&that) +{ + this->queue = that.queue; + this->queue_family = that.queue_family; + this->queue_index = that.queue_index; + + that.queue_family = nullptr; + + return *this; +} + +Queue::~Queue() +{ + if(this->queue_family) + { + std::unique_lock<std::mutex> lock{this->queue_family->queue_mutex}; + this->queue_family->queue_states[this->queue_index].busy = false; + } +} + +} diff --git a/src/vk/queue.hpp b/src/vk/queue.hpp new file mode 100644 index 0000000..955daa5 --- /dev/null +++ b/src/vk/queue.hpp @@ -0,0 +1,82 @@ +/* + * Copyright 2022 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CANDY_GEAR_VK_QUEUE_H +#define CANDY_GEAR_VK_QUEUE_H 1 + +#include "core.hpp" + +namespace VK +{ +class QueueFamily; + +struct Queue +{ + friend class VK::QueueFamily; + + Queue(const Queue &t) = delete; + Queue& operator=(const Queue &t) = delete; + + VkQueue queue; + + template<typename T> + void submit_one_time_command( + const VkCommandBuffer vk_command_buffer, T commands); + + Queue(Queue &&that); + Queue& operator=(Queue &&that); + + ~Queue(); + +private: + VK::QueueFamily *queue_family; + int queue_index; + + Queue(VK::QueueFamily *queue_family, VkQueue queue, int queue_index); +}; + +template<typename T> void +Queue::submit_one_time_command( + const VkCommandBuffer vk_command_buffer, T commands) +{ + VkCommandBufferBeginInfo buffer_begin_info{}; + buffer_begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + buffer_begin_info.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + + vkBeginCommandBuffer(vk_command_buffer, &buffer_begin_info); + + commands(); + + vkEndCommandBuffer(vk_command_buffer); + + VkSubmitInfo submit_info{}; + submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + submit_info.pNext = nullptr; + submit_info.waitSemaphoreCount = 0; + submit_info.pWaitSemaphores = nullptr; + submit_info.pWaitDstStageMask = nullptr; + submit_info.commandBufferCount = 1; + submit_info.pCommandBuffers = &vk_command_buffer; + submit_info.signalSemaphoreCount = 0; + submit_info.pSignalSemaphores = nullptr; + + vkQueueSubmit(this->queue, 1, &submit_info, VK_NULL_HANDLE); + vkQueueWaitIdle(this->queue); +} + +} + +#endif /* CANDY_GEAR_VK_QUEUE_H */ diff --git a/src/vk/queue_family.cpp b/src/vk/queue_family.cpp new file mode 100644 index 0000000..7c91ba2 --- /dev/null +++ b/src/vk/queue_family.cpp @@ -0,0 +1,80 @@ +/* + * Copyright 2022 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "queue_family.hpp" + +#include <iostream> + +#include "../core.hpp" + +namespace VK +{ + +QueueFamily::QueueFamily( + VK::Device *device, uint32_t family_index, + const VkQueueFamilyProperties &queue_family_properties): + queue_mutex{} +{ + +#ifdef DEBUG + std::cout << "Queue quantity: " << queue_family_properties.queueCount << + std::endl; + std::cout << "Graphics: " << + (queue_family_properties.queueFlags & VK_QUEUE_GRAPHICS_BIT ? + "true" : "false") << std::endl; + std::cout << "Compute: " << + (queue_family_properties.queueFlags & VK_QUEUE_COMPUTE_BIT ? + "true" : "false") << std::endl; + std::cout << "Transfer: " << + (queue_family_properties.queueFlags & VK_QUEUE_TRANSFER_BIT ? + "true" : "false") << std::endl; + std::cout << "Sparse Binding: " << + (queue_family_properties.queueFlags & VK_QUEUE_SPARSE_BINDING_BIT ? + "true" : "false") << std::endl; +#endif + + this->device = device; + this->family_index = family_index; + this->family_properties = queue_family_properties; + + // Create queues + { + auto queue_count = this->family_properties.queueCount; + this->queue_states.resize(queue_count); + + for(auto i{0}; i < queue_count; i++) + { + vkGetDeviceQueue(device->device, this->family_index, i, + &this->queue_states[i].queue); + if(this->queue_states[i].queue == VK_NULL_HANDLE) + throw std::runtime_error("Failed to get Vulkan queue."); + } + } +} + +Queue +QueueFamily::get_queue() +{ + std::unique_lock<std::mutex> lock{this->queue_mutex}; + + for(auto i{0}; i < this->queue_states.size(); i++) + if(!this->queue_states[i].busy) + return Queue(this, this->queue_states[i].queue, i); + + throw std::length_error("No free queues found."); +} + +} diff --git a/src/vk/queue_family.hpp b/src/vk/queue_family.hpp new file mode 100644 index 0000000..83efc00 --- /dev/null +++ b/src/vk/queue_family.hpp @@ -0,0 +1,58 @@ +/* + * Copyright 2022 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CANDY_GEAR_VK_QUEUE_FAMILY_H +#define CANDY_GEAR_VK_QUEUE_FAMILY_H 1 + +#include <mutex> +#include <vector> + +#include "core.hpp" +#include "queue.hpp" + +namespace VK +{ +class Device; + +struct QueueState +{ + VkQueue queue; + bool busy; +}; + +class QueueFamily +{ + friend class Queue; + + std::mutex queue_mutex; + std::vector<QueueState> queue_states; + +public: + VK::Device *device; + + uint32_t family_index; + VkQueueFamilyProperties family_properties; + + QueueFamily(VK::Device *device, uint32_t family_index, + const VkQueueFamilyProperties &queue_family_properties); + + Queue + get_queue(); +}; + +} + +#endif /* CANDY_GEAR_VK_QUEUE_FAMILY_H */ diff --git a/src/vk/source_buffer.cpp b/src/vk/source_buffer.cpp new file mode 100644 index 0000000..7e6c570 --- /dev/null +++ b/src/vk/source_buffer.cpp @@ -0,0 +1,92 @@ +/* + * Copyright 2022 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "source_buffer.hpp" + +#include <cstring> + +namespace VK +{ + +SourceBuffer::SourceBuffer(Device *device, void *data, size_t data_size): + data{data} +{ + this->device = device; + this->device_size = data_size; + this->buffer_usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; + this->memory_properties = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | + VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; + + try + { + VK::BaseBuffer::loader.execute(static_cast<VK::BaseBuffer*>(this)); + } + catch(const CommandError &command_error) + { + std::string error{"Could not initialize Vulkan source buffer → "}; + error += command_error.what(); + throw std::runtime_error{error}; + } + this->copy_data(); +} + +SourceBuffer::SourceBuffer(SourceBuffer &&that) +{ + this->buffer = that.buffer; + this->device_memory = that.device_memory; + this->device_size = that.device_size; + this->buffer_usage = that.buffer_usage; + this->memory_properties = that.memory_properties; + this->data = that.data; + + that.buffer = VK_NULL_HANDLE; + that.device_memory = VK_NULL_HANDLE; + that.data = nullptr; +} + +SourceBuffer& +SourceBuffer::operator=(SourceBuffer &&that) +{ + this->buffer = that.buffer; + this->device_memory = that.device_memory; + this->device_size = that.device_size; + this->buffer_usage = that.buffer_usage; + this->memory_properties = that.memory_properties; + this->data = that.data; + + that.buffer = VK_NULL_HANDLE; + that.device_memory = VK_NULL_HANDLE; + that.data = nullptr; + + return *this; +} + +SourceBuffer::~SourceBuffer() +{ + VK::BaseBuffer::loader.revert(static_cast<VK::BaseBuffer*>(this)); +} + +void +SourceBuffer::copy_data() +{ + void *dst_data; + vkMapMemory(this->device->device, this->device_memory, 0, this->device_size, + 0, &dst_data); + memcpy(dst_data, this->data, static_cast<size_t>(this->device_size)); + vkUnmapMemory(this->device->device, this->device_memory); +} + +} diff --git a/src/palette.h b/src/vk/source_buffer.hpp index b9e52c2..713d708 100644 --- a/src/palette.h +++ b/src/vk/source_buffer.hpp @@ -14,27 +14,34 @@ * limitations under the License. */ -#ifndef CANDY_GEAR_PALETTE_H -#define CANDY_GEAR_PALETTE_H 1 +#ifndef CANDY_GEAR_VK_SOURCE_BUFFER_H +#define CANDY_GEAR_VK_SOURCE_BUFFER_H 1 -#include "core.h" +#include "base_buffer.hpp" -#ifdef __cplusplus -extern "C" { -#endif +namespace VK +{ -struct cg_palette +struct SourceBuffer: public BaseBuffer { - SDL_Color colors[256]; -}; + SourceBuffer(const SourceBuffer &t) = delete; + SourceBuffer& + operator=(const SourceBuffer &t) = delete; + + void *data; -extern const struct mrb_data_type cg_palette_type; + SourceBuffer(Device *device, void *data, size_t data_size); -void -cg_palette_init(mrb_state *mrb); + SourceBuffer(SourceBuffer &&that); + SourceBuffer& + operator=(SourceBuffer &&that); -#ifdef __cplusplus + ~SourceBuffer(); + + void + copy_data(); }; -#endif -#endif /* CANDY_GEAR_PALETTE_H */ +} + +#endif /* CANDY_GEAR_VK_SOURCE_BUFFER_H */ diff --git a/src/vk/swapchain.cpp b/src/vk/swapchain.cpp new file mode 100644 index 0000000..3b26b00 --- /dev/null +++ b/src/vk/swapchain.cpp @@ -0,0 +1,151 @@ +/* + * Copyright 2022 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "swapchain.hpp" + +#include "../core.hpp" + +#include <vector> +#include <iostream> + +namespace +{ + +void +load_swapchain(void *obj) +{ + auto self = static_cast<VK::Swapchain*>(obj); + + // Surface formats. + uint32_t vk_surface_format_count; + std::vector<VkSurfaceFormatKHR> vk_surface_formats; + vkGetPhysicalDeviceSurfaceFormatsKHR( + cg_core.vk_device_with_swapchain->physical_device, cg_core.window_surface, + &vk_surface_format_count, nullptr); + vk_surface_formats.resize(vk_surface_format_count); + vkGetPhysicalDeviceSurfaceFormatsKHR( + cg_core.vk_device_with_swapchain->physical_device, cg_core.window_surface, + &vk_surface_format_count, vk_surface_formats.data()); + + VkSwapchainCreateInfoKHR swapchain_create_info = {}; + swapchain_create_info.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; + swapchain_create_info.pNext = nullptr; + swapchain_create_info.flags = 0; + swapchain_create_info.surface = cg_core.window_surface; + swapchain_create_info.minImageCount = 3; // triple buffering. + + self->image_format = vk_surface_formats[0].format; + swapchain_create_info.imageFormat = self->image_format; + swapchain_create_info.imageColorSpace = vk_surface_formats[0].colorSpace; + + swapchain_create_info.imageExtent = { + cg_core.screen_width, cg_core.screen_height}; + swapchain_create_info.imageArrayLayers = 1; + swapchain_create_info.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + swapchain_create_info.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; + swapchain_create_info.queueFamilyIndexCount = 0; + swapchain_create_info.pQueueFamilyIndices = nullptr; + swapchain_create_info.preTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR; + swapchain_create_info.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; + swapchain_create_info.presentMode = VK_PRESENT_MODE_FIFO_KHR; + swapchain_create_info.clipped = VK_FALSE; + swapchain_create_info.oldSwapchain = VK_NULL_HANDLE; + + if(vkCreateSwapchainKHR( + cg_core.vk_device_with_swapchain->device, &swapchain_create_info, + nullptr, &self->swapchain) != VK_SUCCESS) + throw CommandError{"Vulkan failed to create swapchain."}; + + vkGetSwapchainImagesKHR( + cg_core.vk_device_with_swapchain->device, self->swapchain, + &self->images_count, nullptr); + self->images = new VkImage[self->images_count]; + vkGetSwapchainImagesKHR( + cg_core.vk_device_with_swapchain->device, self->swapchain, + &self->images_count, self->images); +} + +void +unload_swapchain(void *obj) +{ + auto self = static_cast<VK::Swapchain*>(obj); + + delete[] self->images; + vkDestroySwapchainKHR( + cg_core.vk_device_with_swapchain->device, self->swapchain, nullptr); +} + +void +load_image_view(void *obj) +{ + auto self = static_cast<VK::Swapchain*>(obj); + + self->image_views = new VkImageView[self->images_count]; + for(auto i{0}; i < self->images_count; i++) + { + VkImageViewCreateInfo create_info = {}; + create_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + create_info.image = self->images[i]; + create_info.viewType = VK_IMAGE_VIEW_TYPE_2D; + create_info.format = self->image_format; + create_info.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; + create_info.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; + create_info.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; + create_info.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; + create_info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + create_info.subresourceRange.baseMipLevel = 0; + create_info.subresourceRange.levelCount = 1; + create_info.subresourceRange.baseArrayLayer = 0; + create_info.subresourceRange.layerCount = 1; + + if(vkCreateImageView( + cg_core.vk_device_with_swapchain->device, &create_info, nullptr, + &self->image_views[i])) + throw CommandError{"Could no create Image View for swapchain."}; + } +} + +void +unload_image_view(void *obj) +{ + auto self = static_cast<VK::Swapchain*>(obj); + + for(auto i{0}; i < self->images_count; i++) + vkDestroyImageView( + cg_core.vk_device_with_swapchain->device, self->image_views[i], nullptr); +} + +const CommandChain loader{ + {&load_swapchain, &unload_swapchain}, + {&load_image_view, &unload_image_view} +}; + +} + +namespace VK +{ + +Swapchain::Swapchain() +{ + loader.execute(this); +} + +Swapchain::~Swapchain() +{ + loader.revert(this); +} + +} diff --git a/src/font.h b/src/vk/swapchain.hpp index 55e4d16..64df208 100644 --- a/src/font.h +++ b/src/vk/swapchain.hpp @@ -14,27 +14,28 @@ * limitations under the License. */ -#ifndef CANDY_GEAR_FONT_H -#define CANDY_GEAR_FONT_H 1 +#ifndef CANDY_GEAR_VK_SWAPCHAIN_H +#define CANDY_GEAR_VK_SWAPCHAIN_H 1 -#include "core.h" +#include "core.hpp" +#include "../command.hpp" -#ifdef __cplusplus -extern "C" { -#endif - -struct cg_font +namespace VK { - TTF_Font *data; -}; -extern const struct mrb_data_type cg_font_type; +struct Swapchain +{ + VkSwapchainKHR swapchain; + VkFormat image_format; -void -cg_font_init(mrb_state *mrb); + uint32_t images_count; + VkImage *images; + VkImageView *image_views; -#ifdef __cplusplus + Swapchain(); + ~Swapchain(); }; -#endif -#endif /* CANDY_GEAR_FONT_H */ +} + +#endif /* CANDY_GEAR_VK_SWAPCHAIN_H */ diff --git a/src/vk/texture.cpp b/src/vk/texture.cpp new file mode 100644 index 0000000..ae35035 --- /dev/null +++ b/src/vk/texture.cpp @@ -0,0 +1,292 @@ +/* + * Copyright 2022 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "texture.hpp" + +#include <SDL2/SDL.h> +#include <SDL2/SDL_image.h> + +#include "../command.hpp" +#include "../core.hpp" +#include "image.hpp" +#include "source_buffer.hpp" + +namespace +{ + +struct TextureBuilder +{ + VK::Texture *texture; + std::string texture_path; + + TextureBuilder(VK::Texture *t, std::string tp); + TextureBuilder(VK::Texture *t, const char* tp); +}; + +TextureBuilder::TextureBuilder(VK::Texture *t, std::string tp): + texture{t}, + texture_path{tp} +{ +} + +TextureBuilder::TextureBuilder(VK::Texture *t, const char* tp): + TextureBuilder{t, std::string(tp)} +{ +} + +void move_image_state( + VkCommandBuffer vk_command_buffer, VkImage vk_image, VkFormat format, + VkAccessFlags src_access_mask, VkAccessFlags dst_access_mask, + VkImageLayout old_layout, VkImageLayout new_layout, + VkPipelineStageFlags source_stage, VkPipelineStageFlags destination_stage) +{ + VkImageMemoryBarrier barrier{}; + barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + barrier.pNext = nullptr; + barrier.srcAccessMask = src_access_mask; + barrier.dstAccessMask = dst_access_mask; + barrier.oldLayout = old_layout; + barrier.newLayout = new_layout; + barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.image = vk_image; + barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + barrier.subresourceRange.baseMipLevel = 0; + barrier.subresourceRange.levelCount = 1; + barrier.subresourceRange.baseArrayLayer = 0; + barrier.subresourceRange.layerCount = 1; + + vkCmdPipelineBarrier( + vk_command_buffer, source_stage, destination_stage, 0, 0, nullptr, + 0, nullptr, 1, &barrier); +} + +void +load_image(void *obj) +{ + auto self = static_cast<TextureBuilder*>(obj); + + SDL_Surface *image{nullptr}; + + // Load file image from file. + { + SDL_Surface *raw_surface{nullptr}; + raw_surface = IMG_Load(self->texture_path.c_str()); + if(raw_surface == nullptr) + { + std::string error{"Failed to load image. SDL2_image Error: "}; + error += IMG_GetError(); + throw CommandError{error}; + } + + image = SDL_ConvertSurfaceFormat(raw_surface, SDL_PIXELFORMAT_ARGB8888, 0); + if(image == nullptr) + { + std::string error{"Failed to convert image. SDL2 Error: "}; + error += SDL_GetError(); + throw CommandError{error}; + } + + self->texture->width = static_cast<uint32_t>(image->w); + self->texture->height = static_cast<uint32_t>(image->h); + self->texture->mip_levels = 1; + + SDL_FreeSurface(raw_surface); + } + + // Load file image into a vulkan buffer. + size_t image_size{static_cast<size_t>( + image->format->BytesPerPixel * image->w * image->h)}; + VK::SourceBuffer source_image_buffer{ + cg_core.vk_device_with_swapchain, image->pixels, image_size}; + + // Create vulkan image. + { + try + { + VkExtent3D vk_extent3d{}; + vk_extent3d.width = self->texture->width; + vk_extent3d.height = self->texture->height; + vk_extent3d.depth = 1; + + VK::Image::create( + cg_core.vk_device_with_swapchain, + &self->texture->image, + &self->texture->device_memory, + VK_FORMAT_R8G8B8A8_UNORM, + vk_extent3d, + self->texture->mip_levels, + VK_IMAGE_TILING_OPTIMAL, + VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT); + } + catch(VK::Image::Error error) + { + throw CommandError{error.what()}; + } + } + + // Copy image from vulkan buffer into vulkan image. + { + auto queue_family{ + cg_core.vk_device_with_swapchain->get_queue_family_with_presentation()}; + auto queue{queue_family->get_queue()}; + VK::CommandPool command_pool{queue_family, 1}; + VkCommandBuffer vk_command_buffer{command_pool.command_buffers[0]}; + + queue.submit_one_time_command(vk_command_buffer, [&](){ + move_image_state( + vk_command_buffer, self->texture->image, VK_FORMAT_R8G8B8A8_UNORM, + 0, VK_ACCESS_TRANSFER_WRITE_BIT, + VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT); + + VkBufferImageCopy image_copy{}; + image_copy.bufferOffset = 0; + image_copy.bufferRowLength = 0; + image_copy.bufferImageHeight = 0; + image_copy.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + image_copy.imageSubresource.mipLevel = 0; + image_copy.imageSubresource.baseArrayLayer = 0; + image_copy.imageSubresource.layerCount = 1; + image_copy.imageOffset = {0, 0, 0}; + image_copy.imageExtent = {self->texture->width, self->texture->height, 1}; + + vkCmdCopyBufferToImage( + vk_command_buffer, source_image_buffer.buffer, self->texture->image, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &image_copy); + + move_image_state( + vk_command_buffer, self->texture->image, VK_FORMAT_R8G8B8A8_UNORM, + VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + VK_PIPELINE_STAGE_TRANSFER_BIT, + VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT); + }); + } + + // Free resources. + SDL_FreeSurface(image); +} + +void +unload_image(void *obj) +{ + auto self = static_cast<TextureBuilder*>(obj); + + vkDestroyImage( + cg_core.vk_device_with_swapchain->device, self->texture->image, nullptr); + vkFreeMemory( + cg_core.vk_device_with_swapchain->device, self->texture->device_memory, + nullptr); +} + +void +load_sampler(void *obj) +{ + auto self = static_cast<TextureBuilder*>(obj); + + VkSamplerCreateInfo sampler_info{}; + sampler_info.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; + sampler_info.pNext = nullptr; + sampler_info.flags = 0; + sampler_info.magFilter = VK_FILTER_LINEAR; + sampler_info.minFilter = VK_FILTER_LINEAR; + sampler_info.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; + sampler_info.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT; + sampler_info.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT; + sampler_info.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT; + sampler_info.mipLodBias = 0.0f; + sampler_info.anisotropyEnable = VK_TRUE; + sampler_info.maxAnisotropy = 16; + sampler_info.compareEnable = VK_FALSE; + sampler_info.compareOp = VK_COMPARE_OP_NEVER; + sampler_info.minLod = 0.0f; + sampler_info.maxLod = 0.0f; + sampler_info.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE; + sampler_info.unnormalizedCoordinates = VK_FALSE; + + if(vkCreateSampler( + cg_core.vk_device_with_swapchain->device, &sampler_info, nullptr, + &self->texture->sampler) != VK_SUCCESS) + throw CommandError{"Failed to create texture sampler."}; +} + +void +unload_sampler(void *obj) +{ + auto self = static_cast<TextureBuilder*>(obj); + + vkDestroySampler( + cg_core.vk_device_with_swapchain->device, self->texture->sampler, nullptr); +} + +void +load_view(void *obj) +{ + auto self = static_cast<TextureBuilder*>(obj); + + try + { + VK::Image::create_view( + cg_core.vk_device_with_swapchain, &self->texture->view, + self->texture->image, + VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_ASPECT_COLOR_BIT); + } + catch(VK::Image::Error error) + { + throw CommandError{error.what()}; + } +} + +void +unload_view(void *obj) +{ + auto self = static_cast<TextureBuilder*>(obj); + + vkDestroyImageView( + cg_core.vk_device_with_swapchain->device, self->texture->view, nullptr); +} + +const CommandChain loader{ + {&load_image, &unload_image}, + {&load_sampler, &unload_sampler}, + {&load_view, &unload_view} +}; + +} + +namespace VK +{ + +Texture::Texture(std::string texture_path) +{ + TextureBuilder texture_builder(this, texture_path); + loader.execute(&texture_builder); +} + +Texture::Texture(const char* texture_path): + Texture{std::string(texture_path)} +{ +} + +Texture::~Texture() +{ + TextureBuilder texture_builder(this, ""); + loader.revert(&texture_builder); +} + +} diff --git a/src/color.h b/src/vk/texture.hpp index ee1b3ed..35772c5 100644 --- a/src/color.h +++ b/src/vk/texture.hpp @@ -14,27 +14,30 @@ * limitations under the License. */ -#ifndef CANDY_GEAR_COLOR_H -#define CANDY_GEAR_COLOR_H 1 +#ifndef CANDY_GEAR_VK_TEXTURE_H +#define CANDY_GEAR_VK_TEXTURE_H 1 -#include "core.h" +#include <string> -#ifdef __cplusplus -extern "C" { -#endif +#include "core.hpp" -struct cg_color +namespace VK { - SDL_Color data; -}; - -extern const struct mrb_data_type cg_color_type; - -void -cg_color_init(mrb_state *mrb); -#ifdef __cplusplus +struct Texture +{ + VkImage image; + VkSampler sampler; + VkImageView view; + VkDeviceMemory device_memory; + uint32_t width, height; + uint32_t mip_levels; + + Texture(std::string texture_path); + Texture(const char* texture_path); + ~Texture(); }; -#endif -#endif /* CANDY_GEAR_COLOR_H */ +} + +#endif /* CANDY_GEAR_TEXTURE_H */ diff --git a/src/vk/uniform_buffer.cpp b/src/vk/uniform_buffer.cpp new file mode 100644 index 0000000..dd61898 --- /dev/null +++ b/src/vk/uniform_buffer.cpp @@ -0,0 +1,89 @@ +/* + * Copyright 2022 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "uniform_buffer.hpp" + +#include <cstring> +#include <stdexcept> + +namespace VK +{ + +UniformBuffer::UniformBuffer(Device *device, VkDeviceSize data_size) +{ + this->device = device; + this->device_size = data_size; + this->buffer_usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT; + this->memory_properties = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | + VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; + + try + { + BaseBuffer::loader.execute(static_cast<BaseBuffer*>(this)); + } + catch(const CommandError &command_error) + { + std::string error{"Could not initialize Vulkan uniform buffer → "}; + error += command_error.what(); + throw CommandError{error}; + } +} + +UniformBuffer::~UniformBuffer() +{ + BaseBuffer::loader.revert(static_cast<BaseBuffer*>(this)); +} + +UniformBuffer::UniformBuffer(UniformBuffer &&that) +{ + this->device = that.device; + this->buffer = that.buffer; + this->device_memory = that.device_memory; + this->device_size = that.device_size; + this->buffer_usage = that.buffer_usage; + this->memory_properties = that.memory_properties; + + that.buffer = VK_NULL_HANDLE; + that.device_memory = VK_NULL_HANDLE; +} + +UniformBuffer& +UniformBuffer::operator=(UniformBuffer &&that) +{ + this->device = that.device; + this->buffer = that.buffer; + this->device_memory = that.device_memory; + this->device_size = that.device_size; + this->buffer_usage = that.buffer_usage; + this->memory_properties = that.memory_properties; + + that.buffer = VK_NULL_HANDLE; + that.device_memory = VK_NULL_HANDLE; + + return *this; +} + +void +UniformBuffer::copy_data(void *ubo) +{ + void *data; + vkMapMemory(this->device->device, this->device_memory, 0, + this->device_size, 0, &data); + memcpy(data, ubo, this->device_size); + vkUnmapMemory(this->device->device, this->device_memory); +} + +} diff --git a/src/vk/uniform_buffer.hpp b/src/vk/uniform_buffer.hpp new file mode 100644 index 0000000..c044699 --- /dev/null +++ b/src/vk/uniform_buffer.hpp @@ -0,0 +1,60 @@ +/* + * Copyright 2022 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CANDY_GEAR_VK_UNIFORM_BUFFER_H +#define CANDY_GEAR_VK_UNIFORM_BUFFER_H 1 + +#include <memory> + +#include "core.hpp" + +#include "base_buffer.hpp" + +namespace VK +{ + +struct UBOModelInstance +{ + glm::mat4 model[128]; +}; + +struct UBOViewProjection +{ + glm::mat4 view; + glm::mat4 proj; +}; + +// FIXME: this class need to delete or create custom copy constructors! +class UniformBuffer: public BaseBuffer +{ + UniformBuffer(const UniformBuffer &t) = delete; + UniformBuffer& + operator=(const UniformBuffer &t) = delete; + +public: + UniformBuffer(Device *device, VkDeviceSize data_size); + ~UniformBuffer(); + + UniformBuffer(UniformBuffer &&that); + UniformBuffer& + operator=(UniformBuffer &&that); + + void copy_data(void* ubo); +}; + +} + +#endif /* CANDY_GEAR_VK_UNIFORM_BUFFER_H */ diff --git a/src/vk/vertex.hpp b/src/vk/vertex.hpp new file mode 100644 index 0000000..735d0a7 --- /dev/null +++ b/src/vk/vertex.hpp @@ -0,0 +1,35 @@ +/* + * Copyright 2022 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CANDY_GEAR_VK_VERTEX_H +#define CANDY_GEAR_VK_VERTEX_H 1 + +#include "core.hpp" + +namespace VK +{ + +struct Vertex +{ + glm::vec3 position; + glm::vec3 normal; + glm::vec3 color; + glm::vec2 texture_coord; +}; + +} + +#endif /* CANDY_GEAR_VK_VERTEX_H */ diff --git a/test/models/cube.cgmodel b/test/models/cube.cgmodel Binary files differnew file mode 100644 index 0000000..1cb35a0 --- /dev/null +++ b/test/models/cube.cgmodel diff --git a/test/models/tetrahedron.cgmodel b/test/models/tetrahedron.cgmodel Binary files differnew file mode 100644 index 0000000..7a1f9b9 --- /dev/null +++ b/test/models/tetrahedron.cgmodel diff --git a/test/src/main.rb b/test/src/main.rb index 9e37faa..31affb8 100644 --- a/test/src/main.rb +++ b/test/src/main.rb @@ -12,14 +12,70 @@ # See the License for the specific language governing permissions and # limitations under the License. +ROTATION_SPEED = Math::PI/45; +TRANSLATION_SPEED = 0.5; + def init() - $mode = Mode::Collision.new(); + $texture = CandyGear::Texture.from_image("textures/color_texture.png"); + $model = CandyGear::Model.new("models/cube.cgmodel", $texture); + + $instances = [ + CandyGear::Model::Instance.new( + $model, + 5.0, 0.0, 0.0, + 0.0, 0.0, 0.0), + CandyGear::Model::Instance.new( + $model, + -5.0, 0.0, 0.0, + 0.0, 0.0, 0.0), + CandyGear::Model::Instance.new( + $model, + 0.0, 5.0, 0.0, + 0.0, 0.0, 0.0), + CandyGear::Model::Instance.new( + $model, + 0.0, -5.0, 0.0, + 0.0, 0.0, 0.0), + CandyGear::Model::Instance.new( + $model, + 0.0, 0.0, 5.0, + 0.0, 0.0, 0.0), + CandyGear::Model::Instance.new( + $model, + 0.0, 0.0, -5.0, + 0.0, 0.0, 0.0) + ]; + + $camera = CandyGear::Camera.new(0.0, 0.0, 0.0, 0.0, 0.0, 0.0); + $camera.use(); end -def key_down(key) = $mode.controller.key_down(key); +def key_down(key) + case key + when CandyGear::Key::I + $camera.rotate(ROTATION_SPEED, 0.0, 0.0); + when CandyGear::Key::K + $camera.rotate(-ROTATION_SPEED, 0.0, 0.0); + when CandyGear::Key::J + $camera.rotate(0.0, ROTATION_SPEED, 0.0); + when CandyGear::Key::L + $camera.rotate(0.0, -ROTATION_SPEED, 0.0); + when CandyGear::Key::E + $camera.translate_by_rotation(0.0, 0.0, -TRANSLATION_SPEED); + when CandyGear::Key::D + $camera.translate_by_rotation(0.0, 0.0, TRANSLATION_SPEED); + when CandyGear::Key::S + $camera.translate_by_rotation(-TRANSLATION_SPEED, 0.0, 0.0); + when CandyGear::Key::F + $camera.translate_by_rotation(TRANSLATION_SPEED, 0.0, 0.0); + end +end -def key_up(key) = $mode.controller.key_up(key); +def key_up(key) +end def quit() = CandyGear.quit(); -def tick() = $mode.tick(); +def tick() + $instances.each {|i| i.draw();}; +end diff --git a/test/src/mode/collision.rb b/test/src/mode/collision.rb deleted file mode 100644 index dddfd5a..0000000 --- a/test/src/mode/collision.rb +++ /dev/null @@ -1,87 +0,0 @@ -# Copyright 2022 Frederico de Oliveira Linhares -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -module Mode - class Collision - WINDOW_WIDTH = 640; - WINDOW_HEIGHT = 360; - - class Square - attr_reader(:rect); - - def initialize(x, y, vertical_direction, horizontal_direction) - @rect = CandyGear::Rect.new(x, y, 16, 16); - @vertical_direction = vertical_direction; - @horizontal_direction = horizontal_direction; - end - - def x() = @rect.x; - def y() = @rect.y; - - def draw() = @rect.draw_fill(); - - def tick() - case @vertical_direction - when :left - @rect.x -= 2; - @vertical_direction = :right if @rect.x < 0; - when :right - @rect.x += 2; - if @rect.x > WINDOW_WIDTH - @rect.width() then - @vertical_direction = :left; - end - end - - case @horizontal_direction - when :up - @rect.y -= 2; - @horizontal_direction = :down if @rect.y < 0; - when :down - @rect.y += 2; - if @rect.y > WINDOW_HEIGHT - @rect.height() then - @horizontal_direction = :up; - end - end - end - end - - def initialize() - @normal_color = CandyGear::Color.new(0x99, 0x99, 0x99); - @collision_color = CandyGear::Color.new(0x99, 0x33, 0x33); - @vertical_align_color = CandyGear::Color.new(0x33, 0x99, 0x33); - @horizontal_align_color = CandyGear::Color.new(0x33, 0x33, 0x99); - - @square1 = Square.new(213, 120, :left, :down); - @square2 = Square.new(427, 240, :right, :down); - end - - def tick() - @square1.tick(); - @square2.tick(); - - if @square1.rect.collide?(@square2.rect) then - CandyGear::Graphic.set_color(@collision_color); - elsif @square1.rect.align_horizontally?(@square2.rect) then - CandyGear::Graphic.set_color(@horizontal_align_color); - elsif @square1.rect.align_vertically?(@square2.rect) then - CandyGear::Graphic.set_color(@vertical_align_color); - else - CandyGear::Graphic.set_color(@normal_color); - end - - @square1.draw(); - @square2.draw(); - end - end -end diff --git a/test/textures/color_texture.png b/test/textures/color_texture.png Binary files differnew file mode 100644 index 0000000..2c02811 --- /dev/null +++ b/test/textures/color_texture.png |