From 8cb751ff015271e1844feece5db2113eaec64af3 Mon Sep 17 00:00:00 2001
From: Frederico Linhares <fred@linhares.blue>
Date: Fri, 23 Jun 2023 20:45:23 -0300
Subject: feat Create an interface to read binary files

* test/meshes/cube.cgmesh: Store data using Big-Endian order.
---
 src/binary_reader.cpp | 90 +++++++++++++++++++++++++++++++++++++++++++++++++++
 src/binary_reader.hpp | 54 +++++++++++++++++++++++++++++++
 src/vk/mesh.cpp       | 45 ++++++--------------------
 src/vk/qoi.cpp        | 85 +++++++++++++-----------------------------------
 4 files changed, 175 insertions(+), 99 deletions(-)
 create mode 100644 src/binary_reader.cpp
 create mode 100644 src/binary_reader.hpp

(limited to 'src')

diff --git a/src/binary_reader.cpp b/src/binary_reader.cpp
new file mode 100644
index 0000000..5847328
--- /dev/null
+++ b/src/binary_reader.cpp
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2022-2023 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;
+};
+
+}
+
+BinaryReader::BinaryReader(std::string file_path):
+  _pointer{0}
+{
+  std::ifstream file(file_path, std::ios::binary | std::ios::ate);
+  if(!file.is_open()) throw std::runtime_error{"failed to open file"};
+
+  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;
+}
+
+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};
+}
+
+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/binary_reader.hpp b/src/binary_reader.hpp
new file mode 100644
index 0000000..b2e4de5
--- /dev/null
+++ b/src/binary_reader.hpp
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2022-2023 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 "vk/core.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();
+
+  glm::vec2
+  read_vec2();
+
+  glm::vec3
+  read_vec3();
+
+  void
+  read_chars(char *str, int size);
+};
diff --git a/src/vk/mesh.cpp b/src/vk/mesh.cpp
index 70b9d45..9afeac3 100644
--- a/src/vk/mesh.cpp
+++ b/src/vk/mesh.cpp
@@ -1,5 +1,5 @@
 /*
- * Copyright 2022 Frederico de Oliveira Linhares
+ * Copyright 2022-2023 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.
@@ -16,9 +16,7 @@
 
 #include "mesh.hpp"
 
-#include <array>
-#include <fstream>
-
+#include "../binary_reader.hpp"
 #include "../command.hpp"
 #include "../core.hpp"
 #include "vertex_3d.hpp"
@@ -48,51 +46,26 @@ MeshBuilder::MeshBuilder(VK::Mesh *m, const char *mp):
 {
 }
 
-uint32_t read_uint32_from_file(std::ifstream &input_file)
-{
-  uint32_t data{};
-  input_file.read((char*)&data, sizeof(uint32_t));
-  return data;
-}
-
-glm::vec2 read_vec2_from_file(std::ifstream &input_file)
-{
-  glm::vec2 data{};
-  input_file.read((char*)&data.x, sizeof(glm::vec2::value_type));
-  input_file.read((char*)&data.y, sizeof(glm::vec2::value_type));
-  return data;
-}
-
-glm::vec3 read_vec3_from_file(std::ifstream &input_file)
-{
-  glm::vec3 data{};
-  input_file.read((char*)&data.x, sizeof(glm::vec3::value_type));
-  input_file.read((char*)&data.y, sizeof(glm::vec3::value_type));
-  input_file.read((char*)&data.z, sizeof(glm::vec3::value_type));
-  return data;
-}
-
 void
 load_mesh(void *obj)
 {
   auto self = static_cast<MeshBuilder*>(obj);
 
-  std::ifstream input_file{self->mesh_path};
-  if(!input_file.is_open()) throw CommandError{"Failed to open file."};
+  BinaryReader input{self->mesh_path};
 
   self->mesh->queue_family =
     cg_core.vk_device_with_swapchain->get_queue_family_with_graphics();
 
   // Load vertexes.
   {
-    auto vertex_count{read_uint32_from_file(input_file)};
+    auto vertex_count{input.read_ui32()};
     std::vector<VK::Vertex3D> vertexes{vertex_count};
 
     for(auto i{0}; i < vertex_count; i++)
     {
-      vertexes[i].position = read_vec3_from_file(input_file);
-      vertexes[i].normal = read_vec3_from_file(input_file);
-      vertexes[i].texture_coord = read_vec2_from_file(input_file);
+      vertexes[i].position = input.read_vec3();
+      vertexes[i].normal = input.read_vec3();
+      vertexes[i].texture_coord = input.read_vec2();
     }
 
     void *vertexes_data{vertexes.data()};
@@ -106,11 +79,11 @@ load_mesh(void *obj)
 
   // Load indexes.
   {
-    self->mesh->index_count = read_uint32_from_file(input_file);
+    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] = read_uint32_from_file(input_file);
+      indexes[i] = input.read_ui32();
 
     void *indexes_data{indexes.data()};
     size_t indexes_size{sizeof(indexes[0]) * indexes.size()};
diff --git a/src/vk/qoi.cpp b/src/vk/qoi.cpp
index 8b7ef9e..4e2b2a4 100644
--- a/src/vk/qoi.cpp
+++ b/src/vk/qoi.cpp
@@ -19,6 +19,8 @@
 #include <array>
 #include <fstream>
 
+#include "../binary_reader.hpp"
+
 namespace
 {
 
@@ -88,26 +90,6 @@ color_hash(const RGBA &colors)
   return colors.red*3 + colors.green*5 + colors.blue*7 + colors.alpha*11;
 }
 
-void
-read_chars(uint8_t *data, int &p, char *str, int size)
-{
-  for(int i{0}; i < size; i++) str[i] = (char)data[p++];
-}
-
-void
-read_ui8(uint8_t *data, int &p, uint8_t &num)
-{
-  num = data[p++];
-}
-
-void
-read_ui32(uint8_t *data, int &p, uint32_t &num)
-{
-  uint8_t b1{data[p++]}, b2{data[p++]}, b3{data[p++]}, b4{data[p++]};
-
-  num = b1 << 24 | b2 << 16 | b3 << 8 | b4;
-}
-
 }
 
 namespace VK::QOI
@@ -116,38 +98,22 @@ namespace VK::QOI
 Image::Image(const char *file_path, uint8_t channels):
   channels{channels}
 {
-  int data_p{0};
-  int data_size;
-  uint8_t *data;
-
   if(this->channels != 0 && this->channels != 3 && this->channels != 4)
   {
     throw std::invalid_argument{"invalid number of channels"};
   }
 
-  { // Read data from file.
-    std::ifstream file(file_path, std::ios::binary | std::ios::ate);
-    if(!file)
-    {
-      throw std::runtime_error{"failed to open QOI file"};
-    }
-
-    data_size = file.tellg();
-    if(data_size < HEADER_SIZE + (int)sizeof(PADDING))
-    {
-      throw std::runtime_error{"invalid QOI file"};
-    }
-
-    file.seekg(0);
-    data = new uint8_t[data_size];
-    file.read((char*)data, data_size);
+  BinaryReader input{file_path};
+  if(input.size() < HEADER_SIZE + (int)sizeof(PADDING))
+  {
+    throw std::runtime_error{"invalid QOI file"};
   }
 
-  read_chars(data, data_p, this->header.magic, 4);
-  read_ui32(data, data_p, this->header.width);
-  read_ui32(data, data_p, this->header.height);
-  read_ui8(data, data_p, this->header.channels);
-  read_ui8(data, data_p, this->header.colorspace);
+  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 ||
@@ -165,7 +131,7 @@ Image::Image(const char *file_path, uint8_t channels):
 
   std::array<Pixel, 64> index;
   Pixel pixel{};
-  int chunks_len = data_size - (int)sizeof(PADDING);
+  int chunks_len = input.size() - (int)sizeof(PADDING);
 
   /*
     This algorithm is based on the original implementation that is in GitHub:
@@ -181,25 +147,22 @@ Image::Image(const char *file_path, uint8_t channels):
     {
       run--;
     }
-    else if(data_p < chunks_len)
+    else if(input.pointer() < chunks_len)
     {
-      uint8_t output;
-      int b1;
-      read_ui8(data, data_p, output);
-      b1 = output;
+      int b1 = input.read_ui8();
 
       if (b1 == OP_RGB)
       {
-        read_ui8(data, data_p, pixel.colors.red);
-        read_ui8(data, data_p, pixel.colors.green);
-        read_ui8(data, data_p, pixel.colors.blue);
+        pixel.colors.red = input.read_ui8();
+        pixel.colors.green = input.read_ui8();
+        pixel.colors.blue = input.read_ui8();
       }
       else if (b1 == OP_RGBA)
       {
-        read_ui8(data, data_p, pixel.colors.red);
-        read_ui8(data, data_p, pixel.colors.green);
-        read_ui8(data, data_p, pixel.colors.blue);
-        read_ui8(data, data_p, pixel.colors.alpha);
+        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)
       {
@@ -213,9 +176,7 @@ Image::Image(const char *file_path, uint8_t channels):
       }
       else if ((b1 & MASK_2_BITS) == OP_LUMA)
       {
-        int b2;
-        read_ui8(data, data_p, output);
-        b2 = output;
+        int b2 = input.read_ui8();
         int vg = (b1 & 0x3f) - 32;
         pixel.colors.red += vg - 8 + ((b2 >> 4) & 0x0f);
         pixel.colors.green += vg;
@@ -234,8 +195,6 @@ Image::Image(const char *file_path, uint8_t channels):
     this->pixels[pixel_p++] = pixel.colors.blue;
     if(this->channels == 4) this->pixels[pixel_p++] = pixel.colors.alpha;
   }
-
-  delete[] data;
 }
 
 Image::~Image()
-- 
cgit v1.2.3