summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--Rakefile51
-rw-r--r--glsl/shader.frag14
-rw-r--r--glsl/shader.vert30
-rw-r--r--lib/animation.rb58
-rw-r--r--lib/menu.rb220
-rw-r--r--src/camera.cpp143
-rw-r--r--src/camera.hpp25
-rw-r--r--src/candy_gear.cpp4
-rw-r--r--src/candy_gear.hpp (renamed from src/candy_gear.h)10
-rw-r--r--src/color.c61
-rw-r--r--src/command.cpp78
-rw-r--r--src/command.hpp92
-rw-r--r--src/core.cpp490
-rw-r--r--src/core.hpp (renamed from src/core.h)66
-rw-r--r--src/font.c60
-rw-r--r--src/graphic.c67
-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.c162
-rw-r--r--src/loader.h159
-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.cpp66
-rw-r--r--src/model.cpp67
-rw-r--r--src/model.hpp (renamed from src/graphic.h)19
-rw-r--r--src/model/instance.cpp98
-rw-r--r--src/model/instance.hpp25
-rw-r--r--src/palette.c58
-rw-r--r--src/palette_implementation.cpp35
-rw-r--r--src/palette_implementation.h33
-rw-r--r--src/pgm_image.cpp10
-rw-r--r--src/pgm_image.hpp (renamed from src/pgm_image.h)10
-rw-r--r--src/point.c122
-rw-r--r--src/point.h40
-rw-r--r--src/rect.c249
-rw-r--r--src/rect.h40
-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.c198
-rw-r--r--src/sprite.h40
-rw-r--r--src/sprite_implementation.cpp6
-rw-r--r--src/sprite_implementation.hpp (renamed from src/sprite_implementation.h)10
-rw-r--r--src/texture.c223
-rw-r--r--src/texture.cpp186
-rw-r--r--src/texture.hpp (renamed from src/texture.h)17
-rw-r--r--src/vk/base_buffer.cpp96
-rw-r--r--src/vk/base_buffer.hpp56
-rw-r--r--src/vk/camera.hpp33
-rw-r--r--src/vk/command_pool.cpp87
-rw-r--r--src/vk/command_pool.hpp59
-rw-r--r--src/vk/core.hpp32
-rw-r--r--src/vk/destination_buffer.cpp105
-rw-r--r--src/vk/destination_buffer.hpp51
-rw-r--r--src/vk/device.cpp254
-rw-r--r--src/vk/device.hpp61
-rw-r--r--src/vk/graphics_pipeline.cpp1009
-rw-r--r--src/vk/graphics_pipeline.hpp80
-rw-r--r--src/vk/image.cpp122
-rw-r--r--src/vk/image.hpp61
-rw-r--r--src/vk/model.cpp324
-rw-r--r--src/vk/model.hpp55
-rw-r--r--src/vk/model_instance.hpp32
-rw-r--r--src/vk/queue.cpp61
-rw-r--r--src/vk/queue.hpp82
-rw-r--r--src/vk/queue_family.cpp80
-rw-r--r--src/vk/queue_family.hpp58
-rw-r--r--src/vk/source_buffer.cpp92
-rw-r--r--src/vk/source_buffer.hpp (renamed from src/palette.h)37
-rw-r--r--src/vk/swapchain.cpp151
-rw-r--r--src/vk/swapchain.hpp (renamed from src/font.h)33
-rw-r--r--src/vk/texture.cpp292
-rw-r--r--src/vk/texture.hpp (renamed from src/color.h)37
-rw-r--r--src/vk/uniform_buffer.cpp89
-rw-r--r--src/vk/uniform_buffer.hpp60
-rw-r--r--src/vk/vertex.hpp35
-rw-r--r--test/models/cube.cgmodelbin0 -> 952 bytes
-rw-r--r--test/models/tetrahedron.cgmodelbin0 -> 376 bytes
-rw-r--r--test/src/main.rb64
-rw-r--r--test/src/mode/collision.rb87
-rw-r--r--test/textures/color_texture.pngbin0 -> 3594 bytes
81 files changed, 4860 insertions, 2296 deletions
diff --git a/.gitignore b/.gitignore
index a3fd407..546cc23 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,3 +4,4 @@ pkg
*.mrb
*.o
+*.spv \ No newline at end of file
diff --git a/Rakefile b/Rakefile
index ef51ee2..6e6388e 100644
--- a/Rakefile
+++ b/Rakefile
@@ -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));
-}
diff --git a/src/key.c b/src/key.cpp
index 01fc42a..808c527 100644
--- a/src/key.c
+++ b/src/key.cpp
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-#include "key.h"
+#include "key.hpp"
void
cg_key_init(mrb_state *mrb)
diff --git a/src/key.h b/src/key.hpp
index 5544518..d3f4f5f 100644
--- a/src/key.h
+++ b/src/key.hpp
@@ -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 */
diff --git a/src/log.c b/src/log.cpp
index 92862ae..201370c 100644
--- a/src/log.c
+++ b/src/log.cpp
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-#include "log.h"
+#include "log.hpp"
#include <stdio.h>
diff --git a/src/log.h b/src/log.hpp
index 0196908..90e632c 100644
--- a/src/log.h
+++ b/src/log.hpp
@@ -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,
+ &copy_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
new file mode 100644
index 0000000..1cb35a0
--- /dev/null
+++ b/test/models/cube.cgmodel
Binary files differ
diff --git a/test/models/tetrahedron.cgmodel b/test/models/tetrahedron.cgmodel
new file mode 100644
index 0000000..7a1f9b9
--- /dev/null
+++ b/test/models/tetrahedron.cgmodel
Binary files differ
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
new file mode 100644
index 0000000..2c02811
--- /dev/null
+++ b/test/textures/color_texture.png
Binary files differ