diff options
Diffstat (limited to 'src/blu_cat')
111 files changed, 12089 insertions, 0 deletions
| diff --git a/src/blu_cat/com/binary_reader.cpp b/src/blu_cat/com/binary_reader.cpp new file mode 100644 index 0000000..d572ec9 --- /dev/null +++ b/src/blu_cat/com/binary_reader.cpp @@ -0,0 +1,183 @@ +/* + * Copyright 2022-2025 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 <bit> +#include <fstream> + +namespace +{ + +union IntAndFloat32bit{ +  UI32 i; +  F32 f; +}; + +union IntAndFloat64bit{ +  UI64 i; +  F64 f; +}; + +} + +BinaryReader::BinaryReader(const 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 UI8[this->_size]; +  file.read((char*)data, this->_size); +} + +BinaryReader::BinaryReader(const char *file_path): +  BinaryReader{std::string(file_path)} +{ +} + +BinaryReader::~BinaryReader() +{ +  delete[] this->data; +} + +UI8 +BinaryReader::read_ui8() +{ +  return this->data[this->_pointer++]; +} + +UI32 +BinaryReader::read_ui16() +{ +  UI8 b1{this->data[_pointer++]}, b2{this->data[_pointer++]}; + +  return b1 << 8 | b2; +} + +UI32 +BinaryReader::read_ui32() +{ +  UI8 b1{this->data[_pointer++]}, b2{this->data[_pointer++]}, +    b3{this->data[_pointer++]}, b4{this->data[_pointer++]}; + +  return b1 << 24 | b2 << 16 | b3 << 8 | b4; +} + +UI64 +BinaryReader::read_ui64() +{ +  UI8 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 (UI64)b1 << 56 | (UI64)b2 << 48 | (UI64)b3 << 40 | +    (UI64)b4 << 32 | (UI64)b5 << 24 | (UI64)b6 << 16 | +    (UI64)b7 << 8 | (UI64)b8; +} + +I8 +BinaryReader::read_i8() +{ +	return std::bit_cast<I8>(this->read_ui8()); +} + +I32 +BinaryReader::read_i32() +{ +	return std::bit_cast<I32>(this->read_ui32()); +} + +I64 +BinaryReader::read_i64() +{ +	return std::bit_cast<I64>(this->read_ui64()); +} + +F32 +BinaryReader::read_f32() +{ +  IntAndFloat32bit num; +  num.i = read_ui32(); + +  return num.f; +} + +F64 +BinaryReader::read_f64() +{ +  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/com/binary_reader.hpp b/src/blu_cat/com/binary_reader.hpp new file mode 100644 index 0000000..7b7309c --- /dev/null +++ b/src/blu_cat/com/binary_reader.hpp @@ -0,0 +1,85 @@ +/* + * Copyright 2022-2025 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_BINARY_READER_H +#define BLU_CAT_COM_BINARY_READER_H 1 + +#include <string> + +#include "numbers.hpp" + +class BinaryReader +{ +  UI64F _pointer; +  UI64F _size; +  UI8 *data; + +public: + +  BinaryReader(const std::string &file_path); +  BinaryReader(const char *file_path); +  ~BinaryReader(); + +  inline UI64F +  pointer(){return this->_pointer;}; + +  inline UI64F +  size(){return this->_size;}; + +  UI8 +  read_ui8(); + +  UI32 +  read_ui16(); + +  UI32 +  read_ui32(); + +  UI64 +  read_ui64(); + +  I8 +  read_i8(); + +  I32 +  read_i32(); + +  I64 +  read_i64(); + +  F32 +  read_f32(); + +  F64 +  read_f64(); + +  glm::vec2 +  read_vec2(); + +  glm::vec3 +  read_vec3(); + +  glm::quat +  read_quat(); + +  glm::mat4 +  read_mat4(); + +  void +  read_chars(char *str, int size); +}; + +#endif /* BLU_CAT_COM_BINARY_READER_H */ diff --git a/src/blu_cat/com/binary_writer.cpp b/src/blu_cat/com/binary_writer.cpp new file mode 100644 index 0000000..fe06195 --- /dev/null +++ b/src/blu_cat/com/binary_writer.cpp @@ -0,0 +1,180 @@ +/* + * Copyright 2022-2025 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_writer.hpp" + +namespace +{ + +union IntAndFloat32bit{ +  UI32 i; +  F32 f; +}; + +union IntAndFloat64bit{ +  UI64 i; +  F64 f; +}; + +} + +BinaryWriter::BinaryWriter(const char *file_path): +	output{file_path, std::ios::binary} +{ +} + +BinaryWriter::BinaryWriter(const std::string &file_path): +	BinaryWriter{file_path.c_str()} +{ +} + +void +BinaryWriter::write_ui8(UI8 var) +{ +	this->output.put(var); +} + +void +BinaryWriter::write_ui32(UI32 var) +{ +  using namespace std; + +  UI8 b1 = var >> 24; +  UI8 b2 = var >> 16; +  UI8 b3 = var >> 8; +  UI8 b4 = var; + +  this->output.put(b1); +  this->output.put(b2); +  this->output.put(b3); +  this->output.put(b4); +} + +void +BinaryWriter::write_ui64(UI64 var) +{ +  using namespace std; + +  UI8 b1 = var >> 56; +  UI8 b2 = var >> 48; +  UI8 b3 = var >> 40; +  UI8 b4 = var >> 32; +  UI8 b5 = var >> 24; +  UI8 b6 = var >> 16; +  UI8 b7 = var >> 8; +  UI8 b8 = var; + +  this->output.put(b1); +  this->output.put(b2); +  this->output.put(b3); +  this->output.put(b4); +  this->output.put(b5); +  this->output.put(b6); +  this->output.put(b7); +  this->output.put(b8); +} + +void +BinaryWriter::write_i8(I8 var) +{ +	this->write_ui8(std::bit_cast<UI8>(var)); +} + +void +BinaryWriter::write_i32(I32 var) +{ +	this->write_ui32(std::bit_cast<UI32>(var)); +} + +void +BinaryWriter::write_i64(I64 var) +{ +	this->write_ui64(std::bit_cast<UI64>(var)); +} + +void +BinaryWriter::write_f32(F32 var) +{ +  IntAndFloat32bit num; +  num.f = var; +  write_ui32(num.i); +} + +void +BinaryWriter::write_f64(F64 var) +{ +  IntAndFloat64bit num; +  num.f = var; +  write_ui64(num.i); +} + +void +BinaryWriter::write_vec2(glm::vec2 var) +{ +  IntAndFloat32bit x, y; +  x.f = var.x; +  y.f = var.y; + +  this->write_ui32(x.i); +  this->write_ui32(y.i); +} + +void +BinaryWriter::write_vec3(glm::vec3 var) +{ +  IntAndFloat32bit x, y, z; +  x.f = var.x; +  y.f = var.y; +  z.f = var.z; + +  this->write_ui32(x.i); +  this->write_ui32(y.i); +  this->write_ui32(z.i); +} + +void +BinaryWriter::write_quat(glm::quat var) +{ +  IntAndFloat32bit w, x, y, z; +  w.f = var.w; +  x.f = var.x; +  y.f = var.y; +  z.f = var.z; + +  this->write_ui32(w.i); +  this->write_ui32(x.i); +  this->write_ui32(y.i); +  this->write_ui32(z.i); +} + +void +BinaryWriter::write_mat4(glm::mat4 var) +{ +  float *offset_matrix_data{glm::value_ptr(var)}; +  IntAndFloat32bit num; + +  for(int i{0}; i < 16; i++) +  { +    num.f = offset_matrix_data[i]; +    this->write_ui32(num.i); +  } +} + +void +BinaryWriter::write_chars(const char *str, int size) +{ +  for(int i{0}; i < size; i++) this->write_ui8((UI8)str[i]); +} diff --git a/src/blu_cat/com/binary_writer.hpp b/src/blu_cat/com/binary_writer.hpp new file mode 100644 index 0000000..76bccb3 --- /dev/null +++ b/src/blu_cat/com/binary_writer.hpp @@ -0,0 +1,74 @@ +/* + * Copyright 2022-2025 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_BINARY_WRITER_H +#define BLU_CAT_COM_BINARY_WRITER_H 1 + +#include <fstream> +#include <string> + +#include "numbers.hpp" + +class BinaryWriter +{ +  std::ofstream output; + +public: + +  BinaryWriter(const std::string &file_path); +	BinaryWriter(const char *file_path); + +  void +  write_ui8(UI8 var); + +  void +  write_ui32(UI32 var); + +  void +  write_ui64(UI64 var); + +  void +  write_i8(I8 var); + +  void +  write_i32(I32 var); + +  void +  write_i64(I64 var); + +  void +  write_f32(F32 var); + +  void +  write_f64(F64 var); + +  void +  write_vec2(glm::vec2 var); + +  void +  write_vec3(glm::vec3 var); + +  void +  write_quat(glm::quat var); + +  void +  write_mat4(glm::mat4 var); + +	void +	write_chars(const char *str, int size); +}; + +#endif /* BLU_CAT_COM_BINARY_WRITER_H */ 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/numbers.hpp b/src/blu_cat/com/numbers.hpp new file mode 100644 index 0000000..6f0ca47 --- /dev/null +++ b/src/blu_cat/com/numbers.hpp @@ -0,0 +1,67 @@ +/* + * Copyright 2022-2025 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_NUMBERS_H +#define BLU_CAT_COM_NUMBERS_H 1 + +#include <cstdint> +#include <stdfloat> + +// 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> + +// Signed int +typedef int8_t I8; +typedef int16_t I16; +typedef int32_t I32; +typedef int64_t I64; + +// Unsigned int +typedef uint8_t UI8; +typedef uint16_t UI16; +typedef uint32_t UI32; +typedef uint64_t UI64; + +// Fast signed int +typedef int_fast8_t I8F; +typedef int_fast16_t I16F; +typedef int_fast32_t I32F; +typedef int_fast64_t I64F; + +// Fast unsigned int +typedef uint_fast8_t UI8F; +typedef uint_fast16_t UI16F; +typedef uint_fast32_t UI32F; +typedef uint_fast64_t UI64F; + +// Floats +typedef std::float32_t F32; +typedef std::float64_t F64; + +constexpr UI32F SIZE_16_BIT{2}; +constexpr UI32F SIZE_32_BIT{4}; +constexpr UI32F SIZE_64_BIT{8}; + +#endif /* BLU_CAT_COM_NUMBERS_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..062fb48 --- /dev/null +++ b/src/blu_cat/gra/animation.cpp @@ -0,0 +1,28 @@ +/* + * Copyright 2022-2025 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, UI16 parent): +  offset_matrix{offset_matrix}, +	parent{parent} +{ +} + +} diff --git a/src/blu_cat/gra/animation.hpp b/src/blu_cat/gra/animation.hpp new file mode 100644 index 0000000..11b1d0a --- /dev/null +++ b/src/blu_cat/gra/animation.hpp @@ -0,0 +1,52 @@ +/* + * Copyright 2022-2025 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; +	UI16 parent; + +  Bone(glm::mat4 offset_matrix, UI16 parent); +}; + +struct BoneTransform +{ +  uint32_t bone_id; +  Channel<glm::quat> rotations; +  Channel<glm::vec3> positions; +  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/bitmap_character.cpp b/src/blu_cat/gra/bitmap_character.cpp new file mode 100644 index 0000000..d5352a0 --- /dev/null +++ b/src/blu_cat/gra/bitmap_character.cpp @@ -0,0 +1,36 @@ +/* + * Copyright 2022-2025 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 "bitmap_character.hpp" + +namespace BluCat::GRA +{ + +BitmapCharacter::BitmapCharacter( +	F32 x, F32 y, F32 width, F32 height, +	std::shared_ptr<BluCat::GRA::Texture> texture): +	width{width}, +	height{height}, +	sprite{std::make_shared<Sprite>( +			texture, glm::vec4( +				x / texture->width, +				y / texture->height, +				(x + width)  / texture->width, +				(y + height)  / texture->height))} +{ +} + +} diff --git a/src/blu_cat/gra/bitmap_character.hpp b/src/blu_cat/gra/bitmap_character.hpp new file mode 100644 index 0000000..31f88ed --- /dev/null +++ b/src/blu_cat/gra/bitmap_character.hpp @@ -0,0 +1,40 @@ +/* + * Copyright 2022-2025 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_BITMAP_CHARACTER_H +#define BLU_CAT_GRA_BITMAP_CHARACTER_H 1 + +#include <memory> +#include <vector> + +#include "../com/numbers.hpp" +#include "sprite.hpp" + +namespace BluCat::GRA +{ + +struct BitmapCharacter +{ +	F32 width, height; +	std::shared_ptr<BluCat::GRA::Sprite> sprite; + +	BitmapCharacter(F32 x, F32 y, F32 width, F32 height, +									std::shared_ptr<BluCat::GRA::Texture> texture); +}; + +} + +#endif /* BLU_CAT_GRA_BITMAP_CHARACTER_H */ diff --git a/src/blu_cat/gra/bitmap_font.cpp b/src/blu_cat/gra/bitmap_font.cpp new file mode 100644 index 0000000..0cafc2a --- /dev/null +++ b/src/blu_cat/gra/bitmap_font.cpp @@ -0,0 +1,47 @@ +/* + * Copyright 2022-2025 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 "bitmap_font.hpp" + +#include <format> + +namespace BluCat::GRA +{ + +void +BitmapFont::add_letters( +	const char *texture_path, std::span<NewBitmapCharacter> characters) +{ +	std::shared_ptr<BluCat::GRA::Texture> texture{ +		std::make_shared<BluCat::GRA::Texture>(texture_path)}; +	this->textures.push_back(texture); + +	for(NewBitmapCharacter &ch: characters) +		this->characters.insert({ +				ch.code, BitmapCharacter( +					ch.x, ch.y, ch.width, ch.height, texture)}); +} + +BitmapFont::BitmapFont( +	I32F width, I32F height, I32F white_space_width, I32F letter_spacing): +	width{width}, +	height{height}, +	white_space_width{white_space_width}, +	letter_spacing{letter_spacing} +	{ +	} + +} diff --git a/src/blu_cat/gra/bitmap_font.hpp b/src/blu_cat/gra/bitmap_font.hpp new file mode 100644 index 0000000..7918354 --- /dev/null +++ b/src/blu_cat/gra/bitmap_font.hpp @@ -0,0 +1,52 @@ +/* + * Copyright 2022-2025 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_BITMAP_FONT_H +#define BLU_CAT_GRA_BITMAP_FONT_H 1 + +#include <memory> +#include <unordered_map> +#include <vector> + +#include "bitmap_character.hpp" +#include "texture.hpp" + +namespace BluCat::GRA +{ + +struct NewBitmapCharacter +{ +	const UI32F code; +	const F32 x, y, width, height; +}; + +struct BitmapFont +{ +	const I32F width, height, white_space_width, letter_spacing; +	std::vector<std::shared_ptr<BluCat::GRA::Texture>> textures; +	std::unordered_map<UI32F, BitmapCharacter> characters; + +	void +	add_letters( +		const char *texture_path, std::span<NewBitmapCharacter> characters); + +	BitmapFont( +		I32F width, I32F height, I32F white_space_width, I32F letter_spacing); +}; + +} + +#endif /* BLU_CAT_GRA_BITMAP_FONT_H */ 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, +    ©_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..0d72fc9 --- /dev/null +++ b/src/blu_cat/gra/graphics_pipeline_2d_solid.cpp @@ -0,0 +1,281 @@ +/* + * Copyright 2022-2025 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( +  const VkCommandBuffer draw_command_buffer, const size_t current_frame, +	const size_t next_frame, const uint32_t image_index) +{ +  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(BluCat::INT::core.vk_renderer->sprites_to_draw[ +							current_frame].begin(), +            BluCat::INT::core.vk_renderer->sprites_to_draw[ +							current_frame].end()); + +  // Draw sprites +  for(auto& sprite_to_draw: +				BluCat::INT::core.vk_renderer->sprites_to_draw[current_frame]) +  { +    std::array<VkDescriptorSet, 2> vk_descriptor_sets{ +      BluCat::INT::core.vk_renderer->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. +  BluCat::INT::core.vk_renderer->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..c2d85d5 --- /dev/null +++ b/src/blu_cat/gra/graphics_pipeline_2d_solid.hpp @@ -0,0 +1,42 @@ +/* + * Copyright 2022-2025 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" + +namespace BluCat::GRA +{ + +struct GraphicsPipeline2DSolid +{ +  VkPipeline graphic_pipeline; + +  GraphicsPipeline2DSolid(); +  ~GraphicsPipeline2DSolid(); + +  void +  draw(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..209dafd --- /dev/null +++ b/src/blu_cat/gra/graphics_pipeline_2d_wired.cpp @@ -0,0 +1,297 @@ +/* + * Copyright 2022-2025 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( +  const VkCommandBuffer draw_command_buffer, const size_t current_frame, +	const size_t next_frame, const uint32_t image_index) +{ + +  // Draw rectangles +  { +    std::array<VkDescriptorSet, 1> vk_descriptor_sets{ +      BluCat::INT::core.vk_renderer->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 < BluCat::INT::core.vk_renderer->rectangles_to_draw[ +					current_frame].size(); i++) +    { +      auto &rect{BluCat::INT::core.vk_renderer->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. +  BluCat::INT::core.vk_renderer->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..9fc46c4 --- /dev/null +++ b/src/blu_cat/gra/graphics_pipeline_2d_wired.hpp @@ -0,0 +1,47 @@ +/* + * Copyright 2022-2025 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 "command_pool.hpp" +#include "destination_buffer.hpp" + +namespace BluCat::GRA +{ + +struct GraphicsPipeline2DWired +{ +  QueueFamily *queue_family; + +  VkPipeline graphic_pipeline; + +  DestinationBuffer *index_buffer; + +  GraphicsPipeline2DWired(); +  ~GraphicsPipeline2DWired(); + +  void +  draw(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..288e186 --- /dev/null +++ b/src/blu_cat/gra/graphics_pipeline_3d.cpp @@ -0,0 +1,308 @@ +/* + * Copyright 2022-2025 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<View> 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[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..2e30e02 --- /dev/null +++ b/src/blu_cat/gra/graphics_pipeline_3d.hpp @@ -0,0 +1,43 @@ +/* + * Copyright 2022-2025 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.hpp" + +namespace BluCat::GRA +{ + +struct GraphicsPipeline3D +{ +  VkPipeline graphic_pipeline; + +  GraphicsPipeline3D(); +  ~GraphicsPipeline3D(); + +  void +  draw(std::shared_ptr<View> 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..79a85f1 --- /dev/null +++ b/src/blu_cat/gra/graphics_pipeline_3d_skeletal.cpp @@ -0,0 +1,322 @@ +/* + * Copyright 2022-2025 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<View> 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[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..16d65b0 --- /dev/null +++ b/src/blu_cat/gra/graphics_pipeline_3d_skeletal.hpp @@ -0,0 +1,43 @@ +/* + * Copyright 2022-2025 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.hpp" + +namespace BluCat::GRA +{ + +struct GraphicsPipeline3DSkeletal +{ +  VkPipeline graphic_pipeline; + +  GraphicsPipeline3DSkeletal(); +  ~GraphicsPipeline3DSkeletal(); + +  void +  draw(std::shared_ptr<View> 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..63e10b9 --- /dev/null +++ b/src/blu_cat/gra/graphics_pipeline_sprite_3d.cpp @@ -0,0 +1,315 @@ +/* + * Copyright 2022-2025 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<View> 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[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..d03b8e8 --- /dev/null +++ b/src/blu_cat/gra/graphics_pipeline_sprite_3d.hpp @@ -0,0 +1,44 @@ +/* + * Copyright 2022-2025 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.hpp" + +namespace BluCat::GRA +{ + +struct GraphicsPipelineSprite3D +{ +  VkPipeline graphic_pipeline; + +  GraphicsPipelineSprite3D(); +  ~GraphicsPipelineSprite3D(); + +  void +  draw(std::shared_ptr<View> 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..40968df --- /dev/null +++ b/src/blu_cat/gra/qoi.cpp @@ -0,0 +1,205 @@ +/* + * Copyright 2022-2025 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 "../com/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..eb11899 --- /dev/null +++ b/src/blu_cat/gra/renderer.cpp @@ -0,0 +1,521 @@ +/* + * Copyright 2022-2025 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_2d_uniform_buffer(void *obj) +{ +  auto self = static_cast<BluCat::GRA::Renderer*>(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::Renderer*>(obj); + +  self->ub_2d.clear(); +} + +void +load_descriptor_pool(void *obj) +{ +  auto self = static_cast<BluCat::GRA::Renderer*>(obj); + +  uint32_t uniform_buffer_count = self->ub_2d.size(); +  for(auto &view : self->views) uniform_buffer_count += view->ub_3d.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) +    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) view->unload_descriptor_sets(); + +  vkDestroyDescriptorPool( +    BluCat::INT::core.vk_device_with_swapchain->device, self->descriptor_pool, +    nullptr); +} + +void +load_descriptor_sets_2d(void *obj) +{ +  auto self = static_cast<BluCat::GRA::Renderer*>(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::Renderer*>(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); +  } +} + +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_2d_uniform_buffer, &unload_2d_uniform_buffer}, +  {&load_descriptor_pool, &unload_descriptor_pool}, +	{&load_descriptor_sets_2d, nullptr}, +	{&load_resources_to_descriptor_sets_2d, nullptr}, +  {&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<View>> views, F32 width, F32 height): +  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}, +	rectangles_to_draw{BluCat::INT::core.vk_swapchain->images_count}, +	sprites_to_draw{BluCat::INT::core.vk_swapchain->images_count}, +  projection_width{width}, +	projection_height{height}, +	clear_screen_color{0.0f, 0.0f, 0.0f, 1.0f}, +  views{views} +{ +  loader.execute(this); +} + +Renderer::Renderer( +	std::initializer_list<std::shared_ptr<View>> views, +	F32 width, F32 height): +  Renderer(std::vector(views), width, height) +{ +} + +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. +    { +      std::array<VkClearValue, 2> clear_values{}; +      clear_values[0].color = this->clear_screen_color; +      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) +		{ +			{ // 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); +    } + +		{ // Set viewport +			VkViewport vk_viewport{}; +			vk_viewport.x = 0; +			vk_viewport.y = 0; +			vk_viewport.width = static_cast<float>(BluCat::INT::core.display_width); +			vk_viewport.height = static_cast<float>(BluCat::INT::core.display_height); +			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 = 0; +			vk_scissor.offset.y = 0; +			vk_scissor.extent.width = BluCat::INT::core.display_width; +			vk_scissor.extent.height = BluCat::INT::core.display_height; +			vkCmdSetScissor(draw_command_buffer, 0, 1, &vk_scissor); +		} + +   { // 2D solid drawing +			BluCat::INT::core.vk_graphics_pipeline_2d_solid->draw( +				draw_command_buffer, BluCat::INT::core.vk_swapchain->current_frame, +				next_frame, image_index); +    } + +    { // 2D wired drawing +			BluCat::INT::core.vk_graphics_pipeline_2d_wired->draw( +				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(); +			this->rectangles_to_draw[next_frame].clear(); +			this->sprites_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(); +		this->sprites_to_draw[ +			BluCat::INT::core.vk_swapchain->current_frame].clear(); +		this->rectangles_to_draw[ +			BluCat::INT::core.vk_swapchain->current_frame].clear(); +		this->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..58b9972 --- /dev/null +++ b/src/blu_cat/gra/renderer.hpp @@ -0,0 +1,86 @@ +/* + * Copyright 2022-2025 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 "queue_family.hpp" +#include "rectangle.hpp" +#include "skeletal_mesh.hpp" +#include "skeletal_model.hpp" +#include "sprite_3d.hpp" +#include "sprite_to_draw.hpp" +#include "static_mesh.hpp" +#include "static_model.hpp" +#include "view.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; +	VkClearColorValue clear_screen_color; + +  float projection_width, projection_height; + +  // FIXME: if these vectors get resized, they can cause a segmentation fault! +  std::vector<UniformBuffer> ub_2d; +  std::vector<VkDescriptorSet> descriptor_sets_2d; + +  std::vector<std::vector<Rectangle>> rectangles_to_draw; +  std::vector<std::vector<SpriteToDraw>> sprites_to_draw; + +  std::vector<std::shared_ptr<View>> views; + +  QueueFamily *queue_family; +  VkCommandPool command_pool; +  std::vector<VkCommandBuffer> draw_command_buffers; + +  Renderer(std::vector<std::shared_ptr<View>> views, F32 width, F32 height); +  Renderer(std::initializer_list<std::shared_ptr<View>> views, +					 F32 width, F32 height); +  ~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..8e2e39f --- /dev/null +++ b/src/blu_cat/gra/skeletal_mesh.cpp @@ -0,0 +1,256 @@ +/* + * Copyright 2022-2025 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 "../com/binary_reader.hpp" +#include "../com/command.hpp" +#include "../int/core.hpp" +#include "skeletal_mesh_vertex.hpp" + +namespace +{ + +struct StaticMeshHeader +{ +	char file_magic[8]; +	UI32 version; +	UI32 num_vertexes; +	UI32 num_indexes; +	UI32 num_bones; +	UI32 num_animations; +	UI32 num_bone_transforms; // For all animations +	UI32 num_bone_positions; // For all animations +	UI32 num_bone_rotations; // For all animations +	UI32 num_bone_scales; // For all animations +}; + +constexpr UI64F SKELETAL_MESH_HEADER_SIZE{44}; + +// 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); + +	StaticMeshHeader header; +  BinaryReader input{self->mesh_path}; + +	{ // Read header +		input.read_chars(header.file_magic, 8); +		header.version = input.read_ui32(); +		header.num_vertexes = input.read_ui32(); +		header.num_indexes = input.read_ui32(); +		header.num_bones = input.read_ui32(); +		header.num_animations = input.read_ui32(); +		header.num_bone_transforms = input.read_ui32(); +		header.num_bone_positions = input.read_ui32(); +		header.num_bone_rotations = input.read_ui32(); +		header.num_bone_scales = input.read_ui32(); + +		size_t total_file_size{ +			SKELETAL_MESH_HEADER_SIZE + +			header.num_vertexes * 16 * SIZE_32_BIT + +			header.num_indexes * SIZE_32_BIT + +			header.num_bones * (16 * SIZE_32_BIT + SIZE_16_BIT) + +			header.num_animations * (2 * SIZE_64_BIT + SIZE_32_BIT) + +			header.num_bone_transforms * 4 * SIZE_32_BIT + +			header.num_bone_positions * (3 * SIZE_32_BIT + SIZE_64_BIT) + +			header.num_bone_rotations * (4 * SIZE_32_BIT + SIZE_64_BIT) + +			header.num_bone_scales * (3 * SIZE_32_BIT + SIZE_64_BIT)}; + +		if(total_file_size != input.size()) +		{ +			std::string error{"File size does not match header information: "}; +			error += self->mesh_path; +			throw CommandError{error}; +		} +	} + +  self->mesh->queue_family = +    BluCat::INT::core.vk_device_with_swapchain-> +		get_queue_family_with_graphics(); + +  { // Load vertexes. +    std::vector<BluCat::GRA::SkeletalMeshVertex> vertexes{header.num_vertexes}; + +    for(auto i{0}; i < header.num_vertexes; 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_f32(); +    } + +    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 = header.num_indexes; +    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 +    self->mesh->bones.reserve(header.num_bones); +    for(int i{0}; i < header.num_bones; i++) +		{ +			auto matrix{input.read_mat4()}; +			auto parent{input.read_ui16()}; +      self->mesh->bones.emplace_back(matrix, parent); +		} +  } + +  { // Load animations +    self->mesh->animations.resize(header.num_animations); +    for(uint32_t i{0}; i < header.num_animations; i++) +    { +      auto duration{input.read_f64()}; +      self->mesh->animations[i].final_time = (float)duration; + +      auto ticks_per_second{input.read_f64()}; + +      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_f64()}; +					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_f64()}; +					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_f64()}; +					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..726b97e --- /dev/null +++ b/src/blu_cat/gra/skeletal_model.cpp @@ -0,0 +1,248 @@ +/* + * Copyright 2022-2025 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 ¤t_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(UI16 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); +					})}; + +		auto parent{this->skeletal_mesh->bones[i].parent}; +		auto node_transform{(this->skeletal_mesh->bones[i].offset_matrix * +												 (position * rotation * scale))}; + +		if(parent == i) +			this->bone_transforms[i] = node_transform; +		else +			this->bone_transforms[i] = +				this->bone_transforms[parent] * node_transform; +  } +} + +} 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 ▭ + +  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..0b6088c --- /dev/null +++ b/src/blu_cat/gra/static_mesh.cpp @@ -0,0 +1,162 @@ +/* + * Copyright 2022-2025 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 "../com/binary_reader.hpp" +#include "../com/command.hpp" +#include "../int/core.hpp" +#include "static_mesh_vertex.hpp" + +namespace +{ + +struct StaticMeshHeader +{ +	char file_magic[8]; +	UI32 version; +	UI32 num_vertexes; +	UI32 num_indexes; +}; + +constexpr UI64F STATIC_MESH_HEADER_SIZE{20}; + +// 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); + +  self->mesh->queue_family = +    BluCat::INT::core.vk_device_with_swapchain-> +		get_queue_family_with_graphics(); + +	StaticMeshHeader header; +  BinaryReader input{self->mesh_path}; + +	{ // Read header +		input.read_chars(header.file_magic, 8); +		header.version = input.read_ui32(); +		header.num_vertexes = input.read_ui32(); +		header.num_indexes = input.read_ui32(); + +		size_t total_file_size{ +			STATIC_MESH_HEADER_SIZE + +			header.num_vertexes * 8 * SIZE_32_BIT + +			header.num_indexes * SIZE_32_BIT}; + +		if(total_file_size != input.size()) +		{ +			std::string error{"File size does not match header information: "}; +			error += self->mesh_path; +			throw CommandError{error}; +		} +	} + +  { // Load vertexes. +    std::vector<BluCat::GRA::StaticMeshVertex> vertexes{header.num_vertexes}; + +    for(auto i{0}; i < header.num_vertexes; 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 = header.num_indexes; +    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/text.cpp b/src/blu_cat/gra/text.cpp new file mode 100644 index 0000000..42341a3 --- /dev/null +++ b/src/blu_cat/gra/text.cpp @@ -0,0 +1,90 @@ +/* + * Copyright 2022-2025 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 "text.hpp" + +#include "../int/core.hpp" + +namespace BluCat::GRA +{ + +Text::Text( +	const BitmapFont &font, const char *str, const I32F x, const I32F y, +	F32 z_index): +	x{x}, +	y{y}, +	width{0}, +	height{0}, +	z_index{z_index} +{ +	const std::vector<UI32> text{BluCat::GRA::Character::str_to_unicode(str)}; +	this->letters.reserve(text.size()); + +	I32F current_column{0}; +	I32F current_line{0}; + +  for(I32F index{0}; index < text.size(); index++) +	{ +		switch(str[index]) +		{ +		case 10: +			if((current_column + 1) * font.width > this->width) +				this->width = (current_column + 1) * font.width; +			current_column = 0; +			current_line++; +			continue; + +		case 32: +			current_column++; +			continue; +		} + +		glm::vec4 rect{ +			x + current_column * font.width, +			y + current_line * font.height, +			x + (current_column + 1) * font.width, +			y + (current_line + 1) * font.height}; + +		this->letters.emplace_back(font.characters.at(text[index]).sprite, rect); +		current_column++; +	} + +	if((current_column + 1) * font.width > this->width) +		this->width = (current_column + 1) * font.width; +	this->height = font.height * (current_line + 1); +} + +Text::Text(): +	letters(0), +	x{0}, +	y{0}, +	width{0}, +	height{0}, +	z_index{4.5f} +{ +} + +void +Text::draw() +{ +	auto &sprites_to_draw = BluCat::INT::core.vk_renderer->sprites_to_draw[ +		BluCat::INT::core.vk_swapchain->current_frame]; + +	for(Letter &l: this->letters) +		sprites_to_draw.emplace_back(l.sprite, l.position, this->z_index); +} + +} diff --git a/src/blu_cat/gra/text.hpp b/src/blu_cat/gra/text.hpp new file mode 100644 index 0000000..5b7dcde --- /dev/null +++ b/src/blu_cat/gra/text.hpp @@ -0,0 +1,47 @@ +/* + * Copyright 2022-2025 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_TEXT_H +#define BLU_CAT_GRA_TEXT_H 1 + +#include "bitmap_font.hpp" + +namespace BluCat::GRA +{ + +struct Letter +{ +	std::shared_ptr<Sprite> sprite; +	glm::vec4 position; +}; + +struct Text +{ +	std::vector<Letter> letters; +	I32F x, y, width, height; +	F32 z_index; + +	Text(const BitmapFont &font, const char *str, const I32F x, const I32F y, +		F32 z_index); +	Text(); + +	void +	draw(); +}; + +} + +#endif /* BLU_CAT_GRA_TEXT_H */ diff --git a/src/blu_cat/gra/texture.cpp b/src/blu_cat/gra/texture.cpp new file mode 100644 index 0000000..7546d56 --- /dev/null +++ b/src/blu_cat/gra/texture.cpp @@ -0,0 +1,566 @@ +/* + * Copyright 2022-2025 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_NEAREST; +  sampler_info.minFilter = VK_FILTER_NEAREST; +  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.cpp b/src/blu_cat/gra/view.cpp new file mode 100644 index 0000000..26017ce --- /dev/null +++ b/src/blu_cat/gra/view.cpp @@ -0,0 +1,158 @@ +/* + * Copyright 2022-2025 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.hpp" + +#include <array> + +#include "../int/core.hpp" +#include "uniform_data_object.hpp" + +#include <iostream> + +namespace +{ + +void +load_uniform_buffer(void *obj) +{ +  auto self = static_cast<BluCat::GRA::View*>(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_uniform_buffer(void *obj) +{ +  auto self = static_cast<BluCat::GRA::View*>(obj); + +  self->ub_3d.clear(); +} + +void +load_descriptor_sets(void *obj) +{ +  auto self = static_cast<BluCat::GRA::View*>(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.resize(layouts.size()); +  if(vkAllocateDescriptorSets( +       BluCat::INT::core.vk_device_with_swapchain->device, &alloc_info, +       self->descriptor_sets.data()) != VK_SUCCESS) +    throw CommandError{"Failed to create Vulkan descriptor sets for view."}; +} + +void +load_resources_to_descriptor_sets(void *obj) +{ +  auto self = static_cast<BluCat::GRA::View*>(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[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_uniform_buffer, &unload_uniform_buffer} +}; + +const CommandChain descriptor_sets_loader{ +  {&load_descriptor_sets, nullptr}, +  {&load_resources_to_descriptor_sets, nullptr} +}; + +} + +namespace BluCat::GRA +{ + +View::View( +	glm::vec4 region, float projection_width, +	float projection_height): +  region{region}, +	projection_width{projection_width}, +	projection_height{projection_height}, +  field_of_view{45.0f}, +	descriptor_pool{VK_NULL_HANDLE}, +  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); +} + +View::~View() +{ +  loader.revert(this); +} + +void +View::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 +View::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.hpp b/src/blu_cat/gra/view.hpp new file mode 100644 index 0000000..a61ac34 --- /dev/null +++ b/src/blu_cat/gra/view.hpp @@ -0,0 +1,55 @@ +/* + * Copyright 2022-2025 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_H +#define BLU_CAT_GRA_VIEW_H 1 + +#include <memory> +#include <unordered_map> +#include <vector> + +#include "vulkan.hpp" +#include "uniform_buffer.hpp" + +namespace BluCat::GRA +{ + +struct View +{ +	glm::vec4 region; +  float field_of_view, projection_width, projection_height; + +  // FIXME: if this vector get resized, it can cause a segmentation fault! +  std::vector<UniformBuffer> ub_3d; + +  VkDescriptorPool descriptor_pool; +  std::vector<VkDescriptorSet> descriptor_sets; + +  std::shared_ptr<glm::vec3> camera_position; +  std::shared_ptr<glm::quat> camera_orientation; + +  void +  load_descriptor_sets(VkDescriptorPool descriptor_pool); +  void +  unload_descriptor_sets(); + +  View(glm::vec4 region, float projection_width, float projection_height); +  ~View(); +}; + +} + +#endif /* BLU_CAT_GRA_VIEW_H */ diff --git a/src/blu_cat/gra/vulkan.hpp b/src/blu_cat/gra/vulkan.hpp new file mode 100644 index 0000000..73c599f --- /dev/null +++ b/src/blu_cat/gra/vulkan.hpp @@ -0,0 +1,24 @@ +/* + * Copyright 2022-2025 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 + +#include "../com/numbers.hpp" + +#include <vulkan/vulkan.h> + +#endif /* BLU_CAT_GRA_VULKAN_H */ diff --git a/src/blu_cat/int/controller.hpp b/src/blu_cat/int/controller.hpp new file mode 100644 index 0000000..deedde3 --- /dev/null +++ b/src/blu_cat/int/controller.hpp @@ -0,0 +1,50 @@ +/* + * Copyright 2022-2025 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_CONTROLLER_H +#define BLU_CAT_INT_CONTROLLER_H 1 + +#include <SDL3/SDL.h> + +namespace BluCat::INT +{ + +struct Controller +{ +  virtual void +  key_down(SDL_Keycode keycode){}; +  virtual void +  key_up(SDL_Keycode keycode){}; + +  virtual void +  mouse_button_down(SDL_MouseButtonEvent& e){}; +  virtual void +  mouse_button_up(SDL_MouseButtonEvent& e){}; +  virtual void +  mouse_motion(int x, int y, int xrel, int yrel){}; + +	virtual void +	tick(){}; +	virtual void +	render(){}; + +	virtual +	~Controller(){}; +}; + +} + +#endif /* BLU_CAT_INT_CONTROLLER_H */ diff --git a/src/blu_cat/int/core.cpp b/src/blu_cat/int/core.cpp new file mode 100644 index 0000000..6f67b98 --- /dev/null +++ b/src/blu_cat/int/core.cpp @@ -0,0 +1,799 @@ +/* + * Copyright 2022-2025 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_sdl(void *obj) +{ +	if(!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS)) +	{ +		std::string error{"SDL could not initialize! SDL Error → "}; +		error += SDL_GetError(); +		throw CommandError{error}; +	} + +	if(!SDL_Vulkan_LoadLibrary(nullptr)) +	{ +		SDL_Quit(); +		std::string error{"SDL could not initialize Vulkan! SDL_Error → "}; +		error += SDL_GetError(); +		throw CommandError{error}; +	} +} + +void +unload_sdl(void *obj) +{ +	SDL_Vulkan_UnloadLibrary(); +	SDL_Quit(); +} + +void +load_window(void *obj) +{ +	BluCat::INT::core.window = SDL_CreateWindow( +		BluCat::INT::core.game_name.c_str(), +		BluCat::INT::core.display_width, BluCat::INT::core.display_height, +		SDL_WINDOW_VULKAN); +	if(BluCat::INT::core.window == NULL) +	{ +		std::string error{"Window could not be created! SDL_Error → "}; +		error += SDL_GetError(); +		throw CommandError{error}; +	} +} + +void +unload_window(void *obj) +{ +	SDL_DestroyWindow(BluCat::INT::core.window); +} + +void +load_vk_instance(void *obj) +{ +	std::vector<const char*> vk_extensions; +	std::vector<const char*> vk_required_layers_names; + +	// Get extensions. +	{ +		uint32_t vk_extensions_count; +		std::vector<const char*> vk_required_extensions; + +		// Load debuging layers. +#ifdef DEBUG +		vk_required_extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); +		vk_required_layers_names.push_back("VK_LAYER_KHRONOS_validation"); +#endif + +		// Get extensions for SDL. +		{ +			uint32_t vk_sdl_extension_count; +			std::vector<const char*> vk_sdl_extensions; + +			const char * const *instance_extensions = +				SDL_Vulkan_GetInstanceExtensions(&vk_sdl_extension_count); + +			if(instance_extensions == NULL) +			{ +				std::string error{ +					"Vulkan extensions could not be loaded by SDL! SDL_Error: "}; +				error += SDL_GetError(); +				throw CommandError{error}; +			} + +			for(auto i{0}; i < vk_sdl_extension_count; i++) +				vk_sdl_extensions.push_back(instance_extensions[i]); + +			// Combine all extensions. +			vk_extensions_count = vk_sdl_extension_count + +				vk_required_extensions.size(); +			vk_extensions.resize(vk_extensions_count); +			for(auto i{0}; i < vk_sdl_extension_count; i++) +				vk_extensions[i] = vk_sdl_extensions[i]; +			for(auto i{0}; i < vk_required_extensions.size(); i++) +				vk_extensions[i + vk_sdl_extension_count] = vk_required_extensions[i]; +		} + +#ifdef DEBUG +		BluCat::INT::core.log.message(Log::Level::Trace, "Enabled VK extensions."); +		for(auto vk_extension: vk_extensions) +		{ +			std::string message{"Extension name: "}; +			message += vk_extension; +			BluCat::INT::core.log.message(Log::Level::Trace, message); +		} +#endif +	} + +	// Get available instance layers. +	{ +		uint32_t vk_available_layers_count; +		std::vector<VkLayerProperties> vk_available_layers; +		std::vector<const char*> vk_available_layers_names; + +		vkEnumerateInstanceLayerProperties(&vk_available_layers_count, nullptr); +		vk_available_layers.resize(vk_available_layers_count); +		vkEnumerateInstanceLayerProperties(&vk_available_layers_count, +																			 vk_available_layers.data()); +		vk_available_layers_names.resize(vk_available_layers_count); +#ifdef DEBUG +		BluCat::INT::core.log.message( +			Log::Level::Trace, "Available VK instance layers."); +#endif +		for(uint32_t i = 0; i < vk_available_layers_count; i++) +		{ +#ifdef DEBUG +			std::stringstream message{}; +			message << "\nname: " << vk_available_layers[i].layerName << std::endl; +			message << "Description: " << vk_available_layers[i].description << +				std::endl; +			message << "Spec version: " << vk_available_layers[i].specVersion << +				std::endl; +			message << "Implementation version: " << +				vk_available_layers[i].implementationVersion << std::endl; +			BluCat::INT::core.log.message(Log::Level::Trace, message.str()); +#endif + +			vk_available_layers_names[i] = vk_available_layers[i].layerName; +		} + +		// If required layers are not all available. +		if(!std::includes( +				 vk_available_layers_names.begin(), vk_available_layers_names.end(), +				 vk_required_layers_names.begin(), vk_required_layers_names.begin())) +			throw CommandError{"Some required Vulkan layers are not available."}; +	} + +	{ +		VkApplicationInfo app_info; +		app_info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; +		app_info.pNext = nullptr; +		app_info.pApplicationName = BluCat::INT::core.game_name.c_str(); +		app_info.applicationVersion = VK_MAKE_VERSION( +			BluCat::INT::core.game_version_major, +			BluCat::INT::core.game_version_minor, +			BluCat::INT::core.game_version_patch); +		app_info.pEngineName = "BluCat::GRA"; +		app_info.engineVersion = VK_MAKE_VERSION( +			BLU_CAT_VERSION_MAJOR, +			BLU_CAT_VERSION_MINOR, +			BLU_CAT_VERSION_PATCH); +		app_info.apiVersion = VK_API_VERSION_1_0; + +		VkInstanceCreateInfo create_info; +		create_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; +		create_info.pNext = nullptr; +		create_info.flags = 0; +		create_info.pApplicationInfo = &app_info; +		create_info.enabledExtensionCount = vk_extensions.size(); +		create_info.ppEnabledExtensionNames = vk_extensions.data(); +		create_info.enabledLayerCount = vk_required_layers_names.size(); +		create_info.ppEnabledLayerNames = vk_required_layers_names.data(); + +		VkResult result = +			vkCreateInstance(&create_info, nullptr, &BluCat::INT::core.vk_instance); +		if(result != VK_SUCCESS) +		{ +			std::string error{""}; +			switch(result) +			{ +			case VK_ERROR_LAYER_NOT_PRESENT: +				error = " Layer not present."; +				break; +			case VK_ERROR_EXTENSION_NOT_PRESENT: +				error = " Extension not present."; +			} +			error = "Failed to create Vulkan instance." + error; +			throw CommandError{error}; +		} +	} +} + +void +unload_vk_instance(void *obj) +{ +	vkDestroyInstance(BluCat::INT::core.vk_instance, nullptr); +} + +void +load_window_surface(void *obj) +{ +	if(!SDL_Vulkan_CreateSurface( +			 BluCat::INT::core.window, BluCat::INT::core.vk_instance, +			 nullptr, &BluCat::INT::core.window_surface)) +	{ +		std::string error{"Failed to create a window surface → "}; +		error += SDL_GetError(); +		throw CommandError{error}; +	} +} + +void +unload_window_surface(void *obj) +{ +	vkDestroySurfaceKHR( +		BluCat::INT::core.vk_instance, BluCat::INT::core.window_surface, nullptr); +} + +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 +  { +		auto width{static_cast<float>(BluCat::INT::core.display_width)}; +		auto height{static_cast<float>(BluCat::INT::core.display_height)}; +    glm::vec4 region( +      0.f, 0.f, width, height); +		BluCat::INT::core.vk_renderer = new BluCat::GRA::Renderer( +      {std::make_shared<BluCat::GRA::View>(region, region.z, region.w)}, +			width, height); +  } +  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_sdl, &unload_sdl}, +	{&load_window, &unload_window}, +	{&load_vk_instance, &unload_vk_instance}, +	{&load_window_surface, &unload_window_surface}, +  {&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; + +void +run_game(std::unique_ptr<BluCat::INT::Mode> _mode) +{ +  using namespace std::chrono; +  using namespace std::this_thread; + +	std::unique_ptr<BluCat::INT::Mode> mode{std::move(_mode)}; +	std::unique_ptr<BluCat::INT::Controller> controller{ +		mode->default_controller()}; + +	bool quit{false}; +	int x, y, xrel, yrel; +	SDL_Event event; +	auto frame_start = steady_clock::now(); + +	while(!quit) +	{ +		if(core.next_game_mode) +		{ +			mode = std::move(core.next_game_mode); +			controller = mode->default_controller(); +		} +		else if(core.next_game_controller) +			controller = std::move(core.next_game_controller); + +		while(SDL_PollEvent(&event)) +		{ +			switch(event.type) +			{ +			case SDL_EVENT_KEY_DOWN: +				if(event.key.repeat != 0) continue; +				controller->key_down(event.key.key); +				break; +			case SDL_EVENT_KEY_UP: +				if(event.key.repeat != 0) continue; +				controller->key_up(event.key.key); +				break; +			case SDL_EVENT_MOUSE_BUTTON_DOWN: +				controller->mouse_button_down(event.button); +				break; +			case SDL_EVENT_MOUSE_BUTTON_UP: +				controller->mouse_button_up(event.button); +				break; +			case SDL_EVENT_MOUSE_MOTION: +				x = event.motion.x; +				y = event.motion.y; + +				xrel = event.motion.xrel; +				yrel = event.motion.yrel; + +				controller->mouse_motion(x, y, xrel, yrel); +				break; +			case SDL_EVENT_QUIT: +				quit = true; +			} +		} + +		controller->tick(); +		mode->render(); +		controller->render(); +		BluCat::INT::core.vk_renderer->draw(); + +		{ // Timer +			auto frame_stop = steady_clock::now(); +			auto frame_duration = frame_stop - frame_start; + +			// If frame take less time than maximum allowed. +			if(core.max_frame_duration > frame_duration) +				sleep_for(core.max_frame_duration - frame_duration); + +			frame_start = frame_stop; +		} +	} + +	controller = nullptr; +	mode = nullptr; +} + +} diff --git a/src/blu_cat/int/core.hpp b/src/blu_cat/int/core.hpp new file mode 100644 index 0000000..4737123 --- /dev/null +++ b/src/blu_cat/int/core.hpp @@ -0,0 +1,147 @@ +/* + * Copyright 2022-2025 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> + +#define SDL_MAIN_HANDLED + +#ifdef _WIN64 +#include <Windows.h> +#endif + +#include <SDL3/SDL.h> +#include <SDL3/SDL_vulkan.h> + +#include <ft2build.h> +#include FT_FREETYPE_H + +#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" +#include "mode.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{"BluCat Game"}; + +  /** +   * @{ +   * This is the ammount of pixel that the games uses when rendering to the +   * screen. +   */ +	uint32_t display_width{800}; +	uint32_t display_height{600}; +  /// @} + +	int game_version_major{0}; +	int game_version_minor{1}; +	int game_version_patch{0}; + +	uint32_t fps{30}; +  std::chrono::duration<long long, std::milli> max_frame_duration; +  float delta_time; + +	SDL_Window *window; + +  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; + +	std::unique_ptr<BluCat::INT::Mode> next_game_mode{nullptr}; +	std::unique_ptr<BluCat::INT::Controller> next_game_controller{nullptr}; +}; + +extern Core core; + +void +run_game(std::unique_ptr<Mode> initial_mode); + +} + +#endif /* BLU_CAT_INT_CORE_H */ diff --git a/src/blu_cat/int/mode.hpp b/src/blu_cat/int/mode.hpp new file mode 100644 index 0000000..8197f75 --- /dev/null +++ b/src/blu_cat/int/mode.hpp @@ -0,0 +1,41 @@ +/* + * Copyright 2022-2025 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_MODE_H +#define BLU_CAT_INT_MODE_H 1 + +#include <memory> + +#include "controller.hpp" + +namespace BluCat::INT +{ + +struct Mode +{ +	virtual std::unique_ptr<Controller> +	default_controller() = 0; + +	virtual void +	render() = 0; + +	virtual +	~Mode(){}; +}; + +} + +#endif /* BLU_CAT_INT_MODE_H */ diff --git a/src/blu_cat/net/client/client.cpp b/src/blu_cat/net/client/client.cpp new file mode 100644 index 0000000..6ee6046 --- /dev/null +++ b/src/blu_cat/net/client/client.cpp @@ -0,0 +1,64 @@ +/* + * Copyright 2022-2025 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 "client.hpp" + +namespace BluCat::NET +{ + +void +Client::connect(const char *host, const UI16 port) +{ +	if(this->connection) return; + +	asio::error_code error; +	asio::ip::tcp::endpoint endpoint{asio::ip::make_address(host, error), port}; + +	asio::ip::tcp::socket socket(this->io_context); +	socket.connect(endpoint); + +	this->connection = new Connection( +		this, this->io_context, std::move(socket), 0); + +	this->thread_context = std::thread([this](){this->io_context.run();}); +} + +void +Client::disconnect(unsigned long index) +{ +	if(!this->connection) return; + +	this->io_context.stop(); +	delete this->connection; +	this->connection = nullptr; +} + +Client::Client(const char *host, const UI16 port) +{ +	this->connect(host, port); +} + +Client::Client() +{ +} + +Client::~Client() +{ +	this->disconnect(0); +	if(this->thread_context.joinable()) this->thread_context.join(); +} + +} diff --git a/src/blu_cat/net/client/client.hpp b/src/blu_cat/net/client/client.hpp new file mode 100644 index 0000000..ad0e298 --- /dev/null +++ b/src/blu_cat/net/client/client.hpp @@ -0,0 +1,61 @@ +/* + * Copyright 2022-2025 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_NET_CLIENT_CLIENT_H +#define BLU_CAT_NET_CLIENT_CLIENT_H 1 + +#include "../../com/numbers.hpp" +#include "../common/connection.hpp" +#include "../common/connection_callback.hpp" + +namespace BluCat::NET +{ + +class Client: public ConnectionCallback +{ +	asio::io_context io_context; +	std::thread thread_context; + +	Connection *connection; + +public: +	inline bool +	send(const UI32 id, std::vector<UI8> &msg) +		{return this->connection->send(id, msg);}; + +	inline bool +	read_message(Message *m) +		{return this->connection->read_message(m);}; + +	inline bool +	is_connected() +		{return this->connection != nullptr;}; + +	void +	connect(const char *host, const UI16 port); + +	void +	disconnect(unsigned long index); + +	Client( +		const char *host, const UI16 port); +	Client(); +	~Client(); +}; + +} + +#endif /* BLU_CAT_NET_CLIENT_CLIENT_H */ diff --git a/src/blu_cat/net/common/connection.cpp b/src/blu_cat/net/common/connection.cpp new file mode 100644 index 0000000..643d30c --- /dev/null +++ b/src/blu_cat/net/common/connection.cpp @@ -0,0 +1,133 @@ +/* + * Copyright 2022-2025 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 "connection.hpp" + +#include <iostream> + +namespace BluCat::NET +{ + +void +Connection::read_header() +{ +	asio::async_read( +		this->socket, asio::buffer( +			&this->reading_message.header, sizeof(MessageHeader)), +		[this](std::error_code error, std::size_t length) +		{ +			if(!error) +			{ +				if(this->reading_message.header.size > 0) +				{ +					this->reading_message.body.resize(this->reading_message.header.size); +					this->read_body(); +				} +				else +				{ +					this->reading_message.body.resize(0); +					this->messages.push_back(this->reading_message); +					this->read_header(); +				} +			} +			else +			{ +				std::cout << "Failed to read header: " << error.message() << std::endl; +				this->connection_callback->disconnect(this->index); +			} +		}); +} + +void +Connection::read_body() +{ +	asio::async_read( +		this->socket, asio::buffer( +			this->reading_message.body.data(), this->reading_message.body.size()), +		[this](std::error_code error, std::size_t length) +		{ +			if(!error) +			{ +				this->messages.push_back(this->reading_message); +				this->read_header(); +			} +			else +			{ +				std::cout << "Failed to read body." << std::endl; +				this->connection_callback->disconnect(this->index); +			} +		}); +} + +bool +Connection::send(const uint32_t id, const std::vector<uint8_t> &msg) +{ +	std::vector<uint8_t> *buffered_msg = +		new std::vector<uint8_t>(msg.size() + 8); + +	{ // Create header +		std::memcpy(buffered_msg->data(), &id, 4); + +		uint32_t size{static_cast<uint32_t>(msg.size())}; +		std::memcpy(buffered_msg->data() + 4, &size, 4); +	} + +	// Append string to buffer. +	copy(msg.begin(), msg.end(), buffered_msg->begin() + 8); + +	asio::async_write( +		this->socket, asio::buffer(buffered_msg->data(), buffered_msg->size()), +		[this, buffered_msg](std::error_code error, std::size_t length) +		{ +			if(error) +			{ +				std::cout << "Failed to send message: " << error.message() << +					std::endl; +				this->connection_callback->disconnect(this->index); +			} +			delete buffered_msg; +		}); + +	return true; +} + +bool +Connection::read_message(Message *m) +{ +	if(this->messages.size() <= 0) return false; + +	*m = std::move(this->messages.pop_back()); +	return true; +} + +Connection::Connection( +	ConnectionCallback *connection_callback, +	asio::io_context &io_context, asio::ip::tcp::socket socket, +	unsigned long index): +	connection_callback{connection_callback}, +	io_context{io_context}, +	socket{std::move(socket)}, +	index{index} +{ +	this->read_header(); +} + +Connection::~Connection() +{ +	this->socket.close(); +} + +} diff --git a/src/blu_cat/net/common/connection.hpp b/src/blu_cat/net/common/connection.hpp new file mode 100644 index 0000000..bdce8b3 --- /dev/null +++ b/src/blu_cat/net/common/connection.hpp @@ -0,0 +1,69 @@ +/* + * Copyright 2022-2025 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_NET_COMMON_CONNECTION_H +#define BLU_CAT_NET_COMMON_CONNECTION_H 1 + +#include <iostream> + +#define ASIO_STANDALONE +#include <asio.hpp> +#include <asio/ts/buffer.hpp> +#include <asio/ts/internet.hpp> + +#include "connection_callback.hpp" +#include "message.hpp" +#include "tsqueue.hpp" + +namespace BluCat::NET +{ + +class Connection +{ +protected: +  unsigned long index; +	asio::io_context &io_context; + +	asio::ip::tcp::socket socket; +	TSQueue<Message> messages; + +	Message reading_message; +	ConnectionCallback *connection_callback; + +	void +	read_header(); + +	void +	read_body(); + +public: +	bool +	send(const uint32_t id, const std::vector<uint8_t> &msg); + +	bool +	read_message(Message *m); + +	Connection(ConnectionCallback *connection_callback, +						 asio::io_context &io_context, asio::ip::tcp::socket socket, +						 unsigned long index); + +	virtual +	~Connection(); +}; + +} + +#endif /* BLU_CAT_NET_COMMON_CONNECTION_H */ diff --git a/src/blu_cat/net/common/connection_callback.hpp b/src/blu_cat/net/common/connection_callback.hpp new file mode 100644 index 0000000..5241c76 --- /dev/null +++ b/src/blu_cat/net/common/connection_callback.hpp @@ -0,0 +1,39 @@ +/* + * Copyright 2022-2025 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_NET_COMMON_CONNECTION_CALLBACK_H +#define BLU_CAT_NET_COMMON_CONNECTION_CALLBACK_H 1 + +#include <memory> +#include <vector> + +namespace BluCat::NET +{ + +class Connection; + +struct ConnectionCallback +{ +	virtual void +	disconnect(unsigned long index)=0; + +	virtual +	~ConnectionCallback(){}; +}; + +} + +#endif /* BLU_CAT_NET_COMMON_CONNECTION_CALLBACK_H */ diff --git a/src/blu_cat/net/common/message.hpp b/src/blu_cat/net/common/message.hpp new file mode 100644 index 0000000..286a3bc --- /dev/null +++ b/src/blu_cat/net/common/message.hpp @@ -0,0 +1,40 @@ +/* + * Copyright 2022-2025 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_NET_COMMON_MESSAGE_H +#define BLU_CAT_NET_COMMON_MESSAGE_H 1 + +#include <cstdint> +#include <vector> + +namespace BluCat::NET +{ + +struct MessageHeader +{ +	uint32_t id; +	uint32_t size; +}; + +struct Message +{ +	MessageHeader header{}; +	std::vector<uint8_t> body; +}; + +} + +#endif /* BLU_CAT_NET_COMMON_MESSAGE_H */ diff --git a/src/blu_cat/net/common/tsqueue.hpp b/src/blu_cat/net/common/tsqueue.hpp new file mode 100644 index 0000000..673fe3c --- /dev/null +++ b/src/blu_cat/net/common/tsqueue.hpp @@ -0,0 +1,106 @@ +/* + * Copyright 2022-2025 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_NET_COMMON_TSQUEUE_H +#define BLU_CAT_NET_COMMON_TSQUEUE_H 1 + +#include <deque> +#include <mutex> + +namespace BluCat::NET +{ + +template<typename T> +class TSQueue +{ +protected: +	std::mutex mut; +	std::deque<T> deq_queue; + +public: +	TSQueue() = default; +	TSQueue(const TSQueue<T>&) = delete; + +	const T& +	front() +	{ +		std::scoped_lock lock(this->mut); +		return this->deq_queue.front(); +	} + +	const T& +	back() +	{ +		std::scoped_lock lock(this->mut); +		return this->deq_queue.back(); +	} + +	void +	push_front(const T& item) +	{ +		std::scoped_lock lock(this->mut); +		this->deq_queue.emplace_front(std::move(item)); +	} + +	void +	push_back(const T& item) +	{ +		std::scoped_lock lock(this->mut); +		this->deq_queue.emplace_back(std::move(item)); +	} + +	size_t +	size() +	{ +		std::scoped_lock lock(this->mut); +		return this->deq_queue.size(); +	} + +	void +	clear() +	{ +		std::scoped_lock lock(this->mut); +		return this->deq_queue.clear(); +	} + +	T +	pop_front() +	{ +		std::scoped_lock lock(this->mut); +		auto t = std::move(this->deq_queue.front()); +		deq_queue.pop_front(); +		return t; +	} + +	T +	pop_back() +	{ +		std::scoped_lock lock(this->mut); +		auto t = std::move(this->deq_queue.back()); +		deq_queue.pop_back(); +		return t; +	} + +	virtual +	~TSQueue() +	{ +		this->clear(); +	} +}; + +} + +#endif /* BLU_CAT_NET_COMMON_TSQUEUE_H */ diff --git a/src/blu_cat/net/server/server.cpp b/src/blu_cat/net/server/server.cpp new file mode 100644 index 0000000..b9d007e --- /dev/null +++ b/src/blu_cat/net/server/server.cpp @@ -0,0 +1,94 @@ +/* + * Copyright 2022-2025 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 "server.hpp" + +#include <iostream> + +namespace BluCat::NET +{ + +void +Server::wait_for_client_connections() +{ +	acceptor.async_accept( +		[this](std::error_code error, asio::ip::tcp::socket socket) +		{ +			if(!error) +			{ +				std::scoped_lock lock(this->mut); + +				std::cout << "[SERVER] New connection: " << socket.remote_endpoint() << +					std::endl; + +				if(this->free_connection_slots.size() > 0) +				{ +					unsigned long pos{this->free_connection_slots.front()}; +					this->free_connection_slots.pop(); +					std::cout << "Working " << pos << std::endl; +					std::unique_ptr<Connection> new_connection( +						std::make_unique<Connection>( +							this, this->io_context, std::move(socket), pos)); +					this->connections[pos] = new_connection.get(); +					this->get_new_connection(std::move(new_connection)); +				} +				else +				{ +					unsigned long pos{this->connections.size()}; +					std::unique_ptr<Connection> new_connection( +						std::make_unique<Connection>( +							this, this->io_context, std::move(socket), pos)); +					this->connections.push_back(new_connection.get()); +					this->get_new_connection(std::move(new_connection)); +				} +			} +			else +			{ +				std::cout << "error connecting to client." << std::endl; +			} + +			std::cout << "num clients: " << connections.size() << std::endl; + +			this->wait_for_client_connections(); +		}); +} + +void +Server::disconnect(unsigned long index) +{ +	std::scoped_lock lock(this->mut); +	delete this->connections[index]; +	this->connections[index] = nullptr; +	this->free_connection_slots.push(index); +} + +Server::Server(void (*get_new_connection)(std::unique_ptr<Connection> c), +							 const uint16_t port): +	get_new_connection{get_new_connection}, +	acceptor{io_context, asio::ip::tcp::endpoint(asio::ip::tcp::v4(), port)} +{ +	this->thread_context = std::thread([this](){this->io_context.run();}); +	this->wait_for_client_connections(); +} + +Server::~Server() +{ +	this->connections.clear(); +	this->io_context.stop(); +	if(this->thread_context.joinable()) thread_context.join(); +} + +} diff --git a/src/blu_cat/net/server/server.hpp b/src/blu_cat/net/server/server.hpp new file mode 100644 index 0000000..00b277f --- /dev/null +++ b/src/blu_cat/net/server/server.hpp @@ -0,0 +1,53 @@ +/* + * Copyright 2022-2025 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_SERVER_SERVER_H +#define BLU_CAT_SERVER_SERVER_H 1 + +#include <queue> + +#include "../common/connection.hpp" +#include "../common/connection_callback.hpp" + +namespace BluCat::NET +{ + +class Server: public ConnectionCallback +{ +	std::mutex mut; +	asio::io_context io_context; +	std::thread thread_context; +	asio::ip::tcp::acceptor acceptor; +	void (*get_new_connection)(std::unique_ptr<Connection> c); + +	std::queue<unsigned long> free_connection_slots; +	std::vector<Connection*> connections; + +	void +	wait_for_client_connections(); + +public: +	void +	disconnect(unsigned long index); + +	Server(void (*get_new_connection)(std::unique_ptr<Connection> c), +				 const uint16_t port); +	~Server(); +}; + +} + +#endif /* BLU_CAT_SERVER_SERVER_H */ | 
