From 736637680ac7b2cd0d0b878401a7e044fde0ee6a Mon Sep 17 00:00:00 2001
From: Frederico Linhares <fred@linhares.blue>
Date: Tue, 31 Dec 2024 12:32:36 -0300
Subject: refa Split BluCat into several namespaces

---
 src/blu_cat/com/command.cpp                        |  78 +++
 src/blu_cat/com/command.hpp                        |  92 ++++
 src/blu_cat/com/job_queue.cpp                      |  66 +++
 src/blu_cat/com/job_queue.hpp                      |  57 +++
 src/blu_cat/com/worker.cpp                         |  39 ++
 src/blu_cat/com/worker.hpp                         |  38 ++
 src/blu_cat/gra/animation.cpp                      |  27 +
 src/blu_cat/gra/animation.hpp                      |  51 ++
 src/blu_cat/gra/animation/frame.hpp                |  85 ++++
 src/blu_cat/gra/base_buffer.cpp                    |  96 ++++
 src/blu_cat/gra/base_buffer.hpp                    |  56 ++
 src/blu_cat/gra/binary_reader.cpp                  | 156 ++++++
 src/blu_cat/gra/binary_reader.hpp                  |  69 +++
 src/blu_cat/gra/character.cpp                      | 275 ++++++++++
 src/blu_cat/gra/character.hpp                      |  49 ++
 src/blu_cat/gra/command_pool.cpp                   |  87 ++++
 src/blu_cat/gra/command_pool.hpp                   |  59 +++
 src/blu_cat/gra/descriptor_set_layout.cpp          | 197 +++++++
 src/blu_cat/gra/descriptor_set_layout.hpp          |  38 ++
 src/blu_cat/gra/destination_buffer.cpp             | 109 ++++
 src/blu_cat/gra/destination_buffer.hpp             |  54 ++
 src/blu_cat/gra/device.cpp                         | 393 ++++++++++++++
 src/blu_cat/gra/device.hpp                         |  69 +++
 src/blu_cat/gra/font.cpp                           |  54 ++
 src/blu_cat/gra/font.hpp                           |  42 ++
 src/blu_cat/gra/framebuffer.cpp                    | 200 ++++++++
 src/blu_cat/gra/framebuffer.hpp                    |  43 ++
 src/blu_cat/gra/graphics_pipeline_2d_solid.cpp     | 298 +++++++++++
 src/blu_cat/gra/graphics_pipeline_2d_solid.hpp     |  44 ++
 .../gra/graphics_pipeline_2d_solid_layout.cpp      |  83 +++
 .../gra/graphics_pipeline_2d_solid_layout.hpp      |  35 ++
 src/blu_cat/gra/graphics_pipeline_2d_wired.cpp     | 314 ++++++++++++
 src/blu_cat/gra/graphics_pipeline_2d_wired.hpp     |  47 ++
 .../gra/graphics_pipeline_2d_wired_layout.cpp      |  87 ++++
 .../gra/graphics_pipeline_2d_wired_layout.hpp      |  35 ++
 src/blu_cat/gra/graphics_pipeline_3d.cpp           | 308 +++++++++++
 src/blu_cat/gra/graphics_pipeline_3d.hpp           |  43 ++
 src/blu_cat/gra/graphics_pipeline_3d_layout.cpp    |  79 +++
 src/blu_cat/gra/graphics_pipeline_3d_layout.hpp    |  35 ++
 src/blu_cat/gra/graphics_pipeline_3d_skeletal.cpp  | 322 ++++++++++++
 src/blu_cat/gra/graphics_pipeline_3d_skeletal.hpp  |  43 ++
 src/blu_cat/gra/graphics_pipeline_sprite_3d.cpp    | 315 ++++++++++++
 src/blu_cat/gra/graphics_pipeline_sprite_3d.hpp    |  44 ++
 src/blu_cat/gra/image.cpp                          | 149 ++++++
 src/blu_cat/gra/image.hpp                          |  73 +++
 src/blu_cat/gra/light.cpp                          | 205 ++++++++
 src/blu_cat/gra/light.hpp                          |  41 ++
 src/blu_cat/gra/log.cpp                            |  53 ++
 src/blu_cat/gra/log.hpp                            |  54 ++
 src/blu_cat/gra/qoi.cpp                            | 205 ++++++++
 src/blu_cat/gra/qoi.hpp                            |  41 ++
 src/blu_cat/gra/queue.cpp                          |  61 +++
 src/blu_cat/gra/queue.hpp                          |  82 +++
 src/blu_cat/gra/queue_family.cpp                   |  84 +++
 src/blu_cat/gra/queue_family.hpp                   |  58 +++
 src/blu_cat/gra/rectangle.cpp                      |  30 ++
 src/blu_cat/gra/rectangle.hpp                      |  42 ++
 src/blu_cat/gra/render_pass.cpp                    | 203 ++++++++
 src/blu_cat/gra/render_pass.hpp                    |  36 ++
 src/blu_cat/gra/renderer.cpp                       | 425 ++++++++++++++++
 src/blu_cat/gra/renderer.hpp                       |  75 +++
 src/blu_cat/gra/skeletal_mesh.cpp                  | 204 ++++++++
 src/blu_cat/gra/skeletal_mesh.hpp                  |  52 ++
 src/blu_cat/gra/skeletal_mesh_vertex.hpp           |  42 ++
 src/blu_cat/gra/skeletal_model.cpp                 | 239 +++++++++
 src/blu_cat/gra/skeletal_model.hpp                 |  55 ++
 src/blu_cat/gra/source_buffer.cpp                  |  87 ++++
 src/blu_cat/gra/source_buffer.hpp                  |  45 ++
 src/blu_cat/gra/sprite.cpp                         |  99 ++++
 src/blu_cat/gra/sprite.hpp                         |  50 ++
 src/blu_cat/gra/sprite_3d.cpp                      | 168 ++++++
 src/blu_cat/gra/sprite_3d.hpp                      |  46 ++
 src/blu_cat/gra/sprite_to_draw.cpp                 |  40 ++
 src/blu_cat/gra/sprite_to_draw.hpp                 |  44 ++
 src/blu_cat/gra/static_mesh.cpp                    | 132 +++++
 src/blu_cat/gra/static_mesh.hpp                    |  48 ++
 src/blu_cat/gra/static_mesh_vertex.hpp             |  34 ++
 src/blu_cat/gra/static_model.cpp                   | 165 ++++++
 src/blu_cat/gra/static_model.hpp                   |  49 ++
 src/blu_cat/gra/swapchain.cpp                      | 207 ++++++++
 src/blu_cat/gra/swapchain.hpp                      |  47 ++
 src/blu_cat/gra/texture.cpp                        | 566 +++++++++++++++++++++
 src/blu_cat/gra/texture.hpp                        |  51 ++
 src/blu_cat/gra/uniform_buffer.cpp                 |  89 ++++
 src/blu_cat/gra/uniform_buffer.hpp                 |  49 ++
 src/blu_cat/gra/uniform_data_object.hpp            |  80 +++
 src/blu_cat/gra/view_2d.cpp                        | 160 ++++++
 src/blu_cat/gra/view_2d.hpp                        |  60 +++
 src/blu_cat/gra/view_3d.cpp                        | 155 ++++++
 src/blu_cat/gra/view_3d.hpp                        |  48 ++
 src/blu_cat/gra/vulkan.hpp                         |  34 ++
 src/blu_cat/int/core.cpp                           | 503 ++++++++++++++++++
 src/blu_cat/int/core.hpp                           | 123 +++++
 93 files changed, 10399 insertions(+)
 create mode 100644 src/blu_cat/com/command.cpp
 create mode 100644 src/blu_cat/com/command.hpp
 create mode 100644 src/blu_cat/com/job_queue.cpp
 create mode 100644 src/blu_cat/com/job_queue.hpp
 create mode 100644 src/blu_cat/com/worker.cpp
 create mode 100644 src/blu_cat/com/worker.hpp
 create mode 100644 src/blu_cat/gra/animation.cpp
 create mode 100644 src/blu_cat/gra/animation.hpp
 create mode 100644 src/blu_cat/gra/animation/frame.hpp
 create mode 100644 src/blu_cat/gra/base_buffer.cpp
 create mode 100644 src/blu_cat/gra/base_buffer.hpp
 create mode 100644 src/blu_cat/gra/binary_reader.cpp
 create mode 100644 src/blu_cat/gra/binary_reader.hpp
 create mode 100644 src/blu_cat/gra/character.cpp
 create mode 100644 src/blu_cat/gra/character.hpp
 create mode 100644 src/blu_cat/gra/command_pool.cpp
 create mode 100644 src/blu_cat/gra/command_pool.hpp
 create mode 100644 src/blu_cat/gra/descriptor_set_layout.cpp
 create mode 100644 src/blu_cat/gra/descriptor_set_layout.hpp
 create mode 100644 src/blu_cat/gra/destination_buffer.cpp
 create mode 100644 src/blu_cat/gra/destination_buffer.hpp
 create mode 100644 src/blu_cat/gra/device.cpp
 create mode 100644 src/blu_cat/gra/device.hpp
 create mode 100644 src/blu_cat/gra/font.cpp
 create mode 100644 src/blu_cat/gra/font.hpp
 create mode 100644 src/blu_cat/gra/framebuffer.cpp
 create mode 100644 src/blu_cat/gra/framebuffer.hpp
 create mode 100644 src/blu_cat/gra/graphics_pipeline_2d_solid.cpp
 create mode 100644 src/blu_cat/gra/graphics_pipeline_2d_solid.hpp
 create mode 100644 src/blu_cat/gra/graphics_pipeline_2d_solid_layout.cpp
 create mode 100644 src/blu_cat/gra/graphics_pipeline_2d_solid_layout.hpp
 create mode 100644 src/blu_cat/gra/graphics_pipeline_2d_wired.cpp
 create mode 100644 src/blu_cat/gra/graphics_pipeline_2d_wired.hpp
 create mode 100644 src/blu_cat/gra/graphics_pipeline_2d_wired_layout.cpp
 create mode 100644 src/blu_cat/gra/graphics_pipeline_2d_wired_layout.hpp
 create mode 100644 src/blu_cat/gra/graphics_pipeline_3d.cpp
 create mode 100644 src/blu_cat/gra/graphics_pipeline_3d.hpp
 create mode 100644 src/blu_cat/gra/graphics_pipeline_3d_layout.cpp
 create mode 100644 src/blu_cat/gra/graphics_pipeline_3d_layout.hpp
 create mode 100644 src/blu_cat/gra/graphics_pipeline_3d_skeletal.cpp
 create mode 100644 src/blu_cat/gra/graphics_pipeline_3d_skeletal.hpp
 create mode 100644 src/blu_cat/gra/graphics_pipeline_sprite_3d.cpp
 create mode 100644 src/blu_cat/gra/graphics_pipeline_sprite_3d.hpp
 create mode 100644 src/blu_cat/gra/image.cpp
 create mode 100644 src/blu_cat/gra/image.hpp
 create mode 100644 src/blu_cat/gra/light.cpp
 create mode 100644 src/blu_cat/gra/light.hpp
 create mode 100644 src/blu_cat/gra/log.cpp
 create mode 100644 src/blu_cat/gra/log.hpp
 create mode 100644 src/blu_cat/gra/qoi.cpp
 create mode 100644 src/blu_cat/gra/qoi.hpp
 create mode 100644 src/blu_cat/gra/queue.cpp
 create mode 100644 src/blu_cat/gra/queue.hpp
 create mode 100644 src/blu_cat/gra/queue_family.cpp
 create mode 100644 src/blu_cat/gra/queue_family.hpp
 create mode 100644 src/blu_cat/gra/rectangle.cpp
 create mode 100644 src/blu_cat/gra/rectangle.hpp
 create mode 100644 src/blu_cat/gra/render_pass.cpp
 create mode 100644 src/blu_cat/gra/render_pass.hpp
 create mode 100644 src/blu_cat/gra/renderer.cpp
 create mode 100644 src/blu_cat/gra/renderer.hpp
 create mode 100644 src/blu_cat/gra/skeletal_mesh.cpp
 create mode 100644 src/blu_cat/gra/skeletal_mesh.hpp
 create mode 100644 src/blu_cat/gra/skeletal_mesh_vertex.hpp
 create mode 100644 src/blu_cat/gra/skeletal_model.cpp
 create mode 100644 src/blu_cat/gra/skeletal_model.hpp
 create mode 100644 src/blu_cat/gra/source_buffer.cpp
 create mode 100644 src/blu_cat/gra/source_buffer.hpp
 create mode 100644 src/blu_cat/gra/sprite.cpp
 create mode 100644 src/blu_cat/gra/sprite.hpp
 create mode 100644 src/blu_cat/gra/sprite_3d.cpp
 create mode 100644 src/blu_cat/gra/sprite_3d.hpp
 create mode 100644 src/blu_cat/gra/sprite_to_draw.cpp
 create mode 100644 src/blu_cat/gra/sprite_to_draw.hpp
 create mode 100644 src/blu_cat/gra/static_mesh.cpp
 create mode 100644 src/blu_cat/gra/static_mesh.hpp
 create mode 100644 src/blu_cat/gra/static_mesh_vertex.hpp
 create mode 100644 src/blu_cat/gra/static_model.cpp
 create mode 100644 src/blu_cat/gra/static_model.hpp
 create mode 100644 src/blu_cat/gra/swapchain.cpp
 create mode 100644 src/blu_cat/gra/swapchain.hpp
 create mode 100644 src/blu_cat/gra/texture.cpp
 create mode 100644 src/blu_cat/gra/texture.hpp
 create mode 100644 src/blu_cat/gra/uniform_buffer.cpp
 create mode 100644 src/blu_cat/gra/uniform_buffer.hpp
 create mode 100644 src/blu_cat/gra/uniform_data_object.hpp
 create mode 100644 src/blu_cat/gra/view_2d.cpp
 create mode 100644 src/blu_cat/gra/view_2d.hpp
 create mode 100644 src/blu_cat/gra/view_3d.cpp
 create mode 100644 src/blu_cat/gra/view_3d.hpp
 create mode 100644 src/blu_cat/gra/vulkan.hpp
 create mode 100644 src/blu_cat/int/core.cpp
 create mode 100644 src/blu_cat/int/core.hpp

(limited to 'src/blu_cat')

diff --git a/src/blu_cat/com/command.cpp b/src/blu_cat/com/command.cpp
new file mode 100644
index 0000000..a1d3240
--- /dev/null
+++ b/src/blu_cat/com/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/blu_cat/com/command.hpp b/src/blu_cat/com/command.hpp
new file mode 100644
index 0000000..5079c58
--- /dev/null
+++ b/src/blu_cat/com/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 BLU_CAT_COM_COMMAND_CHAIN_H
+#define BLU_CAT_COM_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 /* BLU_CAT_COM_COMMAND_CHAIN_H */
diff --git a/src/blu_cat/com/job_queue.cpp b/src/blu_cat/com/job_queue.cpp
new file mode 100644
index 0000000..beaf989
--- /dev/null
+++ b/src/blu_cat/com/job_queue.cpp
@@ -0,0 +1,66 @@
+/*
+ * 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 "job_queue.hpp"
+
+#include <chrono>
+
+namespace BluCat::COM
+{
+
+JobQueue::JobQueue():
+  jobs{},
+  _stop{false}
+{
+}
+
+void
+JobQueue::stop()
+{
+  using namespace std::chrono_literals;
+
+  while(!this->jobs.empty()) std::this_thread::sleep_for(1000ms);
+  this->_stop = true;
+  this->condition.notify_all();
+}
+
+void
+JobQueue::push(Job job)
+{
+  std::unique_lock<std::mutex> lock{this->access};
+  this->jobs.push_back(job);
+  this->condition.notify_one();
+}
+
+Job
+JobQueue::pop()
+{
+  std::unique_lock<std::mutex> lock{this->access};
+  this->condition.wait(lock, [this]{
+    return !this->jobs.empty() || this->_stop;});
+  if(!this->jobs.empty())
+  {
+    auto job{std::move(this->jobs.front())};
+    this->jobs.pop_front();
+    return job;
+  }
+  else
+  {
+    return Job{nullptr};
+  }
+}
+
+}
diff --git a/src/blu_cat/com/job_queue.hpp b/src/blu_cat/com/job_queue.hpp
new file mode 100644
index 0000000..856946d
--- /dev/null
+++ b/src/blu_cat/com/job_queue.hpp
@@ -0,0 +1,57 @@
+/*
+ * 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 BLU_CAT_COM_JOB_QUEUE_H
+#define BLU_CAT_COM_JOB_QUEUE_H 1
+
+#include <atomic>
+#include <condition_variable>
+#include <deque>
+#include <functional>
+#include <mutex>
+
+namespace BluCat::COM
+{
+
+class Worker;
+
+typedef std::function<void(void)> Job;
+
+class JobQueue
+{
+  friend Worker;
+
+  std::mutex access;
+  std::condition_variable condition;
+  std::atomic<bool> _stop;
+  std::deque<Job> jobs;
+
+  Job
+  pop();
+
+public:
+  JobQueue();
+
+  void
+  stop();
+
+  void
+  push(Job job);
+};
+
+}
+
+#endif /* BLU_CAT_COM_JOB_QUEUE_H */
diff --git a/src/blu_cat/com/worker.cpp b/src/blu_cat/com/worker.cpp
new file mode 100644
index 0000000..847b571
--- /dev/null
+++ b/src/blu_cat/com/worker.cpp
@@ -0,0 +1,39 @@
+/*
+ * 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 "worker.hpp"
+
+#include <thread>
+
+namespace BluCat::COM
+{
+
+Worker::Worker(JobQueue *job_queue):
+  job_queue{job_queue}
+{
+}
+
+void
+Worker::operator()()
+{
+  while(!this->job_queue->_stop)
+  {
+    auto job{this->job_queue->pop()};
+    if(job) job();
+  }
+}
+
+}
diff --git a/src/blu_cat/com/worker.hpp b/src/blu_cat/com/worker.hpp
new file mode 100644
index 0000000..d74aa24
--- /dev/null
+++ b/src/blu_cat/com/worker.hpp
@@ -0,0 +1,38 @@
+/*
+ * 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 BLU_CAT_COM_WORKER_H
+#define BLU_CAT_COM_WORKER_H 1
+
+#include "job_queue.hpp"
+
+namespace BluCat::COM
+{
+
+class Worker
+{
+  JobQueue *job_queue;
+
+public:
+  Worker(JobQueue *job_queue);
+
+  void
+  operator()();
+};
+
+}
+
+#endif /* BLU_CAT_COM_WORKER_H */
diff --git a/src/blu_cat/gra/animation.cpp b/src/blu_cat/gra/animation.cpp
new file mode 100644
index 0000000..dc2c281
--- /dev/null
+++ b/src/blu_cat/gra/animation.cpp
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2022-2024 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 "animation.hpp"
+
+namespace BluCat::GRA
+{
+
+Bone::Bone(glm::mat4 offset_matrix):
+  offset_matrix{offset_matrix}
+{
+}
+
+}
diff --git a/src/blu_cat/gra/animation.hpp b/src/blu_cat/gra/animation.hpp
new file mode 100644
index 0000000..1275b48
--- /dev/null
+++ b/src/blu_cat/gra/animation.hpp
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2022-2024 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 BLU_CAT_GRA_ANIMATION_H
+#define BLU_CAT_GRA_ANIMATION_H 1
+
+#include <vector>
+
+#include "vulkan.hpp"
+#include "animation/frame.hpp"
+
+namespace BluCat::GRA
+{
+
+struct Bone
+{
+  glm::mat4x4 offset_matrix;
+
+  Bone(glm::mat4 offset_matrix);
+};
+
+struct BoneTransform
+{
+  uint32_t bone_id;
+  Channel<glm::vec3> positions;
+  Channel<glm::quat> rotations;
+  Channel<glm::vec3> scales;
+};
+
+struct Animation
+{
+  std::vector<BoneTransform> bone_transforms;
+  float final_time;
+};
+
+}
+
+#endif /* BLU_CAT_GRA_ANIMATION_H */
diff --git a/src/blu_cat/gra/animation/frame.hpp b/src/blu_cat/gra/animation/frame.hpp
new file mode 100644
index 0000000..b1ea913
--- /dev/null
+++ b/src/blu_cat/gra/animation/frame.hpp
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2022-2024 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 BLU_CAT_GRA_FRAME_H
+#define BLU_CAT_GRA_FRAME_H 1
+
+#include <vector>
+
+#include "../vulkan.hpp"
+
+namespace BluCat::GRA
+{
+
+template<typename T>
+struct Frame
+{
+  const T value;
+  const float timestamp;
+
+  Frame(T value, float timestamp):
+  value{value},
+  timestamp{timestamp}
+  {
+  }
+
+};
+
+template<typename T>
+struct Channel
+{
+  int current_index{0};
+  std::vector<Frame<T>> key_frames;
+
+  inline glm::mat4
+  interpolate(
+    float animation_time,
+    glm::mat4 (*single_frame)(T frame),
+    glm::mat4 (*multiple_frames)(T current_frame, T next_frame, float scale))
+  {
+    if(this->key_frames.size() == 1)
+      return single_frame(this->key_frames[0].value);
+    else
+    {
+      while(animation_time > this->key_frames[current_index].timestamp)
+        this->current_index++;
+
+      float scale_factor;
+      Frame<T> *previous_frame;
+      Frame<T> *next_frame{&(this->key_frames[this->current_index])};
+      if(this->current_index == 0)
+      {
+	previous_frame = &(this->key_frames[this->key_frames.size() - 1]);
+        float midway_length{animation_time - 0};
+        float frames_diff{next_frame->timestamp - 0};
+        scale_factor = midway_length / frames_diff;
+      }
+      else
+      {
+	previous_frame = &(this->key_frames[this->current_index - 1]);
+        float midway_length{animation_time - previous_frame->timestamp};
+        float frames_diff{next_frame->timestamp - previous_frame->timestamp};
+        scale_factor = midway_length / frames_diff;
+      }
+
+      return multiple_frames(
+	previous_frame->value, next_frame->value, scale_factor);
+    }
+  };
+};
+
+}
+#endif /* BLU_CAT_GRA_FRAME_H */
diff --git a/src/blu_cat/gra/base_buffer.cpp b/src/blu_cat/gra/base_buffer.cpp
new file mode 100644
index 0000000..1add9fb
--- /dev/null
+++ b/src/blu_cat/gra/base_buffer.cpp
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2022-2024 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 BluCat::GRA
+{
+
+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/blu_cat/gra/base_buffer.hpp b/src/blu_cat/gra/base_buffer.hpp
new file mode 100644
index 0000000..cb4cdf8
--- /dev/null
+++ b/src/blu_cat/gra/base_buffer.hpp
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2022-2024 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 BLU_CAT_GRA_BASE_BUFFER_H
+#define BLU_CAT_GRA_BASE_BUFFER_H 1
+
+#include "../com/command.hpp"
+#include "vulkan.hpp"
+#include "device.hpp"
+
+namespace BluCat::GRA
+{
+
+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 /* BLU_CAT_GRA_BASE_BUFFER_H */
diff --git a/src/blu_cat/gra/binary_reader.cpp b/src/blu_cat/gra/binary_reader.cpp
new file mode 100644
index 0000000..14fc39c
--- /dev/null
+++ b/src/blu_cat/gra/binary_reader.cpp
@@ -0,0 +1,156 @@
+/*
+ * Copyright 2022-2024 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 "binary_reader.hpp"
+
+#include <fstream>
+
+namespace
+{
+
+union IntAndFloat32bit{
+  uint32_t i;
+  float f;
+};
+
+union IntAndFloat64bit{
+  uint64_t i;
+  double f;
+};
+
+}
+
+BinaryReader::BinaryReader(std::string file_path):
+  _pointer{0}
+{
+  std::ifstream file(file_path, std::ios::binary | std::ios::ate);
+	if(!file.is_open())
+	{
+		std::string error{"failed to open file: "};
+		error += file_path;
+		throw std::runtime_error{error};
+	}
+
+  this->_size = file.tellg();
+  file.seekg(0);
+  this->data = new uint8_t[this->_size];
+  file.read((char*)data, this->_size);
+}
+
+BinaryReader::BinaryReader(const char *file_path):
+  BinaryReader{std::string(file_path)}
+{
+}
+
+BinaryReader::~BinaryReader()
+{
+  delete[] this->data;
+}
+
+uint8_t
+BinaryReader::read_ui8()
+{
+  return this->data[this->_pointer++];
+}
+
+uint32_t
+BinaryReader::read_ui32()
+{
+  uint8_t b1{this->data[_pointer++]}, b2{this->data[_pointer++]},
+    b3{this->data[_pointer++]}, b4{this->data[_pointer++]};
+
+  return b1 << 24 | b2 << 16 | b3 << 8 | b4;
+}
+
+uint64_t
+BinaryReader::read_ui64()
+{
+  uint8_t b1{this->data[_pointer++]}, b2{this->data[_pointer++]},
+    b3{this->data[_pointer++]}, b4{this->data[_pointer++]},
+    b5{this->data[_pointer++]}, b6{this->data[_pointer++]},
+    b7{this->data[_pointer++]}, b8{this->data[_pointer++]};
+
+  return (uint64_t)b1 << 56 | (uint64_t)b2 << 48 | (uint64_t)b3 << 40 |
+    (uint64_t)b4 << 32 | (uint64_t)b5 << 24 | (uint64_t)b6 << 16 |
+    (uint64_t)b7 << 8 | (uint64_t)b8;
+}
+
+float
+BinaryReader::read_float()
+{
+  IntAndFloat32bit num;
+  num.i = read_ui32();
+
+  return num.f;
+}
+
+double
+BinaryReader::read_double()
+{
+  IntAndFloat64bit num;
+  num.i = read_ui64();
+
+  return num.f;
+}
+
+glm::vec2
+BinaryReader::read_vec2()
+{
+  IntAndFloat32bit x{read_ui32()}, y{read_ui32()};
+
+  return glm::vec2{x.f, y.f};
+}
+
+glm::vec3
+BinaryReader::read_vec3()
+{
+  IntAndFloat32bit x{read_ui32()}, y{read_ui32()}, z{read_ui32()};
+
+  return glm::vec3{x.f, y.f, z.f};
+}
+
+glm::quat
+BinaryReader::read_quat()
+{
+  IntAndFloat32bit w{read_ui32()}, x{read_ui32()}, y{read_ui32()},
+    z{read_ui32()};
+
+  return glm::quat{w.f, x.f, y.f, z.f};
+}
+
+glm::mat4
+BinaryReader::read_mat4()
+{
+  glm::mat4 matrix;
+  float *offset_matrix_data{glm::value_ptr(matrix)};
+
+  for(int i{0}; i < 16; i++)
+  {
+    IntAndFloat32bit num;
+    num.i = read_ui32();
+    offset_matrix_data[i] = num.f;
+  }
+
+  return matrix;
+}
+
+void
+BinaryReader::read_chars(char *str, int size)
+{
+  for(int i{0}; i < size; i++)
+    str[i] = (char)data[this->_pointer++];
+}
+
diff --git a/src/blu_cat/gra/binary_reader.hpp b/src/blu_cat/gra/binary_reader.hpp
new file mode 100644
index 0000000..1995402
--- /dev/null
+++ b/src/blu_cat/gra/binary_reader.hpp
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2022-2024 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 <cstdint>
+#include <string>
+
+#include "vulkan.hpp"
+
+class BinaryReader
+{
+  int _pointer;
+  int _size;
+  uint8_t *data;
+
+public:
+
+  BinaryReader(const std::string file_path);
+  BinaryReader(const char *file_path);
+  ~BinaryReader();
+
+  inline int
+  pointer(){return this->_pointer;};
+
+  inline int
+  size(){return this->_size;};
+
+  uint8_t
+  read_ui8();
+
+  uint32_t
+  read_ui32();
+
+  uint64_t
+  read_ui64();
+
+  float
+  read_float();
+
+  double
+  read_double();
+
+  glm::vec2
+  read_vec2();
+
+  glm::vec3
+  read_vec3();
+
+  glm::quat
+  read_quat();
+
+  glm::mat4
+  read_mat4();
+
+  void
+  read_chars(char *str, int size);
+};
diff --git a/src/blu_cat/gra/character.cpp b/src/blu_cat/gra/character.cpp
new file mode 100644
index 0000000..f0e7512
--- /dev/null
+++ b/src/blu_cat/gra/character.cpp
@@ -0,0 +1,275 @@
+/*
+ * Copyright 2022-2024 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 "character.hpp"
+
+#include "../com/command.hpp"
+#include "../int/core.hpp"
+#include "font.hpp"
+#include "image.hpp"
+#include "source_buffer.hpp"
+
+namespace
+{
+
+struct CharacterBuilder
+{
+  BluCat::GRA::Character *character;
+  FT_Face face;
+  uint32_t character_code;
+
+  CharacterBuilder(
+    BluCat::GRA::Character *character, FT_Face face, uint32_t character_code);
+};
+
+CharacterBuilder::CharacterBuilder(
+  BluCat::GRA::Character *character, FT_Face face, uint32_t character_code):
+  character{character},
+  face{face},
+  character_code{character_code}
+{
+}
+
+// TODO: creating one image with one device memory for each character is
+// ineficient
+void
+load_image(void *obj)
+{
+  auto self = static_cast<CharacterBuilder*>(obj);
+
+  FT_Error error;
+  std::vector<uint8_t> source_image_raw;
+  const int num_channels = 4; // all images are converted to RGBA
+  auto glyph_index{FT_Get_Char_Index(self->face, self->character_code)};
+
+  error = FT_Load_Glyph(self->face, glyph_index, FT_LOAD_DEFAULT);
+  if(error) throw CommandError{"failed to load glyph"};
+
+  error = FT_Render_Glyph(self->face->glyph, FT_RENDER_MODE_NORMAL);
+  if(error) throw CommandError{"failed to render glyph"};
+
+  self->character->bearing_x = self->face->glyph->bitmap_left;
+  self->character->bearing_y = self->face->glyph->bitmap_top;
+  self->character->advance = (self->face->glyph->advance.x >> 6);
+  self->character->width = self->face->glyph->bitmap.width;
+  self->character->height = self->face->glyph->bitmap.rows;
+  self->character->mip_levels = 1;
+
+  // Character is a white-space.
+  if(self->character->width <= 0)
+  {
+    self->character->image = VK_NULL_HANDLE;
+    self->character->device_memory = VK_NULL_HANDLE;
+
+    return;
+  }
+
+  auto image_size{static_cast<size_t>(
+      self->face->glyph->bitmap.width *
+      self->face->glyph->bitmap.rows * num_channels)};
+
+  { // Create the data for the image buffer
+    source_image_raw.resize(image_size, 0);
+
+    for(auto y{0}; y < self->face->glyph->bitmap.width; y++)
+    {
+      for(auto x{0}; x < self->face->glyph->bitmap.rows; x++)
+      {
+        auto image_coord = y * self->face->glyph->bitmap.rows * num_channels +
+          x * num_channels;
+        auto glyph_coord = y * self->face->glyph->bitmap.rows + x;
+        // Red
+        source_image_raw[image_coord] = 255;
+        // Green
+        source_image_raw[image_coord + 1] = 255;
+        // Blue
+        source_image_raw[image_coord + 2] = 255;
+        // Alpha
+        source_image_raw[image_coord + 3] =
+          self->face->glyph->bitmap.buffer[glyph_coord];
+      }
+    }
+  }
+
+  BluCat::GRA::SourceBuffer source_image_buffer{
+		BluCat::INT::core.vk_device_with_swapchain, source_image_raw.data(),
+    image_size};
+
+  { // Create Vulkan image.
+    try
+    {
+      VkExtent3D vk_extent3d;
+      vk_extent3d.width = self->face->glyph->bitmap.width;
+      vk_extent3d.height = self->face->glyph->bitmap.rows;
+      vk_extent3d.depth = 1;
+
+      BluCat::GRA::Image::create(
+        BluCat::INT::core.vk_device_with_swapchain,
+	&self->character->image,
+	&self->character->device_memory,
+	VK_FORMAT_R8G8B8A8_UNORM,
+	vk_extent3d,
+	self->character->mip_levels,
+	VK_IMAGE_TILING_OPTIMAL,
+	VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT);
+    }
+    catch(BluCat::GRA::Image::Error error)
+    {
+      throw CommandError{error.what()};
+    }
+  }
+
+  { // Copy image from buffer into image.
+    auto queue_family{BluCat::INT::core.vk_device_with_swapchain->
+      get_queue_family_with_presentation()};
+    auto queue{queue_family->get_queue()};
+    BluCat::GRA::CommandPool command_pool{queue_family, 1};
+    VkCommandBuffer vk_command_buffer{command_pool.command_buffers[0]};
+
+    queue.submit_one_time_command(vk_command_buffer, [&](){
+      BluCat::GRA::Image::move_image_state(
+        vk_command_buffer, self->character->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->character->width, self->character->height, 1};
+
+      vkCmdCopyBufferToImage(
+        vk_command_buffer, source_image_buffer.buffer, self->character->image,
+        VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &image_copy);
+
+      BluCat::GRA::Image::move_image_state(
+        vk_command_buffer, self->character->image, VK_FORMAT_R8G8B8A8_UNORM,
+        VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT,
+        VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+        VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
+        VK_PIPELINE_STAGE_TRANSFER_BIT,
+        VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT);
+    });
+  }
+}
+
+void
+unload_image(void *obj)
+{
+  auto self = static_cast<CharacterBuilder*>(obj);
+
+  vkDestroyImage(
+    BluCat::INT::core.vk_device_with_swapchain->device, self->character->image,
+		nullptr);
+  vkFreeMemory(
+    BluCat::INT::core.vk_device_with_swapchain->device,
+		self->character->device_memory, nullptr);
+}
+
+const CommandChain loader{
+  {&load_image, &unload_image}
+};
+
+}
+
+namespace BluCat::GRA
+{
+
+Character::Character(FT_Face face, uint32_t character_code)
+{
+  CharacterBuilder character_builder(this, face, character_code);
+  loader.execute(&character_builder);
+}
+
+Character::~Character()
+{
+  CharacterBuilder character_builder(this, nullptr, 0);
+  loader.revert(&character_builder);
+}
+
+std::vector<uint32_t>
+Character::str_to_unicode(const char* str)
+{
+  std::vector<uint32_t> unicode_text;
+  int text_width{0};
+  int text_height{0};
+
+  { // Reserve memory
+    int size{0};
+    for(auto i{0}; str[i] != '\0'; i++)
+      if((str[i] & 0b11000000) != 0b10000000) size++;
+    unicode_text.reserve(size);
+  }
+
+  for(auto i{0}; str[i] != '\0'; i++)
+  {
+    int num_bytes;
+    uint32_t codepoint{0};
+
+    if(str[i] >= 0 && str[i] < 127)
+    { // Normal ASCI character, 1-byte.
+      num_bytes = 1;
+      codepoint = str[i];
+    }
+    else if((str[i] & 0xE0) == 0xC0)
+    { // 2-byte character.
+      num_bytes = 2;
+      codepoint += ((str[i] & 0b00011111) << 6);
+    }
+    else if((str[i] & 0xF0) == 0xE0)
+    { // 3-byte character.
+      num_bytes = 3;
+      codepoint += ((str[i] & 0b00001111) << 12);
+    }
+    else if((str[i] & 0xF8) == 0xF0)
+    { // 4-byte character.
+      num_bytes = 4;
+      codepoint += ((str[i] & 0b00000111) << 18);
+    }
+    else
+    { // FIXME: Add support to 5-byte and 6-byte characters.
+    }
+
+    switch (num_bytes)
+    {
+    case 4:
+      i++;
+      codepoint += ((str[i] & 0b00111111) << 12);
+    case 3:
+      i++;
+      codepoint += ((str[i] & 0b00111111) << 6);
+    case 2:
+      i++;
+      codepoint += (str[i] & 0b00111111);
+    case 1:
+    default:
+      break;
+    }
+
+    unicode_text.push_back(codepoint);
+  }
+
+  return unicode_text;
+}
+
+}
diff --git a/src/blu_cat/gra/character.hpp b/src/blu_cat/gra/character.hpp
new file mode 100644
index 0000000..8968384
--- /dev/null
+++ b/src/blu_cat/gra/character.hpp
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2022-2024 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 BLU_CAT_GRA_CHARACTER_H
+#define BLU_CAT_GRA_CHARACTER_H 1
+
+#include <ft2build.h>
+#include FT_FREETYPE_H
+
+#include "vulkan.hpp"
+
+#include <vector>
+
+namespace BluCat::GRA
+{
+
+struct Character
+{
+  VkImage image;
+  VkDeviceMemory device_memory;
+  int32_t bearing_y, bearing_x;
+  uint32_t width, height, advance, mip_levels;
+
+  Character(FT_Face face, uint32_t character_code);
+  ~Character();
+
+  Character(Character const&) = delete;
+  Character& operator=(Character const&) = delete;
+
+  static std::vector<uint32_t>
+  str_to_unicode(const char* str);
+};
+
+}
+
+#endif /* BLU_CAT_GRA_CHARACTER_H */
diff --git a/src/blu_cat/gra/command_pool.cpp b/src/blu_cat/gra/command_pool.cpp
new file mode 100644
index 0000000..2602522
--- /dev/null
+++ b/src/blu_cat/gra/command_pool.cpp
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2022-2024 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 BluCat::GRA
+{
+
+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/blu_cat/gra/command_pool.hpp b/src/blu_cat/gra/command_pool.hpp
new file mode 100644
index 0000000..7487365
--- /dev/null
+++ b/src/blu_cat/gra/command_pool.hpp
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2022-2024 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 BLU_CAT_GRA_COMMAND_POOL_H
+#define BLU_CAT_GRA_COMMAND_POOL_H 1
+
+#include <vector>
+
+#include "../com/command.hpp"
+#include "device.hpp"
+#include "vulkan.hpp"
+
+namespace BluCat::GRA
+{
+
+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 /* BLU_CAT_GRA_COMMAND_POOL_H */
diff --git a/src/blu_cat/gra/descriptor_set_layout.cpp b/src/blu_cat/gra/descriptor_set_layout.cpp
new file mode 100644
index 0000000..e1e8852
--- /dev/null
+++ b/src/blu_cat/gra/descriptor_set_layout.cpp
@@ -0,0 +1,197 @@
+/*
+ * Copyright 2022-2024 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 "descriptor_set_layout.hpp"
+
+#include <array>
+
+#include "../int/core.hpp"
+
+namespace
+{
+
+void
+load_world(void *obj)
+{
+  auto self = static_cast<BluCat::GRA::DescriptorSetLayout*>(obj);
+
+  std::array<VkDescriptorSetLayoutBinding, 2> set_layouts{};
+  set_layouts[0].binding = 0;
+  set_layouts[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
+  set_layouts[0].descriptorCount = 1;
+  set_layouts[0].stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
+  set_layouts[0].pImmutableSamplers = nullptr;
+
+  set_layouts[1].binding = 1;
+  set_layouts[1].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
+  set_layouts[1].descriptorCount = 1;
+  set_layouts[1].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
+  set_layouts[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 = set_layouts.size();
+  layout_info.pBindings = set_layouts.data();
+
+  if(vkCreateDescriptorSetLayout(
+       BluCat::INT::core.vk_device_with_swapchain->device, &layout_info, nullptr,
+       &self->world) != VK_SUCCESS)
+    throw CommandError{
+      "Failed to create Vulkan descriptor set layout for world view."};
+}
+
+void
+unload_world(void *obj)
+{
+  auto self = static_cast<BluCat::GRA::DescriptorSetLayout*>(obj);
+
+  vkDestroyDescriptorSetLayout(
+    BluCat::INT::core.vk_device_with_swapchain->device, self->world, nullptr);
+}
+
+void
+load_view(void *obj)
+{
+  auto self = static_cast<BluCat::GRA::DescriptorSetLayout*>(obj);
+
+  std::array<VkDescriptorSetLayoutBinding, 1> 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;
+
+  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(
+       BluCat::INT::core.vk_device_with_swapchain->device, &layout_info, nullptr,
+       &self->view) != VK_SUCCESS)
+    throw CommandError{
+      "Failed to create Vulkan descriptor set layout for view."};
+}
+
+void
+unload_view(void *obj)
+{
+  auto self = static_cast<BluCat::GRA::DescriptorSetLayout*>(obj);
+
+  vkDestroyDescriptorSetLayout(
+    BluCat::INT::core.vk_device_with_swapchain->device, self->view, nullptr);
+}
+
+void
+load_texture(void *obj)
+{
+  auto self = static_cast<BluCat::GRA::DescriptorSetLayout*>(obj);
+
+  std::array<VkDescriptorSetLayoutBinding, 1> layout_bindings{};
+
+  layout_bindings[0].binding = 0;
+  layout_bindings[0].descriptorType =
+    VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
+  layout_bindings[0].descriptorCount = 1;
+  layout_bindings[0].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
+  layout_bindings[0].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(
+       BluCat::INT::core.vk_device_with_swapchain->device, &layout_info, nullptr,
+       &self->texture) != VK_SUCCESS)
+    throw CommandError{
+      "Failed to create Vulkan descriptor set layout for textures."};
+}
+
+void
+unload_texture(void *obj)
+{
+  auto self = static_cast<BluCat::GRA::DescriptorSetLayout*>(obj);
+
+  vkDestroyDescriptorSetLayout(
+    BluCat::INT::core.vk_device_with_swapchain->device, self->texture, nullptr);
+}
+
+void
+load_model(void *obj)
+{
+  auto self = static_cast<BluCat::GRA::DescriptorSetLayout*>(obj);
+
+  std::array<VkDescriptorSetLayoutBinding, 1> 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;
+
+  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(
+       BluCat::INT::core.vk_device_with_swapchain->device, &layout_info, nullptr,
+       &self->model) != VK_SUCCESS)
+    throw CommandError{
+      "Failed to create Vulkan descriptor set layout for model instance."};
+}
+
+void
+unload_model(void *obj)
+{
+  auto self = static_cast<BluCat::GRA::DescriptorSetLayout*>(obj);
+
+  vkDestroyDescriptorSetLayout(
+    BluCat::INT::core.vk_device_with_swapchain->device, self->model, nullptr);
+}
+
+const CommandChain loader{
+  {&load_world, &unload_world},
+  {&load_view, &unload_view},
+  {&load_texture, &unload_texture},
+  {&load_model, &unload_model},
+};
+
+}
+
+namespace BluCat::GRA
+{
+
+DescriptorSetLayout::DescriptorSetLayout()
+{
+  loader.execute(this);
+}
+
+DescriptorSetLayout::~DescriptorSetLayout()
+{
+  loader.revert(this);
+}
+
+}
diff --git a/src/blu_cat/gra/descriptor_set_layout.hpp b/src/blu_cat/gra/descriptor_set_layout.hpp
new file mode 100644
index 0000000..42003b9
--- /dev/null
+++ b/src/blu_cat/gra/descriptor_set_layout.hpp
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2022-2024 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 BLU_CAT_GRA_DESCRIPTOR_SET_LAYOUT_H
+#define BLU_CAT_GRA_DESCRIPTOR_SET_LAYOUT_H 1
+
+#include "vulkan.hpp"
+
+namespace BluCat::GRA
+{
+
+struct DescriptorSetLayout
+{
+  VkDescriptorSetLayout world;
+  VkDescriptorSetLayout view;
+  VkDescriptorSetLayout texture;
+  VkDescriptorSetLayout model;
+
+  DescriptorSetLayout();
+  ~DescriptorSetLayout();
+};
+
+}
+
+#endif /* BLU_CAT_GRA_DESCRIPTOR_SET_LAYOUT_H */
diff --git a/src/blu_cat/gra/destination_buffer.cpp b/src/blu_cat/gra/destination_buffer.cpp
new file mode 100644
index 0000000..3c53db4
--- /dev/null
+++ b/src/blu_cat/gra/destination_buffer.cpp
@@ -0,0 +1,109 @@
+/*
+ * Copyright 2022-2024 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 BluCat::GRA
+{
+
+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));
+
+  this->copy_data();
+}
+
+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));
+}
+
+void
+DestinationBuffer::copy_data()
+{
+  CommandPool command_pool(this->queue_family, 1);
+  Queue transfer_queue{this->queue_family->get_queue()};
+  this->device_size = source_buffer->device_size;
+
+  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);
+}
+
+}
diff --git a/src/blu_cat/gra/destination_buffer.hpp b/src/blu_cat/gra/destination_buffer.hpp
new file mode 100644
index 0000000..7df1e50
--- /dev/null
+++ b/src/blu_cat/gra/destination_buffer.hpp
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2022-2024 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 BLU_CAT_GRA_DESTINATION_BUFFER_H
+#define BLU_CAT_GRA_DESTINATION_BUFFER_H 1
+
+#include "base_buffer.hpp"
+#include "vulkan.hpp"
+#include "source_buffer.hpp"
+#include "queue_family.hpp"
+
+namespace BluCat::GRA
+{
+
+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();
+
+  void
+  copy_data();
+};
+
+}
+
+#endif /* BLU_CAT_GRA_DESTINATION_BUFFER_H */
diff --git a/src/blu_cat/gra/device.cpp b/src/blu_cat/gra/device.cpp
new file mode 100644
index 0000000..b2d114a
--- /dev/null
+++ b/src/blu_cat/gra/device.cpp
@@ -0,0 +1,393 @@
+/*
+ * Copyright 2022-2024 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 <new>
+#include <vector>
+#ifdef DEBUG
+#include <sstream>
+#endif
+
+#include "../int/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 BluCat::GRA
+{
+
+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::stringstream message{};
+    message << "Name: " << physical_properties.deviceName << std::endl;
+    message << "API version: " << physical_properties.apiVersion <<
+      std::endl;
+    message << "Driver version: " << physical_properties.driverVersion <<
+      std::endl;
+    message << "Vendor ID: " << physical_properties.vendorID << std::endl;
+    message << "Device ID: " << physical_properties.deviceID << std::endl;
+    message << "Device type: " << physical_properties.deviceType <<
+      std::endl;
+    BluCat::INT::core.log.message(Log::Level::Trace, message.str());
+#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.fillModeNonSolid = VK_TRUE;
+    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
+  {
+    std::string data_dir{""};
+#ifdef _WIN64
+    HKEY hKey;
+    LPCSTR lpSubKey = "SOFTWARE\\CandyGear";
+    LPCSTR lpValueName = "InstallLocation";
+    DWORD dwType = REG_SZ;
+    DWORD dwSize = 0;
+    LPBYTE lpData = NULL;
+
+    if(RegOpenKeyEx(HKEY_LOCAL_MACHINE, lpSubKey, 0, KEY_READ, &hKey) ==
+       ERROR_SUCCESS)
+    {
+      if(RegQueryValueEx(hKey, lpValueName, NULL, &dwType, NULL, &dwSize) ==
+	 ERROR_SUCCESS)
+      {
+	lpData = new BYTE[dwSize];
+	if(RegQueryValueEx(
+	     hKey, lpValueName, NULL, &dwType, lpData, &dwSize) ==
+	   ERROR_SUCCESS)
+	{
+	  data_dir = reinterpret_cast<char const*>(lpData);
+	}
+	delete[] lpData;
+      }
+      RegCloseKey(hKey);
+    }
+
+    if(data_dir == "")
+      throw std::runtime_error("Failed to read CandyGear registry.");
+
+    std::string vert_sprite_3d_shader_module{data_dir};
+    vert_sprite_3d_shader_module.append("\\glsl\\shader_sprite_3d.vert.spv");
+    this->vert_sprite_3d_shader_module = create_shader_module(
+      this->device, vert_sprite_3d_shader_module.c_str());
+
+    std::string frag_sprite_3d_shader_module{data_dir};
+    frag_sprite_3d_shader_module.append("\\glsl\\shader_sprite_3d.frag.spv");
+    this->frag_sprite_3d_shader_module = create_shader_module(
+      this->device, frag_sprite_3d_shader_module.c_str());
+
+    std::string vert3d_shader_module{data_dir};
+    vert3d_shader_module.append("\\glsl\\shader_3d.vert.spv");
+    this->vert3d_shader_module = create_shader_module(
+      this->device, vert3d_shader_module.c_str());
+
+    std::string vert3d_skeletal_shader_module{data_dir};
+    vert3d_skeletal_shader_module.append(
+			"\\glsl\\shader_3d_skeletal.vert.spv");
+    this->vert3d_skeletal_shader_module = create_shader_module(
+      this->device, vert3d_skeletal_shader_module.c_str());
+
+    std::string frag3d_shader_module{data_dir};
+    frag3d_shader_module.append("\\glsl\\shader_3d.frag.spv");
+    this->frag3d_shader_module = create_shader_module(
+      this->device, frag3d_shader_module.c_str());
+
+    std::string vert2d_solid_shader_module{data_dir};
+    vert2d_solid_shader_module.append("\\glsl\\shader_2d_solid.vert.spv");
+    this->vert2d_solid_shader_module = create_shader_module(
+      this->device, vert2d_solid_shader_module.c_str());
+
+    std::string frag2d_solid_shader_module{data_dir};
+    frag2d_solid_shader_module.append("\\glsl\\shader_2d_solid.frag.spv");
+    this->frag2d_solid_shader_module = create_shader_module(
+      this->device, frag2d_solid_shader_module.c_str());
+
+    std::string vert2d_wired_shader_module{data_dir};
+    vert2d_wired_shader_module.append("\\glsl\\shader_2d_wired.vert.spv");
+    this->vert2d_wired_shader_module = create_shader_module(
+      this->device, vert2d_wired_shader_module.c_str());
+
+    std::string frag2d_wired_shader_module{data_dir};
+    frag2d_wired_shader_module.append("\\glsl\\shader_2d_wired.frag.spv");
+    this->frag2d_wired_shader_module = create_shader_module(
+      this->device, frag2d_wired_shader_module.c_str());
+#else
+    data_dir = "/usr/local/share/candy_gear";
+
+    std::string vert_sprite_3d_shader_module{data_dir};
+    vert_sprite_3d_shader_module.append("/glsl/shader_sprite_3d.vert.spv");
+    this->vert_sprite_3d_shader_module = create_shader_module(
+      this->device, vert_sprite_3d_shader_module.c_str());
+
+    std::string frag_sprite_3d_shader_module{data_dir};
+    frag_sprite_3d_shader_module.append("/glsl/shader_sprite_3d.frag.spv");
+    this->frag_sprite_3d_shader_module = create_shader_module(
+      this->device, frag_sprite_3d_shader_module.c_str());
+
+    std::string vert3d_shader_module{data_dir};
+    vert3d_shader_module.append("/glsl/shader_3d.vert.spv");
+    this->vert3d_shader_module = create_shader_module(
+      this->device, vert3d_shader_module.c_str());
+
+    std::string vert3d_skeletal_shader_module{data_dir};
+    vert3d_skeletal_shader_module.append("/glsl/shader_3d_skeletal.vert.spv");
+    this->vert3d_skeletal_shader_module = create_shader_module(
+      this->device, vert3d_skeletal_shader_module.c_str());
+
+    std::string frag3d_shader_module{data_dir};
+    frag3d_shader_module.append("/glsl/shader_3d.frag.spv");
+    this->frag3d_shader_module = create_shader_module(
+      this->device, frag3d_shader_module.c_str());
+
+    std::string vert2d_solid_shader_module{data_dir};
+    vert2d_solid_shader_module.append("/glsl/shader_2d_solid.vert.spv");
+    this->vert2d_solid_shader_module = create_shader_module(
+      this->device, vert2d_solid_shader_module.c_str());
+
+    std::string frag2d_solid_shader_module{data_dir};
+    frag2d_solid_shader_module.append("/glsl/shader_2d_solid.frag.spv");
+    this->frag2d_solid_shader_module = create_shader_module(
+      this->device, frag2d_solid_shader_module.c_str());
+
+    std::string vert2d_wired_shader_module{data_dir};
+    vert2d_wired_shader_module.append("/glsl/shader_2d_wired.vert.spv");
+    this->vert2d_wired_shader_module = create_shader_module(
+      this->device, vert2d_wired_shader_module.c_str());
+
+    std::string frag2d_wired_shader_module{data_dir};
+    frag2d_wired_shader_module.append("/glsl/shader_2d_wired.frag.spv");
+    this->frag2d_wired_shader_module = create_shader_module(
+      this->device, frag2d_wired_shader_module.c_str());
+#endif
+  }
+
+  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, BluCat::INT::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_sprite_3d_shader_module, nullptr);
+  vkDestroyShaderModule(
+    this->device, this->frag_sprite_3d_shader_module, nullptr);
+  vkDestroyShaderModule(this->device, this->vert3d_shader_module, nullptr);
+  vkDestroyShaderModule(
+		this->device, this->vert3d_skeletal_shader_module, nullptr);
+  vkDestroyShaderModule(this->device, this->frag3d_shader_module, nullptr);
+  vkDestroyShaderModule(
+    this->device, this->vert2d_solid_shader_module, nullptr);
+  vkDestroyShaderModule(
+    this->device, this->frag2d_solid_shader_module, nullptr);
+  vkDestroyShaderModule(
+    this->device, this->vert2d_wired_shader_module, nullptr);
+  vkDestroyShaderModule(
+    this->device, this->frag2d_wired_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(INT::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(INT::random_number_generator);
+  return this->queue_families_with_presentation[0];
+}
+
+}
diff --git a/src/blu_cat/gra/device.hpp b/src/blu_cat/gra/device.hpp
new file mode 100644
index 0000000..fd53198
--- /dev/null
+++ b/src/blu_cat/gra/device.hpp
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2022-2024 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 BLU_CAT_GRA_DEVICE_H
+#define BLU_CAT_GRA_DEVICE_H 1
+
+#include <cstdlib>
+#include <vector>
+
+#include "vulkan.hpp"
+#include "queue_family.hpp"
+
+namespace BluCat::GRA
+{
+
+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_sprite_3d_shader_module;
+  VkShaderModule frag_sprite_3d_shader_module;
+  VkShaderModule vert3d_shader_module;
+  VkShaderModule vert3d_skeletal_shader_module;
+  VkShaderModule frag3d_shader_module;
+  VkShaderModule vert2d_solid_shader_module;
+  VkShaderModule frag2d_solid_shader_module;
+  VkShaderModule vert2d_wired_shader_module;
+  VkShaderModule frag2d_wired_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 /* BLU_CAT_GRA_DEVICE_H */
diff --git a/src/blu_cat/gra/font.cpp b/src/blu_cat/gra/font.cpp
new file mode 100644
index 0000000..a9c2074
--- /dev/null
+++ b/src/blu_cat/gra/font.cpp
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2022-2024 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.hpp"
+
+#include "../int/core.hpp"
+
+namespace BluCat::GRA
+{
+
+Font::Font(const char* font_path, int font_size)
+{
+  FT_Error error;
+  error = FT_New_Face(
+		BluCat::INT::core.font_library, font_path, 0, &this->face);
+  if(error == FT_Err_Unknown_File_Format) throw std::invalid_argument(
+    "The font file could be opened and read, but it appears that its font "
+    "format is unsupported.");
+  else if(error) throw std::invalid_argument(
+    "The font file could not be opened or read, or it is broken.");
+
+  error = FT_Set_Pixel_Sizes(this->face, 0, font_size);
+  if(error) throw std::invalid_argument("Failed to load font size.");
+}
+
+Font::~Font()
+{
+  FT_Done_Face(this->face);
+}
+
+std::shared_ptr<Character>
+Font::character(uint32_t character_code)
+{
+  if(!this->characters.contains(character_code))
+    this->characters.emplace(
+      character_code, std::make_shared<Character>(this->face, character_code));
+
+  return this->characters.at(character_code);
+}
+
+}
diff --git a/src/blu_cat/gra/font.hpp b/src/blu_cat/gra/font.hpp
new file mode 100644
index 0000000..72ec6d3
--- /dev/null
+++ b/src/blu_cat/gra/font.hpp
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2022-2024 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 BLU_CAT_GRA_FONT_H
+#define BLU_CAT_GRA_FONT_H 1
+
+#include <memory>
+#include <unordered_map>
+
+#include "character.hpp"
+
+namespace BluCat::GRA
+{
+
+struct Font
+{
+  FT_Face face;
+  std::unordered_map<uint32_t, std::shared_ptr<Character>> characters;
+
+  Font(const char* font_path, int font_size);
+  ~Font();
+
+  std::shared_ptr<Character>
+  character(uint32_t character_code);
+};
+
+}
+
+#endif /* BLU_CAT_GRA_FONT_H */
diff --git a/src/blu_cat/gra/framebuffer.cpp b/src/blu_cat/gra/framebuffer.cpp
new file mode 100644
index 0000000..e2d0b7c
--- /dev/null
+++ b/src/blu_cat/gra/framebuffer.cpp
@@ -0,0 +1,200 @@
+/*
+ * Copyright 2022-2024 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 "framebuffer.hpp"
+
+#include "../com/command.hpp"
+#include "../int/core.hpp"
+#include "image.hpp"
+
+namespace
+{
+void
+load_depth_image(void *obj)
+{
+  auto self = static_cast<BluCat::GRA::Framebuffer*>(obj);
+
+  VkExtent3D extent3d{};
+  extent3d.width = BluCat::INT::core.display_width;
+  extent3d.height = BluCat::INT::core.display_height;
+  extent3d.depth = 1;
+
+  try
+  {
+    BluCat::GRA::Image::create(
+      BluCat::INT::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(BluCat::GRA::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<BluCat::GRA::Framebuffer*>(obj);
+
+  vkDestroyImage(
+    BluCat::INT::core.vk_device_with_swapchain->device, self->depth_image,
+    nullptr);
+  vkFreeMemory(
+    BluCat::INT::core.vk_device_with_swapchain->device,
+    self->depth_image_memory, nullptr);
+}
+
+void
+load_depth_image_view(void *obj)
+{
+  auto self = static_cast<BluCat::GRA::Framebuffer*>(obj);
+
+  try
+  {
+    BluCat::GRA::Image::create_view(
+      BluCat::INT::core.vk_device_with_swapchain, &self->depth_image_view,
+      self->depth_image,
+      VK_FORMAT_D32_SFLOAT, VK_IMAGE_ASPECT_DEPTH_BIT);
+  }
+  catch(BluCat::GRA::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<BluCat::GRA::Framebuffer*>(obj);
+
+  vkDestroyImageView(
+    BluCat::INT::core.vk_device_with_swapchain->device, self->depth_image_view,
+		nullptr);
+}
+
+void
+load_3d(void *obj)
+{
+  auto self = static_cast<BluCat::GRA::Framebuffer*>(obj);
+
+  self->pipeline_3d.resize(BluCat::INT::core.vk_swapchain->images_count);
+  for (auto i{0}; i < BluCat::INT::core.vk_swapchain->images_count; i++)
+  {
+    std::array<VkImageView, 2> attachments = {
+      BluCat::INT::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 = BluCat::INT::core.vk_render_pass->pipeline_3d;
+    framebuffer_info.attachmentCount = attachments.size();
+    framebuffer_info.pAttachments = attachments.data();
+    framebuffer_info.width = BluCat::INT::core.display_width;
+    framebuffer_info.height = BluCat::INT::core.display_height;
+
+    framebuffer_info.layers = 1;
+
+    if(vkCreateFramebuffer(
+	 BluCat::INT::core.vk_device_with_swapchain->device, &framebuffer_info, nullptr,
+	 &self->pipeline_3d[i]) != VK_SUCCESS)
+      throw CommandError{"Failed to create Vulkan Framebuffer."};
+  }
+}
+
+void
+unload_3d(void *obj)
+{
+  auto self = static_cast<BluCat::GRA::Framebuffer*>(obj);
+
+  for(auto framebuffer: self->pipeline_3d)
+    vkDestroyFramebuffer(
+      BluCat::INT::core.vk_device_with_swapchain->device, framebuffer, nullptr);
+}
+
+void
+load_2d(void *obj)
+{
+  auto self = static_cast<BluCat::GRA::Framebuffer*>(obj);
+
+  self->pipeline_2d.resize(BluCat::INT::core.vk_swapchain->images_count);
+  for (auto i{0}; i < BluCat::INT::core.vk_swapchain->images_count; i++)
+  {
+    std::array<VkImageView, 1> attachments = {
+      BluCat::INT::core.vk_swapchain->image_views[i]
+    };
+
+    VkFramebufferCreateInfo framebuffer_info{};
+    framebuffer_info.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
+    framebuffer_info.renderPass = BluCat::INT::core.vk_render_pass->pipeline_2d;
+    framebuffer_info.attachmentCount = attachments.size();
+    framebuffer_info.pAttachments = attachments.data();
+    framebuffer_info.width = BluCat::INT::core.display_width;
+    framebuffer_info.height = BluCat::INT::core.display_height;
+    framebuffer_info.layers = 1;
+
+    if(vkCreateFramebuffer(
+         BluCat::INT::core.vk_device_with_swapchain->device, &framebuffer_info,
+				 nullptr, &self->pipeline_2d[i])
+       != VK_SUCCESS)
+      throw CommandError{"Failed to create Vulkan Framebuffer."};
+  }
+}
+
+void
+unload_2d(void *obj)
+{
+  auto self = static_cast<BluCat::GRA::Framebuffer*>(obj);
+
+  for(auto framebuffer: self->pipeline_2d)
+    vkDestroyFramebuffer(
+      BluCat::INT::core.vk_device_with_swapchain->device, framebuffer, nullptr);
+}
+
+const CommandChain loader{
+  {&load_depth_image, &unload_depth_image},
+  {&load_depth_image_view, &unload_depth_image_view},
+  {&load_3d, &unload_3d},
+  {&load_2d, &unload_2d}
+};
+
+}
+
+namespace BluCat::GRA
+{
+
+Framebuffer::Framebuffer()
+{
+  loader.execute(this);
+}
+
+Framebuffer::~Framebuffer()
+{
+  loader.revert(this);
+}
+
+}
diff --git a/src/blu_cat/gra/framebuffer.hpp b/src/blu_cat/gra/framebuffer.hpp
new file mode 100644
index 0000000..ddbf5c4
--- /dev/null
+++ b/src/blu_cat/gra/framebuffer.hpp
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2022-2024 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 BLU_CAT_GRA_FRAMEBUFFER_H
+#define BLU_CAT_GRA_FRAMEBUFFER_H 1
+
+#include <vector>
+
+#include "vulkan.hpp"
+
+namespace BluCat::GRA
+{
+
+struct Framebuffer
+{
+  // Depth image.
+  VkImage depth_image;
+  VkDeviceMemory depth_image_memory;
+  VkImageView depth_image_view;
+
+  std::vector<VkFramebuffer> pipeline_3d;
+  std::vector<VkFramebuffer> pipeline_2d;
+
+  Framebuffer();
+  ~Framebuffer();
+};
+
+}
+
+#endif /* BLU_CAT_GRA_FRAMEBUFFER_H */
diff --git a/src/blu_cat/gra/graphics_pipeline_2d_solid.cpp b/src/blu_cat/gra/graphics_pipeline_2d_solid.cpp
new file mode 100644
index 0000000..7acca09
--- /dev/null
+++ b/src/blu_cat/gra/graphics_pipeline_2d_solid.cpp
@@ -0,0 +1,298 @@
+/*
+ * Copyright 2022-2024 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_2d_solid.hpp"
+
+#include <array>
+
+#include "../int/core.hpp"
+#include "sprite.hpp"
+#include "uniform_data_object.hpp"
+
+namespace
+{
+
+void
+load_pipeline(void *obj)
+{
+  auto self = static_cast<BluCat::GRA::GraphicsPipeline2DSolid*>(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 =
+    BluCat::INT::core.vk_device_with_swapchain->vert2d_solid_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 =
+    BluCat::INT::core.vk_device_with_swapchain->frag2d_solid_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(glm::vec2);
+  vertex_input_binding.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
+
+  std::array<VkVertexInputAttributeDescription, 1> vertex_attribute{};
+  // Texture coordinate.
+  vertex_attribute[0].location = 0;
+  vertex_attribute[0].binding = 0;
+  vertex_attribute[0].format = VK_FORMAT_R32G32_SFLOAT;
+  vertex_attribute[0].offset = 0;
+
+  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_STRIP;
+  input_assembly.primitiveRestartEnable = VK_FALSE;
+
+  VkViewport viewport = {};
+  viewport.x = 0;
+  viewport.y = 0;
+  viewport.width = BluCat::INT::core.display_width;
+  viewport.height = BluCat::INT::core.display_height;
+  viewport.minDepth = 0.0f;
+  viewport.maxDepth = 1.0f;
+
+  VkRect2D scissor = {};
+  scissor.offset = {0, 0};
+  scissor.extent = {BluCat::INT::core.display_width, BluCat::INT::core.display_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;
+
+  VkPipelineColorBlendAttachmentState color_blend_attachment = {};
+  color_blend_attachment.blendEnable = VK_TRUE;
+  color_blend_attachment.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
+  color_blend_attachment.dstColorBlendFactor =
+    VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
+  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;
+
+  std::array<VkDynamicState, 3> dynamic_states{
+    VK_DYNAMIC_STATE_VIEWPORT,
+    VK_DYNAMIC_STATE_LINE_WIDTH,
+    VK_DYNAMIC_STATE_BLEND_CONSTANTS
+  };
+
+  VkPipelineDynamicStateCreateInfo dynamic_state_info = {};
+  dynamic_state_info.sType =
+      VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
+  dynamic_state_info.dynamicStateCount = dynamic_states.size();
+  dynamic_state_info.pDynamicStates = dynamic_states.data();
+
+  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 = nullptr;
+  pipeline_info.pColorBlendState = &color_blending;
+  pipeline_info.pDynamicState = &dynamic_state_info;
+  pipeline_info.layout =
+    BluCat::INT::core.vk_graphics_pipeline_2d_solid_layout->pipeline;
+  pipeline_info.renderPass = BluCat::INT::core.vk_render_pass->pipeline_2d;
+  pipeline_info.subpass = 0;
+  pipeline_info.basePipelineHandle = VK_NULL_HANDLE;
+  pipeline_info.basePipelineIndex = -1;
+
+  if(vkCreateGraphicsPipelines(
+       BluCat::INT::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<BluCat::GRA::GraphicsPipeline2DSolid*>(obj);
+
+  vkDestroyPipeline(
+    BluCat::INT::core.vk_device_with_swapchain->device, self->graphic_pipeline,
+		nullptr);
+}
+
+const CommandChain loader{
+  {&load_pipeline, &unload_pipeline}
+};
+
+}
+
+namespace BluCat::GRA
+{
+
+GraphicsPipeline2DSolid::GraphicsPipeline2DSolid()
+{
+  loader.execute(this);
+}
+
+GraphicsPipeline2DSolid::~GraphicsPipeline2DSolid()
+{
+  loader.revert(this);
+}
+
+void
+GraphicsPipeline2DSolid::draw(
+  std::shared_ptr<View2D> view, const VkCommandBuffer draw_command_buffer,
+  const size_t current_frame, const size_t next_frame,
+  const uint32_t image_index)
+{
+  // TODO set viewport just once per view, not once per pipeline.
+  { // Set viewport
+    VkViewport vk_viewport{};
+    vk_viewport.x = view->region.x;
+    vk_viewport.y = view->region.y;
+    vk_viewport.width = view->region.z;
+    vk_viewport.height = view->region.w;
+    vk_viewport.minDepth = 0.0f;
+    vk_viewport.maxDepth = 1.0f;
+    vkCmdSetViewport(draw_command_buffer, 0, 1, &vk_viewport);
+
+    VkRect2D vk_scissor{};
+    vk_scissor.offset.x = static_cast<int32_t>(view->region.x);
+    vk_scissor.offset.y = static_cast<int32_t>(view->region.y);
+    vk_scissor.extent.width = static_cast<uint32_t>(view->region.z);
+    vk_scissor.extent.height = static_cast<uint32_t>(view->region.w);
+    vkCmdSetScissor(draw_command_buffer, 0, 1, &vk_scissor);
+  }
+
+  vkCmdBindPipeline(
+    draw_command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS,
+    this->graphic_pipeline);
+
+  // FIXME: I know sorting is expensive, but I need to figure out better what
+  // to do here.
+  std::sort(view->sprites_to_draw[current_frame].begin(),
+            view->sprites_to_draw[current_frame].end());
+
+  // Draw sprites
+  for(auto& sprite_to_draw: view->sprites_to_draw[current_frame])
+  {
+    std::array<VkDescriptorSet, 2> vk_descriptor_sets{
+      view->descriptor_sets_2d[image_index],
+      sprite_to_draw.sprite->texture->descriptor_sets[image_index]};
+    VkDeviceSize offsets[]{0};
+
+    vkCmdBindDescriptorSets(
+      draw_command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS,
+      INT::core.vk_graphics_pipeline_2d_solid_layout->pipeline, 0,
+      vk_descriptor_sets.size(), vk_descriptor_sets.data(), 0, nullptr);
+    vkCmdBindVertexBuffers(
+      draw_command_buffer, 0, 1, &sprite_to_draw.sprite->vertex_buffer->buffer,
+      offsets);
+
+    UDOVector4D position{sprite_to_draw.position};
+    vkCmdPushConstants(
+      draw_command_buffer,
+      INT::core.vk_graphics_pipeline_2d_solid_layout->pipeline,
+      VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(UDOVector4D), &position);
+    vkCmdDraw(draw_command_buffer, Sprite::vertex_count, 1, 0, 0);
+  }
+
+  // Prepare for the next frame.
+  view->sprites_to_draw[next_frame].clear();
+}
+
+}
diff --git a/src/blu_cat/gra/graphics_pipeline_2d_solid.hpp b/src/blu_cat/gra/graphics_pipeline_2d_solid.hpp
new file mode 100644
index 0000000..c1520c2
--- /dev/null
+++ b/src/blu_cat/gra/graphics_pipeline_2d_solid.hpp
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2022-2024 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 BLU_CAT_GRA_GRAPHICS_PIPELINE_2D_SOLID_H
+#define BLU_CAT_GRA_GRAPHICS_PIPELINE_2D_SOLID_H 1
+
+#include <memory>
+
+#include "vulkan.hpp"
+#include "command_pool.hpp"
+#include "view_2d.hpp"
+
+namespace BluCat::GRA
+{
+
+struct GraphicsPipeline2DSolid
+{
+  VkPipeline graphic_pipeline;
+
+  GraphicsPipeline2DSolid();
+  ~GraphicsPipeline2DSolid();
+
+  void
+  draw(std::shared_ptr<View2D> view, const VkCommandBuffer draw_command_buffer,
+       const size_t current_frame, const size_t next_frame,
+       const uint32_t image_index);
+};
+
+}
+
+#endif /* BLU_CAT_GRA_GRAPHICS_PIPELINE_2D_SOLID_H */
diff --git a/src/blu_cat/gra/graphics_pipeline_2d_solid_layout.cpp b/src/blu_cat/gra/graphics_pipeline_2d_solid_layout.cpp
new file mode 100644
index 0000000..476bac1
--- /dev/null
+++ b/src/blu_cat/gra/graphics_pipeline_2d_solid_layout.cpp
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2022-2024 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_2d_solid_layout.hpp"
+
+#include <array>
+
+#include "../int/core.hpp"
+#include "uniform_data_object.hpp"
+
+namespace
+{
+
+void
+load_pipeline(void *obj)
+{
+  auto self = static_cast<BluCat::GRA::GraphicsPipeline2DSolidLayout*>(obj);
+
+  std::array<VkDescriptorSetLayout, 2> set_layouts{
+    BluCat::INT::core.vk_descriptor_set_layout->view,
+    BluCat::INT::core.vk_descriptor_set_layout->texture
+  };
+
+  std::array<VkPushConstantRange, 1> push_constants;
+  push_constants[0].stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
+  push_constants[0].offset = 0;
+  push_constants[0].size = sizeof(BluCat::GRA::UDOVector4D);
+
+  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 = push_constants.size();
+  pipeline_layout_info.pPushConstantRanges = push_constants.data();
+
+  if(vkCreatePipelineLayout(
+       BluCat::INT::core.vk_device_with_swapchain->device, &pipeline_layout_info,
+       nullptr, &self->pipeline) != VK_SUCCESS)
+    throw CommandError{"Failed to create Vulkan pipeline layout."};
+}
+
+void
+unload_pipeline(void *obj)
+{
+  auto self = static_cast<BluCat::GRA::GraphicsPipeline2DSolidLayout*>(obj);
+
+  vkDestroyPipelineLayout(
+    BluCat::INT::core.vk_device_with_swapchain->device, self->pipeline, nullptr);
+}
+
+const CommandChain loader{
+  {&load_pipeline, &unload_pipeline}
+};
+
+}
+
+namespace BluCat::GRA
+{
+
+GraphicsPipeline2DSolidLayout::GraphicsPipeline2DSolidLayout()
+{
+  loader.execute(this);
+}
+
+GraphicsPipeline2DSolidLayout::~GraphicsPipeline2DSolidLayout()
+{
+  loader.revert(this);
+}
+
+}
diff --git a/src/blu_cat/gra/graphics_pipeline_2d_solid_layout.hpp b/src/blu_cat/gra/graphics_pipeline_2d_solid_layout.hpp
new file mode 100644
index 0000000..6abd65d
--- /dev/null
+++ b/src/blu_cat/gra/graphics_pipeline_2d_solid_layout.hpp
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2022-2024 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 BLU_CAT_GRA_GRAPHICS_PIPELINE_2D_LAYOUT_H
+#define BLU_CAT_GRA_GRAPHICS_PIPELINE_2D_LAYOUT_H 1
+
+#include "vulkan.hpp"
+
+namespace BluCat::GRA
+{
+
+struct GraphicsPipeline2DSolidLayout
+{
+  VkPipelineLayout pipeline;
+
+  GraphicsPipeline2DSolidLayout();
+  ~GraphicsPipeline2DSolidLayout();
+};
+
+}
+
+#endif /* BLU_CAT_GRA_GRAPHICS_PIPELINE_2D_LAYOUT_H */
diff --git a/src/blu_cat/gra/graphics_pipeline_2d_wired.cpp b/src/blu_cat/gra/graphics_pipeline_2d_wired.cpp
new file mode 100644
index 0000000..20491ed
--- /dev/null
+++ b/src/blu_cat/gra/graphics_pipeline_2d_wired.cpp
@@ -0,0 +1,314 @@
+/*
+ * Copyright 2022-2024 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_2d_wired.hpp"
+
+#include <array>
+
+#include "../int/core.hpp"
+#include "rectangle.hpp"
+#include "sprite.hpp"
+#include "uniform_data_object.hpp"
+
+namespace
+{
+
+void
+load_indexes(void *obj)
+{
+  auto self = static_cast<BluCat::GRA::GraphicsPipeline2DWired*>(obj);
+
+  self->queue_family =
+    BluCat::INT::core.vk_device_with_swapchain->get_queue_family_with_graphics();
+
+  std::array<uint32_t, 4> indexes{0, 1, 2, 3};
+  void *indexes_data{indexes.data()};
+  size_t indexes_size{sizeof(indexes[0]) * indexes.size()};
+  BluCat::GRA::SourceBuffer source_index_buffer{
+    self->queue_family->device, indexes_data, indexes_size};
+  self->index_buffer = new BluCat::GRA::DestinationBuffer{
+    self->queue_family, &source_index_buffer,
+    VK_BUFFER_USAGE_INDEX_BUFFER_BIT};
+}
+
+void
+unload_indexes(void *obj)
+{
+  auto self = static_cast<BluCat::GRA::GraphicsPipeline2DWired*>(obj);
+
+  delete self->index_buffer;
+}
+
+void
+load_pipeline(void *obj)
+{
+  auto self = static_cast<BluCat::GRA::GraphicsPipeline2DWired*>(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 =
+    BluCat::INT::core.vk_device_with_swapchain->vert2d_wired_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 =
+    BluCat::INT::core.vk_device_with_swapchain->frag2d_wired_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
+  };
+
+  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 = 0;
+  vertex_input_info.pVertexBindingDescriptions = nullptr;
+  vertex_input_info.vertexAttributeDescriptionCount = 0;
+  vertex_input_info.pVertexAttributeDescriptions = nullptr;
+
+  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_STRIP;
+  input_assembly.primitiveRestartEnable = VK_FALSE;
+
+  VkViewport viewport = {};
+  viewport.x = 0;
+  viewport.y = 0;
+  viewport.width = BluCat::INT::core.display_width;
+  viewport.height = BluCat::INT::core.display_height;
+  viewport.minDepth = 0.0f;
+  viewport.maxDepth = 1.0f;
+
+  VkRect2D scissor = {};
+  scissor.offset = {0, 0};
+  scissor.extent = {BluCat::INT::core.display_width, BluCat::INT::core.display_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_LINE;
+  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;
+
+  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 = nullptr;
+  pipeline_info.pColorBlendState = &color_blending;
+  pipeline_info.pDynamicState = &dynamic_state_info;
+  pipeline_info.layout =
+    BluCat::INT::core.vk_graphics_pipeline_2d_wired_layout->pipeline;
+  pipeline_info.renderPass = BluCat::INT::core.vk_render_pass->pipeline_2d;
+  pipeline_info.subpass = 0;
+  pipeline_info.basePipelineHandle = VK_NULL_HANDLE;
+  pipeline_info.basePipelineIndex = -1;
+
+  if(vkCreateGraphicsPipelines(
+       BluCat::INT::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<BluCat::GRA::GraphicsPipeline2DWired*>(obj);
+
+  vkDestroyPipeline(
+    BluCat::INT::core.vk_device_with_swapchain->device, self->graphic_pipeline,
+		nullptr);
+}
+
+const CommandChain loader{
+  {&load_indexes, &unload_indexes},
+  {&load_pipeline, &unload_pipeline}
+};
+
+}
+
+namespace BluCat::GRA
+{
+
+GraphicsPipeline2DWired::GraphicsPipeline2DWired()
+{
+  loader.execute(this);
+}
+
+GraphicsPipeline2DWired::~GraphicsPipeline2DWired()
+{
+  loader.revert(this);
+}
+
+void
+GraphicsPipeline2DWired::draw(
+  std::shared_ptr<View2D> view, const VkCommandBuffer draw_command_buffer,
+  const size_t current_frame, const size_t next_frame,
+  const uint32_t image_index)
+{
+  // Set viewport
+  {
+    VkViewport vk_viewport{};
+    vk_viewport.x = view->region.x;
+    vk_viewport.y = view->region.y;
+    vk_viewport.width = view->region.z;
+    vk_viewport.height = view->region.w;
+    vk_viewport.minDepth = 0.0f;
+    vk_viewport.maxDepth = 1.0f;
+    vkCmdSetViewport(draw_command_buffer, 0, 1, &vk_viewport);
+
+    VkRect2D vk_scissor{};
+    vk_scissor.offset.x = static_cast<int32_t>(view->region.x);
+    vk_scissor.offset.y = static_cast<int32_t>(view->region.y);
+    vk_scissor.extent.width = static_cast<uint32_t>(view->region.z);
+    vk_scissor.extent.height = static_cast<uint32_t>(view->region.w);
+    vkCmdSetScissor(draw_command_buffer, 0, 1, &vk_scissor);
+  }
+
+  // Draw rectangles
+  {
+    std::array<VkDescriptorSet, 1> vk_descriptor_sets{
+      view->descriptor_sets_2d[image_index]};
+    VkDeviceSize offsets[]{0};
+
+    vkCmdBindDescriptorSets(
+      draw_command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS,
+      INT::core.vk_graphics_pipeline_2d_wired_layout->pipeline, 0,
+      vk_descriptor_sets.size(), vk_descriptor_sets.data(), 0, nullptr);
+    vkCmdBindPipeline(
+      draw_command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS,
+      this->graphic_pipeline);
+    vkCmdBindIndexBuffer(
+      draw_command_buffer, this->index_buffer->buffer, 0,
+      VK_INDEX_TYPE_UINT32);
+
+    for(auto i{0}; i < view->rectangles_to_draw[current_frame].size(); i++)
+    {
+      auto &rect{view->rectangles_to_draw[current_frame][i]};
+
+      UDOVector4D position{rect.position};
+      UDOVector3D color{rect.color};
+      vkCmdPushConstants(
+        draw_command_buffer,
+        INT::core.vk_graphics_pipeline_2d_wired_layout->pipeline,
+        VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(UDOVector4D), &position);
+      vkCmdPushConstants(
+        draw_command_buffer,
+        INT::core.vk_graphics_pipeline_2d_wired_layout->pipeline,
+        VK_SHADER_STAGE_FRAGMENT_BIT, sizeof(UDOVector4D), sizeof(UDOVector3D),
+        &color);
+      vkCmdDrawIndexed(
+        draw_command_buffer, Rectangle::VertexCount, 1, 0, 0, 0);
+    }
+  }
+
+  // Prepare for the next frame.
+  view->rectangles_to_draw[next_frame].clear();
+}
+
+}
diff --git a/src/blu_cat/gra/graphics_pipeline_2d_wired.hpp b/src/blu_cat/gra/graphics_pipeline_2d_wired.hpp
new file mode 100644
index 0000000..7d2752e
--- /dev/null
+++ b/src/blu_cat/gra/graphics_pipeline_2d_wired.hpp
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2022-2024 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 BLU_CAT_GRA_GRAPHICS_PIPELINE_2D_WIRED_H
+#define BLU_CAT_GRA_GRAPHICS_PIPELINE_2D_WIRED_H 1
+
+#include <memory>
+
+#include "vulkan.hpp"
+#include "view_2d.hpp"
+
+namespace BluCat::GRA
+{
+
+struct GraphicsPipeline2DWired
+{
+  QueueFamily *queue_family;
+
+  VkPipeline graphic_pipeline;
+
+  DestinationBuffer *index_buffer;
+
+  GraphicsPipeline2DWired();
+  ~GraphicsPipeline2DWired();
+
+  void
+  draw(std::shared_ptr<View2D> view, const VkCommandBuffer draw_command_buffer,
+       const size_t current_frame, const size_t next_frame,
+       const uint32_t image_index);
+};
+
+}
+
+#endif /* BLU_CAT_GRA_GRAPHICS_PIPELINE_2D_WIRED_H */
diff --git a/src/blu_cat/gra/graphics_pipeline_2d_wired_layout.cpp b/src/blu_cat/gra/graphics_pipeline_2d_wired_layout.cpp
new file mode 100644
index 0000000..431a845
--- /dev/null
+++ b/src/blu_cat/gra/graphics_pipeline_2d_wired_layout.cpp
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2022-2024 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_2d_wired_layout.hpp"
+
+#include <array>
+
+#include "../int/core.hpp"
+#include "graphics_pipeline_2d_solid_layout.hpp"
+#include "uniform_data_object.hpp"
+
+namespace
+{
+
+void
+load_pipeline(void *obj)
+{
+  auto self = static_cast<BluCat::GRA::GraphicsPipeline2DWiredLayout*>(obj);
+
+  std::array<VkDescriptorSetLayout, 1> set_layouts{
+    BluCat::INT::core.vk_descriptor_set_layout->view
+  };
+
+  std::array<VkPushConstantRange, 2> push_constants;
+  push_constants[0].stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
+  push_constants[0].offset = 0;
+  push_constants[0].size = sizeof(BluCat::GRA::UDOVector4D);
+
+  push_constants[1].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
+  push_constants[1].offset = sizeof(BluCat::GRA::UDOVector4D);
+  push_constants[1].size = sizeof(BluCat::GRA::UDOVector3D);
+
+  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 = push_constants.size();
+  pipeline_layout_info.pPushConstantRanges = push_constants.data();
+
+  if(vkCreatePipelineLayout(
+       BluCat::INT::core.vk_device_with_swapchain->device, &pipeline_layout_info,
+       nullptr, &self->pipeline) != VK_SUCCESS)
+    throw CommandError{"Failed to create Vulkan pipeline layout."};
+}
+
+void
+unload_pipeline(void *obj)
+{
+  auto self = static_cast<BluCat::GRA::GraphicsPipeline2DWiredLayout*>(obj);
+
+  vkDestroyPipelineLayout(
+    BluCat::INT::core.vk_device_with_swapchain->device, self->pipeline, nullptr);
+}
+
+const CommandChain loader{
+  {&load_pipeline, &unload_pipeline}
+};
+
+}
+
+namespace BluCat::GRA
+{
+
+GraphicsPipeline2DWiredLayout::GraphicsPipeline2DWiredLayout()
+{
+  loader.execute(this);
+}
+
+GraphicsPipeline2DWiredLayout::~GraphicsPipeline2DWiredLayout()
+{
+  loader.revert(this);
+}
+
+}
diff --git a/src/blu_cat/gra/graphics_pipeline_2d_wired_layout.hpp b/src/blu_cat/gra/graphics_pipeline_2d_wired_layout.hpp
new file mode 100644
index 0000000..16f23ea
--- /dev/null
+++ b/src/blu_cat/gra/graphics_pipeline_2d_wired_layout.hpp
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2022-2024 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 BLU_CAT_GRA_GRAPHICS_PIPELINE_2D_WIRED_LAYOUT_H
+#define BLU_CAT_GRA_GRAPHICS_PIPELINE_2D_WIRED_LAYOUT_H 1
+
+#include "vulkan.hpp"
+
+namespace BluCat::GRA
+{
+
+struct GraphicsPipeline2DWiredLayout
+{
+  VkPipelineLayout pipeline;
+
+  GraphicsPipeline2DWiredLayout();
+  ~GraphicsPipeline2DWiredLayout();
+};
+
+}
+
+#endif /* BLU_CAT_GRA_GRAPHICS_PIPELINE_2D_LAYOUT_H */
diff --git a/src/blu_cat/gra/graphics_pipeline_3d.cpp b/src/blu_cat/gra/graphics_pipeline_3d.cpp
new file mode 100644
index 0000000..1f7324a
--- /dev/null
+++ b/src/blu_cat/gra/graphics_pipeline_3d.cpp
@@ -0,0 +1,308 @@
+/*
+ * Copyright 2022-2024 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_3d.hpp"
+
+#include <array>
+#include <stdexcept>
+
+#include "../int/core.hpp"
+#include "vulkan.hpp"
+#include "static_mesh_vertex.hpp"
+#include "uniform_data_object.hpp"
+
+namespace
+{
+
+void
+load_pipeline(void *obj)
+{
+  auto self = static_cast<BluCat::GRA::GraphicsPipeline3D*>(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 =
+    BluCat::INT::core.vk_device_with_swapchain->vert3d_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 =
+    BluCat::INT::core.vk_device_with_swapchain->frag3d_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(BluCat::GRA::StaticMeshVertex);
+  vertex_input_binding.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
+
+  std::array<VkVertexInputAttributeDescription, 3> 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(BluCat::GRA::StaticMeshVertex, 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(BluCat::GRA::StaticMeshVertex, normal);
+  // Texture coordinate.
+  vertex_attribute[2].location = 2;
+  vertex_attribute[2].binding = 0;
+  vertex_attribute[2].format = VK_FORMAT_R32G32_SFLOAT;
+  vertex_attribute[2].offset = offsetof(BluCat::GRA::StaticMeshVertex, 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;
+  viewport.y = 0;
+  viewport.width = BluCat::INT::core.display_width;
+  viewport.height = BluCat::INT::core.display_height;
+  viewport.minDepth = 0.0f;
+  viewport.maxDepth = 1.0f;
+
+  VkRect2D scissor = {};
+  scissor.offset = {0, 0};
+  scissor.extent = {BluCat::INT::core.display_width, BluCat::INT::core.display_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 = BluCat::INT::core.vk_graphics_pipeline_3d_layout->pipeline;
+  pipeline_info.renderPass = BluCat::INT::core.vk_render_pass->pipeline_3d;
+  pipeline_info.subpass = 0;
+  pipeline_info.basePipelineHandle = VK_NULL_HANDLE;
+  pipeline_info.basePipelineIndex = -1;
+
+  if(vkCreateGraphicsPipelines(
+       BluCat::INT::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<BluCat::GRA::GraphicsPipeline3D*>(obj);
+
+  vkDestroyPipeline(
+    BluCat::INT::core.vk_device_with_swapchain->device, self->graphic_pipeline,
+		nullptr);
+}
+
+const CommandChain loader{
+  {&load_pipeline, &unload_pipeline}
+};
+
+}
+
+namespace BluCat::GRA
+{
+
+GraphicsPipeline3D::GraphicsPipeline3D()
+{
+  loader.execute(this);
+}
+
+GraphicsPipeline3D::~GraphicsPipeline3D()
+{
+  loader.revert(this);
+}
+
+void
+GraphicsPipeline3D::draw(
+  std::shared_ptr<View3D> view, const VkCommandBuffer draw_command_buffer,
+  const size_t current_frame, const uint32_t image_index)
+{
+  vkCmdBindPipeline(
+    draw_command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS,
+    this->graphic_pipeline);
+
+  // Draw models
+  for(auto& [static_mesh, instances]:
+				INT::core.vk_renderer->static_models_to_draw[current_frame])
+  {
+      VkBuffer vertex_buffers[]{static_mesh->vertex_buffer->buffer};
+      VkDeviceSize offsets[]{0};
+
+      vkCmdBindVertexBuffers(
+	draw_command_buffer, 0, 1, vertex_buffers, offsets);
+      vkCmdBindIndexBuffer(
+	draw_command_buffer, static_mesh->index_buffer->buffer, 0,
+	VK_INDEX_TYPE_UINT32);
+
+      for(auto &instance: instances)
+      { // Object matrix.
+        glm::mat4 translation_matrix{1.0f};
+        translation_matrix = glm::translate(
+	  translation_matrix, *instance->position);
+	glm::mat4 rotation_matrix{glm::toMat4(*instance->orientation)};
+
+	std::array<VkDescriptorSet, 4> vk_descriptor_sets{
+	  INT::core.vk_light->descriptor_sets_world[image_index],
+	  view->descriptor_sets_3d[image_index],
+	  instance->descriptor_sets[image_index],
+	  instance->texture->descriptor_sets[image_index]};
+
+	vkCmdBindDescriptorSets(
+	  draw_command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS,
+	  INT::core.vk_graphics_pipeline_3d_layout->pipeline, 0,
+	  vk_descriptor_sets.size(), vk_descriptor_sets.data(), 0, nullptr);
+
+        vkCmdDrawIndexed(
+          draw_command_buffer, static_mesh->index_count, 1, 0, 0, 0);
+
+	BluCat::GRA::UDOStaticModel udo_static_model{};
+	udo_static_model.base_matrix = translation_matrix * rotation_matrix;
+	instance->uniform_buffers[image_index].copy_data(&udo_static_model);
+      }
+  }
+}
+
+}
diff --git a/src/blu_cat/gra/graphics_pipeline_3d.hpp b/src/blu_cat/gra/graphics_pipeline_3d.hpp
new file mode 100644
index 0000000..0dc0b03
--- /dev/null
+++ b/src/blu_cat/gra/graphics_pipeline_3d.hpp
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2022-2024 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 BLU_CAT_GRA_GRAPHICS_PIPELINE_3D_H
+#define BLU_CAT_GRA_GRAPHICS_PIPELINE_3D_H 1
+
+#include <memory>
+
+#include "vulkan.hpp"
+#include "command_pool.hpp"
+#include "view_3d.hpp"
+
+namespace BluCat::GRA
+{
+
+struct GraphicsPipeline3D
+{
+  VkPipeline graphic_pipeline;
+
+  GraphicsPipeline3D();
+  ~GraphicsPipeline3D();
+
+  void
+  draw(std::shared_ptr<View3D> view, const VkCommandBuffer draw_command_buffer,
+       const size_t current_frame, const uint32_t image_index);
+};
+
+}
+
+#endif /* BLU_CAT_GRA_GRAPHICS_PIPELINE_3D_H */
diff --git a/src/blu_cat/gra/graphics_pipeline_3d_layout.cpp b/src/blu_cat/gra/graphics_pipeline_3d_layout.cpp
new file mode 100644
index 0000000..504b46e
--- /dev/null
+++ b/src/blu_cat/gra/graphics_pipeline_3d_layout.cpp
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2022-2024 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_3d_layout.hpp"
+
+#include <array>
+
+#include "../int/core.hpp"
+#include "uniform_data_object.hpp"
+
+namespace
+{
+
+void
+load_pipeline(void *obj)
+{
+  auto self = static_cast<BluCat::GRA::GraphicsPipeline3DLayout*>(obj);
+
+  std::array<VkDescriptorSetLayout, 4> set_layouts{
+    BluCat::INT::core.vk_descriptor_set_layout->world,
+    BluCat::INT::core.vk_descriptor_set_layout->view,
+    BluCat::INT::core.vk_descriptor_set_layout->model,
+    BluCat::INT::core.vk_descriptor_set_layout->texture};
+
+  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(
+       BluCat::INT::core.vk_device_with_swapchain->device,
+       &pipeline_layout_info, nullptr, &self->pipeline) != VK_SUCCESS)
+    throw CommandError{"Failed to create Vulkan pipeline layout."};
+}
+
+void
+unload_pipeline(void *obj)
+{
+  auto self = static_cast<BluCat::GRA::GraphicsPipeline3DLayout*>(obj);
+
+  vkDestroyPipelineLayout(
+    BluCat::INT::core.vk_device_with_swapchain->device, self->pipeline, nullptr);
+}
+
+const CommandChain loader{
+  {&load_pipeline, &unload_pipeline}
+};
+
+}
+
+namespace BluCat::GRA
+{
+
+GraphicsPipeline3DLayout::GraphicsPipeline3DLayout()
+{
+  loader.execute(this);
+}
+
+GraphicsPipeline3DLayout::~GraphicsPipeline3DLayout()
+{
+  loader.revert(this);
+}
+
+}
diff --git a/src/blu_cat/gra/graphics_pipeline_3d_layout.hpp b/src/blu_cat/gra/graphics_pipeline_3d_layout.hpp
new file mode 100644
index 0000000..13820c4
--- /dev/null
+++ b/src/blu_cat/gra/graphics_pipeline_3d_layout.hpp
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2022-2024 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 BLU_CAT_GRA_GRAPHICS_PIPELINE_3D_LAYOUT_H
+#define BLU_CAT_GRA_GRAPHICS_PIPELINE_3D_LAYOUT_H 1
+
+#include "vulkan.hpp"
+
+namespace BluCat::GRA
+{
+
+struct GraphicsPipeline3DLayout
+{
+  VkPipelineLayout pipeline;
+
+  GraphicsPipeline3DLayout();
+  ~GraphicsPipeline3DLayout();
+};
+
+}
+
+#endif /* BLU_CAT_GRA_GRAPHICS_PIPELINE_3D_LAYOUT_H */
diff --git a/src/blu_cat/gra/graphics_pipeline_3d_skeletal.cpp b/src/blu_cat/gra/graphics_pipeline_3d_skeletal.cpp
new file mode 100644
index 0000000..e8eda6b
--- /dev/null
+++ b/src/blu_cat/gra/graphics_pipeline_3d_skeletal.cpp
@@ -0,0 +1,322 @@
+/*
+ * Copyright 2022-2024 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_3d.hpp"
+
+#include <array>
+#include <stdexcept>
+
+#include "../int/core.hpp"
+#include "skeletal_mesh_vertex.hpp"
+#include "uniform_data_object.hpp"
+
+namespace
+{
+
+void
+load_pipeline(void *obj)
+{
+  auto self = static_cast<BluCat::GRA::GraphicsPipeline3DSkeletal*>(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 =
+    BluCat::INT::core.vk_device_with_swapchain->vert3d_skeletal_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 =
+    BluCat::INT::core.vk_device_with_swapchain->frag3d_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(BluCat::GRA::SkeletalMeshVertex);
+  vertex_input_binding.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
+
+  std::array<VkVertexInputAttributeDescription, 5> 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(BluCat::GRA::SkeletalMeshVertex, 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(BluCat::GRA::SkeletalMeshVertex, normal);
+  // Texture coordinate.
+  vertex_attribute[2].location = 2;
+  vertex_attribute[2].binding = 0;
+  vertex_attribute[2].format = VK_FORMAT_R32G32_SFLOAT;
+  vertex_attribute[2].offset = offsetof(BluCat::GRA::SkeletalMeshVertex, texture_coord);
+  // Bones ids.
+  vertex_attribute[3].location = 3;
+  vertex_attribute[3].binding = 0;
+  vertex_attribute[3].format = VK_FORMAT_R32G32B32A32_SINT;
+  vertex_attribute[3].offset = offsetof(BluCat::GRA::SkeletalMeshVertex, bone_ids);
+  // Bones weights.
+  vertex_attribute[4].location = 4;
+  vertex_attribute[4].binding = 0;
+  vertex_attribute[4].format = VK_FORMAT_R32G32B32A32_SFLOAT;
+  vertex_attribute[4].offset = offsetof(BluCat::GRA::SkeletalMeshVertex, bone_weights);
+
+  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;
+  viewport.y = 0;
+  viewport.width = BluCat::INT::core.display_width;
+  viewport.height = BluCat::INT::core.display_height;
+  viewport.minDepth = 0.0f;
+  viewport.maxDepth = 1.0f;
+
+  VkRect2D scissor = {};
+  scissor.offset = {0, 0};
+  scissor.extent = {BluCat::INT::core.display_width, BluCat::INT::core.display_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 = BluCat::INT::core.vk_graphics_pipeline_3d_layout->pipeline;
+  pipeline_info.renderPass = BluCat::INT::core.vk_render_pass->pipeline_3d;
+  pipeline_info.subpass = 0;
+  pipeline_info.basePipelineHandle = VK_NULL_HANDLE;
+  pipeline_info.basePipelineIndex = -1;
+
+  if(vkCreateGraphicsPipelines(
+       BluCat::INT::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<BluCat::GRA::GraphicsPipeline3DSkeletal*>(obj);
+
+  vkDestroyPipeline(
+    BluCat::INT::core.vk_device_with_swapchain->device, self->graphic_pipeline,
+		nullptr);
+}
+
+const CommandChain loader{
+  {&load_pipeline, &unload_pipeline}
+};
+
+}
+
+namespace BluCat::GRA
+{
+
+GraphicsPipeline3DSkeletal::GraphicsPipeline3DSkeletal()
+{
+  loader.execute(this);
+}
+
+GraphicsPipeline3DSkeletal::~GraphicsPipeline3DSkeletal()
+{
+  loader.revert(this);
+}
+
+void
+GraphicsPipeline3DSkeletal::draw(
+  std::shared_ptr<View3D> view, const VkCommandBuffer draw_command_buffer,
+  const size_t current_frame, const uint32_t image_index)
+{
+	vkCmdBindPipeline(
+		draw_command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS,
+		this->graphic_pipeline);
+
+	// Draw models
+	for(auto& [skeletal_mesh, instances]:
+				INT::core.vk_renderer->skeletal_models_to_draw[current_frame])
+	{
+		VkBuffer vertex_buffers[]{skeletal_mesh->vertex_buffer->buffer};
+		VkDeviceSize offsets[]{0};
+
+		vkCmdBindVertexBuffers(
+			draw_command_buffer, 0, 1, vertex_buffers, offsets);
+		vkCmdBindIndexBuffer(
+			draw_command_buffer, skeletal_mesh->index_buffer->buffer, 0,
+			VK_INDEX_TYPE_UINT32);
+
+		for(auto &instance: instances)
+		{ // Object matrix.
+			glm::mat4 translation_matrix{1.0f};
+			translation_matrix = glm::translate(
+				translation_matrix, *instance->position);
+			glm::mat4 rotation_matrix{glm::toMat4(*instance->orientation)};
+
+			std::array<VkDescriptorSet, 4> vk_descriptor_sets{
+				INT::core.vk_light->descriptor_sets_world[image_index],
+				view->descriptor_sets_3d[image_index],
+				instance->descriptor_sets[image_index],
+				instance->texture->descriptor_sets[image_index]};
+
+			vkCmdBindDescriptorSets(
+				draw_command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS,
+				INT::core.vk_graphics_pipeline_3d_layout->pipeline, 0,
+				vk_descriptor_sets.size(), vk_descriptor_sets.data(), 0, nullptr);
+
+			vkCmdDrawIndexed(
+				draw_command_buffer, skeletal_mesh->index_count, 1, 0, 0, 0);
+
+			BluCat::GRA::UDOSkeletalModel udo_skeletal_model{};
+			instance->tick(INT::core.delta_time);
+			udo_skeletal_model.base_matrix = translation_matrix * rotation_matrix;
+			std::copy(instance->bone_transforms.begin(),
+								instance->bone_transforms.end(),
+								udo_skeletal_model.bone_matrices);
+			instance->uniform_buffers[image_index].copy_data(&udo_skeletal_model);
+		}
+  }
+
+}
+
+}
diff --git a/src/blu_cat/gra/graphics_pipeline_3d_skeletal.hpp b/src/blu_cat/gra/graphics_pipeline_3d_skeletal.hpp
new file mode 100644
index 0000000..bad117d
--- /dev/null
+++ b/src/blu_cat/gra/graphics_pipeline_3d_skeletal.hpp
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2022-2024 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 BLU_CAT_GRA_GRAPHICS_PIPELINE_3D_SKELETAL_H
+#define BLU_CAT_GRA_GRAPHICS_PIPELINE_3D_SKELETAL_H 1
+
+#include <memory>
+
+#include "vulkan.hpp"
+#include "command_pool.hpp"
+#include "view_3d.hpp"
+
+namespace BluCat::GRA
+{
+
+struct GraphicsPipeline3DSkeletal
+{
+  VkPipeline graphic_pipeline;
+
+  GraphicsPipeline3DSkeletal();
+  ~GraphicsPipeline3DSkeletal();
+
+  void
+  draw(std::shared_ptr<View3D> view, const VkCommandBuffer draw_command_buffer,
+       const size_t current_frame, const uint32_t image_index);
+};
+
+}
+
+#endif /* BLU_CAT_GRA_GRAPHICS_PIPELINE_SKELETAL_3D_H */
diff --git a/src/blu_cat/gra/graphics_pipeline_sprite_3d.cpp b/src/blu_cat/gra/graphics_pipeline_sprite_3d.cpp
new file mode 100644
index 0000000..14796d3
--- /dev/null
+++ b/src/blu_cat/gra/graphics_pipeline_sprite_3d.cpp
@@ -0,0 +1,315 @@
+/*
+ * Copyright 2022-2024 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_sprite_3d.hpp"
+
+#include <array>
+
+#include "../int/core.hpp"
+#include "sprite.hpp"
+#include "uniform_data_object.hpp"
+
+namespace
+{
+
+struct Sprite3DOrder
+{
+  std::shared_ptr<BluCat::GRA::Sprite3D> sprite_3d;
+  float distance;
+};
+
+bool
+operator<(const Sprite3DOrder &a, const Sprite3DOrder &b)
+{
+  return a.distance < b.distance;
+}
+
+bool
+operator>(const Sprite3DOrder &a, const Sprite3DOrder &b)
+{
+  return a.distance > b.distance;
+}
+
+void
+load_pipeline(void *obj)
+{
+  auto self = static_cast<BluCat::GRA::GraphicsPipelineSprite3D*>(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 =
+    BluCat::INT::core.vk_device_with_swapchain->vert_sprite_3d_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 =
+    BluCat::INT::core.vk_device_with_swapchain->frag_sprite_3d_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(glm::vec2);
+  vertex_input_binding.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
+
+  std::array<VkVertexInputAttributeDescription, 1> vertex_attribute{};
+  // Texture coordinate.
+  vertex_attribute[0].location = 0;
+  vertex_attribute[0].binding = 0;
+  vertex_attribute[0].format = VK_FORMAT_R32G32_SFLOAT;
+  vertex_attribute[0].offset = 0;
+
+  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_STRIP;
+  input_assembly.primitiveRestartEnable = VK_FALSE;
+
+  VkViewport viewport = {};
+  viewport.x = 0;
+  viewport.y = 0;
+  viewport.width = BluCat::INT::core.display_width;
+  viewport.height = BluCat::INT::core.display_height;
+  viewport.minDepth = 0.0f;
+  viewport.maxDepth = 1.0f;
+
+  VkRect2D scissor = {};
+  scissor.offset = {0, 0};
+  scissor.extent = {BluCat::INT::core.display_width, BluCat::INT::core.display_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_TRUE;
+  color_blend_attachment.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
+  color_blend_attachment.dstColorBlendFactor =
+    VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
+  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;
+
+  std::array<VkDynamicState, 3> dynamic_states{
+    VK_DYNAMIC_STATE_VIEWPORT,
+    VK_DYNAMIC_STATE_LINE_WIDTH,
+    VK_DYNAMIC_STATE_BLEND_CONSTANTS
+  };
+
+  VkPipelineDynamicStateCreateInfo dynamic_state_info = {};
+  dynamic_state_info.sType =
+      VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
+  dynamic_state_info.dynamicStateCount = dynamic_states.size();
+  dynamic_state_info.pDynamicStates = dynamic_states.data();
+
+  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 = BluCat::INT::core.vk_graphics_pipeline_3d_layout->pipeline;
+  pipeline_info.renderPass = BluCat::INT::core.vk_render_pass->pipeline_3d;
+  pipeline_info.subpass = 0;
+  pipeline_info.basePipelineHandle = VK_NULL_HANDLE;
+  pipeline_info.basePipelineIndex = -1;
+
+  if(vkCreateGraphicsPipelines(
+       BluCat::INT::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 sprite 3d."};
+}
+
+void
+unload_pipeline(void *obj)
+{
+  auto self = static_cast<BluCat::GRA::GraphicsPipelineSprite3D*>(obj);
+
+  vkDestroyPipeline(
+    BluCat::INT::core.vk_device_with_swapchain->device, self->graphic_pipeline,
+		nullptr);
+}
+
+const CommandChain loader{
+  {&load_pipeline, &unload_pipeline}
+};
+
+}
+
+namespace BluCat::GRA
+{
+
+GraphicsPipelineSprite3D::GraphicsPipelineSprite3D()
+{
+  loader.execute(this);
+}
+
+GraphicsPipelineSprite3D::~GraphicsPipelineSprite3D()
+{
+  loader.revert(this);
+}
+
+void
+GraphicsPipelineSprite3D::draw(
+  std::shared_ptr<View3D> view, const VkCommandBuffer draw_command_buffer,
+  const size_t current_frame, const uint32_t image_index)
+{
+  vkCmdBindPipeline(
+    draw_command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS,
+    this->graphic_pipeline);
+
+  std::vector<Sprite3DOrder> sprite_3d_order;
+  { // Sort sprites 3D
+    sprite_3d_order.reserve(
+      INT::core.vk_renderer->sprites_3d_to_draw[current_frame].size());
+
+    for(std::shared_ptr<BluCat::GRA::Sprite3D> sprite:
+	  INT::core.vk_renderer->sprites_3d_to_draw[current_frame])
+      sprite_3d_order.emplace_back(
+	sprite, glm::distance(*view->camera_position, *sprite->position));
+
+    std::sort(sprite_3d_order.rbegin(), sprite_3d_order.rend());
+  }
+
+  // Draw sprites
+  for(auto& sprite: sprite_3d_order)
+  {
+    std::array<VkDescriptorSet, 4> vk_descriptor_sets{
+			INT::core.vk_light->descriptor_sets_world[image_index],
+      view->descriptor_sets_3d[image_index],
+      sprite.sprite_3d->descriptor_sets[image_index],
+      sprite.sprite_3d->sprite->texture->descriptor_sets[image_index]};
+    VkDeviceSize offsets[]{0};
+
+    vkCmdBindVertexBuffers(
+      draw_command_buffer, 0, 1,
+      &sprite.sprite_3d->sprite->vertex_buffer->buffer, offsets);
+    vkCmdBindDescriptorSets(
+      draw_command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS,
+      INT::core.vk_graphics_pipeline_3d_layout->pipeline, 0,
+      vk_descriptor_sets.size(), vk_descriptor_sets.data(), 0, nullptr);
+
+    UDOSprite3D ubo_sprite_3d{};
+    ubo_sprite_3d.position = *sprite.sprite_3d->position;
+    ubo_sprite_3d.size = sprite.sprite_3d->size;
+    sprite.sprite_3d->uniform_buffers[image_index].copy_data(&ubo_sprite_3d);
+
+    vkCmdDraw(draw_command_buffer,  Sprite::vertex_count, 1, 0, 0);
+ }
+}
+
+}
diff --git a/src/blu_cat/gra/graphics_pipeline_sprite_3d.hpp b/src/blu_cat/gra/graphics_pipeline_sprite_3d.hpp
new file mode 100644
index 0000000..325d943
--- /dev/null
+++ b/src/blu_cat/gra/graphics_pipeline_sprite_3d.hpp
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2022-2024 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 BLU_CAT_GRA_GRAPHICS_PIPELINE_SPRITE_3D_H
+#define BLU_CAT_GRA_GRAPHICS_PIPELINE_SPRITE_3D_H 1
+
+#include <memory>
+
+#include "vulkan.hpp"
+#include "command_pool.hpp"
+#include "sprite_3d.hpp"
+#include "view_3d.hpp"
+
+namespace BluCat::GRA
+{
+
+struct GraphicsPipelineSprite3D
+{
+  VkPipeline graphic_pipeline;
+
+  GraphicsPipelineSprite3D();
+  ~GraphicsPipelineSprite3D();
+
+  void
+  draw(std::shared_ptr<View3D> view, const VkCommandBuffer draw_command_buffer,
+       const size_t current_frame, const uint32_t image_index);
+};
+
+}
+
+#endif /* BLU_CAT_GRA_GRAPHICS_PIPELINE_SPRITE_3D_H */
diff --git a/src/blu_cat/gra/image.cpp b/src/blu_cat/gra/image.cpp
new file mode 100644
index 0000000..76e4f9d
--- /dev/null
+++ b/src/blu_cat/gra/image.cpp
@@ -0,0 +1,149 @@
+/*
+ * Copyright 2022-2024 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 "../int/core.hpp"
+
+namespace BluCat::GRA::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(
+  BluCat::GRA::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 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 create_view(
+  BluCat::GRA::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/blu_cat/gra/image.hpp b/src/blu_cat/gra/image.hpp
new file mode 100644
index 0000000..ba64b76
--- /dev/null
+++ b/src/blu_cat/gra/image.hpp
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2022-2024 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 BLU_CAT_GRA_IMAGE_H
+#define BLU_CAT_GRA_IMAGE_H 1
+
+#include <memory>
+#include <string>
+
+#include "vulkan.hpp"
+#include "device.hpp"
+
+namespace BluCat::GRA::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(
+  BluCat::GRA::Device *device,
+  VkImage *image,
+  VkDeviceMemory *image_memory,
+  VkFormat format,
+  const VkExtent3D &extent3d,
+  uint32_t mip_levels,
+  VkImageTiling image_tiling,
+  VkImageUsageFlags usage);
+
+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);
+
+void
+create_view(
+  BluCat::GRA::Device *device,
+  VkImageView *image_view,
+  const VkImage &image,
+  VkFormat format,
+  VkImageAspectFlags image_aspect_flags);
+}
+
+#endif /* BLU_CAT_GRA_IMAGE_H */
diff --git a/src/blu_cat/gra/light.cpp b/src/blu_cat/gra/light.cpp
new file mode 100644
index 0000000..403dcf4
--- /dev/null
+++ b/src/blu_cat/gra/light.cpp
@@ -0,0 +1,205 @@
+/*
+ * Copyright 2022-2024 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 "light.hpp"
+
+#include <array>
+
+#include "../int/core.hpp"
+#include "uniform_data_object.hpp"
+
+namespace
+{
+
+void
+load_world_vert_uniform_buffer(void *obj)
+{
+  auto self = static_cast<BluCat::GRA::Light*>(obj);
+
+  try
+  {
+    self->ub_world_vert.reserve(BluCat::INT::core.vk_swapchain->images_count);
+    for(auto i{0}; i < BluCat::INT::core.vk_swapchain->images_count; i++)
+      self->ub_world_vert.emplace_back(
+	BluCat::INT::core.vk_device_with_swapchain, sizeof(BluCat::GRA::UDOWorld3D_Vert));
+  }
+  catch(const std::exception& e)
+  {
+    throw CommandError{e.what()};
+  }
+}
+
+void
+unload_world_vert_uniform_buffer(void *obj)
+{
+  auto self = static_cast<BluCat::GRA::Light*>(obj);
+
+  self->ub_world_vert.clear();
+}
+
+void
+load_world_frag_uniform_buffer(void *obj)
+{
+  auto self = static_cast<BluCat::GRA::Light*>(obj);
+
+  try
+  {
+    self->ub_world_frag.reserve(BluCat::INT::core.vk_swapchain->images_count);
+    for(auto i{0}; i < BluCat::INT::core.vk_swapchain->images_count; i++)
+      self->ub_world_frag.emplace_back(
+	BluCat::INT::core.vk_device_with_swapchain, sizeof(BluCat::GRA::UDOWorld3D_Frag));
+  }
+  catch(const std::exception& e)
+  {
+    throw CommandError{e.what()};
+  }
+}
+
+void
+unload_world_frag_uniform_buffer(void *obj)
+{
+  auto self = static_cast<BluCat::GRA::Light*>(obj);
+
+  self->ub_world_frag.clear();
+}
+
+void
+load_descriptor_pool(void *obj)
+{
+  auto self = static_cast<BluCat::GRA::Light*>(obj);
+
+  uint32_t uniform_buffers_count =
+    self->ub_world_vert.size() + self->ub_world_vert.size();
+
+  VkDescriptorPoolSize descriptor_pool_size{};
+  descriptor_pool_size.type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
+  descriptor_pool_size.descriptorCount = uniform_buffers_count;
+
+  VkDescriptorPoolCreateInfo pool_info{};
+  pool_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
+  pool_info.pNext = nullptr;
+  pool_info.flags = 0;
+  pool_info.maxSets = uniform_buffers_count;
+  pool_info.poolSizeCount = 1;
+  pool_info.pPoolSizes = &descriptor_pool_size;
+
+  if(vkCreateDescriptorPool(
+       BluCat::INT::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<BluCat::GRA::Light*>(obj);
+
+  vkDestroyDescriptorPool(
+    BluCat::INT::core.vk_device_with_swapchain->device, self->descriptor_pool,
+    nullptr);
+}
+
+void
+load_descriptor_sets_world(void *obj)
+{
+  auto self = static_cast<BluCat::GRA::Light*>(obj);
+
+  std::vector<VkDescriptorSetLayout> layouts(
+    BluCat::INT::core.vk_swapchain->images_count,
+    BluCat::INT::core.vk_descriptor_set_layout->world);
+
+  VkDescriptorSetAllocateInfo alloc_info{};
+  alloc_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
+  alloc_info.descriptorPool = self->descriptor_pool;
+  alloc_info.descriptorSetCount = layouts.size();
+  alloc_info.pSetLayouts = layouts.data();
+
+  self->descriptor_sets_world.resize(layouts.size());
+  if(vkAllocateDescriptorSets(
+       BluCat::INT::core.vk_device_with_swapchain->device, &alloc_info,
+       self->descriptor_sets_world.data()) != VK_SUCCESS)
+    throw CommandError{"Failed to create Vulkan world descriptor set."};
+}
+
+void
+load_resources_to_descriptor_sets(void *obj)
+{
+  auto self = static_cast<BluCat::GRA::Light*>(obj);
+
+  for(auto i{0}; i < BluCat::INT::core.vk_swapchain->images_count; i++)
+  {
+    VkDescriptorBufferInfo world_vert_info{};
+    world_vert_info.buffer = self->ub_world_vert[i].buffer;
+    world_vert_info.offset = 0;
+    world_vert_info.range = sizeof(BluCat::GRA::UDOWorld3D_Vert);
+
+    VkDescriptorBufferInfo world_frag_info{};
+    world_frag_info.buffer = self->ub_world_frag[i].buffer;
+    world_frag_info.offset = 0;
+    world_frag_info.range = sizeof(BluCat::GRA::UDOWorld3D_Frag);
+
+    std::array<VkWriteDescriptorSet, 2> write_descriptors{};
+    write_descriptors[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+    write_descriptors[0].dstSet = self->descriptor_sets_world[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 = &world_vert_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->descriptor_sets_world[i];
+    write_descriptors[1].dstBinding = 1;
+    write_descriptors[1].dstArrayElement = 0;
+    write_descriptors[1].descriptorCount = 1;
+    write_descriptors[1].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
+    write_descriptors[1].pBufferInfo = &world_frag_info;
+    write_descriptors[1].pImageInfo = nullptr;
+    write_descriptors[1].pTexelBufferView = nullptr;
+
+    vkUpdateDescriptorSets(
+      BluCat::INT::core.vk_device_with_swapchain->device, write_descriptors.size(),
+      write_descriptors.data(), 0, nullptr);
+  }
+}
+
+const CommandChain loader{
+  {&load_world_vert_uniform_buffer, &unload_world_vert_uniform_buffer},
+  {&load_world_frag_uniform_buffer, &unload_world_frag_uniform_buffer},
+  {&load_descriptor_pool, &unload_descriptor_pool},
+  // By destroying the pool the sets are also destroyed.
+  {&load_descriptor_sets_world, nullptr},
+  {&load_resources_to_descriptor_sets, nullptr}
+};
+
+}
+
+namespace BluCat::GRA
+{
+
+Light::Light()
+{
+  loader.execute(this);
+}
+
+Light::~Light()
+{
+  loader.revert(this);
+}
+
+}
diff --git a/src/blu_cat/gra/light.hpp b/src/blu_cat/gra/light.hpp
new file mode 100644
index 0000000..22a9e2d
--- /dev/null
+++ b/src/blu_cat/gra/light.hpp
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2022-2024 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 BLU_CAT_GRA_LIGHT_H
+#define BLU_CAT_GRA_LIGHT_H 1
+
+#include "vulkan.hpp"
+#include "uniform_buffer.hpp"
+
+namespace BluCat::GRA
+{
+
+struct Light
+{
+  // FIXME: if this vector get resized, it will cause a segmentation fault!
+  std::vector<UniformBuffer> ub_world_vert;
+  std::vector<UniformBuffer> ub_world_frag;
+
+  VkDescriptorPool descriptor_pool;
+  std::vector<VkDescriptorSet> descriptor_sets_world;
+
+  Light();
+  ~Light();
+};
+
+}
+
+#endif /* BLU_CAT_GRA_LIGHT_H */
diff --git a/src/blu_cat/gra/log.cpp b/src/blu_cat/gra/log.cpp
new file mode 100644
index 0000000..78044c4
--- /dev/null
+++ b/src/blu_cat/gra/log.cpp
@@ -0,0 +1,53 @@
+/*
+ * 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 "log.hpp"
+
+#include <iostream>
+
+namespace Log
+{
+
+void
+Logger::message(Level lvl, const char* text)
+{
+  std::cout << "[";
+  switch(lvl)
+  {
+  case Level::Fatal:
+    std::cout << "\e[1;35mFatal\e[0;0m";
+    break;
+  case Level::Error:
+    std::cout << "\e[1;31mError\e[0;0m";
+    break;
+  case Level::Warning:
+    std::cout << "\e[1;33mWarning\e[0;0m";
+    break;
+  case Level::Information:
+    std::cout << "\e[1;32mInformation\e[0;0m";
+    break;
+  case Level::Debug:
+    std::cout << "\e[1;36mDebug\e[0;0m";
+    break;
+  case Level::Trace:
+    std::cout << "\e[1;34mTrace\e[0;0m";
+    break;
+  }
+  std::cout << "] ";
+  std::cout << text << std::endl;
+}
+
+}
diff --git a/src/blu_cat/gra/log.hpp b/src/blu_cat/gra/log.hpp
new file mode 100644
index 0000000..2856f20
--- /dev/null
+++ b/src/blu_cat/gra/log.hpp
@@ -0,0 +1,54 @@
+/*
+ * 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_LOG_H
+#define CANDY_GEAR_LOG_H 1
+
+#include <string>
+
+#define CANDY_GEAR_LOG_LEVEL_FATAL 0
+#define CANDY_GEAR_LOG_LEVEL_ERROR 1
+#define CANDY_GEAR_LOG_LEVEL_WARNING 2
+#define CANDY_GEAR_LOG_LEVEL_INFORMATION 3
+#define CANDY_GEAR_LOG_LEVEL_DEBUG 4
+#define CANDY_GEAR_LOG_LEVEL_TRACE 5
+
+namespace Log
+{
+
+enum class Level
+{
+  Fatal = CANDY_GEAR_LOG_LEVEL_FATAL,
+  Error = CANDY_GEAR_LOG_LEVEL_ERROR,
+  Warning = CANDY_GEAR_LOG_LEVEL_WARNING,
+  Information = CANDY_GEAR_LOG_LEVEL_INFORMATION,
+  Debug = CANDY_GEAR_LOG_LEVEL_DEBUG,
+  Trace = CANDY_GEAR_LOG_LEVEL_TRACE
+};
+
+struct Logger
+{
+
+  void
+  message(Level lvl, const char* text);
+
+  inline void
+  message(Level lvl, const std::string &text) {message(lvl, text.c_str());}
+};
+
+}
+
+#endif /* CANDY_GEAR_LOG_H */
diff --git a/src/blu_cat/gra/qoi.cpp b/src/blu_cat/gra/qoi.cpp
new file mode 100644
index 0000000..790516d
--- /dev/null
+++ b/src/blu_cat/gra/qoi.cpp
@@ -0,0 +1,205 @@
+/*
+ * Copyright 2022-2024 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 "qoi.hpp"
+
+#include <array>
+#include <fstream>
+
+#include "binary_reader.hpp"
+
+namespace
+{
+
+const char MAGIC[]{"qoif"};
+const int HEADER_SIZE{14};
+const uint8_t PADDING[8]{0,0,0,0,0,0,0,1};
+const unsigned int PIXELS_MAX{400000000};
+
+const int OP_INDEX{0b00000000};
+const int OP_DIFF {0b01000000};
+const int OP_LUMA {0b10000000};
+const int OP_RUN  {0b11000000};
+const int OP_RGB  {0b11111110};
+const int OP_RGBA {0b11111111};
+
+const int MASK_2_BITS{0b11000000};
+
+struct RGBA
+{
+  uint8_t red, green, blue, alpha;
+
+  RGBA();
+  RGBA(uint8_t red, uint8_t green, uint8_t blue, uint8_t alpha);
+};
+
+RGBA::RGBA():
+  red{0},
+  green{0},
+  blue{0},
+  alpha{0}
+{
+}
+
+RGBA::RGBA(uint8_t red, uint8_t green, uint8_t blue, uint8_t alpha):
+  red{red},
+  green{green},
+  blue{blue},
+  alpha{alpha}
+{
+}
+
+struct Pixel
+{
+  union
+  {
+    RGBA colors;
+    uint32_t value;
+  };
+
+  Pixel();
+  Pixel(uint8_t red, uint8_t green, uint8_t blue, uint8_t alpha);
+};
+
+Pixel::Pixel():
+  colors{}
+{
+}
+
+Pixel::Pixel(uint8_t red, uint8_t green, uint8_t blue, uint8_t alpha):
+  colors(red, green, blue, alpha)
+{
+}
+
+inline int
+color_hash(const RGBA &colors)
+{
+  return colors.red*3 + colors.green*5 + colors.blue*7 + colors.alpha*11;
+}
+
+}
+
+namespace BluCat::GRA::QOI
+{
+
+Image::Image(const char *file_path, uint8_t channels):
+  channels{channels}
+{
+  if(this->channels != 0 && this->channels != 3 && this->channels != 4)
+  {
+    throw std::invalid_argument{"invalid number of channels"};
+  }
+
+  BinaryReader input{file_path};
+  if(input.size() < HEADER_SIZE + (int)sizeof(PADDING))
+  {
+    throw std::runtime_error{"invalid QOI file"};
+  }
+
+  input.read_chars(this->header.magic, 4);
+  this->header.width = input.read_ui32();
+  this->header.height = input.read_ui32();
+  this->header.channels = input.read_ui8();
+  this->header.colorspace = input.read_ui8();
+
+  if(this->header.width == 0 || this->header.height == 0 ||
+     this->header.channels < 3 || this->header.channels > 4 ||
+     this->header.colorspace > 1 ||
+     this->header.height >= PIXELS_MAX / this->header.width)
+  {
+    throw std::runtime_error{"QOI file have an invalid header"};
+  }
+
+  if(this->channels == 0) this->channels = this->header.channels;
+
+  uint32_t num_pixels{this->header.width * this->header.height};
+  this->pixels_len = num_pixels * this->channels;
+  this->pixels = new uint8_t[this->pixels_len];
+
+  std::array<Pixel, 64> index;
+  Pixel pixel(0, 0, 0, 255);
+  int chunks_len = input.size() - (int)sizeof(PADDING);
+
+  /*
+    This algorithm is based on the original implementation that is in GitHub:
+    https://github.com/phoboslab/qoi
+
+    For information about the QOI image format, see: https://qoiformat.org/
+   */ 
+  int pixel_p{0};
+  int run{0};
+  for(uint32_t decoded_pixel{0}; decoded_pixel < num_pixels; decoded_pixel++)
+  {
+    if(run > 0)
+    {
+      run--;
+    }
+    else if(input.pointer() < chunks_len)
+    {
+      int b1 = input.read_ui8();
+
+      if (b1 == OP_RGB)
+      {
+        pixel.colors.red = input.read_ui8();
+        pixel.colors.green = input.read_ui8();
+        pixel.colors.blue = input.read_ui8();
+      }
+      else if (b1 == OP_RGBA)
+      {
+        pixel.colors.red = input.read_ui8();
+        pixel.colors.green = input.read_ui8();
+        pixel.colors.blue = input.read_ui8();
+        pixel.colors.alpha = input.read_ui8();
+      }
+      else if ((b1 & MASK_2_BITS) == OP_INDEX)
+      {
+        pixel = index[b1];
+      }
+      else if ((b1 & MASK_2_BITS) == OP_DIFF)
+      {
+        pixel.colors.red += ((b1 >> 4) & 0x03) - 2;
+        pixel.colors.green += ((b1 >> 2) & 0x03) - 2;
+        pixel.colors.blue += (b1 & 0x03) - 2;
+      }
+      else if ((b1 & MASK_2_BITS) == OP_LUMA)
+      {
+        int b2 = input.read_ui8();
+        int vg = (b1 & 0x3f) - 32;
+        pixel.colors.red += vg - 8 + ((b2 >> 4) & 0x0f);
+        pixel.colors.green += vg;
+        pixel.colors.blue += vg - 8 + (b2 & 0x0f);
+      }
+      else if ((b1 & MASK_2_BITS) == OP_RUN)
+      {
+        run = (b1 & 0x3f);
+      }
+
+      index[color_hash(pixel.colors) % 64] = pixel;
+    }
+
+    this->pixels[pixel_p++] = pixel.colors.red;
+    this->pixels[pixel_p++] = pixel.colors.green;
+    this->pixels[pixel_p++] = pixel.colors.blue;
+    if(this->channels == 4) this->pixels[pixel_p++] = pixel.colors.alpha;
+  }
+}
+
+Image::~Image()
+{
+  delete[] this->pixels;
+}
+
+}
diff --git a/src/blu_cat/gra/qoi.hpp b/src/blu_cat/gra/qoi.hpp
new file mode 100644
index 0000000..f8be4ec
--- /dev/null
+++ b/src/blu_cat/gra/qoi.hpp
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2022-2024 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 <cstdint>
+
+namespace BluCat::GRA::QOI
+{
+  struct Header
+  {
+    char magic[4];
+    uint32_t width;
+    uint32_t height;
+    uint8_t channels;
+    uint8_t colorspace;
+  };
+
+  struct Image
+  {
+    Header header;
+    uint32_t pixels_len;
+    uint8_t channels;
+    uint8_t *pixels;
+
+    Image(const char *file_path, uint8_t channels);
+    ~Image();
+  };
+
+}
diff --git a/src/blu_cat/gra/queue.cpp b/src/blu_cat/gra/queue.cpp
new file mode 100644
index 0000000..3f47a19
--- /dev/null
+++ b/src/blu_cat/gra/queue.cpp
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2022-2024 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 BluCat::GRA
+{
+
+Queue::Queue(
+  BluCat::GRA::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/blu_cat/gra/queue.hpp b/src/blu_cat/gra/queue.hpp
new file mode 100644
index 0000000..3dc9833
--- /dev/null
+++ b/src/blu_cat/gra/queue.hpp
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2022-2024 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 BLU_CAT_GRA_QUEUE_H
+#define BLU_CAT_GRA_QUEUE_H 1
+
+#include "vulkan.hpp"
+
+namespace BluCat::GRA
+{
+class QueueFamily;
+
+struct Queue
+{
+  friend class BluCat::GRA::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:
+  BluCat::GRA::QueueFamily *queue_family;
+  int queue_index;
+
+  Queue(BluCat::GRA::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 /* BLU_CAT_GRA_QUEUE_H */
diff --git a/src/blu_cat/gra/queue_family.cpp b/src/blu_cat/gra/queue_family.cpp
new file mode 100644
index 0000000..59086b5
--- /dev/null
+++ b/src/blu_cat/gra/queue_family.cpp
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2022-2024 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"
+
+#ifdef DEBUG
+#include <sstream>
+#endif
+
+#include "../int/core.hpp"
+
+namespace BluCat::GRA
+{
+
+QueueFamily::QueueFamily(
+  BluCat::GRA::Device *device, uint32_t family_index,
+  const VkQueueFamilyProperties &queue_family_properties):
+  queue_mutex{}
+{
+
+#ifdef DEBUG
+  std::stringstream message{};
+  message << "Queue quantity: " << queue_family_properties.queueCount <<
+    std::endl;
+  message << "Graphics: " <<
+    (queue_family_properties.queueFlags & VK_QUEUE_GRAPHICS_BIT ?
+     "true" : "false") << std::endl;
+  message << "Compute: " <<
+    (queue_family_properties.queueFlags & VK_QUEUE_COMPUTE_BIT ?
+     "true" : "false") << std::endl;
+  message << "Transfer: " <<
+    (queue_family_properties.queueFlags & VK_QUEUE_TRANSFER_BIT ?
+     "true" : "false") << std::endl;
+  message << "Sparse Binding: " <<
+    (queue_family_properties.queueFlags & VK_QUEUE_SPARSE_BINDING_BIT ?
+     "true" : "false") << std::endl;
+  INT::core.log.message(Log::Level::Trace, message.str());
+#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/blu_cat/gra/queue_family.hpp b/src/blu_cat/gra/queue_family.hpp
new file mode 100644
index 0000000..4673058
--- /dev/null
+++ b/src/blu_cat/gra/queue_family.hpp
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2022-2024 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 BLU_CAT_GRA_QUEUE_FAMILY_H
+#define BLU_CAT_GRA_QUEUE_FAMILY_H 1
+
+#include <mutex>
+#include <vector>
+
+#include "vulkan.hpp"
+#include "queue.hpp"
+
+namespace BluCat::GRA
+{
+class Device;
+
+struct QueueState
+{
+  VkQueue queue;
+  bool busy;
+};
+
+class QueueFamily
+{
+  friend class Queue;
+
+  std::mutex queue_mutex;
+  std::vector<QueueState> queue_states;
+
+public:
+  BluCat::GRA::Device *device;
+
+  uint32_t family_index;
+  VkQueueFamilyProperties family_properties;
+
+  QueueFamily(BluCat::GRA::Device *device, uint32_t family_index,
+	      const VkQueueFamilyProperties &queue_family_properties);
+
+  Queue
+  get_queue();
+};
+
+}
+
+#endif /* BLU_CAT_GRA_QUEUE_FAMILY_H */
diff --git a/src/blu_cat/gra/rectangle.cpp b/src/blu_cat/gra/rectangle.cpp
new file mode 100644
index 0000000..ea01a04
--- /dev/null
+++ b/src/blu_cat/gra/rectangle.cpp
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2022-2024 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 "rectangle.hpp"
+
+namespace BluCat::GRA
+{
+
+const int Rectangle::VertexCount{4};
+
+Rectangle::Rectangle(glm::vec4 position, glm::vec3 color):
+  position{position},
+  color{color}
+{
+}
+
+}
diff --git a/src/blu_cat/gra/rectangle.hpp b/src/blu_cat/gra/rectangle.hpp
new file mode 100644
index 0000000..cd09239
--- /dev/null
+++ b/src/blu_cat/gra/rectangle.hpp
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2022-2024 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 BLU_CAT_GRA_RECTANGLE_H
+#define BLU_CAT_GRA_RECTANGLE_H 1
+
+#include <vector>
+
+#include "vulkan.hpp"
+#include "destination_buffer.hpp"
+#include "queue_family.hpp"
+#include "uniform_buffer.hpp"
+
+namespace BluCat::GRA
+{
+
+struct Rectangle
+{
+  static const int VertexCount;
+
+  glm::vec4 position;
+  glm::vec3 color;
+
+  Rectangle(glm::vec4 position, glm::vec3 color);
+};
+
+}
+
+#endif /* BLU_CAT_GRA_RECTANGLE_H */
diff --git a/src/blu_cat/gra/render_pass.cpp b/src/blu_cat/gra/render_pass.cpp
new file mode 100644
index 0000000..64fdf56
--- /dev/null
+++ b/src/blu_cat/gra/render_pass.cpp
@@ -0,0 +1,203 @@
+/*
+ * Copyright 2022-2024 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 "render_pass.hpp"
+
+#include <array>
+
+#include "../int/core.hpp"
+
+namespace
+{
+
+void
+load_3d(void *obj)
+{
+  auto self = static_cast<BluCat::GRA::RenderPass*>(obj);
+
+  std::array<VkAttachmentDescription, 2> attachments{};
+  // Color attachment.
+  attachments[0].flags = 0;
+  attachments[0].format = BluCat::INT::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_COLOR_ATTACHMENT_OPTIMAL;
+  // 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(
+       BluCat::INT::core.vk_device_with_swapchain->device, &render_pass_info,
+			 nullptr, &self->pipeline_3d) != VK_SUCCESS)
+    throw CommandError{"Failed to create Vulkan Render Pass 3D."};
+}
+
+void
+unload_3d(void *obj)
+{
+  auto self = static_cast<BluCat::GRA::RenderPass*>(obj);
+
+  vkDestroyRenderPass(
+    BluCat::INT::core.vk_device_with_swapchain->device, self->pipeline_3d, nullptr);
+}
+
+void
+load_2d(void *obj)
+{
+  auto self = static_cast<BluCat::GRA::RenderPass*>(obj);
+
+  std::array<VkAttachmentDescription, 1> attachments{};
+  // Color attachment.
+  attachments[0].flags = 0;
+  attachments[0].format = BluCat::INT::core.vk_swapchain->image_format;
+  attachments[0].samples = VK_SAMPLE_COUNT_1_BIT;
+  attachments[0].loadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
+  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_COLOR_ATTACHMENT_OPTIMAL;
+  attachments[0].finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
+
+  VkAttachmentReference color_attachment_ref{};
+  color_attachment_ref.attachment = 0;
+  color_attachment_ref.layout = VK_IMAGE_LAYOUT_COLOR_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 = nullptr;
+  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(
+       BluCat::INT::core.vk_device_with_swapchain->device, &render_pass_info,
+       nullptr, &self->pipeline_2d) != VK_SUCCESS)
+    throw CommandError{"Failed to create Vulkan Render Pass 2D."};
+}
+
+void
+unload_2d(void *obj)
+{
+  auto self = static_cast<BluCat::GRA::RenderPass*>(obj);
+
+  vkDestroyRenderPass(
+    BluCat::INT::core.vk_device_with_swapchain->device, self->pipeline_2d, nullptr);
+}
+
+const CommandChain loader{
+  {&load_3d, &unload_3d},
+  {&load_2d, &unload_2d}
+};
+
+}
+
+namespace BluCat::GRA
+{
+
+RenderPass::RenderPass()
+{
+  loader.execute(this);
+}
+
+RenderPass::~RenderPass()
+{
+  loader.revert(this);
+}
+
+}
diff --git a/src/blu_cat/gra/render_pass.hpp b/src/blu_cat/gra/render_pass.hpp
new file mode 100644
index 0000000..4187261
--- /dev/null
+++ b/src/blu_cat/gra/render_pass.hpp
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2022-2024 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 BLU_CAT_GRA_RENDER_PASS_H
+#define BLU_CAT_GRA_RENDER_PASS_H 1
+
+#include "vulkan.hpp"
+
+namespace BluCat::GRA
+{
+
+struct RenderPass
+{
+  VkRenderPass pipeline_2d;
+  VkRenderPass pipeline_3d;
+
+  RenderPass();
+  ~RenderPass();
+};
+
+}
+
+#endif /* BLU_CAT_GRA_RENDER_PASS_H */
diff --git a/src/blu_cat/gra/renderer.cpp b/src/blu_cat/gra/renderer.cpp
new file mode 100644
index 0000000..ad66003
--- /dev/null
+++ b/src/blu_cat/gra/renderer.cpp
@@ -0,0 +1,425 @@
+/*
+ * Copyright 2022-2024 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 "renderer.hpp"
+
+#include <array>
+
+#include "../int/core.hpp"
+#include "uniform_data_object.hpp"
+
+namespace
+{
+
+void
+load_descriptor_pool(void *obj)
+{
+  auto self = static_cast<BluCat::GRA::Renderer*>(obj);
+
+  uint32_t uniform_buffer_count = 0;
+  for(auto &view : self->views_3d)
+    uniform_buffer_count += (view->ub_3d.size() + view->ub_2d.size());
+  for(auto &view : self->views_2d)
+    uniform_buffer_count += (view->ub_2d.size());
+
+  VkDescriptorPoolSize descriptor_pool_size{};
+  descriptor_pool_size.type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
+  descriptor_pool_size.descriptorCount = uniform_buffer_count;
+
+  VkDescriptorPoolCreateInfo pool_info{};
+  pool_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
+  pool_info.pNext = nullptr;
+  pool_info.flags = 0;
+  pool_info.maxSets = uniform_buffer_count;
+  pool_info.poolSizeCount = 1;
+  pool_info.pPoolSizes = &descriptor_pool_size;
+
+  if(vkCreateDescriptorPool(
+       BluCat::INT::core.vk_device_with_swapchain->device, &pool_info, nullptr,
+       &self->descriptor_pool) != VK_SUCCESS)
+    throw CommandError{"Failed to create a Vulkan descriptor pool."};
+
+  for(auto &view : self->views_3d)
+    view->load_descriptor_sets(self->descriptor_pool);
+  for(auto &view : self->views_2d)
+    view->load_descriptor_sets(self->descriptor_pool);
+}
+
+void
+unload_descriptor_pool(void *obj)
+{
+  auto self = static_cast<BluCat::GRA::Renderer*>(obj);
+
+  for(auto &view : self->views_3d) view->unload_descriptor_sets();
+  for(auto &view : self->views_2d) view->unload_descriptor_sets();
+
+  vkDestroyDescriptorPool(
+    BluCat::INT::core.vk_device_with_swapchain->device, self->descriptor_pool,
+    nullptr);
+}
+
+void
+load_queue_family(void *obj)
+{
+  auto self = static_cast<BluCat::GRA::Renderer*>(obj);
+
+  self->queue_family =
+    BluCat::INT::core.vk_device_with_swapchain->get_queue_family_with_presentation();
+}
+
+void
+load_command_pool(void *obj)
+{
+  auto self = static_cast<BluCat::GRA::Renderer*>(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<BluCat::GRA::Renderer*>(obj);
+
+	self->wait_frame();
+  vkDestroyCommandPool(
+    self->queue_family->device->device, self->command_pool, nullptr);
+}
+
+void
+load_draw_command_buffer(void *obj)
+{
+  auto self = static_cast<BluCat::GRA::Renderer*>(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_pool, &unload_descriptor_pool},
+  {&load_queue_family, nullptr},
+  {&load_command_pool, &unload_command_pool},
+  {&load_draw_command_buffer, nullptr}
+};
+
+}
+
+namespace BluCat::GRA
+{
+
+Renderer::Renderer(std::vector<std::shared_ptr<View2D>> views_2d,
+		   std::vector<std::shared_ptr<View3D>> views_3d):
+  skeletal_models_to_draw{BluCat::INT::core.vk_swapchain->images_count},
+  static_models_to_draw{BluCat::INT::core.vk_swapchain->images_count},
+  sprites_3d_to_draw{BluCat::INT::core.vk_swapchain->images_count},
+  views_2d{views_2d},
+  views_3d{views_3d}
+{
+  loader.execute(this);
+}
+
+Renderer::Renderer(std::initializer_list<std::shared_ptr<View2D>> views_2d,
+		   std::initializer_list<std::shared_ptr<View3D>> views_3d):
+  Renderer(std::vector(views_2d), std::vector(views_3d))
+{
+}
+
+Renderer::~Renderer()
+{
+  loader.revert(this);
+}
+
+// FIXME: this is a workaround to prevent a code to free some resource while
+// it still being rendered.
+void
+Renderer::wait_frame()
+{
+  vkWaitForFences(BluCat::INT::core.vk_device_with_swapchain->device,
+		  BluCat::GRA::Swapchain::max_frames_in_flight,
+		  BluCat::INT::core.vk_swapchain->in_flight_fences.data(), VK_TRUE,
+		  std::numeric_limits<uint64_t>::max());
+}
+
+void
+Renderer::draw()
+{
+  auto fence_status = vkGetFenceStatus(
+    BluCat::INT::core.vk_device_with_swapchain->device,
+    BluCat::INT::core.vk_swapchain->in_flight_fences[
+      BluCat::INT::core.vk_swapchain->current_frame]);
+
+  if(fence_status == VK_SUCCESS)
+  {
+    auto next_frame = BluCat::INT::core.vk_swapchain->current_frame + 1;
+    if(next_frame == Swapchain::max_frames_in_flight) next_frame = 0;
+
+    vkResetFences(INT::core.vk_device_with_swapchain->device, 1,
+		  &BluCat::INT::core.vk_swapchain->in_flight_fences[
+		    BluCat::INT::core.vk_swapchain->current_frame]);
+
+    uint32_t image_index;
+    vkAcquireNextImageKHR(
+      BluCat::INT::core.vk_device_with_swapchain->device,
+      BluCat::INT::core.vk_swapchain->swapchain,
+			std::numeric_limits<uint64_t>::max(),
+      BluCat::INT::core.vk_swapchain->image_available_semaphores[
+	BluCat::INT::core.vk_swapchain->current_frame], VK_NULL_HANDLE, &image_index);
+
+    VkCommandBuffer draw_command_buffer =
+      this->draw_command_buffers[BluCat::INT::core.vk_swapchain->current_frame];
+    vkResetCommandBuffer(draw_command_buffer, 0);
+
+    // Begin command buffer.
+    {
+      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."};
+    }
+
+    // 3D drawing.
+    {
+      // 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};
+
+      { // Update world uniform buffer
+	UDOWorld3D_Vert ubo_world_3d_vert{};
+	ubo_world_3d_vert.ambient_light_color =
+	  glm::vec4{0.25, 0.25, 0.25, 1.0};
+	BluCat::INT::core.vk_light->ub_world_vert[image_index].copy_data(
+	  &ubo_world_3d_vert);
+
+	UDOWorld3D_Frag ubo_world_3d_frag{};
+	ubo_world_3d_frag.directional_light_direction =
+	  glm::vec3{-0.57735, 0.57735, -0.57735};
+	ubo_world_3d_frag.directional_light_color =
+	  glm::vec4{0.8, 0.8, 0.8, 1.0};
+	BluCat::INT::core.vk_light->ub_world_frag[image_index].copy_data(
+	  &ubo_world_3d_frag);
+      }
+
+      VkRenderPassBeginInfo render_pass_begin{};
+      render_pass_begin.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
+      render_pass_begin.pNext = nullptr;
+      render_pass_begin.renderPass = BluCat::INT::core.vk_render_pass->pipeline_3d;
+      render_pass_begin.framebuffer =
+	BluCat::INT::core.vk_framebuffer->pipeline_3d[image_index];
+      render_pass_begin.renderArea.offset = {0, 0};
+      render_pass_begin.renderArea.extent = {
+	static_cast<uint32_t>(BluCat::INT::core.display_width),
+	static_cast<uint32_t>(BluCat::INT::core.display_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);
+    }
+
+		for(auto &view: this->views_3d)
+		{
+			{ // Set viewport
+				VkViewport vk_viewport{};
+				vk_viewport.x = view->region.x;
+				vk_viewport.y = view->region.y;
+				vk_viewport.width = view->region.z;
+				vk_viewport.height = view->region.w;
+				vk_viewport.minDepth = 0.0f;
+				vk_viewport.maxDepth = 1.0f;
+				vkCmdSetViewport(draw_command_buffer, 0, 1, &vk_viewport);
+
+				VkRect2D vk_scissor{};
+				vk_scissor.offset.x = static_cast<int32_t>(view->region.x);
+				vk_scissor.offset.y = static_cast<int32_t>(view->region.y);
+				vk_scissor.extent.width = static_cast<uint32_t>(view->region.z);
+				vk_scissor.extent.height = static_cast<uint32_t>(view->region.w);
+				vkCmdSetScissor(draw_command_buffer, 0, 1, &vk_scissor);
+			}
+
+			BluCat::INT::core.vk_graphics_pipeline_3d->draw(
+				view, draw_command_buffer, BluCat::INT::core.vk_swapchain->current_frame,
+				image_index);
+
+			BluCat::INT::core.vk_graphics_pipeline_sprite_3d->draw(
+				view, draw_command_buffer, BluCat::INT::core.vk_swapchain->current_frame,
+				image_index);
+
+			BluCat::INT::core.vk_graphics_pipeline_3d_skeletal->draw(
+				view, draw_command_buffer, BluCat::INT::core.vk_swapchain->current_frame,
+				image_index);
+
+			{ // Update view uniform buffers
+				BluCat::GRA::UDOView3D ubo_view_3d{};
+
+				// View matrix.
+				glm::mat4 translation_matrix{1.0f};
+				translation_matrix = glm::translate(
+					translation_matrix, *view->camera_position);
+				glm::mat4 rotation_matrix{glm::toMat4(*view->camera_orientation)};
+				ubo_view_3d.view = glm::inverse(translation_matrix * rotation_matrix);
+
+				// Projection matrix.
+				ubo_view_3d.proj = glm::perspective(
+					glm::radians(view->field_of_view),
+					view->region.z / view->region.w,
+					0.1f, 100.0f);
+
+				view->ub_3d[image_index].copy_data(&ubo_view_3d);
+			}
+		}
+
+    vkCmdEndRenderPass(draw_command_buffer);
+
+    { // 2D render pass
+      VkRenderPassBeginInfo render_pass_begin{};
+      render_pass_begin.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
+      render_pass_begin.pNext = nullptr;
+      render_pass_begin.renderPass = BluCat::INT::core.vk_render_pass->pipeline_2d;
+      render_pass_begin.framebuffer =
+	BluCat::INT::core.vk_framebuffer->pipeline_2d[image_index];
+      render_pass_begin.renderArea.offset = {0, 0};
+      render_pass_begin.renderArea.extent = {
+	static_cast<uint32_t>(BluCat::INT::core.display_width),
+	static_cast<uint32_t>(BluCat::INT::core.display_height)};
+      render_pass_begin.clearValueCount = 0;
+      render_pass_begin.pClearValues = nullptr;
+
+      vkCmdBeginRenderPass(
+	draw_command_buffer, &render_pass_begin, VK_SUBPASS_CONTENTS_INLINE);
+
+    }
+
+    { // 2D solid drawing
+      for(auto &view: this->views_2d)
+	BluCat::INT::core.vk_graphics_pipeline_2d_solid->draw(
+	  view, draw_command_buffer, BluCat::INT::core.vk_swapchain->current_frame,
+	  next_frame, image_index);
+
+      for(auto &view: this->views_3d)
+	BluCat::INT::core.vk_graphics_pipeline_2d_solid->draw(
+	  view, draw_command_buffer, BluCat::INT::core.vk_swapchain->current_frame,
+	  next_frame, image_index);
+    }
+
+    { // 2D wired drawing
+      for(auto &view: this->views_2d)
+	BluCat::INT::core.vk_graphics_pipeline_2d_wired->draw(
+	  view, draw_command_buffer, BluCat::INT::core.vk_swapchain->current_frame,
+	  next_frame, image_index);
+
+      for(auto &view: this->views_3d)
+	BluCat::INT::core.vk_graphics_pipeline_2d_wired->draw(
+	  view, draw_command_buffer, BluCat::INT::core.vk_swapchain->current_frame,
+	  next_frame, image_index);
+    }
+
+    vkCmdEndRenderPass(draw_command_buffer);
+
+    // End command buffer.
+    if(vkEndCommandBuffer(draw_command_buffer) != VK_SUCCESS)
+      throw std::runtime_error{"Failed to end draw command buffer."};
+
+    // Submit drawing command.
+    {
+      auto queue{this->queue_family->get_queue()};
+
+      VkSemaphore wait_semaphores[]{
+	BluCat::INT::core.vk_swapchain->image_available_semaphores[
+	  BluCat::INT::core.vk_swapchain->current_frame]};
+      VkPipelineStageFlags wait_stages[] =
+	{VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT};
+      VkSemaphore signal_semaphores[]{
+	BluCat::INT::core.vk_swapchain->render_finished_semaphores[
+	  BluCat::INT::core.vk_swapchain->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, BluCat::INT::core.vk_swapchain->in_flight_fences[
+	     BluCat::INT::core.vk_swapchain->current_frame]) != VK_SUCCESS)
+	throw std::runtime_error{"Failed to submit draw command buffer."};
+
+      VkSwapchainKHR swap_chains[]{BluCat::INT::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->skeletal_models_to_draw[next_frame].clear();
+      this->static_models_to_draw[next_frame].clear();
+      this->sprites_3d_to_draw[next_frame].clear();
+      BluCat::INT::core.vk_swapchain->current_frame = next_frame;
+    }
+  }
+  else
+  {
+    // Clear images for the current frame because we are skipping this frame.
+    this->skeletal_models_to_draw[
+			BluCat::INT::core.vk_swapchain->current_frame].clear();
+    this->static_models_to_draw[
+			BluCat::INT::core.vk_swapchain->current_frame].clear();
+    this->sprites_3d_to_draw[
+			BluCat::INT::core.vk_swapchain->current_frame].clear();
+    for(auto &view: this->views_2d)
+      view->sprites_to_draw[BluCat::INT::core.vk_swapchain->current_frame].clear();
+    for(auto &view: this->views_3d)
+      view->sprites_to_draw[BluCat::INT::core.vk_swapchain->current_frame].clear();
+  }
+}
+
+}
diff --git a/src/blu_cat/gra/renderer.hpp b/src/blu_cat/gra/renderer.hpp
new file mode 100644
index 0000000..0a3d15f
--- /dev/null
+++ b/src/blu_cat/gra/renderer.hpp
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2022-2024 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 BLU_CAT_GRA_RENDERER_H
+#define BLU_CAT_GRA_RENDERER_H 1
+
+#include <initializer_list>
+#include <memory>
+#include <vector>
+
+#include "vulkan.hpp"
+#include "skeletal_mesh.hpp"
+#include "skeletal_model.hpp"
+#include "sprite_3d.hpp"
+#include "static_mesh.hpp"
+#include "static_model.hpp"
+#include "queue_family.hpp"
+#include "view_2d.hpp"
+#include "view_3d.hpp"
+
+namespace BluCat::GRA
+{
+
+struct Renderer
+{
+  std::vector<
+    std::unordered_map<
+      std::shared_ptr<SkeletalMesh>,
+      std::vector<std::shared_ptr<SkeletalModel>>>>
+  skeletal_models_to_draw;
+
+  std::vector<
+    std::unordered_map<
+      std::shared_ptr<StaticMesh>,
+      std::vector<std::shared_ptr<StaticModel>>>>
+  static_models_to_draw;
+
+  std::vector<std::vector<std::shared_ptr<Sprite3D>>> sprites_3d_to_draw;
+
+  VkDescriptorPool descriptor_pool;
+  std::vector<std::shared_ptr<View2D>> views_2d;
+  std::vector<std::shared_ptr<View3D>> views_3d;
+  QueueFamily *queue_family;
+  VkCommandPool command_pool;
+  std::vector<VkCommandBuffer> draw_command_buffers;
+
+  Renderer(std::vector<std::shared_ptr<View2D>> views_2d,
+           std::vector<std::shared_ptr<View3D>> views_3d);
+  Renderer(std::initializer_list<std::shared_ptr<View2D>> views_2d,
+           std::initializer_list<std::shared_ptr<View3D>> views_3d);
+  ~Renderer();
+
+	void
+	wait_frame();
+
+  void
+  draw();
+};
+
+}
+
+#endif /* BLU_CAT_GRA_RENDERER_H */
diff --git a/src/blu_cat/gra/skeletal_mesh.cpp b/src/blu_cat/gra/skeletal_mesh.cpp
new file mode 100644
index 0000000..89d701b
--- /dev/null
+++ b/src/blu_cat/gra/skeletal_mesh.cpp
@@ -0,0 +1,204 @@
+/*
+ * Copyright 2022-2024 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 "skeletal_mesh.hpp"
+
+#include "binary_reader.hpp"
+#include "../com/command.hpp"
+#include "../int/core.hpp"
+#include "skeletal_mesh_vertex.hpp"
+
+namespace
+{
+
+// Data that is only needed for the command chain but not for the SkeletalMesh
+// goes here.
+struct MeshBuilder
+{
+  std::string mesh_path;
+  BluCat::GRA::SkeletalMesh *mesh;
+
+  MeshBuilder(BluCat::GRA::SkeletalMesh *m, std::string mp);
+  MeshBuilder(BluCat::GRA::SkeletalMesh *m, const char* mp);
+};
+
+MeshBuilder::MeshBuilder(BluCat::GRA::SkeletalMesh *m, std::string mp):
+  mesh{m},
+  mesh_path{mp}
+{
+}
+
+MeshBuilder::MeshBuilder(BluCat::GRA::SkeletalMesh *m, const char *mp):
+  MeshBuilder{m, std::string(mp)}
+{
+}
+
+void
+load_mesh(void *obj)
+{
+  auto self = static_cast<MeshBuilder*>(obj);
+
+  BinaryReader input{self->mesh_path};
+
+  self->mesh->queue_family =
+    BluCat::INT::core.vk_device_with_swapchain->get_queue_family_with_graphics();
+
+  { // Load vertexes.
+    auto vertex_count{input.read_ui32()};
+    std::vector<BluCat::GRA::SkeletalMeshVertex> vertexes{vertex_count};
+
+    for(auto i{0}; i < vertex_count; i++)
+    {
+      vertexes[i].position = input.read_vec3();
+      vertexes[i].normal = input.read_vec3();
+      vertexes[i].texture_coord = input.read_vec2();
+
+      for(auto ii{0}; ii < BluCat::GRA::SKELETAL_MESH_MAX_NUM_OF_INFLUENCING_BONES;
+	  ii++)
+	vertexes[i].bone_ids[ii] = input.read_ui32();
+
+      for(auto ii{0}; ii < BluCat::GRA::SKELETAL_MESH_MAX_NUM_OF_INFLUENCING_BONES;
+	  ii++)
+	vertexes[i].bone_weights[ii] = input.read_float();
+    }
+
+    void *vertexes_data{vertexes.data()};
+    size_t vertexes_size = sizeof(vertexes[0]) * vertexes.size();
+    self->mesh->source_vertex_buffer = new BluCat::GRA::SourceBuffer{
+      self->mesh->queue_family->device, vertexes_data, vertexes_size};
+    self->mesh->vertex_buffer = new BluCat::GRA::DestinationBuffer{
+      self->mesh->queue_family, self->mesh->source_vertex_buffer,
+      VK_BUFFER_USAGE_VERTEX_BUFFER_BIT};
+  }
+
+  { // Load indexes.
+    self->mesh->index_count = input.read_ui32();
+    std::vector<uint32_t> indexes(self->mesh->index_count);
+
+    for(auto i{0}; i < self->mesh->index_count; i++)
+      indexes[i] = input.read_ui32();
+
+    void *indexes_data{indexes.data()};
+    size_t indexes_size{sizeof(indexes[0]) * indexes.size()};
+    BluCat::GRA::SourceBuffer source_index_buffer{
+      self->mesh->queue_family->device, indexes_data, indexes_size};
+    self->mesh->index_buffer = new BluCat::GRA::DestinationBuffer{
+      self->mesh->queue_family, &source_index_buffer,
+      VK_BUFFER_USAGE_INDEX_BUFFER_BIT};
+  }
+
+  { // Load bones
+    auto bone_count{input.read_ui32()};
+    self->mesh->bones.reserve(bone_count);
+    for(int i{0}; i < bone_count; i++)
+      self->mesh->bones.emplace_back(input.read_mat4());
+  }
+
+  { // Load animations
+    auto num_animations{input.read_ui32()};
+    self->mesh->animations.resize(num_animations);
+    for(uint32_t i{0}; i < num_animations; i++)
+    {
+      auto duration{input.read_double()};
+      self->mesh->animations[i].final_time = (float)duration;
+
+      auto ticks_per_second{input.read_double()};
+
+      auto num_bone_transforms{input.read_ui32()};
+      std::vector<BluCat::GRA::BoneTransform> *bone_transforms =
+	&(self->mesh->animations[i].bone_transforms);
+      bone_transforms->resize(num_bone_transforms);
+      for(uint32_t bone_transform_index{0};
+	  bone_transform_index < num_bone_transforms; bone_transform_index++)
+      {
+	auto bone_id{input.read_ui32()};
+
+	auto num_positions{input.read_ui32()};
+	BluCat::GRA::Channel<glm::vec3> *positions =
+	  &((*bone_transforms)[bone_transform_index].positions);
+	for(auto position_key_index{0}; position_key_index < num_positions;
+	    position_key_index++)
+	{
+	  auto vec3{input.read_vec3()};
+	  auto timestamp{input.read_double()};
+	  positions->key_frames.emplace_back(
+	    vec3, static_cast<float>(timestamp));
+	}
+
+	auto num_rotations{input.read_ui32()};
+	BluCat::GRA::Channel<glm::quat> *rotations =
+	  &((*bone_transforms)[bone_transform_index].rotations);
+	for(auto rotation_key_index{0}; rotation_key_index < num_rotations;
+	    rotation_key_index++)
+	{
+	  auto quat{input.read_quat()};
+	  auto timestamp{input.read_double()};
+	  rotations->key_frames.emplace_back(
+	    quat, static_cast<float>(timestamp));
+	}
+
+	auto num_scales{input.read_ui32()};
+	BluCat::GRA::Channel<glm::vec3> *scales =
+	  &((*bone_transforms)[bone_transform_index].scales);
+	for(auto scaling_key_index{0}; scaling_key_index < num_scales;
+	    scaling_key_index++)
+	{
+	  auto vec3{input.read_vec3()};
+	  auto timestamp{input.read_double()};
+	  scales->key_frames.emplace_back(vec3, static_cast<float>(timestamp));
+	}
+      }
+    }
+  }
+}
+
+void
+unload_mesh(void *obj)
+{
+  auto self = static_cast<MeshBuilder*>(obj);
+
+  delete self->mesh->index_buffer;
+  delete self->mesh->vertex_buffer;
+  delete self->mesh->source_vertex_buffer;
+}
+
+static const CommandChain loader{
+  {&load_mesh, &unload_mesh}
+};
+
+}
+
+namespace BluCat::GRA
+{
+
+SkeletalMesh::SkeletalMesh(std::string mesh_path)
+{
+  MeshBuilder mesh_builder(this, mesh_path);
+  loader.execute(&mesh_builder);
+}
+
+SkeletalMesh::SkeletalMesh(const char* mesh_path):
+  SkeletalMesh{std::string(mesh_path)}
+{
+}
+
+SkeletalMesh::~SkeletalMesh()
+{
+  MeshBuilder mesh_builder(this, "");
+  loader.revert(&mesh_builder);
+}
+
+}
diff --git a/src/blu_cat/gra/skeletal_mesh.hpp b/src/blu_cat/gra/skeletal_mesh.hpp
new file mode 100644
index 0000000..f586ea3
--- /dev/null
+++ b/src/blu_cat/gra/skeletal_mesh.hpp
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2022-2024 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 BLU_CAT_GRA_SKELETAL_MESH_H
+#define BLU_CAT_GRA_SKELETAL_MESH_H 1
+
+#include <string>
+#include <vector>
+
+#include "animation.hpp"
+#include "vulkan.hpp"
+#include "destination_buffer.hpp"
+#include "queue_family.hpp"
+#include "uniform_buffer.hpp"
+#include "texture.hpp"
+
+namespace BluCat::GRA
+{
+
+struct SkeletalMesh
+{
+  QueueFamily *queue_family;
+
+  uint32_t index_count;
+  SourceBuffer *source_vertex_buffer;
+  DestinationBuffer *index_buffer;
+  DestinationBuffer *vertex_buffer;
+
+  std::vector<Bone> bones;
+  std::vector<Animation> animations;
+
+  SkeletalMesh(std::string mesh_path);
+  SkeletalMesh(const char* mesh_path);
+  ~SkeletalMesh();
+};
+
+}
+
+#endif /* BLU_CAT_GRA_SKELETAL_MESH_H */
diff --git a/src/blu_cat/gra/skeletal_mesh_vertex.hpp b/src/blu_cat/gra/skeletal_mesh_vertex.hpp
new file mode 100644
index 0000000..1bd70ad
--- /dev/null
+++ b/src/blu_cat/gra/skeletal_mesh_vertex.hpp
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2022-2024 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 BLU_CAT_GRA_SKELETAL_MESH_VERTEX_H
+#define BLU_CAT_GRA_SKELETAL_MESH_VERTEX_H 1
+
+#include "vulkan.hpp"
+
+namespace BluCat::GRA
+{
+
+// This variable define the maximum ammount of bones that can influence a
+// vertex.
+const int SKELETAL_MESH_MAX_NUM_OF_INFLUENCING_BONES{4};
+const int SKELETAL_MESH_MAX_NUM_OF_BONES{50};
+
+struct SkeletalMeshVertex
+{
+  glm::vec3 position;
+  glm::vec3 normal;
+  glm::vec2 texture_coord;
+
+  uint32_t bone_ids[SKELETAL_MESH_MAX_NUM_OF_INFLUENCING_BONES];
+  float bone_weights[SKELETAL_MESH_MAX_NUM_OF_INFLUENCING_BONES];
+};
+
+}
+
+#endif /* BLU_CAT_GRA_SKELETAL_MESH_VERTEX_H */
diff --git a/src/blu_cat/gra/skeletal_model.cpp b/src/blu_cat/gra/skeletal_model.cpp
new file mode 100644
index 0000000..b58982d
--- /dev/null
+++ b/src/blu_cat/gra/skeletal_model.cpp
@@ -0,0 +1,239 @@
+/*
+ * Copyright 2022-2024 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 "skeletal_model.hpp"
+
+#include "../int/core.hpp"
+#include "uniform_data_object.hpp"
+
+namespace
+{
+
+void
+load_uniform_buffers(void *obj)
+{
+  auto self = static_cast<BluCat::GRA::SkeletalModel*>(obj);
+
+  try
+  {
+    self->uniform_buffers.reserve(BluCat::INT::core.vk_swapchain->images_count);
+    for(auto i{0}; i < BluCat::INT::core.vk_swapchain->images_count; i++)
+      self->uniform_buffers.emplace_back(
+				BluCat::INT::core.vk_device_with_swapchain,
+				sizeof(BluCat::GRA::UDOSkeletalModel));
+  }
+  catch(const std::exception& e)
+  {
+    throw CommandError{e.what()};
+  }
+}
+
+void
+unload_uniform_buffers(void *obj)
+{
+  auto self = static_cast<BluCat::GRA::SkeletalModel*>(obj);
+
+  self->uniform_buffers.clear();
+}
+
+void
+load_descriptor_set_pool(void *obj)
+{
+  auto self = static_cast<BluCat::GRA::SkeletalModel*>(obj);
+
+  std::array<VkDescriptorPoolSize, 1> descriptor_pool_sizes{};
+  descriptor_pool_sizes[0].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
+  descriptor_pool_sizes[0].descriptorCount =
+    self->uniform_buffers.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->uniform_buffers.size();
+  pool_info.poolSizeCount = descriptor_pool_sizes.size();
+  pool_info.pPoolSizes = descriptor_pool_sizes.data();
+
+  if(vkCreateDescriptorPool(
+       self->skeletal_mesh->queue_family->device->device, &pool_info, nullptr,
+       &self->descriptor_pool) != VK_SUCCESS)
+    throw CommandError{"Failed to create a Vulkan descriptor pool."};
+}
+
+void
+unload_descriptor_set_pool(void *obj)
+{
+  auto self = static_cast<BluCat::GRA::SkeletalModel*>(obj);
+
+  vkDestroyDescriptorPool(
+    self->skeletal_mesh->queue_family->device->device, self->descriptor_pool,
+    nullptr);
+}
+
+void
+load_descriptor_sets(void *obj)
+{
+  auto self = static_cast<BluCat::GRA::SkeletalModel*>(obj);
+
+  std::vector<VkDescriptorSetLayout> layouts(
+    BluCat::INT::core.vk_swapchain->images_count,
+    BluCat::INT::core.vk_descriptor_set_layout->model);
+
+  VkDescriptorSetAllocateInfo alloc_info{};
+  alloc_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
+  alloc_info.descriptorPool = self->descriptor_pool;
+  alloc_info.descriptorSetCount = layouts.size();
+  alloc_info.pSetLayouts = layouts.data();
+
+  self->descriptor_sets.resize(layouts.size());
+  if(vkAllocateDescriptorSets(
+       self->skeletal_mesh->queue_family->device->device, &alloc_info,
+       self->descriptor_sets.data()) != VK_SUCCESS)
+    CommandError{"Failed to create Vulkan descriptor set."};
+}
+
+void
+load_buffers_to_descriptor_sets(void *obj)
+{
+  auto self = static_cast<BluCat::GRA::SkeletalModel*>(obj);
+
+  for(auto i{0}; i < self->uniform_buffers.size(); i++)
+  {
+    VkDescriptorBufferInfo buffer_info{};
+    buffer_info.buffer = self->uniform_buffers[i].buffer;
+    buffer_info.offset = 0;
+    buffer_info.range = sizeof(BluCat::GRA::UDOSkeletalModel);
+
+    std::array<VkWriteDescriptorSet, 1> write_descriptors{};
+    write_descriptors[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+    write_descriptors[0].dstSet = self->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;
+
+    vkUpdateDescriptorSets(
+      BluCat::INT::core.vk_device_with_swapchain->device, write_descriptors.size(),
+      write_descriptors.data(), 0, nullptr);
+  }
+}
+
+static const CommandChain loader{
+  {&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 BluCat::GRA
+{
+
+SkeletalModel::SkeletalModel(
+  std::shared_ptr<SkeletalMesh> skeletal_mesh,
+  std::shared_ptr<Texture> texture, std::shared_ptr<glm::vec3> position,
+  std::shared_ptr<glm::quat> orientation):
+  skeletal_mesh{skeletal_mesh},
+  texture{texture},
+  position{position},
+  orientation{orientation},
+  animation_index{0},
+  animation_time{0.0f},
+  bone_transforms(SKELETAL_MESH_MAX_NUM_OF_BONES)
+{
+  loader.execute(this);
+
+  for(int i{0}; i < skeletal_mesh->bones.size(); i++)
+    this->bone_transforms[i] = skeletal_mesh->bones[i].offset_matrix;
+}
+
+SkeletalModel::~SkeletalModel()
+{
+  loader.revert(this);
+}
+
+void
+SkeletalModel::tick(float delta)
+{
+  BluCat::GRA::Animation *current_animation =
+    &this->skeletal_mesh->animations[this->animation_index];
+
+  { // update time
+    this->animation_time += delta;
+    if(this->animation_time > current_animation->final_time)
+    {
+      this->animation_time -= current_animation->final_time;
+      for(BluCat::GRA::BoneTransform &bone_transform:
+	    current_animation->bone_transforms)
+      {
+	bone_transform.positions.current_index = 0;
+	bone_transform.rotations.current_index = 0;
+	bone_transform.scales.current_index = 0;
+      }
+    }
+  }
+
+  for(int i{0}; i < current_animation->bone_transforms.size(); i++)
+  {
+    BluCat::GRA::BoneTransform *bone_transform = &current_animation->bone_transforms[i];
+
+    auto position{bone_transform->positions.interpolate(
+	this->animation_time,
+	[](glm::vec3 frame)
+	  {
+	    return glm::translate(glm::mat4(1.0f), frame);
+	  },
+	[](glm::vec3 previous_frame, glm::vec3 next_frame, float scale_factor)
+	  {
+	    glm::vec3 final_position{glm::mix(
+		previous_frame, next_frame, scale_factor)};
+	    return glm::translate(glm::mat4(1.0f), final_position);
+	  })};
+
+    auto rotation{bone_transform->rotations.interpolate(
+	this->animation_time,
+	[](glm::quat frame)
+	  {
+	    return glm::toMat4(glm::normalize(frame));
+	  },
+	[](glm::quat previous_frame, glm::quat next_frame, float scale_factor)
+	  {
+	    return glm::toMat4(glm::slerp(
+				 previous_frame, next_frame, scale_factor));
+	  })};
+
+    auto scale{bone_transform->scales.interpolate(
+	this->animation_time,
+	[](glm::vec3 frame)
+	  {
+	    return glm::scale(glm::mat4(1.0f), frame);
+	  },
+	[](glm::vec3 previous_frame, glm::vec3 next_frame, float scale_factor)
+	  {
+	    glm::vec3 scale{glm::mix(
+		previous_frame, next_frame, scale_factor)};
+	    return glm::scale(glm::mat4(1.0f), scale);
+	  })};
+
+    this->bone_transforms[i] = position * rotation * scale;
+  }
+}
+
+}
diff --git a/src/blu_cat/gra/skeletal_model.hpp b/src/blu_cat/gra/skeletal_model.hpp
new file mode 100644
index 0000000..73998cb
--- /dev/null
+++ b/src/blu_cat/gra/skeletal_model.hpp
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2022-2024 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 BLU_CAT_GRA_SKELETAL_MODEL_H
+#define BLU_CAT_GRA_SKELETAL_MODEL_H 1
+
+#include <memory>
+#include <vector>
+
+#include "vulkan.hpp"
+#include "skeletal_mesh.hpp"
+
+namespace BluCat::GRA
+{
+
+struct SkeletalModel
+{
+  std::shared_ptr<SkeletalMesh> skeletal_mesh;
+  std::shared_ptr<Texture> texture;
+  std::vector<UniformBuffer> uniform_buffers;
+  std::shared_ptr<glm::vec3> position;
+  std::shared_ptr<glm::quat> orientation;
+  int animation_index;
+  float animation_time;
+  std::vector<glm::mat4> bone_transforms;
+
+  VkDescriptorPool descriptor_pool;
+  std::vector<VkDescriptorSet> descriptor_sets;
+
+  SkeletalModel(
+    std::shared_ptr<SkeletalMesh> skeletal_mesh,
+    std::shared_ptr<Texture> texture, std::shared_ptr<glm::vec3> position,
+    std::shared_ptr<glm::quat> orientation);
+  ~SkeletalModel();
+
+  void
+  tick(float delta);
+};
+
+}
+
+#endif /* BLU_CAT_GRA_SKELETAL_MODEL_H */
diff --git a/src/blu_cat/gra/source_buffer.cpp b/src/blu_cat/gra/source_buffer.cpp
new file mode 100644
index 0000000..18f263f
--- /dev/null
+++ b/src/blu_cat/gra/source_buffer.cpp
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2022-2024 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 BluCat::GRA
+{
+
+SourceBuffer::SourceBuffer(Device *device, void *data, size_t data_size)
+{
+  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
+  {
+    BluCat::GRA::BaseBuffer::loader.execute(static_cast<BluCat::GRA::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(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;
+
+  that.buffer = VK_NULL_HANDLE;
+  that.device_memory = VK_NULL_HANDLE;
+}
+
+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;
+
+  that.buffer = VK_NULL_HANDLE;
+  that.device_memory = VK_NULL_HANDLE;
+
+  return *this;
+}
+
+SourceBuffer::~SourceBuffer()
+{
+  BluCat::GRA::BaseBuffer::loader.revert(static_cast<BluCat::GRA::BaseBuffer*>(this));
+}
+
+void
+SourceBuffer::copy_data(void *src_data)
+{
+  void *dst_data;
+  vkMapMemory(this->device->device, this->device_memory, 0, this->device_size,
+	      0, &dst_data);
+  memcpy(dst_data, src_data, static_cast<size_t>(this->device_size));
+  vkUnmapMemory(this->device->device, this->device_memory);
+}
+
+}
diff --git a/src/blu_cat/gra/source_buffer.hpp b/src/blu_cat/gra/source_buffer.hpp
new file mode 100644
index 0000000..4eecec9
--- /dev/null
+++ b/src/blu_cat/gra/source_buffer.hpp
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2022-2024 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 BLU_CAT_GRA_SOURCE_BUFFER_H
+#define BLU_CAT_GRA_SOURCE_BUFFER_H 1
+
+#include "base_buffer.hpp"
+
+namespace BluCat::GRA
+{
+
+struct SourceBuffer: public BaseBuffer
+{
+  SourceBuffer(const SourceBuffer &t) = delete;
+  SourceBuffer&
+  operator=(const SourceBuffer &t) = delete;
+
+  SourceBuffer(Device *device, void *data, size_t data_size);
+
+  SourceBuffer(SourceBuffer &&that);
+  SourceBuffer&
+  operator=(SourceBuffer &&that);
+
+  ~SourceBuffer();
+
+  void
+  copy_data(void *src_data);
+};
+
+}
+
+#endif /* BLU_CAT_GRA_SOURCE_BUFFER_H */
diff --git a/src/blu_cat/gra/sprite.cpp b/src/blu_cat/gra/sprite.cpp
new file mode 100644
index 0000000..ee64efe
--- /dev/null
+++ b/src/blu_cat/gra/sprite.cpp
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2022-2024 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.hpp"
+
+#include <array>
+
+#include "../int/core.hpp"
+#include "sprite.hpp"
+#include "uniform_data_object.hpp"
+
+namespace
+{
+
+struct SpriteBuilder
+{
+  BluCat::GRA::Sprite *sprite;
+  const glm::vec4 &rect;
+
+  SpriteBuilder(BluCat::GRA::Sprite *sprite, const glm::vec4 &rect);
+};
+
+SpriteBuilder::SpriteBuilder(BluCat::GRA::Sprite *sprite, const glm::vec4 &rect):
+  sprite{sprite},
+  rect{rect}
+{
+}
+
+void
+load_mesh(void *obj)
+{
+  auto self = static_cast<SpriteBuilder*>(obj);
+
+  self->sprite->queue_family =
+    BluCat::INT::core.vk_device_with_swapchain->get_queue_family_with_graphics();
+
+  glm::vec2 rect[BluCat::GRA::Sprite::vertex_count]{
+    glm::vec2{self->rect.x, self->rect.y},
+    glm::vec2{self->rect.x, self->rect.w},
+    glm::vec2{self->rect.z, self->rect.y},
+    glm::vec2{self->rect.z, self->rect.w}
+  };
+
+  void *vertexes_data{&rect};
+  static const size_t vertexes_size =
+    sizeof(glm::vec2) * BluCat::GRA::Sprite::vertex_count;
+  self->sprite->source_buffer = new BluCat::GRA::SourceBuffer{
+    self->sprite->queue_family->device, vertexes_data, vertexes_size};
+  self->sprite->vertex_buffer = new BluCat::GRA::DestinationBuffer{
+    self->sprite->queue_family, self->sprite->source_buffer,
+    VK_BUFFER_USAGE_VERTEX_BUFFER_BIT};
+}
+
+void
+unload_mesh(void *obj)
+{
+  auto self = static_cast<SpriteBuilder*>(obj);
+
+  delete self->sprite->vertex_buffer;
+  delete self->sprite->source_buffer;
+}
+
+static const CommandChain loader{
+  {&load_mesh, &unload_mesh},
+};
+
+}
+
+namespace BluCat::GRA
+{
+
+Sprite::Sprite(std::shared_ptr<Texture> texture, const glm::vec4 &rect):
+  texture{texture}
+{
+  SpriteBuilder sprite_builder(this, rect);
+  loader.execute(&sprite_builder);
+}
+
+Sprite::~Sprite()
+{
+  glm::vec4 vector_4d{};
+  SpriteBuilder sprite_builder(this, vector_4d);
+  loader.revert(&sprite_builder);
+}
+
+}
diff --git a/src/blu_cat/gra/sprite.hpp b/src/blu_cat/gra/sprite.hpp
new file mode 100644
index 0000000..1834b22
--- /dev/null
+++ b/src/blu_cat/gra/sprite.hpp
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2022-2024 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 BLU_CAT_GRA_SPRITE_H
+#define BLU_CAT_GRA_SPRITE_H 1
+
+#include <memory>
+#include <unordered_map>
+#include <vector>
+
+#include "vulkan.hpp"
+#include "destination_buffer.hpp"
+#include "queue_family.hpp"
+#include "uniform_buffer.hpp"
+#include "texture.hpp"
+
+namespace BluCat::GRA
+{
+
+struct Sprite
+{
+  static const uint32_t vertex_count{4};
+
+  QueueFamily *queue_family;
+
+  SourceBuffer *source_buffer;
+  DestinationBuffer *vertex_buffer;
+
+  std::shared_ptr<Texture> texture;
+
+  Sprite(std::shared_ptr<Texture> texture, const glm::vec4 &rect);
+  ~Sprite();
+};
+
+}
+
+#endif /* BLU_CAT_GRA_SPRITE_H */
diff --git a/src/blu_cat/gra/sprite_3d.cpp b/src/blu_cat/gra/sprite_3d.cpp
new file mode 100644
index 0000000..fc994a6
--- /dev/null
+++ b/src/blu_cat/gra/sprite_3d.cpp
@@ -0,0 +1,168 @@
+/*
+ * Copyright 2022-2024 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_3d.hpp"
+
+#include "../int/core.hpp"
+#include "uniform_data_object.hpp"
+
+namespace
+{
+
+void
+load_uniform_buffers(void *obj)
+{
+  auto self = static_cast<BluCat::GRA::Sprite3D*>(obj);
+
+  try
+  {
+    self->uniform_buffers.reserve(BluCat::INT::core.vk_swapchain->images_count);
+    for(auto i{0}; i < BluCat::INT::core.vk_swapchain->images_count; i++)
+      self->uniform_buffers.emplace_back(
+				BluCat::INT::core.vk_device_with_swapchain, sizeof(BluCat::GRA::UDOSprite3D));
+  }
+  catch(const std::exception& e)
+  {
+    throw CommandError{e.what()};
+  }
+}
+
+void
+unload_uniform_buffers(void *obj)
+{
+  auto self = static_cast<BluCat::GRA::Sprite3D*>(obj);
+
+  self->uniform_buffers.clear();
+}
+
+void
+load_descriptor_set_pool(void *obj)
+{
+  auto self = static_cast<BluCat::GRA::Sprite3D*>(obj);
+
+  std::array<VkDescriptorPoolSize, 1> descriptor_pool_sizes{};
+  descriptor_pool_sizes[0].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
+  descriptor_pool_sizes[0].descriptorCount =
+    self->uniform_buffers.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->uniform_buffers.size();
+  pool_info.poolSizeCount = descriptor_pool_sizes.size();
+  pool_info.pPoolSizes = descriptor_pool_sizes.data();
+
+  if(vkCreateDescriptorPool(
+       self->queue_family->device->device, &pool_info, nullptr,
+       &self->descriptor_pool) != VK_SUCCESS)
+    throw CommandError{"Failed to create a Vulkan descriptor pool."};
+}
+
+void
+unload_descriptor_set_pool(void *obj)
+{
+  auto self = static_cast<BluCat::GRA::Sprite3D*>(obj);
+
+  vkDestroyDescriptorPool(
+    self->queue_family->device->device, self->descriptor_pool, nullptr);
+}
+void
+load_descriptor_sets(void *obj)
+{
+  auto self = static_cast<BluCat::GRA::Sprite3D*>(obj);
+
+  std::vector<VkDescriptorSetLayout> layouts(
+    BluCat::INT::core.vk_swapchain->images_count,
+    BluCat::INT::core.vk_descriptor_set_layout->model);
+
+  VkDescriptorSetAllocateInfo alloc_info{};
+  alloc_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
+  alloc_info.descriptorPool = self->descriptor_pool;
+  alloc_info.descriptorSetCount = layouts.size();
+  alloc_info.pSetLayouts = layouts.data();
+
+  self->descriptor_sets.resize(layouts.size());
+  if(vkAllocateDescriptorSets(
+       self->queue_family->device->device, &alloc_info,
+       self->descriptor_sets.data()) != VK_SUCCESS)
+    CommandError{"Failed to create Vulkan descriptor set."};
+}
+
+void
+load_buffers_to_descriptor_sets(void *obj)
+{
+  auto self = static_cast<BluCat::GRA::Sprite3D*>(obj);
+
+  for(auto i{0}; i < self->uniform_buffers.size(); i++)
+  {
+    VkDescriptorBufferInfo buffer_info{};
+    buffer_info.buffer = self->uniform_buffers[i].buffer;
+    buffer_info.offset = 0;
+    buffer_info.range = sizeof(BluCat::GRA::UDOSprite3D);
+
+    VkDescriptorImageInfo image_info{};
+    image_info.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+    image_info.imageView = self->sprite->texture->view;
+    image_info.sampler = self->sprite->texture->sampler;
+
+    std::array<VkWriteDescriptorSet, 1> write_descriptors{};
+    write_descriptors[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+    write_descriptors[0].dstSet = self->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;
+
+    vkUpdateDescriptorSets(
+      BluCat::INT::core.vk_device_with_swapchain->device, write_descriptors.size(),
+      write_descriptors.data(), 0, nullptr);
+  }
+}
+
+static const CommandChain loader{
+  {&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 BluCat::GRA
+{
+
+Sprite3D::Sprite3D(
+    std::shared_ptr<BluCat::GRA::Sprite> sprite, std::shared_ptr<glm::vec3> position,
+    glm::vec2 size):
+  sprite{sprite},
+  position{position},
+  size{size}
+{
+  this->queue_family =
+    BluCat::INT::core.vk_device_with_swapchain->get_queue_family_with_graphics();
+  loader.execute(this);
+}
+
+Sprite3D::~Sprite3D()
+{
+  loader.revert(this);
+}
+
+}
diff --git a/src/blu_cat/gra/sprite_3d.hpp b/src/blu_cat/gra/sprite_3d.hpp
new file mode 100644
index 0000000..0cd8c04
--- /dev/null
+++ b/src/blu_cat/gra/sprite_3d.hpp
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2022-2024 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 BLU_CAT_GRA_SPRITE_3D_H
+#define BLU_CAT_GRA_SPRITE_3D_H 1
+
+#include "vulkan.hpp"
+#include "sprite.hpp"
+
+namespace BluCat::GRA
+{
+
+struct Sprite3D
+{
+  std::shared_ptr<Sprite> sprite;
+  std::shared_ptr<glm::vec3> position;
+  glm::vec2 size;
+
+  QueueFamily *queue_family;
+
+  std::vector<UniformBuffer> uniform_buffers;
+  VkDescriptorPool descriptor_pool;
+  std::vector<VkDescriptorSet> descriptor_sets;
+
+  Sprite3D(
+    std::shared_ptr<Sprite> sprite, std::shared_ptr<glm::vec3> position,
+    glm::vec2 size);
+  ~Sprite3D();
+};
+
+}
+
+#endif /* BLU_CAT_GRA_SPRITE_3D_H */
diff --git a/src/blu_cat/gra/sprite_to_draw.cpp b/src/blu_cat/gra/sprite_to_draw.cpp
new file mode 100644
index 0000000..7645d73
--- /dev/null
+++ b/src/blu_cat/gra/sprite_to_draw.cpp
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2022-2024 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_to_draw.hpp"
+
+namespace BluCat::GRA
+{
+  SpriteToDraw::SpriteToDraw(
+    std::shared_ptr<Sprite> sprite, const glm::vec4 &position, float z_index):
+    sprite{sprite},
+    position{position},
+    z_index{z_index}
+  {
+  }
+
+  bool
+  operator<(const SpriteToDraw& a, const SpriteToDraw& b)
+  {
+    return a.z_index < b.z_index;
+  }
+
+  bool
+  operator>(const SpriteToDraw& a, const SpriteToDraw& b)
+  {
+    return a.z_index > b.z_index;
+  }
+}
diff --git a/src/blu_cat/gra/sprite_to_draw.hpp b/src/blu_cat/gra/sprite_to_draw.hpp
new file mode 100644
index 0000000..e7a23c4
--- /dev/null
+++ b/src/blu_cat/gra/sprite_to_draw.hpp
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2022-2024 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 BLU_CAT_GRA_SPRITES_TO_DRAW_H
+#define BLU_CAT_GRA_SPRITES_TO_DRAW_H 1
+
+#include <memory>
+
+#include "vulkan.hpp"
+#include "sprite.hpp"
+
+namespace BluCat::GRA
+{
+  struct SpriteToDraw
+  {
+    std::shared_ptr<Sprite> sprite;
+    glm::vec4 position;
+    float z_index;
+
+    SpriteToDraw(std::shared_ptr<Sprite> sprite, const glm::vec4 &position,
+                 float z_index);
+  };
+
+  bool
+  operator<(const SpriteToDraw &a, const SpriteToDraw &b);
+
+  bool
+  operator>(const SpriteToDraw &a, const SpriteToDraw &b);
+}
+
+#endif /* BLU_CAT_GRA_SPRITES_TO_DRAW_H */
diff --git a/src/blu_cat/gra/static_mesh.cpp b/src/blu_cat/gra/static_mesh.cpp
new file mode 100644
index 0000000..db3a6ee
--- /dev/null
+++ b/src/blu_cat/gra/static_mesh.cpp
@@ -0,0 +1,132 @@
+/*
+ * Copyright 2022-2024 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 "static_mesh.hpp"
+
+#include "binary_reader.hpp"
+#include "../com/command.hpp"
+#include "../int/core.hpp"
+#include "static_mesh_vertex.hpp"
+
+namespace
+{
+
+// Data that is only needed for the command chain but not for the StaticMesh
+// goes here.
+struct MeshBuilder
+{
+  std::string mesh_path;
+  BluCat::GRA::StaticMesh *mesh;
+
+  MeshBuilder(BluCat::GRA::StaticMesh *m, std::string mp);
+  MeshBuilder(BluCat::GRA::StaticMesh *m, const char* mp);
+};
+
+MeshBuilder::MeshBuilder(BluCat::GRA::StaticMesh *m, std::string mp):
+  mesh{m},
+  mesh_path{mp}
+{
+}
+
+MeshBuilder::MeshBuilder(BluCat::GRA::StaticMesh *m, const char *mp):
+  MeshBuilder{m, std::string(mp)}
+{
+}
+
+void
+load_mesh(void *obj)
+{
+  auto self = static_cast<MeshBuilder*>(obj);
+
+  BinaryReader input{self->mesh_path};
+
+  self->mesh->queue_family =
+    BluCat::INT::core.vk_device_with_swapchain->get_queue_family_with_graphics();
+
+  { // Load vertexes.
+    auto vertex_count{input.read_ui32()};
+    std::vector<BluCat::GRA::StaticMeshVertex> vertexes{vertex_count};
+
+    for(auto i{0}; i < vertex_count; i++)
+    {
+      vertexes[i].position = input.read_vec3();
+      vertexes[i].normal = input.read_vec3();
+      vertexes[i].texture_coord = input.read_vec2();
+    }
+
+    void *vertexes_data{vertexes.data()};
+    size_t vertexes_size = sizeof(vertexes[0]) * vertexes.size();
+    self->mesh->source_vertex_buffer = new BluCat::GRA::SourceBuffer{
+      self->mesh->queue_family->device, vertexes_data, vertexes_size};
+    self->mesh->vertex_buffer = new BluCat::GRA::DestinationBuffer{
+      self->mesh->queue_family, self->mesh->source_vertex_buffer,
+      VK_BUFFER_USAGE_VERTEX_BUFFER_BIT};
+  }
+
+  { // Load indexes
+    self->mesh->index_count = input.read_ui32();
+    std::vector<uint32_t> indexes(self->mesh->index_count);
+
+    for(auto i{0}; i < self->mesh->index_count; i++)
+      indexes[i] = input.read_ui32();
+
+    void *indexes_data{indexes.data()};
+    size_t indexes_size{sizeof(indexes[0]) * indexes.size()};
+    BluCat::GRA::SourceBuffer source_index_buffer{
+      self->mesh->queue_family->device, indexes_data, indexes_size};
+    self->mesh->index_buffer = new BluCat::GRA::DestinationBuffer{
+      self->mesh->queue_family, &source_index_buffer,
+      VK_BUFFER_USAGE_INDEX_BUFFER_BIT};
+  }
+}
+
+void
+unload_mesh(void *obj)
+{
+  auto self = static_cast<MeshBuilder*>(obj);
+
+  delete self->mesh->index_buffer;
+  delete self->mesh->vertex_buffer;
+  delete self->mesh->source_vertex_buffer;
+}
+
+static const CommandChain loader{
+  {&load_mesh, &unload_mesh}
+};
+
+}
+
+namespace BluCat::GRA
+{
+
+StaticMesh::StaticMesh(std::string mesh_path)
+{
+  MeshBuilder mesh_builder(this, mesh_path);
+  loader.execute(&mesh_builder);
+}
+
+StaticMesh::StaticMesh(const char* mesh_path):
+  StaticMesh{std::string(mesh_path)}
+{
+}
+
+StaticMesh::~StaticMesh()
+{
+  MeshBuilder mesh_builder(this, "");
+  loader.revert(&mesh_builder);
+}
+
+}
diff --git a/src/blu_cat/gra/static_mesh.hpp b/src/blu_cat/gra/static_mesh.hpp
new file mode 100644
index 0000000..2b059fe
--- /dev/null
+++ b/src/blu_cat/gra/static_mesh.hpp
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2022-2024 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 BLU_CAT_GRA_STATIC_MESH_H
+#define BLU_CAT_GRA_STATIC_MESH_H 1
+
+#include <string>
+#include <vector>
+
+#include "vulkan.hpp"
+#include "destination_buffer.hpp"
+#include "queue_family.hpp"
+#include "uniform_buffer.hpp"
+#include "texture.hpp"
+
+namespace BluCat::GRA
+{
+
+struct StaticMesh
+{
+  QueueFamily *queue_family;
+
+  uint32_t index_count;
+  SourceBuffer *source_vertex_buffer;
+  DestinationBuffer *index_buffer;
+  DestinationBuffer *vertex_buffer;
+
+  StaticMesh(std::string mesh_path);
+  StaticMesh(const char* mesh_path);
+  ~StaticMesh();
+};
+
+}
+
+#endif /* BLU_CAT_GRA_STATIC_MESH_H */
diff --git a/src/blu_cat/gra/static_mesh_vertex.hpp b/src/blu_cat/gra/static_mesh_vertex.hpp
new file mode 100644
index 0000000..510ce40
--- /dev/null
+++ b/src/blu_cat/gra/static_mesh_vertex.hpp
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2022-2024 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 BLU_CAT_GRA_STATIC_MESH_VERTEX_H
+#define BLU_CAT_GRA_STATIC_MESH_VERTEX_H 1
+
+#include "vulkan.hpp"
+
+namespace BluCat::GRA
+{
+
+struct StaticMeshVertex
+{
+  glm::vec3 position;
+  glm::vec3 normal;
+  glm::vec2 texture_coord;
+};
+
+}
+
+#endif /* BLU_CAT_GRA_STATIC_MESH_VERTEX_H */
diff --git a/src/blu_cat/gra/static_model.cpp b/src/blu_cat/gra/static_model.cpp
new file mode 100644
index 0000000..50dc94d
--- /dev/null
+++ b/src/blu_cat/gra/static_model.cpp
@@ -0,0 +1,165 @@
+/*
+ * Copyright 2022-2024 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 "static_model.hpp"
+
+#include "../int/core.hpp"
+#include "uniform_data_object.hpp"
+
+namespace
+{
+
+void
+load_uniform_buffers(void *obj)
+{
+  auto self = static_cast<BluCat::GRA::StaticModel*>(obj);
+
+  try
+  {
+    self->uniform_buffers.reserve(BluCat::INT::core.vk_swapchain->images_count);
+    for(auto i{0}; i < BluCat::INT::core.vk_swapchain->images_count; i++)
+      self->uniform_buffers.emplace_back(
+				BluCat::INT::core.vk_device_with_swapchain, sizeof(BluCat::GRA::UDOStaticModel));
+  }
+  catch(const std::exception& e)
+  {
+    throw CommandError{e.what()};
+  }
+}
+
+void
+unload_uniform_buffers(void *obj)
+{
+  auto self = static_cast<BluCat::GRA::StaticModel*>(obj);
+
+  self->uniform_buffers.clear();
+}
+
+void
+load_descriptor_set_pool(void *obj)
+{
+  auto self = static_cast<BluCat::GRA::StaticModel*>(obj);
+
+  std::array<VkDescriptorPoolSize, 1> descriptor_pool_sizes{};
+  descriptor_pool_sizes[0].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
+  descriptor_pool_sizes[0].descriptorCount =
+    self->uniform_buffers.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->uniform_buffers.size();
+  pool_info.poolSizeCount = descriptor_pool_sizes.size();
+  pool_info.pPoolSizes = descriptor_pool_sizes.data();
+
+  if(vkCreateDescriptorPool(
+       self->static_mesh->queue_family->device->device, &pool_info, nullptr,
+       &self->descriptor_pool) != VK_SUCCESS)
+    throw CommandError{"Failed to create a Vulkan descriptor pool."};
+}
+
+void
+unload_descriptor_set_pool(void *obj)
+{
+  auto self = static_cast<BluCat::GRA::StaticModel*>(obj);
+
+  vkDestroyDescriptorPool(
+    self->static_mesh->queue_family->device->device, self->descriptor_pool,
+    nullptr);
+}
+
+void
+load_descriptor_sets(void *obj)
+{
+  auto self = static_cast<BluCat::GRA::StaticModel*>(obj);
+
+  std::vector<VkDescriptorSetLayout> layouts(
+    BluCat::INT::core.vk_swapchain->images_count,
+    BluCat::INT::core.vk_descriptor_set_layout->model);
+
+  VkDescriptorSetAllocateInfo alloc_info{};
+  alloc_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
+  alloc_info.descriptorPool = self->descriptor_pool;
+  alloc_info.descriptorSetCount = layouts.size();
+  alloc_info.pSetLayouts = layouts.data();
+
+  self->descriptor_sets.resize(layouts.size());
+  if(vkAllocateDescriptorSets(
+       self->static_mesh->queue_family->device->device, &alloc_info,
+       self->descriptor_sets.data()) != VK_SUCCESS)
+    CommandError{"Failed to create Vulkan descriptor set."};
+}
+
+void
+load_buffers_to_descriptor_sets(void *obj)
+{
+  auto self = static_cast<BluCat::GRA::StaticModel*>(obj);
+
+  for(auto i{0}; i < self->uniform_buffers.size(); i++)
+  {
+    VkDescriptorBufferInfo buffer_info{};
+    buffer_info.buffer = self->uniform_buffers[i].buffer;
+    buffer_info.offset = 0;
+    buffer_info.range = sizeof(BluCat::GRA::UDOStaticModel);
+
+    std::array<VkWriteDescriptorSet, 1> write_descriptors{};
+    write_descriptors[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+    write_descriptors[0].dstSet = self->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;
+
+    vkUpdateDescriptorSets(
+      BluCat::INT::core.vk_device_with_swapchain->device, write_descriptors.size(),
+      write_descriptors.data(), 0, nullptr);
+  }
+}
+
+static const CommandChain loader{
+  {&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 BluCat::GRA
+{
+
+StaticModel::StaticModel(
+  std::shared_ptr<StaticMesh> static_mesh,
+  std::shared_ptr<Texture> texture, std::shared_ptr<glm::vec3> position,
+  std::shared_ptr<glm::quat> orientation):
+  static_mesh{static_mesh},
+  texture{texture},
+  position{position},
+  orientation{orientation}
+{
+  loader.execute(this);
+}
+
+StaticModel::~StaticModel()
+{
+  loader.revert(this);
+}
+
+}
diff --git a/src/blu_cat/gra/static_model.hpp b/src/blu_cat/gra/static_model.hpp
new file mode 100644
index 0000000..0d689d0
--- /dev/null
+++ b/src/blu_cat/gra/static_model.hpp
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2022-2024 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 BLU_CAT_GRA_STATIC_MODEL_H
+#define BLU_CAT_GRA_STATIC_MODEL_H 1
+
+#include <memory>
+#include <vector>
+
+#include "vulkan.hpp"
+#include "static_mesh.hpp"
+
+namespace BluCat::GRA
+{
+
+struct StaticModel
+{
+  std::shared_ptr<StaticMesh> static_mesh;
+  std::shared_ptr<Texture> texture;
+  std::vector<UniformBuffer> uniform_buffers;
+  std::shared_ptr<glm::vec3> position;
+  std::shared_ptr<glm::quat> orientation;
+
+  VkDescriptorPool descriptor_pool;
+  std::vector<VkDescriptorSet> descriptor_sets;
+
+  StaticModel(
+    std::shared_ptr<StaticMesh> static_mesh,
+    std::shared_ptr<Texture> texture, std::shared_ptr<glm::vec3> position,
+    std::shared_ptr<glm::quat> orientation);
+  ~StaticModel();
+};
+
+}
+
+#endif /* BLU_CAT_GRA_STATIC_MODEL_H */
diff --git a/src/blu_cat/gra/swapchain.cpp b/src/blu_cat/gra/swapchain.cpp
new file mode 100644
index 0000000..f57e3c2
--- /dev/null
+++ b/src/blu_cat/gra/swapchain.cpp
@@ -0,0 +1,207 @@
+/*
+ * Copyright 2022-2024 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 "../int/core.hpp"
+
+#include <vector>
+
+namespace
+{
+
+void
+load_swapchain(void *obj)
+{
+  auto self = static_cast<BluCat::GRA::Swapchain*>(obj);
+
+  // Surface formats.
+  uint32_t vk_surface_format_count;
+  std::vector<VkSurfaceFormatKHR> vk_surface_formats;
+  vkGetPhysicalDeviceSurfaceFormatsKHR(
+    BluCat::INT::core.vk_device_with_swapchain->physical_device,
+		BluCat::INT::core.window_surface, &vk_surface_format_count, nullptr);
+  vk_surface_formats.resize(vk_surface_format_count);
+  vkGetPhysicalDeviceSurfaceFormatsKHR(
+    BluCat::INT::core.vk_device_with_swapchain->physical_device,
+		BluCat::INT::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 = BluCat::INT::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 = {
+    BluCat::INT::core.display_width, BluCat::INT::core.display_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(
+       BluCat::INT::core.vk_device_with_swapchain->device, &swapchain_create_info,
+       nullptr, &self->swapchain) != VK_SUCCESS)
+    throw CommandError{"Vulkan failed to create swapchain."};
+
+  vkGetSwapchainImagesKHR(
+    BluCat::INT::core.vk_device_with_swapchain->device, self->swapchain,
+    &self->images_count, nullptr);
+  self->images = new VkImage[self->images_count];
+  vkGetSwapchainImagesKHR(
+    BluCat::INT::core.vk_device_with_swapchain->device, self->swapchain,
+    &self->images_count, self->images);
+}
+
+void
+unload_swapchain(void *obj)
+{
+  auto self = static_cast<BluCat::GRA::Swapchain*>(obj);
+
+  delete[] self->images;
+  vkDestroySwapchainKHR(
+    BluCat::INT::core.vk_device_with_swapchain->device, self->swapchain, nullptr);
+}
+
+void
+load_image_view(void *obj)
+{
+  auto self = static_cast<BluCat::GRA::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(
+	 BluCat::INT::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<BluCat::GRA::Swapchain*>(obj);
+
+  for(auto i{0}; i < self->images_count; i++)
+    vkDestroyImageView(
+      BluCat::INT::core.vk_device_with_swapchain->device, self->image_views[i],
+			nullptr);
+}
+
+void
+load_frame_sync(void *obj)
+{
+  auto self = static_cast<BluCat::GRA::Swapchain*>(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(
+	 BluCat::INT::core.vk_device_with_swapchain->device, &semaphore_info,
+	 nullptr, &self->image_available_semaphores[i]) != VK_SUCCESS ||
+       vkCreateSemaphore(
+	 BluCat::INT::core.vk_device_with_swapchain->device, &semaphore_info,
+	 nullptr, &self->render_finished_semaphores[i]) != VK_SUCCESS ||
+       vkCreateFence(
+				 BluCat::INT::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<BluCat::GRA::Swapchain*>(obj);
+
+  vkDeviceWaitIdle(BluCat::INT::core.vk_device_with_swapchain->device);
+
+  for(auto i{0}; i < self->max_frames_in_flight; i++)
+  {
+    vkDestroySemaphore(BluCat::INT::core.vk_device_with_swapchain->device,
+                       self->render_finished_semaphores[i], nullptr);
+    vkDestroySemaphore(BluCat::INT::core.vk_device_with_swapchain->device,
+                       self->image_available_semaphores[i], nullptr);
+    vkDestroyFence(BluCat::INT::core.vk_device_with_swapchain->device,
+									 self->in_flight_fences[i], nullptr);
+  }
+}
+
+const CommandChain loader{
+  {&load_swapchain, &unload_swapchain},
+  {&load_image_view, &unload_image_view},
+  {&load_frame_sync, &unload_frame_sync}
+};
+
+}
+
+namespace BluCat::GRA
+{
+
+Swapchain::Swapchain():
+  current_frame{0}
+{
+  loader.execute(this);
+}
+
+Swapchain::~Swapchain()
+{
+  loader.revert(this);
+}
+
+}
diff --git a/src/blu_cat/gra/swapchain.hpp b/src/blu_cat/gra/swapchain.hpp
new file mode 100644
index 0000000..2b52dd6
--- /dev/null
+++ b/src/blu_cat/gra/swapchain.hpp
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2022-2024 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 BLU_CAT_GRA_SWAPCHAIN_H
+#define BLU_CAT_GRA_SWAPCHAIN_H 1
+
+#include "../com/command.hpp"
+#include "vulkan.hpp"
+
+namespace BluCat::GRA
+{
+
+struct Swapchain
+{
+  VkSwapchainKHR swapchain;
+  VkFormat image_format;
+
+  uint32_t images_count;
+  VkImage *images;
+  VkImageView *image_views;
+
+  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;
+
+  Swapchain();
+  ~Swapchain();
+};
+
+}
+
+#endif /* BLU_CAT_GRA_SWAPCHAIN_H */
diff --git a/src/blu_cat/gra/texture.cpp b/src/blu_cat/gra/texture.cpp
new file mode 100644
index 0000000..5abf751
--- /dev/null
+++ b/src/blu_cat/gra/texture.cpp
@@ -0,0 +1,566 @@
+/*
+ * Copyright 2022-2024 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 "../com/command.hpp"
+#include "../int/core.hpp"
+#include "image.hpp"
+#include "qoi.hpp"
+#include "source_buffer.hpp"
+
+namespace
+{
+
+inline void
+create_vulkan_image(
+  VkImage *image, VkDeviceMemory *device_memory, int width, int height,
+  uint32_t mip_levels)
+{
+  VkExtent3D vk_extent3d{};
+  vk_extent3d.width = width;
+  vk_extent3d.height = height;
+  vk_extent3d.depth = 1;
+
+  BluCat::GRA::Image::create(
+    BluCat::INT::core.vk_device_with_swapchain,
+    image,
+    device_memory,
+    VK_FORMAT_R8G8B8A8_UNORM,
+    vk_extent3d,
+    mip_levels,
+    VK_IMAGE_TILING_OPTIMAL,
+    VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT);
+}
+
+struct ImageBuilder
+{
+  BluCat::GRA::Texture *texture;
+};
+
+struct ImageTextureBuilder: public ImageBuilder
+{
+  std::string texture_path;
+
+  ImageTextureBuilder(BluCat::GRA::Texture *t, const std::string &tp);
+  ImageTextureBuilder(BluCat::GRA::Texture *t, const char* tp);
+};
+
+ImageTextureBuilder::ImageTextureBuilder(
+	BluCat::GRA::Texture *t, const std::string &tp):
+  texture_path{tp}
+{
+  this->texture = t;
+}
+
+ImageTextureBuilder::ImageTextureBuilder(BluCat::GRA::Texture *t, const char* tp):
+  ImageTextureBuilder{t, std::string(tp)}
+{
+}
+
+void
+load_image(void *obj)
+{
+  auto self = static_cast<ImageTextureBuilder*>(obj);
+
+  const int num_channels = 4; // all images are converted to RGBA
+  BluCat::GRA::QOI::Image qoi_image(self->texture_path.c_str(), num_channels);
+  uint8_t *pixels;
+
+  { // Load file image from file.
+    self->texture->width = qoi_image.header.width;
+    self->texture->height = qoi_image.header.height;
+    self->texture->mip_levels = 1;
+
+    pixels = qoi_image.pixels;
+  }
+
+  // Load file image into a vulkan buffer.
+  size_t image_size{static_cast<size_t>(
+      qoi_image.header.width * qoi_image.header.height * num_channels)};
+  BluCat::GRA::SourceBuffer source_image_buffer{
+    BluCat::INT::core.vk_device_with_swapchain, pixels, image_size};
+
+  { // Create vulkan image.
+    try
+    {
+      create_vulkan_image(
+        &self->texture->image,
+        &self->texture->device_memory,
+        self->texture->width,
+        self->texture->height,
+        self->texture->mip_levels);
+    }
+    catch(BluCat::GRA::Image::Error error)
+    {
+      throw CommandError{error.what()};
+    }
+  }
+
+  // Copy image from vulkan buffer into vulkan image.
+  {
+    auto queue_family = self->texture->queue_family;
+    auto queue{queue_family->get_queue()};
+    BluCat::GRA::CommandPool command_pool{queue_family, 1};
+    VkCommandBuffer vk_command_buffer{command_pool.command_buffers[0]};
+
+    queue.submit_one_time_command(vk_command_buffer, [&](){
+      BluCat::GRA::Image::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);
+
+      BluCat::GRA::Image::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);
+    });
+  }
+}
+
+void
+unload_image(void *obj)
+{
+  auto self = static_cast<ImageBuilder*>(obj);
+
+  vkDestroyImage(
+    BluCat::INT::core.vk_device_with_swapchain->device, self->texture->image,
+		nullptr);
+  vkFreeMemory(
+    BluCat::INT::core.vk_device_with_swapchain->device,
+		self->texture->device_memory, nullptr);
+}
+
+void
+load_sampler(void *obj)
+{
+  auto self = static_cast<ImageBuilder*>(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(
+       BluCat::INT::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<ImageBuilder*>(obj);
+
+  vkDestroySampler(
+    BluCat::INT::core.vk_device_with_swapchain->device, self->texture->sampler,
+		nullptr);
+}
+
+void
+load_view(void *obj)
+{
+  auto self = static_cast<ImageBuilder*>(obj);
+
+  try
+  {
+    BluCat::GRA::Image::create_view(
+      BluCat::INT::core.vk_device_with_swapchain, &self->texture->view,
+      self->texture->image,
+      VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_ASPECT_COLOR_BIT);
+  }
+  catch(BluCat::GRA::Image::Error error)
+  {
+    throw CommandError{error.what()};
+  }
+}
+
+void
+unload_view(void *obj)
+{
+  auto self = static_cast<ImageBuilder*>(obj);
+
+  vkDestroyImageView(
+    BluCat::INT::core.vk_device_with_swapchain->device, self->texture->view,
+		nullptr);
+}
+
+const CommandChain image_loader{
+  {&load_image, &unload_image},
+  {&load_sampler, &unload_sampler},
+  {&load_view, &unload_view}
+};
+
+struct CharacterToDraw
+{
+  int pos_x;
+  std::shared_ptr<BluCat::GRA::Character> character;
+
+  CharacterToDraw(int x, std::shared_ptr<BluCat::GRA::Character> character):
+    pos_x{x},
+    character{character}
+    {};
+};
+
+struct TextTextureBuilder: public ImageBuilder
+{
+  BluCat::GRA::Font *font;
+  const char* str;
+  uint32_t max_bearing_y;
+  std::vector<CharacterToDraw> chars_to_draw;
+
+  TextTextureBuilder(BluCat::GRA::Texture *texture, BluCat::GRA::Font *font, const char* str):
+    font{font},
+    str{str}
+    {
+      this->texture = texture;
+    }
+};
+
+void
+load_text_proportions(void *obj)
+{
+  auto self = static_cast<TextTextureBuilder*>(obj);
+
+  uint32_t texture_width{0}, texture_descender{0};
+  auto unicode_text{BluCat::GRA::Character::str_to_unicode(self->str)};
+
+  auto first_image{self->font->character(unicode_text[0])};
+  if(first_image->bearing_x < 0) texture_width = - first_image->bearing_x;
+
+  self->max_bearing_y = 0;
+  self->chars_to_draw.reserve(unicode_text.size());
+
+  // FIXME: I need to test several different fonts to find all bugs in this
+  // code.
+  std::shared_ptr<BluCat::GRA::Character> char_image{};
+  { // Calculate image size
+    int max_height;
+    for(auto char_code : unicode_text)
+    {
+      char_image = self->font->character(char_code);
+      uint32_t descender{char_image->height - char_image->bearing_y};
+      uint32_t pos_x{texture_width + char_image->bearing_x};
+
+      if(char_image->image != VK_NULL_HANDLE)
+        self->chars_to_draw.emplace_back(pos_x, char_image);
+
+      if(char_image->bearing_y > self->max_bearing_y)
+        self->max_bearing_y = char_image->bearing_y;
+      if(descender > texture_descender) texture_descender = descender;
+
+      texture_width += char_image->advance;
+    }
+  }
+
+  { // Restore image width if last character have a negative bearing.
+    int bearing_x_pluss_width = char_image->bearing_x + char_image->width;
+    if(bearing_x_pluss_width > char_image->advance)
+      texture_width += bearing_x_pluss_width - char_image->advance;
+  }
+
+  self->texture->width = texture_width;
+  self->texture->height = self->max_bearing_y + texture_descender;
+  self->texture->mip_levels = 1;
+}
+
+void
+load_text_image(void *obj)
+{
+  auto self = static_cast<TextTextureBuilder*>(obj);
+
+  const int NumChannels = 4;
+
+  size_t image_size{static_cast<size_t>(
+      self->texture->width * self->texture->height * NumChannels)};
+  std::vector<unsigned char> pixels(image_size);
+  for(auto x{0}; x < self->texture->width; x++)
+  {
+    for(auto y{0}; y < self->texture->height; y++)
+    {
+      auto image_coord = y * self->font->face->glyph->bitmap.width +
+        x * NumChannels;
+      pixels[image_coord] = 0; // Red
+      pixels[image_coord + 1] = 0; // Green
+      pixels[image_coord + 2] = 0; // Blue
+      pixels[image_coord + 3] = 0; // Alpha
+    }
+  }
+  BluCat::GRA::SourceBuffer source_image_buffer{
+    BluCat::INT::core.vk_device_with_swapchain, pixels.data(), image_size};
+
+  { // Create vulkan image.
+    try
+    {
+      create_vulkan_image(
+        &self->texture->image,
+        &self->texture->device_memory,
+        self->texture->width,
+        self->texture->height,
+        self->texture->mip_levels);
+    }
+    catch(BluCat::GRA::Image::Error error)
+    {
+      throw CommandError{error.what()};
+    }
+  }
+
+  { // Render text
+    auto queue_family{
+      BluCat::INT::core.vk_device_with_swapchain->
+			get_queue_family_with_presentation()};
+    auto queue{queue_family->get_queue()};
+    BluCat::GRA::CommandPool command_pool{queue_family, 1};
+    VkCommandBuffer vk_command_buffer{command_pool.command_buffers[0]};
+
+    queue.submit_one_time_command(vk_command_buffer, [&](){
+      BluCat::GRA::Image::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);
+
+      for(auto &to_draw: self->chars_to_draw)
+      {
+        VkImageSubresourceLayers
+          source_subresources{}, destination_subresources{};
+        source_subresources.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+        source_subresources.mipLevel = 0;
+        source_subresources.baseArrayLayer = 0;
+        source_subresources.layerCount = 1;
+
+        destination_subresources.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+        destination_subresources.mipLevel = 0;
+        destination_subresources.baseArrayLayer = 0;
+        destination_subresources.layerCount = 1;
+
+        VkImageCopy image_copy{};
+        image_copy.srcSubresource = source_subresources;
+        image_copy.srcOffset = {0, 0, 0};
+        image_copy.dstSubresource = destination_subresources;
+        image_copy.dstOffset = {
+          to_draw.pos_x,
+          (int)(self->max_bearing_y - to_draw.character->bearing_y),
+          0};
+        image_copy.extent = {
+          to_draw.character->width, to_draw.character->height, 1};
+
+        vkCmdCopyImage(
+          vk_command_buffer,
+          to_draw.character->image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
+          self->texture->image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+          1, &image_copy);
+      }
+
+      BluCat::GRA::Image::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);
+    });
+  }
+}
+
+const CommandChain text_loader{
+  {&load_text_proportions, nullptr},
+  {&load_text_image, &unload_image},
+  {&load_sampler, &unload_sampler},
+  {&load_view, &unload_view}
+};
+
+void
+load_descriptor_set_pool(void *obj)
+{
+  auto self = static_cast<BluCat::GRA::Texture*>(obj);
+
+  std::array<VkDescriptorPoolSize, 1> descriptor_pool_sizes{};
+  descriptor_pool_sizes[0].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
+  descriptor_pool_sizes[0].descriptorCount =
+    BluCat::INT::core.vk_swapchain->images_count;
+
+  VkDescriptorPoolCreateInfo pool_info{};
+  pool_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
+  pool_info.pNext = nullptr;
+  pool_info.flags = 0;
+  pool_info.maxSets = BluCat::INT::core.vk_swapchain->images_count;
+  pool_info.poolSizeCount = descriptor_pool_sizes.size();
+  pool_info.pPoolSizes = descriptor_pool_sizes.data();
+
+  if(vkCreateDescriptorPool(
+       self->queue_family->device->device, &pool_info, nullptr,
+       &self->descriptor_pool) != VK_SUCCESS)
+    throw CommandError{"Failed to create a Vulkan descriptor pool."};
+}
+
+void
+unload_descriptor_set_pool(void *obj)
+{
+  auto self = static_cast<BluCat::GRA::Texture*>(obj);
+
+  vkDestroyDescriptorPool(
+    self->queue_family->device->device, self->descriptor_pool, nullptr);
+}
+
+void
+load_descriptor_sets(void *obj)
+{
+  auto self = static_cast<BluCat::GRA::Texture*>(obj);
+
+  std::vector<VkDescriptorSetLayout> layouts(
+    BluCat::INT::core.vk_swapchain->images_count,
+    BluCat::INT::core.vk_descriptor_set_layout->texture);
+
+  VkDescriptorSetAllocateInfo alloc_info{};
+  alloc_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
+  alloc_info.descriptorPool = self->descriptor_pool;
+  alloc_info.descriptorSetCount = layouts.size();
+  alloc_info.pSetLayouts = layouts.data();
+
+  self->descriptor_sets.resize(layouts.size());
+  if(vkAllocateDescriptorSets(
+       self->queue_family->device->device, &alloc_info,
+       self->descriptor_sets.data()) != VK_SUCCESS)
+    CommandError{"Failed to create Vulkan descriptor set."};
+}
+
+void
+load_data_to_descriptor_sets(void *obj)
+{
+  auto self = static_cast<BluCat::GRA::Texture*>(obj);
+
+  for(auto i{0}; i < BluCat::INT::core.vk_swapchain->images_count; i++)
+  {
+    VkDescriptorImageInfo image_info{};
+    image_info.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+    image_info.imageView = self->view;
+    image_info.sampler = self->sampler;
+
+    std::array<VkWriteDescriptorSet, 1> write_descriptors{};
+    write_descriptors[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+    write_descriptors[0].dstSet = self->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_COMBINED_IMAGE_SAMPLER;
+    write_descriptors[0].pBufferInfo = nullptr;
+    write_descriptors[0].pImageInfo = &image_info;
+    write_descriptors[0].pTexelBufferView = nullptr;
+
+    vkUpdateDescriptorSets(
+      BluCat::INT::core.vk_device_with_swapchain->device, write_descriptors.size(),
+      write_descriptors.data(), 0, nullptr);
+  }
+}
+
+const CommandChain descriptor_loader{
+  {&load_descriptor_set_pool, &unload_descriptor_set_pool},
+  {&load_descriptor_sets, nullptr},
+  {&load_data_to_descriptor_sets, nullptr}
+};
+
+}
+
+namespace BluCat::GRA
+{
+
+Texture::Texture(Font *font, const char* str)
+{
+  this->queue_family =
+    BluCat::INT::core.vk_device_with_swapchain->
+		get_queue_family_with_presentation();
+
+  TextTextureBuilder text_builder(this, font, str);
+  text_loader.execute(&text_builder);
+  descriptor_loader.execute(this);
+}
+
+Texture::Texture(const std::string &texture_path)
+{
+  this->queue_family =
+    BluCat::INT::core.vk_device_with_swapchain->
+		get_queue_family_with_presentation();
+
+  ImageTextureBuilder texture_builder(this, texture_path);
+  image_loader.execute(&texture_builder);
+  descriptor_loader.execute(this);
+}
+
+Texture::Texture(const char* texture_path):
+  Texture{std::string(texture_path)}
+{
+}
+
+Texture::~Texture()
+{
+  ImageTextureBuilder texture_builder(this, "");
+  image_loader.revert(&texture_builder);
+  descriptor_loader.revert(this);
+}
+
+}
diff --git a/src/blu_cat/gra/texture.hpp b/src/blu_cat/gra/texture.hpp
new file mode 100644
index 0000000..c3da8cf
--- /dev/null
+++ b/src/blu_cat/gra/texture.hpp
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2022-2024 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 BLU_CAT_GRA_TEXTURE_H
+#define BLU_CAT_GRA_TEXTURE_H 1
+
+#include <string>
+
+#include "vulkan.hpp"
+#include "font.hpp"
+#include "queue_family.hpp"
+
+namespace BluCat::GRA
+{
+
+struct Texture
+{
+  QueueFamily *queue_family;
+
+  VkImage image;
+  VkSampler sampler;
+  VkImageView view;
+  VkDeviceMemory device_memory;
+  uint32_t width, height;
+  uint32_t mip_levels;
+
+  VkDescriptorPool descriptor_pool;
+  std::vector<VkDescriptorSet> descriptor_sets;
+
+  Texture(Font *font, const char *str);
+  Texture(const std::string &texture_path);
+  Texture(const char* texture_path);
+  ~Texture();
+};
+
+}
+
+#endif /* BLU_CAT_GRA_TEXTURE_H */
diff --git a/src/blu_cat/gra/uniform_buffer.cpp b/src/blu_cat/gra/uniform_buffer.cpp
new file mode 100644
index 0000000..b176365
--- /dev/null
+++ b/src/blu_cat/gra/uniform_buffer.cpp
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2022-2024 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 BluCat::GRA
+{
+
+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/blu_cat/gra/uniform_buffer.hpp b/src/blu_cat/gra/uniform_buffer.hpp
new file mode 100644
index 0000000..f37e100
--- /dev/null
+++ b/src/blu_cat/gra/uniform_buffer.hpp
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2022-2024 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 BLU_CAT_GRA_UNIFORM_BUFFER_H
+#define BLU_CAT_GRA_UNIFORM_BUFFER_H 1
+
+#include <memory>
+
+#include "vulkan.hpp"
+
+#include "base_buffer.hpp"
+
+namespace BluCat::GRA
+{
+
+// 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 /* BLU_CAT_GRA_UNIFORM_BUFFER_H */
diff --git a/src/blu_cat/gra/uniform_data_object.hpp b/src/blu_cat/gra/uniform_data_object.hpp
new file mode 100644
index 0000000..68a3fbe
--- /dev/null
+++ b/src/blu_cat/gra/uniform_data_object.hpp
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2022-2024 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 BLU_CAT_GRA_UNIFORM_DATA_OBJECT_H
+#define BLU_CAT_GRA_UNIFORM_DATA_OBJECT_H 1
+
+#include "vulkan.hpp"
+#include "skeletal_mesh_vertex.hpp"
+
+namespace BluCat::GRA
+{
+
+// UDO = "uniform data object"
+
+struct UDOView2D
+{
+  glm::mat4 proj;
+};
+
+struct UDOView3D
+{
+  glm::mat4 view;
+  glm::mat4 proj;
+};
+
+struct UDOWorld3D_Vert
+{
+  glm::vec4 ambient_light_color;
+};
+
+struct UDOWorld3D_Frag
+{
+  glm::vec3 directional_light_direction;
+  glm::vec4 directional_light_color;
+};
+
+struct UDOStaticModel
+{
+  glm::mat4 base_matrix;
+};
+
+struct UDOSkeletalModel
+{
+  glm::mat4 base_matrix;
+  glm::mat4 bone_matrices[SKELETAL_MESH_MAX_NUM_OF_BONES];
+};
+
+struct UDOVector4D
+{
+  glm::vec4 vector;
+};
+
+struct UDOVector3D
+{
+  glm::vec3 vectors;
+};
+
+struct UDOSprite3D
+{
+  glm::vec3 position;
+  uint32_t padding;
+  glm::vec2 size;
+};
+
+}
+
+#endif /* BLU_CAT_GRA_UNIFORM_DATA_OBJECT_H */
diff --git a/src/blu_cat/gra/view_2d.cpp b/src/blu_cat/gra/view_2d.cpp
new file mode 100644
index 0000000..948b145
--- /dev/null
+++ b/src/blu_cat/gra/view_2d.cpp
@@ -0,0 +1,160 @@
+/*
+ * Copyright 2022-2024 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 "view_2d.hpp"
+
+#include <array>
+
+#include "../int/core.hpp"
+#include "uniform_data_object.hpp"
+
+namespace
+{
+
+void
+load_2d_uniform_buffer(void *obj)
+{
+  auto self = static_cast<BluCat::GRA::View2D*>(obj);
+
+  try
+  {
+    self->ub_2d.reserve(BluCat::INT::core.vk_swapchain->images_count);
+    for(auto i{0}; i < BluCat::INT::core.vk_swapchain->images_count; i++)
+      self->ub_2d.emplace_back(
+				BluCat::INT::core.vk_device_with_swapchain, sizeof(BluCat::GRA::UDOView2D));
+  }
+  catch(const std::exception& e)
+  {
+    throw CommandError{e.what()};
+  }
+}
+
+void
+unload_2d_uniform_buffer(void *obj)
+{
+  auto self = static_cast<BluCat::GRA::View2D*>(obj);
+
+  self->ub_2d.clear();
+}
+
+void
+load_descriptor_sets_2d(void *obj)
+{
+  auto self = static_cast<BluCat::GRA::View2D*>(obj);
+
+  std::vector<VkDescriptorSetLayout> layouts(
+    BluCat::INT::core.vk_swapchain->images_count,
+    BluCat::INT::core.vk_descriptor_set_layout->view);
+
+  VkDescriptorSetAllocateInfo alloc_info{};
+  alloc_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
+  alloc_info.descriptorPool = self->descriptor_pool;
+  alloc_info.descriptorSetCount = layouts.size();
+  alloc_info.pSetLayouts = layouts.data();
+
+  self->descriptor_sets_2d.resize(layouts.size());
+  if(vkAllocateDescriptorSets(
+       BluCat::INT::core.vk_device_with_swapchain->device, &alloc_info,
+       self->descriptor_sets_2d.data()) != VK_SUCCESS)
+    throw CommandError{"Failed to create Vulkan descriptor sets for view."};
+}
+
+void
+load_resources_to_descriptor_sets_2d(void *obj)
+{
+  auto self = static_cast<BluCat::GRA::View2D*>(obj);
+
+  for(auto i{0}; i < self->ub_2d.size(); i++)
+  {
+    VkDescriptorBufferInfo view_2d_info{};
+    view_2d_info.buffer = self->ub_2d[i].buffer;
+    view_2d_info.offset = 0;
+    view_2d_info.range = sizeof(BluCat::GRA::UDOView2D);
+
+    std::array<VkWriteDescriptorSet, 1> write_descriptors{};
+    write_descriptors[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+    write_descriptors[0].dstSet = self->descriptor_sets_2d[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 = &view_2d_info;
+    write_descriptors[0].pImageInfo = nullptr;
+    write_descriptors[0].pTexelBufferView = nullptr;
+
+    vkUpdateDescriptorSets(
+      BluCat::INT::core.vk_device_with_swapchain->device, write_descriptors.size(),
+      write_descriptors.data(), 0, nullptr);
+
+    BluCat::GRA::UDOView2D ubo_view_2d;
+    ubo_view_2d.proj = glm::ortho(
+      0.0f, self->projection_width,
+      0.0f, self->projection_height,
+      0.0f, 100.0f);
+    self->ub_2d[i].copy_data(&ubo_view_2d);
+  }
+}
+
+}
+
+namespace BluCat::GRA
+{
+
+const CommandChain View2D::loader{
+  {&load_2d_uniform_buffer, &unload_2d_uniform_buffer}
+};
+
+const CommandChain View2D::descriptor_sets_loader{
+  {&load_descriptor_sets_2d, nullptr},
+  {&load_resources_to_descriptor_sets_2d, nullptr}
+};
+
+View2D::View2D(
+  glm::vec4 region, float projection_width, float projection_height):
+  projection_width{projection_width},
+  projection_height{projection_height},
+  region{region},
+  descriptor_pool{VK_NULL_HANDLE},
+  rectangles_to_draw{BluCat::INT::core.vk_swapchain->images_count},
+  sprites_to_draw{BluCat::INT::core.vk_swapchain->images_count}
+{
+  loader.execute(this);
+}
+
+View2D::~View2D()
+{
+  loader.revert(this);
+}
+
+void
+View2D::load_descriptor_sets(VkDescriptorPool descriptor_pool)
+{
+  if(this->descriptor_pool != VK_NULL_HANDLE) return;
+
+  this->descriptor_pool = descriptor_pool;
+  descriptor_sets_loader.execute(this);
+}
+
+void
+View2D::unload_descriptor_sets()
+{
+  if(this->descriptor_pool == VK_NULL_HANDLE) return;
+
+  this->descriptor_pool = VK_NULL_HANDLE;
+  descriptor_sets_loader.revert(this);
+}
+
+}
diff --git a/src/blu_cat/gra/view_2d.hpp b/src/blu_cat/gra/view_2d.hpp
new file mode 100644
index 0000000..84b9f09
--- /dev/null
+++ b/src/blu_cat/gra/view_2d.hpp
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2022-2024 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 BLU_CAT_GRA_VIEW_2D_H
+#define BLU_CAT_GRA_VIEW_2D_H 1
+
+#include <memory>
+#include <unordered_map>
+#include <vector>
+
+#include "vulkan.hpp"
+#include "sprite_to_draw.hpp"
+#include "rectangle.hpp"
+
+namespace BluCat::GRA
+{
+
+struct View2D
+{
+  glm::vec4 region;
+  float projection_width, projection_height;
+
+  // FIXME: if these vectors get resized, they can cause a segmentation fault!
+  std::vector<UniformBuffer> ub_2d;
+
+  VkDescriptorPool descriptor_pool;
+  std::vector<VkDescriptorSet> descriptor_sets_2d;
+
+  std::vector<std::vector<Rectangle>> rectangles_to_draw;
+  std::vector<std::vector<SpriteToDraw>> sprites_to_draw;
+
+  View2D(glm::vec4 region, float projection_width, float projection_height);
+  virtual ~View2D();
+
+  void
+  virtual load_descriptor_sets(VkDescriptorPool descriptor_pool);
+
+  void
+  virtual unload_descriptor_sets();
+
+protected:
+  static const CommandChain loader, descriptor_sets_loader;
+};
+
+}
+
+#endif /* BLU_CAT_GRA_VIEW_2D_H */
diff --git a/src/blu_cat/gra/view_3d.cpp b/src/blu_cat/gra/view_3d.cpp
new file mode 100644
index 0000000..dc310e3
--- /dev/null
+++ b/src/blu_cat/gra/view_3d.cpp
@@ -0,0 +1,155 @@
+/*
+ * Copyright 2022-2024 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 "view_3d.hpp"
+
+#include <array>
+
+#include "../int/core.hpp"
+#include "uniform_data_object.hpp"
+
+namespace
+{
+
+void
+load_3d_uniform_buffer(void *obj)
+{
+  auto self = static_cast<BluCat::GRA::View3D*>(obj);
+
+  try
+  {
+    self->ub_3d.reserve(BluCat::INT::core.vk_swapchain->images_count);
+    for(auto i{0}; i < BluCat::INT::core.vk_swapchain->images_count; i++)
+      self->ub_3d.emplace_back(
+	BluCat::INT::core.vk_device_with_swapchain, sizeof(BluCat::GRA::UDOView3D));
+  }
+  catch(const std::exception& e)
+  {
+    throw CommandError{e.what()};
+  }
+}
+
+void
+unload_3d_uniform_buffer(void *obj)
+{
+  auto self = static_cast<BluCat::GRA::View3D*>(obj);
+
+  self->ub_3d.clear();
+}
+
+void
+load_descriptor_sets_3d(void *obj)
+{
+  auto self = static_cast<BluCat::GRA::View3D*>(obj);
+
+  std::vector<VkDescriptorSetLayout> layouts(
+    BluCat::INT::core.vk_swapchain->images_count,
+    BluCat::INT::core.vk_descriptor_set_layout->view);
+
+  VkDescriptorSetAllocateInfo alloc_info{};
+  alloc_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
+  alloc_info.descriptorPool = self->descriptor_pool;
+  alloc_info.descriptorSetCount = layouts.size();
+  alloc_info.pSetLayouts = layouts.data();
+
+  self->descriptor_sets_3d.resize(layouts.size());
+  if(vkAllocateDescriptorSets(
+       BluCat::INT::core.vk_device_with_swapchain->device, &alloc_info,
+       self->descriptor_sets_3d.data()) != VK_SUCCESS)
+    throw CommandError{"Failed to create Vulkan descriptor sets for view."};
+}
+
+void
+load_resources_to_descriptor_sets_3d(void *obj)
+{
+  auto self = static_cast<BluCat::GRA::View3D*>(obj);
+
+  for(auto i{0}; i < self->ub_3d.size(); i++)
+  {
+    VkDescriptorBufferInfo view_3d_info{};
+    view_3d_info.buffer = self->ub_3d[i].buffer;
+    view_3d_info.offset = 0;
+    view_3d_info.range = sizeof(BluCat::GRA::UDOView3D);
+
+    std::array<VkWriteDescriptorSet, 1> write_descriptors{};
+    write_descriptors[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+    write_descriptors[0].dstSet = self->descriptor_sets_3d[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 = &view_3d_info;
+    write_descriptors[0].pImageInfo = nullptr;
+    write_descriptors[0].pTexelBufferView = nullptr;
+
+    vkUpdateDescriptorSets(
+      BluCat::INT::core.vk_device_with_swapchain->device, write_descriptors.size(),
+      write_descriptors.data(), 0, nullptr);
+  }
+}
+
+const CommandChain loader{
+  {&load_3d_uniform_buffer, &unload_3d_uniform_buffer}
+};
+
+const CommandChain descriptor_sets_loader{
+  {&load_descriptor_sets_3d, nullptr},
+  {&load_resources_to_descriptor_sets_3d, nullptr}
+};
+
+}
+
+namespace BluCat::GRA
+{
+
+View3D::View3D(
+  glm::vec4 region, float projection_width, float projection_height):
+  View2D{region, projection_width, projection_height},
+  field_of_view{45.0f},
+  camera_position{std::make_shared<glm::vec3>(0.0f, 0.0f, 0.0f)},
+  camera_orientation{std::make_shared<glm::quat>(0.0f, 0.0f, 0.0f, 0.0f)}
+{
+  ::loader.execute(this);
+}
+
+View3D::~View3D()
+{
+  ::loader.revert(this);
+}
+
+void
+View3D::load_descriptor_sets(VkDescriptorPool descriptor_pool)
+{
+  if(this->descriptor_pool != VK_NULL_HANDLE) return;
+
+  auto parent = dynamic_cast<BluCat::GRA::View2D*>(this);
+  this->descriptor_pool = descriptor_pool;
+  View2D::descriptor_sets_loader.execute(parent);
+  ::descriptor_sets_loader.execute(this);
+}
+
+void
+View3D::unload_descriptor_sets()
+{
+  if(this->descriptor_pool == VK_NULL_HANDLE) return;
+
+  auto parent = dynamic_cast<BluCat::GRA::View2D*>(this);
+  this->descriptor_pool = VK_NULL_HANDLE;
+  ::descriptor_sets_loader.revert(this);
+  View2D::descriptor_sets_loader.revert(parent);
+}
+
+}
diff --git a/src/blu_cat/gra/view_3d.hpp b/src/blu_cat/gra/view_3d.hpp
new file mode 100644
index 0000000..f09c70f
--- /dev/null
+++ b/src/blu_cat/gra/view_3d.hpp
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2022-2024 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 BLU_CAT_GRA_VIEW_3D_H
+#define BLU_CAT_GRA_VIEW_3D_H 1
+
+#include "view_2d.hpp"
+
+namespace BluCat::GRA
+{
+
+struct View3D: public View2D
+{
+  float field_of_view;
+  // FIXME: if this vector get resized, it can cause a segmentation fault!
+  std::vector<UniformBuffer> ub_3d;
+
+  std::vector<VkDescriptorSet> descriptor_sets_3d;
+
+  std::shared_ptr<glm::vec3> camera_position;
+  std::shared_ptr<glm::quat> camera_orientation;
+
+  View3D(glm::vec4 region, float projection_width, float projection_height);
+  ~View3D();
+
+  void
+  load_descriptor_sets(VkDescriptorPool descriptor_pool);
+
+  void
+  unload_descriptor_sets();
+};
+
+}
+
+#endif /* BLU_CAT_GRA_VIEW_3D_H */
diff --git a/src/blu_cat/gra/vulkan.hpp b/src/blu_cat/gra/vulkan.hpp
new file mode 100644
index 0000000..fcf6628
--- /dev/null
+++ b/src/blu_cat/gra/vulkan.hpp
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2022-2024 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 BLU_CAT_GRA_VULKAN_H
+#define BLU_CAT_GRA_VULKAN_H 1
+
+// GLM uses some definitions to control their behavior, so you should not
+// include it directly. Instead, use this header.
+#define GLM_ENABLE_EXPERIMENTAL
+#define GLM_FORCE_RADIANS
+#define GLM_FORCE_DEPTH_ZERO_TO_ONE
+
+#include <glm/ext/vector_float3.hpp>
+#include <glm/ext.hpp>
+#include <glm/gtc/matrix_transform.hpp>
+#include <glm/gtx/quaternion.hpp>
+#include <glm/vec3.hpp>
+
+#include <vulkan/vulkan.h>
+
+#endif /* BLU_CAT_GRA_VULKAN_H */
diff --git a/src/blu_cat/int/core.cpp b/src/blu_cat/int/core.cpp
new file mode 100644
index 0000000..eafdab7
--- /dev/null
+++ b/src/blu_cat/int/core.cpp
@@ -0,0 +1,503 @@
+/*
+ * Copyright 2022-2024 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 "core.hpp"
+
+namespace
+{
+
+#ifdef DEBUG
+VKAPI_ATTR VkBool32 VKAPI_CALL
+vk_debug_callback(
+	VkDebugUtilsMessageSeverityFlagBitsEXT message_severity,
+	VkDebugUtilsMessageTypeFlagsEXT message_type,
+	const VkDebugUtilsMessengerCallbackDataEXT* callback_data,
+	void* _obj)
+{
+	// Set level.
+	Log::Level log_level;
+	switch(message_severity)
+	{
+	case VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT:
+		log_level = Log::Level::Trace;
+		break;
+	case VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT:
+		log_level = Log::Level::Information;
+		break;
+	case VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT:
+		log_level = Log::Level::Warning;
+		break;
+	case VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT:
+	default:
+		log_level = Log::Level::Error;
+		break;
+	}
+
+	// Log message.
+	BluCat::INT::core.log.message(log_level, callback_data->pMessage);
+
+	return VK_FALSE;
+}
+#endif
+
+void
+load_threads(void *obj)
+{
+	auto num_threads{std::thread::hardware_concurrency() - 1};
+	for(auto i{0}; i < num_threads; i++)
+		BluCat::INT::core.threads.emplace_back(
+			BluCat::INT::core.workers.emplace_back(&BluCat::INT::core.job_queue));
+}
+
+void
+unload_threads(void *obj)
+{
+	BluCat::INT::core.job_queue.stop();
+	for(auto &t: BluCat::INT::core.threads) t.join();
+}
+
+void
+load_fps(void *obj)
+{
+  using namespace std::chrono;
+
+	BluCat::INT::core.max_frame_duration =
+		duration<long long, std::milli>(1000 / BluCat::INT::core.fps);
+	// FIXME: actually calculates the real delta time.
+	BluCat::INT::core.delta_time = 1.0f / BluCat::INT::core.fps;
+}
+
+void
+load_font_library(void *obj)
+{
+	FT_Error error{FT_Init_FreeType(&BluCat::INT::core.font_library)};
+	if(error) throw CommandError{"Failed to open the FreeType library."};
+}
+
+void
+unload_font_library(void *obj)
+{
+	FT_Done_FreeType(BluCat::INT::core.font_library);
+}
+
+#ifdef DEBUG
+void
+load_vk_debug_callback(void *obj)
+{
+  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_blucat_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(BluCat::INT::core.vk_instance,
+			  "vkCreateDebugUtilsMessengerEXT");
+
+  if(debug_messenger(BluCat::INT::core.vk_instance, &create_info, nullptr,
+		     &BluCat::INT::core.vk_callback) != VK_SUCCESS)
+    CommandError{"Failed to setup debug callback for Vulkan."};
+}
+
+void
+unload_vk_debug_callback(void *obj)
+{
+  PFN_vkDestroyDebugUtilsMessengerEXT debug_messenger;
+
+  debug_messenger = (PFN_vkDestroyDebugUtilsMessengerEXT)
+    vkGetInstanceProcAddr(BluCat::INT::core.vk_instance,
+			  "vkDestroyDebugUtilsMessengerEXT");
+
+  debug_messenger(BluCat::INT::core.vk_instance, BluCat::INT::core.vk_callback, nullptr);
+}
+#endif
+
+void
+load_devices(void *obj)
+{
+  uint32_t devices_count;
+  std::vector<VkPhysicalDevice> vk_physical_devices;
+
+  // Enumerate physical devices
+  {
+    vkEnumeratePhysicalDevices(
+			BluCat::INT::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(
+      BluCat::INT::core.vk_instance, &devices_count, vk_physical_devices.data());
+  }
+
+#ifdef DEBUG
+  BluCat::INT::core.log.message(Log::Level::Trace, "Physical devices properties");
+#endif
+
+  BluCat::INT::core.vk_devices.reserve(devices_count);
+  for(auto i = 0; i < devices_count; i++)
+  {
+    // Use swapchain on first device.
+    if(i == 0)
+    {
+      BluCat::INT::core.vk_devices.emplace_back(vk_physical_devices[i], true);
+      BluCat::INT::core.vk_device_with_swapchain = &BluCat::INT::core.vk_devices[i];
+    }
+    else
+      BluCat::INT::core.vk_devices.emplace_back(vk_physical_devices[i], false);
+  }
+}
+
+void
+unload_devices(void *obj)
+{
+  BluCat::INT::core.vk_devices.clear();
+}
+
+static void
+load_swapchain(void *obj)
+{
+  try { BluCat::INT::core.vk_swapchain = new BluCat::GRA::Swapchain(); }
+  catch(const CommandError &error)
+  {
+    std::string error_message{"Failed to create swapchain → "};
+    error_message += error.what();
+    throw CommandError{error_message};
+  }
+}
+
+void
+unload_swapchain(void *obj)
+{
+  delete BluCat::INT::core.vk_swapchain;
+}
+
+void
+load_framebuffer(void *obj)
+{
+  try
+  {
+    BluCat::INT::core.vk_framebuffer = new BluCat::GRA::Framebuffer();
+  }
+  catch(const CommandError &e)
+  {
+    throw CommandError{"Failed to create framebuffer."};
+  }
+}
+
+void
+unload_framebuffer(void *obj)
+{
+  delete BluCat::INT::core.vk_framebuffer;
+}
+
+void
+load_render_pass(void *obj)
+{
+  try
+  {
+    BluCat::INT::core.vk_render_pass = new BluCat::GRA::RenderPass();
+  }
+  catch(const CommandError &e)
+  {
+    throw CommandError{"Failed to create render pass."};
+  }
+}
+
+void
+unload_render_pass(void *obj)
+{
+  delete BluCat::INT::core.vk_render_pass;
+}
+
+void
+load_descriptor_set_layout(void *obj)
+{
+  try
+  {
+    BluCat::INT::core.vk_descriptor_set_layout = new BluCat::GRA::DescriptorSetLayout();
+  }
+  catch(const CommandError &e)
+  {
+    throw CommandError{"Failed to create descriptor set layouts."};
+  }
+}
+
+void
+unload_descriptor_set_layout(void *obj)
+{
+  delete BluCat::INT::core.vk_descriptor_set_layout;
+}
+
+void
+load_graphics_pipeline_3d_layout(void *obj)
+{
+  try
+  {
+    BluCat::INT::core.vk_graphics_pipeline_3d_layout =
+      new BluCat::GRA::GraphicsPipeline3DLayout();
+  }
+  catch(const CommandError &e)
+  {
+    throw CommandError{"Failed to create 3d graphics pipeline."};
+  }
+}
+
+void
+unload_graphics_pipeline_3d_layout(void *obj)
+{
+  delete BluCat::INT::core.vk_graphics_pipeline_3d_layout;
+}
+
+void
+load_graphics_pipeline_2d_solid_layout(void *obj)
+{
+  try
+  {
+    BluCat::INT::core.vk_graphics_pipeline_2d_solid_layout =
+      new BluCat::GRA::GraphicsPipeline2DSolidLayout();
+  }
+  catch(const CommandError &e)
+  {
+    throw CommandError{"Failed to create 2d graphics pipeline."};
+  }
+}
+
+void
+unload_graphics_pipeline_2d_solid_layout(void *obj)
+{
+  delete BluCat::INT::core.vk_graphics_pipeline_2d_solid_layout;
+}
+
+void
+load_graphics_pipeline_2d_wired_layout(void *obj)
+{
+  try
+  {
+    BluCat::INT::core.vk_graphics_pipeline_2d_wired_layout =
+      new BluCat::GRA::GraphicsPipeline2DWiredLayout();
+  }
+  catch(const CommandError &e)
+  {
+    throw CommandError{"Failed to create 2d graphics pipeline."};
+  }
+}
+
+void
+unload_graphics_pipeline_2d_wired_layout(void *obj)
+{
+  delete BluCat::INT::core.vk_graphics_pipeline_2d_wired_layout;
+}
+
+void
+load_light(void *obj)
+{
+  try
+  {
+    BluCat::INT::core.vk_light = new BluCat::GRA::Light();
+  }
+  catch(const CommandError &e)
+  {
+    throw CommandError{"Failed to descriptor sets for light."};
+  }
+}
+
+void
+unload_light(void *obj)
+{
+  delete BluCat::INT::core.vk_light;
+}
+
+void
+load_graphics_pipeline_3d(void *obj)
+{
+  try
+  {
+    BluCat::INT::core.vk_graphics_pipeline_3d =
+      std::make_unique<BluCat::GRA::GraphicsPipeline3D>();
+  }
+  catch(const CommandError &e)
+  {
+    throw CommandError{"Failed to create 3d graphics pipeline."};
+  }
+}
+
+void
+unload_graphics_pipeline_3d(void *obj)
+{
+  BluCat::INT::core.vk_graphics_pipeline_3d = nullptr;
+}
+
+void
+load_graphics_pipeline_3d_skeletal(void *obj)
+{
+  try
+  {
+    BluCat::INT::core.vk_graphics_pipeline_3d_skeletal =
+      std::make_unique<BluCat::GRA::GraphicsPipeline3DSkeletal>();
+  }
+  catch(const CommandError &e)
+  {
+    throw CommandError{"Failed to create 3d skeletal graphics pipeline."};
+  }
+}
+
+void
+unload_graphics_pipeline_3d_skeletal(void *obj)
+{
+  BluCat::INT::core.vk_graphics_pipeline_3d_skeletal = nullptr;
+}
+
+void
+load_graphics_pipeline_sprite_3d(void *obj)
+{
+  try
+  {
+    BluCat::INT::core.vk_graphics_pipeline_sprite_3d =
+      std::make_unique<BluCat::GRA::GraphicsPipelineSprite3D>();
+  }
+  catch(const CommandError &e)
+  {
+    throw CommandError{"Failed to create sprite 3d graphics pipeline."};
+  }
+}
+
+void
+unload_graphics_pipeline_sprite_3d(void *obj)
+{
+  BluCat::INT::core.vk_graphics_pipeline_sprite_3d = nullptr;
+}
+
+void
+load_graphics_pipeline_2d_solid(void *obj)
+{
+  try
+  {
+    BluCat::INT::core.vk_graphics_pipeline_2d_solid =
+      std::make_unique<BluCat::GRA::GraphicsPipeline2DSolid>();
+  }
+  catch(const CommandError &e)
+  {
+    throw CommandError{"Failed to create 2d graphics pipeline."};
+  }
+}
+
+void
+unload_graphics_pipeline_2d_solid(void *obj)
+{
+  BluCat::INT::core.vk_graphics_pipeline_2d_solid = nullptr;
+}
+
+void
+load_graphics_pipeline_2d_wired(void *obj)
+{
+  try
+  {
+    BluCat::INT::core.vk_graphics_pipeline_2d_wired =
+      std::make_unique<BluCat::GRA::GraphicsPipeline2DWired>();
+  }
+  catch(const CommandError &e)
+  {
+    throw CommandError{"Failed to create 2d graphics pipeline."};
+  }
+}
+
+void
+unload_graphics_pipeline_2d_wired(void *obj)
+{
+  BluCat::INT::core.vk_graphics_pipeline_2d_wired = nullptr;
+}
+
+void
+load_renderer(void *obj)
+{
+  try
+  {
+    glm::vec4 region(
+      0.f, 0.f,
+      static_cast<float>(BluCat::INT::core.display_width),
+      static_cast<float>(BluCat::INT::core.display_height));
+		BluCat::INT::core.vk_renderer = new BluCat::GRA::Renderer(
+      {},
+      {std::make_shared<BluCat::GRA::View3D>(region, region.z, region.w)});
+  }
+  catch(const CommandError &e)
+  {
+    throw CommandError{"Failed to create renderer."};
+  }
+}
+
+void
+unload_renderer(void *obj)
+{
+  delete BluCat::INT::core.vk_renderer;
+}
+
+}
+
+namespace BluCat::INT
+{
+
+std::random_device random_seed;
+std::mt19937 random_number_generator;
+
+const CommandChain Core::loader{
+  {&load_threads, &unload_threads},
+  {&load_fps, nullptr},
+  {&load_font_library, &unload_font_library},
+#ifdef DEBUG
+  {&load_vk_debug_callback, &unload_vk_debug_callback},
+#endif
+  {&load_devices, &unload_devices},
+  {&load_swapchain, &unload_swapchain},
+
+  {&load_render_pass, &unload_render_pass},
+  {&load_framebuffer, &unload_framebuffer},
+  {&load_descriptor_set_layout, &unload_descriptor_set_layout},
+  {&load_graphics_pipeline_3d_layout,
+   &unload_graphics_pipeline_3d_layout},
+  {&load_graphics_pipeline_2d_solid_layout,
+   &unload_graphics_pipeline_2d_solid_layout},
+  {&load_graphics_pipeline_2d_wired_layout,
+   &unload_graphics_pipeline_2d_wired_layout},
+  {&load_light, &unload_light},
+	{&load_graphics_pipeline_3d_skeletal,
+	 &unload_graphics_pipeline_3d_skeletal},
+  {&load_graphics_pipeline_3d, &unload_graphics_pipeline_3d},
+  {&load_graphics_pipeline_sprite_3d,
+   &unload_graphics_pipeline_sprite_3d},
+  {&load_graphics_pipeline_2d_solid, &unload_graphics_pipeline_2d_solid},
+  {&load_graphics_pipeline_2d_wired, &unload_graphics_pipeline_2d_wired},
+  {&load_renderer, &unload_renderer},
+};
+
+Core core;
+
+}
diff --git a/src/blu_cat/int/core.hpp b/src/blu_cat/int/core.hpp
new file mode 100644
index 0000000..0900b81
--- /dev/null
+++ b/src/blu_cat/int/core.hpp
@@ -0,0 +1,123 @@
+/*
+ * Copyright 2022-2024 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 BLU_CAT_INT_CORE_H
+#define BLU_CAT_INT_CORE_H 1
+
+#define BLU_CAT_VERSION_MAJOR 0
+#define BLU_CAT_VERSION_MINOR 1
+#define BLU_CAT_VERSION_PATCH 0
+
+#include <chrono>
+#include <cstdint>
+#include <memory>
+#include <random>
+
+#include "../com/command.hpp"
+#include "../com/job_queue.hpp"
+#include "../com/worker.hpp"
+#include "../gra/device.hpp"
+#include "../gra/descriptor_set_layout.hpp"
+#include "../gra/framebuffer.hpp"
+#include "../gra/graphics_pipeline_2d_solid_layout.hpp"
+#include "../gra/graphics_pipeline_2d_wired_layout.hpp"
+#include "../gra/graphics_pipeline_2d_solid.hpp"
+#include "../gra/graphics_pipeline_2d_wired.hpp"
+#include "../gra/graphics_pipeline_3d_layout.hpp"
+#include "../gra/graphics_pipeline_3d.hpp"
+#include "../gra/graphics_pipeline_3d_skeletal.hpp"
+#include "../gra/graphics_pipeline_sprite_3d.hpp"
+#include "../gra/light.hpp"
+#include "../gra/log.hpp"
+#include "../gra/render_pass.hpp"
+#include "../gra/renderer.hpp"
+#include "../gra/swapchain.hpp"
+#include "../gra/vulkan.hpp"
+
+namespace BluCat::INT
+{
+
+extern std::random_device random_seed;
+extern std::mt19937 random_number_generator;
+
+struct Core
+{
+  static const CommandChain loader;
+
+  Log::Logger log;
+
+  COM::JobQueue job_queue;
+  std::vector<COM::Worker> workers;
+  std::vector<std::thread> threads;
+
+  /// Text displayed in the game window.
+  std::string game_name;
+
+  /**
+   * @{
+   * This is the ammount of pixel that the games uses when rendering to the
+   * screen.
+   */
+  uint32_t display_width, display_height;
+  /// @}
+
+  int game_version_major, game_version_minor, game_version_patch;
+
+  uint32_t fps;
+  std::chrono::duration<long long, std::milli> max_frame_duration;
+  float delta_time;
+
+  FT_Library font_library;
+
+  VkSurfaceKHR window_surface;
+  VkInstance vk_instance;
+
+#ifdef DEBUG
+  VkDebugUtilsMessengerEXT vk_callback;
+#endif
+
+  // Vulkan devices.
+  std::vector<BluCat::GRA::Device> vk_devices;
+  BluCat::GRA::Device *vk_device_with_swapchain;
+  BluCat::GRA::Swapchain *vk_swapchain;
+
+  BluCat::GRA::Framebuffer *vk_framebuffer;
+  BluCat::GRA::RenderPass *vk_render_pass;
+  BluCat::GRA::DescriptorSetLayout *vk_descriptor_set_layout;
+  BluCat::GRA::GraphicsPipeline3DLayout *vk_graphics_pipeline_3d_layout;
+  BluCat::GRA::GraphicsPipeline2DSolidLayout
+	*vk_graphics_pipeline_2d_solid_layout;
+  BluCat::GRA::GraphicsPipeline2DWiredLayout
+	*vk_graphics_pipeline_2d_wired_layout;
+  BluCat::GRA::Light *vk_light;
+  std::unique_ptr<BluCat::GRA::GraphicsPipeline3D> vk_graphics_pipeline_3d;
+  std::unique_ptr<BluCat::GRA::GraphicsPipeline3DSkeletal>
+  vk_graphics_pipeline_3d_skeletal;
+  std::unique_ptr<BluCat::GRA::GraphicsPipelineSprite3D>
+	vk_graphics_pipeline_sprite_3d;
+  std::unique_ptr<BluCat::GRA::GraphicsPipeline2DSolid>
+	vk_graphics_pipeline_2d_solid;
+  std::unique_ptr<BluCat::GRA::GraphicsPipeline2DWired>
+	vk_graphics_pipeline_2d_wired;
+
+  BluCat::GRA::Renderer *vk_renderer;
+};
+
+extern Core core;
+
+}
+
+#endif /* BLU_CAT_INT_CORE_H */
-- 
cgit v1.2.3