mirror of
https://github.com/seekrs/MacroLibX.git
synced 2026-01-11 14:43:34 +00:00
merge indev to master (#20)
This commit is contained in:
13
README.md
13
README.md
@@ -13,9 +13,9 @@
|
||||
###### MacroLibX, a rewrite of 42 School's MiniLibX using SDL2 and Vulkan.
|
||||
The goal of this version is to provide a light, fast, and modern graphical tool while keeping the same API.
|
||||
|
||||
## π« Features
|
||||
## π Features
|
||||
|
||||
### π Performances
|
||||
### π Performances
|
||||
Built on top of Vulkan, the MacroLibX takes advantage of its very low-level nature to achieve high performance with great control over available resources.
|
||||
|
||||
### π» Cross-Platform
|
||||
@@ -27,6 +27,12 @@ One of the guidelines of this lib was to get as close as possible to the old min
|
||||
### π It's all FOSS
|
||||
Everything in this repo is entirely free and open source, all available under the MIT license (even the third-party libraries used).
|
||||
|
||||
### π Valgrind suppressions file
|
||||
Experimental for now, a [suppressions file for valgrind](./valgrind.supp) is given to remove potential leaks comming from Xorg, Nvidia drivers, SDL2, or any other tool which the user has no control. It is far from perfect at the moment and may allow some leaks but it will block the majority.
|
||||
|
||||
### β Error system
|
||||
Strong error handling informing the user of problems with their code and even capable of informing them of graphics memory leaks that tools like Valgrind cannot detect.
|
||||
|
||||
## π₯οΈ Installation
|
||||
|
||||
### Dependencies
|
||||
@@ -101,5 +107,4 @@ You can force the mlx to use your integrated GPU by using `make FORCE_INTEGRATED
|
||||
The mlx can dump it's graphics memory use to json files every two seconds by enabling this option `make GRAPHICS_MEMORY_DUMP=true`.
|
||||
|
||||
## License
|
||||
|
||||
This project and all its files, except the [`third_party`](./third_party) directory or unless otherwise mentionned, are licenced under the [MIT license](./LICENSE).
|
||||
This project and all its files, even the [`third_party`](./third_party) directory or unless otherwise mentionned, are licenced under the [MIT license](./LICENSE).
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
/* By: maldavid <kbz_8.dev@akel-engine.com> +#+ +:+ +#+ */
|
||||
/* +#+#+#+#+#+ +#+ */
|
||||
/* Created: 2023/11/10 08:49:17 by maldavid #+# #+# */
|
||||
/* Updated: 2023/12/11 20:35:57 by kbz_8 ### ########.fr */
|
||||
/* Updated: 2023/12/16 20:20:35 by maldavid ### ########.fr */
|
||||
/* */
|
||||
/* ************************************************************************** */
|
||||
|
||||
@@ -50,19 +50,49 @@
|
||||
#elif defined(unix) || defined(__unix__) || defined(__unix)
|
||||
#define MLX_PLAT_UNIX
|
||||
#else
|
||||
#error "Unknown environment!"
|
||||
#error "Unknown environment (not Windows, not Linux, not MacOS, not Unix)"
|
||||
#endif
|
||||
|
||||
#ifdef MLX_COMPILER_MSVC
|
||||
#ifdef MLX_BUILD
|
||||
#define MLX_API __declspec(dllexport)
|
||||
#ifdef MLX_PLAT_WINDOWS
|
||||
#ifdef MLX_COMPILER_MSVC
|
||||
#ifdef MLX_BUILD
|
||||
#define MLX_API __declspec(dllexport)
|
||||
#else
|
||||
#define MLX_API __declspec(dllimport)
|
||||
#endif
|
||||
#elif defined(MLX_COMPILER_GCC)
|
||||
#ifdef MLX_BUILD
|
||||
#define MLX_API __attribute__((dllexport))
|
||||
#else
|
||||
#define MLX_API __attribute__((dllimport))
|
||||
#endif
|
||||
#else
|
||||
#define MLX_API __declspec(dllimport)
|
||||
#define MLX_API
|
||||
#endif
|
||||
#elif defined(MLX_COMPILER_GCC)
|
||||
#define MLX_API __attribute__((visibility("default")))
|
||||
#else
|
||||
#define MLX_API
|
||||
#endif
|
||||
|
||||
#if defined(__GNUC__) || (defined(__MWERKS__) && (__MWERKS__ >= 0x3000)) || (defined(__ICC) && (__ICC >= 600)) || defined(__ghs__)
|
||||
#define MLX_FUNC_SIG __PRETTY_FUNCTION__
|
||||
#elif defined(__DMC__) && (__DMC__ >= 0x810)
|
||||
#define MLX_FUNC_SIG __PRETTY_FUNCTION__
|
||||
#elif (defined(__FUNCSIG__) || (_MSC_VER))
|
||||
#define MLX_FUNC_SIG __FUNCSIG__
|
||||
#elif (defined(__INTEL_COMPILER) && (__INTEL_COMPILER >= 600)) || (defined(__IBMCPP__) && (__IBMCPP__ >= 500))
|
||||
#define MLX_FUNC_SIG __FUNCTION__
|
||||
#elif defined(__BORLANDC__) && (__BORLANDC__ >= 0x550)
|
||||
#define MLX_FUNC_SIG __FUNC__
|
||||
#elif defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901)
|
||||
#define MLX_FUNC_SIG __func__
|
||||
#elif defined(__cplusplus) && (__cplusplus >= 201103)
|
||||
#define MLX_FUNC_SIG __func__
|
||||
#else
|
||||
#define MLX_FUNC_SIG "Unknown function"
|
||||
#endif
|
||||
|
||||
// Checking common assumptions
|
||||
#ifdef __cplusplus
|
||||
#include <climits>
|
||||
|
||||
@@ -88,14 +88,32 @@ namespace mlx::core
|
||||
|
||||
int Application::getTexturePixel(void* img, int x, int y)
|
||||
{
|
||||
if(img == nullptr)
|
||||
{
|
||||
core::error::report(e_kind::error, "wrong texture (NULL)");
|
||||
return 0;
|
||||
}
|
||||
Texture* texture = static_cast<Texture*>(img);
|
||||
if(!texture->isInit())
|
||||
{
|
||||
core::error::report(e_kind::error, "trying to get a pixel from texture that has been destroyed");
|
||||
return 0;
|
||||
}
|
||||
return texture->getPixel(x, y);
|
||||
}
|
||||
|
||||
void Application::setTexturePixel(void* img, int x, int y, uint32_t color)
|
||||
{
|
||||
if(img == nullptr)
|
||||
{
|
||||
core::error::report(e_kind::error, "wrong texture (NULL)");
|
||||
return;
|
||||
}
|
||||
Texture* texture = static_cast<Texture*>(img);
|
||||
texture->setPixel(x, y, color);
|
||||
if(!texture->isInit())
|
||||
core::error::report(e_kind::error, "trying to set a pixel on texture that has been destroyed");
|
||||
else
|
||||
texture->setPixel(x, y, color);
|
||||
}
|
||||
|
||||
void Application::loopHook(int (*f)(void*), void* param)
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
/* By: maldavid <kbz_8.dev@akel-engine.com> +#+ +:+ +#+ */
|
||||
/* +#+#+#+#+#+ +#+ */
|
||||
/* Created: 2022/10/04 17:35:20 by maldavid #+# #+# */
|
||||
/* Updated: 2023/12/14 17:47:17 by maldavid ### ########.fr */
|
||||
/* Updated: 2023/12/16 20:20:41 by maldavid ### ########.fr */
|
||||
/* */
|
||||
/* ************************************************************************** */
|
||||
|
||||
@@ -17,13 +17,20 @@
|
||||
#include <filesystem>
|
||||
#include <mlx.h>
|
||||
#include <core/memory.h>
|
||||
#include <mlx_profile.h>
|
||||
|
||||
static void* __mlx_ptr = nullptr;
|
||||
|
||||
#define MLX_CHECK_APPLICATION_POINTER(ptr) \
|
||||
if(ptr != __mlx_ptr || ptr == NULL) \
|
||||
mlx::core::error::report(e_kind::fatal_error, "invalid mlx pointer passed to '%s'", MLX_FUNC_SIG); \
|
||||
else {} // just to avoid issues with possible if-else statements outside this macro
|
||||
|
||||
extern "C"
|
||||
{
|
||||
void* mlx_init()
|
||||
{
|
||||
static bool init = false;
|
||||
if(init)
|
||||
if(__mlx_ptr != nullptr)
|
||||
{
|
||||
mlx::core::error::report(e_kind::error, "MLX cannot be initialized multiple times");
|
||||
return NULL; // not nullptr for the C compatibility
|
||||
@@ -33,29 +40,33 @@ extern "C"
|
||||
mlx::Render_Core::get().init();
|
||||
if(app == nullptr)
|
||||
mlx::core::error::report(e_kind::fatal_error, "Tout a pΓ©tΓ©");
|
||||
init = true;
|
||||
return static_cast<void*>(app);
|
||||
__mlx_ptr = static_cast<void*>(app);
|
||||
return __mlx_ptr;
|
||||
}
|
||||
|
||||
void* mlx_new_window(void* mlx, int w, int h, const char* title)
|
||||
{
|
||||
MLX_CHECK_APPLICATION_POINTER(mlx);
|
||||
return static_cast<mlx::core::Application*>(mlx)->newGraphicsSuport(w, h, title);
|
||||
}
|
||||
|
||||
int mlx_loop_hook(void* mlx, int (*f)(void*), void* param)
|
||||
{
|
||||
MLX_CHECK_APPLICATION_POINTER(mlx);
|
||||
static_cast<mlx::core::Application*>(mlx)->loopHook(f, param);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mlx_loop(void* mlx)
|
||||
{
|
||||
MLX_CHECK_APPLICATION_POINTER(mlx);
|
||||
static_cast<mlx::core::Application*>(mlx)->run();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mlx_loop_end(void* mlx)
|
||||
{
|
||||
MLX_CHECK_APPLICATION_POINTER(mlx);
|
||||
static_cast<mlx::core::Application*>(mlx)->loopEnd();
|
||||
return 0;
|
||||
}
|
||||
@@ -72,34 +83,40 @@ extern "C"
|
||||
|
||||
int mlx_mouse_move(void* mlx, void* win, int x, int y)
|
||||
{
|
||||
MLX_CHECK_APPLICATION_POINTER(mlx);
|
||||
static_cast<mlx::core::Application*>(mlx)->mouseMove(win, x, y);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mlx_mouse_get_pos(void* mlx, int* x, int* y)
|
||||
{
|
||||
MLX_CHECK_APPLICATION_POINTER(mlx);
|
||||
static_cast<mlx::core::Application*>(mlx)->getMousePos(x, y);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mlx_on_event(void* mlx, void* win, mlx_event_type event, int (*funct_ptr)(int, void*), void* param)
|
||||
{
|
||||
MLX_CHECK_APPLICATION_POINTER(mlx);
|
||||
static_cast<mlx::core::Application*>(mlx)->onEvent(win, static_cast<int>(event), funct_ptr, param);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void* mlx_new_image(void* mlx, int width, int height)
|
||||
{
|
||||
MLX_CHECK_APPLICATION_POINTER(mlx);
|
||||
return static_cast<mlx::core::Application*>(mlx)->newTexture(width, height);
|
||||
}
|
||||
|
||||
int mlx_get_image_pixel(void* mlx, void* img, int x, int y)
|
||||
{
|
||||
MLX_CHECK_APPLICATION_POINTER(mlx);
|
||||
return static_cast<mlx::core::Application*>(mlx)->getTexturePixel(img, x, y);
|
||||
}
|
||||
|
||||
void mlx_set_image_pixel(void* mlx, void* img, int x, int y, int color)
|
||||
{
|
||||
MLX_CHECK_APPLICATION_POINTER(mlx);
|
||||
unsigned char color_bits[4];
|
||||
color_bits[0] = (color & 0x00FF0000) >> 16;
|
||||
color_bits[1] = (color & 0x0000FF00) >> 8;
|
||||
@@ -110,18 +127,21 @@ extern "C"
|
||||
|
||||
int mlx_put_image_to_window(void* mlx, void* win, void* img, int x, int y)
|
||||
{
|
||||
MLX_CHECK_APPLICATION_POINTER(mlx);
|
||||
static_cast<mlx::core::Application*>(mlx)->texturePut(win, img, x, y);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mlx_destroy_image(void* mlx, void* img)
|
||||
{
|
||||
MLX_CHECK_APPLICATION_POINTER(mlx);
|
||||
static_cast<mlx::core::Application*>(mlx)->destroyTexture(img);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void* mlx_png_file_to_image(void* mlx, char* filename, int* width, int* height)
|
||||
{
|
||||
MLX_CHECK_APPLICATION_POINTER(mlx);
|
||||
std::filesystem::path file(filename);
|
||||
if(file.extension() != ".png")
|
||||
{
|
||||
@@ -133,6 +153,7 @@ extern "C"
|
||||
|
||||
void* mlx_jpg_file_to_image(void* mlx, char* filename, int* width, int* height)
|
||||
{
|
||||
MLX_CHECK_APPLICATION_POINTER(mlx);
|
||||
std::filesystem::path file(filename);
|
||||
if(file.extension() != ".jpg" && file.extension() != ".jpeg")
|
||||
{
|
||||
@@ -144,6 +165,7 @@ extern "C"
|
||||
|
||||
void* mlx_bmp_file_to_image(void* mlx, char* filename, int* width, int* height)
|
||||
{
|
||||
MLX_CHECK_APPLICATION_POINTER(mlx);
|
||||
std::filesystem::path file(filename);
|
||||
if(file.extension() != ".bmp" && file.extension() != ".dib")
|
||||
{
|
||||
@@ -155,6 +177,7 @@ extern "C"
|
||||
|
||||
int mlx_pixel_put(void* mlx, void* win, int x, int y, int color)
|
||||
{
|
||||
MLX_CHECK_APPLICATION_POINTER(mlx);
|
||||
unsigned char color_bits[4];
|
||||
color_bits[0] = (color & 0x00FF0000) >> 16;
|
||||
color_bits[1] = (color & 0x0000FF00) >> 8;
|
||||
@@ -166,6 +189,7 @@ extern "C"
|
||||
|
||||
int mlx_string_put(void* mlx, void* win, int x, int y, int color, char* str)
|
||||
{
|
||||
MLX_CHECK_APPLICATION_POINTER(mlx);
|
||||
unsigned char color_bits[4];
|
||||
color_bits[0] = (color & 0x00FF0000) >> 16;
|
||||
color_bits[1] = (color & 0x0000FF00) >> 8;
|
||||
@@ -177,6 +201,7 @@ extern "C"
|
||||
|
||||
void mlx_set_font(void* mlx, void* win, char* filepath)
|
||||
{
|
||||
MLX_CHECK_APPLICATION_POINTER(mlx);
|
||||
std::filesystem::path file(filepath);
|
||||
if(std::strcmp(filepath, "default") != 0 && file.extension() != ".ttf" && file.extension() != ".tte")
|
||||
{
|
||||
@@ -188,6 +213,7 @@ extern "C"
|
||||
|
||||
void mlx_set_font_scale(void* mlx, void* win, char* filepath, float scale)
|
||||
{
|
||||
MLX_CHECK_APPLICATION_POINTER(mlx);
|
||||
std::filesystem::path file(filepath);
|
||||
if(std::strcmp(filepath, "default") != 0 && file.extension() != ".ttf" && file.extension() != ".tte")
|
||||
{
|
||||
@@ -199,25 +225,30 @@ extern "C"
|
||||
|
||||
int mlx_clear_window(void* mlx, void* win)
|
||||
{
|
||||
MLX_CHECK_APPLICATION_POINTER(mlx);
|
||||
static_cast<mlx::core::Application*>(mlx)->clearGraphicsSupport(win);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mlx_destroy_window(void* mlx, void* win)
|
||||
{
|
||||
MLX_CHECK_APPLICATION_POINTER(mlx);
|
||||
static_cast<mlx::core::Application*>(mlx)->destroyGraphicsSupport(win);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mlx_destroy_display(void* mlx)
|
||||
{
|
||||
MLX_CHECK_APPLICATION_POINTER(mlx);
|
||||
delete static_cast<mlx::core::Application*>(mlx);
|
||||
mlx::Render_Core::get().destroy();
|
||||
__mlx_ptr = nullptr;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mlx_get_screens_size(void* mlx, int* w, int* h)
|
||||
{
|
||||
MLX_CHECK_APPLICATION_POINTER(mlx);
|
||||
static_cast<mlx::core::Application*>(mlx)->getScreenSize(w, h);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
/* By: maldavid <kbz_8.dev@akel-engine.com> +#+ +:+ +#+ */
|
||||
/* +#+#+#+#+#+ +#+ */
|
||||
/* Created: 2022/10/08 18:55:57 by maldavid #+# #+# */
|
||||
/* Updated: 2023/12/12 22:11:47 by kbz_8 ### ########.fr */
|
||||
/* Updated: 2023/12/16 17:10:17 by maldavid ### ########.fr */
|
||||
/* */
|
||||
/* ************************************************************************** */
|
||||
|
||||
@@ -97,22 +97,15 @@ namespace mlx
|
||||
}
|
||||
|
||||
// TODO, use global cmd buffer pool to manage resources
|
||||
CmdPool cmdpool;
|
||||
cmdpool.init();
|
||||
CmdBuffer cmdBuffer;
|
||||
cmdBuffer.init(&cmdpool);
|
||||
|
||||
cmdBuffer.beginRecord(VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT);
|
||||
CmdBuffer& cmd = Render_Core::get().getSingleTimeCmdBuffer();
|
||||
cmd.beginRecord();
|
||||
|
||||
VkBufferCopy copyRegion{};
|
||||
copyRegion.size = _size;
|
||||
vkCmdCopyBuffer(cmdBuffer.get(), buffer._buffer, _buffer, 1, ©Region);
|
||||
vkCmdCopyBuffer(cmd.get(), buffer._buffer, _buffer, 1, ©Region);
|
||||
|
||||
cmdBuffer.endRecord();
|
||||
cmdBuffer.submitIdle();
|
||||
|
||||
cmdBuffer.destroy();
|
||||
cmdpool.destroy();
|
||||
cmd.endRecord();
|
||||
cmd.submitIdle();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
/* By: maldavid <kbz_8.dev@akel-engine.com> +#+ +:+ +#+ */
|
||||
/* +#+#+#+#+#+ +#+ */
|
||||
/* Created: 2023/12/15 19:57:49 by maldavid #+# #+# */
|
||||
/* Updated: 2023/12/15 20:21:54 by maldavid ### ########.fr */
|
||||
/* Updated: 2023/12/16 18:46:26 by maldavid ### ########.fr */
|
||||
/* */
|
||||
/* ************************************************************************** */
|
||||
|
||||
@@ -22,7 +22,24 @@ namespace mlx
|
||||
{
|
||||
_pool.init();
|
||||
for(int i = 0; i < MIN_POOL_SIZE; i++)
|
||||
_buffers.emplace_back().init(&_pool);
|
||||
{
|
||||
_buffers.emplace_back();
|
||||
_buffers.back().init(&_pool);
|
||||
}
|
||||
}
|
||||
|
||||
CmdBuffer& SingleTimeCmdManager::getCmdBuffer() noexcept
|
||||
{
|
||||
for(CmdBuffer& buf : _buffers)
|
||||
{
|
||||
if(buf.isReadyToBeUsed())
|
||||
{
|
||||
buf.reset();
|
||||
return buf;
|
||||
}
|
||||
}
|
||||
_buffers.emplace_back().init(&_pool);
|
||||
return _buffers.back();
|
||||
}
|
||||
|
||||
void SingleTimeCmdManager::destroy() noexcept
|
||||
@@ -33,15 +50,4 @@ namespace mlx
|
||||
});
|
||||
_pool.destroy();
|
||||
}
|
||||
|
||||
CmdBuffer& SingleTimeCmdManager::getCmdBuffer() noexcept
|
||||
{
|
||||
for(CmdBuffer& buf : _buffers)
|
||||
{
|
||||
if(buf.isReadyToBeUsed())
|
||||
return buf;
|
||||
}
|
||||
_buffers.emplace_back().init(&_pool);
|
||||
return _buffers.back();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
/* By: maldavid <kbz_8.dev@akel-engine.com> +#+ +:+ +#+ */
|
||||
/* +#+#+#+#+#+ +#+ */
|
||||
/* Created: 2023/12/15 18:25:57 by maldavid #+# #+# */
|
||||
/* Updated: 2023/12/15 19:59:40 by maldavid ### ########.fr */
|
||||
/* Updated: 2023/12/16 18:09:56 by maldavid ### ########.fr */
|
||||
/* */
|
||||
/* ************************************************************************** */
|
||||
|
||||
@@ -35,6 +35,8 @@ namespace mlx
|
||||
|
||||
inline static constexpr const uint8_t MIN_POOL_SIZE = 8;
|
||||
|
||||
private:
|
||||
|
||||
private:
|
||||
std::vector<CmdBuffer> _buffers;
|
||||
CmdPool _pool;
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
/* By: maldavid <kbz_8.dev@akel-engine.com> +#+ +:+ +#+ */
|
||||
/* +#+#+#+#+#+ +#+ */
|
||||
/* Created: 2022/10/06 18:26:06 by maldavid #+# #+# */
|
||||
/* Updated: 2023/11/08 20:17:49 by maldavid ### ########.fr */
|
||||
/* Updated: 2023/12/16 18:51:03 by maldavid ### ########.fr */
|
||||
/* */
|
||||
/* ************************************************************************** */
|
||||
|
||||
@@ -39,11 +39,14 @@ namespace mlx
|
||||
#endif
|
||||
|
||||
_fence.init();
|
||||
_state = state::idle;
|
||||
}
|
||||
|
||||
void CmdBuffer::beginRecord(VkCommandBufferUsageFlags usage)
|
||||
{
|
||||
if(_is_recording)
|
||||
if(!isInit())
|
||||
core::error::report(e_kind::fatal_error, "Vulkan : begenning record on un uninit command buffer");
|
||||
if(_state == state::recording)
|
||||
return;
|
||||
|
||||
VkCommandBufferBeginInfo beginInfo{};
|
||||
@@ -52,17 +55,19 @@ namespace mlx
|
||||
if(vkBeginCommandBuffer(_cmd_buffer, &beginInfo) != VK_SUCCESS)
|
||||
core::error::report(e_kind::fatal_error, "Vulkan : failed to begin recording command buffer");
|
||||
|
||||
_is_recording = true;
|
||||
_state = state::recording;
|
||||
}
|
||||
|
||||
void CmdBuffer::endRecord()
|
||||
{
|
||||
if(!_is_recording)
|
||||
if(!isInit())
|
||||
core::error::report(e_kind::fatal_error, "Vulkan : ending record on un uninit command buffer");
|
||||
if(_state != state::recording)
|
||||
return;
|
||||
if(vkEndCommandBuffer(_cmd_buffer) != VK_SUCCESS)
|
||||
core::error::report(e_kind::fatal_error, "Vulkan : failed to end recording command buffer");
|
||||
|
||||
_is_recording = false;
|
||||
_state = state::idle;
|
||||
}
|
||||
|
||||
void CmdBuffer::submitIdle() noexcept
|
||||
@@ -83,6 +88,8 @@ namespace mlx
|
||||
vkQueueSubmit(Render_Core::get().getQueue().getGraphic(), 1, &submitInfo, fence);
|
||||
vkWaitForFences(device, 1, &fence, VK_TRUE, UINT64_MAX);
|
||||
vkDestroyFence(device, fence, nullptr);
|
||||
_state = state::submitted;
|
||||
_state = state::ready;
|
||||
}
|
||||
|
||||
void CmdBuffer::submit(Semaphore& semaphores) noexcept
|
||||
@@ -103,11 +110,13 @@ namespace mlx
|
||||
|
||||
if(vkQueueSubmit(Render_Core::get().getQueue().getGraphic(), 1, &submitInfo, _fence.get()) != VK_SUCCESS)
|
||||
core::error::report(e_kind::fatal_error, "Vulkan error : failed to submit draw command buffer");
|
||||
_state = state::submitted;
|
||||
}
|
||||
|
||||
void CmdBuffer::destroy() noexcept
|
||||
{
|
||||
_fence.destroy();
|
||||
_cmd_buffer = VK_NULL_HANDLE;
|
||||
_state = state::uninit;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
/* By: maldavid <kbz_8.dev@akel-engine.com> +#+ +:+ +#+ */
|
||||
/* +#+#+#+#+#+ +#+ */
|
||||
/* Created: 2022/10/06 18:25:42 by maldavid #+# #+# */
|
||||
/* Updated: 2023/12/15 20:19:42 by maldavid ### ########.fr */
|
||||
/* Updated: 2023/12/16 18:44:48 by maldavid ### ########.fr */
|
||||
/* */
|
||||
/* ************************************************************************** */
|
||||
|
||||
@@ -21,6 +21,16 @@ namespace mlx
|
||||
{
|
||||
class CmdBuffer
|
||||
{
|
||||
public:
|
||||
enum class state
|
||||
{
|
||||
uninit = 0, // buffer not initialized or destroyed
|
||||
ready, // buffer ready to be used after having been submitted
|
||||
idle, // buffer has recorded informations but has not been submitted
|
||||
recording, // buffer is currently recording
|
||||
submitted, // buffer has been submitted
|
||||
};
|
||||
|
||||
public:
|
||||
void init(class CmdManager* manager);
|
||||
void init(class CmdPool* pool);
|
||||
@@ -29,13 +39,15 @@ namespace mlx
|
||||
void beginRecord(VkCommandBufferUsageFlags usage = 0);
|
||||
void submit(class Semaphore& semaphores) noexcept;
|
||||
void submitIdle() noexcept;
|
||||
inline void waitForExecution() noexcept { _fence.waitAndReset(); }
|
||||
inline void waitForExecution() noexcept { _fence.waitAndReset(); _state = state::ready; }
|
||||
inline void reset() noexcept { vkResetCommandBuffer(_cmd_buffer, 0); }
|
||||
inline bool isReadyToBeUsed() const noexcept { return _fence.isReady(); }
|
||||
void endRecord();
|
||||
|
||||
inline bool isRecording() const noexcept { return _is_recording; }
|
||||
inline bool isInit() const noexcept { return _cmd_buffer != VK_NULL_HANDLE; }
|
||||
inline bool isInit() const noexcept { return _state != state::uninit; }
|
||||
inline bool isReadyToBeUsed() const noexcept { return _state == state::ready; }
|
||||
inline bool isRecording() const noexcept { return _state == state::recording; }
|
||||
inline bool hasBeenSubmitted() const noexcept { return _state == state::submitted; }
|
||||
inline state getCurrentState() const noexcept { return _state; }
|
||||
|
||||
inline VkCommandBuffer& operator()() noexcept { return _cmd_buffer; }
|
||||
inline VkCommandBuffer& get() noexcept { return _cmd_buffer; }
|
||||
@@ -45,7 +57,7 @@ namespace mlx
|
||||
Fence _fence;
|
||||
VkCommandBuffer _cmd_buffer = VK_NULL_HANDLE;
|
||||
class CmdPool* _pool = nullptr;
|
||||
bool _is_recording = false;
|
||||
state _state = state::uninit;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
/* By: kbz_8 <kbz_8.dev@akel-engine.com> +#+ +:+ +#+ */
|
||||
/* +#+#+#+#+#+ +#+ */
|
||||
/* Created: 2023/10/20 22:02:37 by kbz_8 #+# #+# */
|
||||
/* Updated: 2023/12/10 22:44:55 by kbz_8 ### ########.fr */
|
||||
/* Updated: 2023/12/16 19:14:15 by maldavid ### ########.fr */
|
||||
/* */
|
||||
/* ************************************************************************** */
|
||||
|
||||
@@ -17,7 +17,11 @@
|
||||
#define VMA_STATIC_VULKAN_FUNCTIONS 0
|
||||
#define VMA_DYNAMIC_VULKAN_FUNCTIONS 0
|
||||
#define VMA_VULKAN_VERSION 1002000
|
||||
#define VMA_ASSERT(expr) (static_cast<bool>(expr) ? void(0) : mlx::core::error::report(e_kind::fatal_error, "Graphics allocator : an assertion has been catched : '%s'", #expr))
|
||||
#ifdef DEBUG
|
||||
#define VMA_ASSERT(expr) (static_cast<bool>(expr) ? void(0) : mlx::core::error::report(e_kind::fatal_error, "Graphics allocator : an assertion has been catched : '%s'", #expr))
|
||||
#else
|
||||
#define VMA_ASSERT(expr) ((void)0)
|
||||
#endif
|
||||
#define VMA_IMPLEMENTATION
|
||||
|
||||
#ifdef MLX_COMPILER_CLANG
|
||||
@@ -93,6 +97,7 @@ namespace mlx
|
||||
#ifdef DEBUG
|
||||
core::error::report(e_kind::message, "Graphics Allocator : created new buffer");
|
||||
#endif
|
||||
_active_buffers_allocations++;
|
||||
return allocation;
|
||||
}
|
||||
|
||||
@@ -103,6 +108,7 @@ namespace mlx
|
||||
#ifdef DEBUG
|
||||
core::error::report(e_kind::message, "Graphics Allocator : destroyed buffer");
|
||||
#endif
|
||||
_active_buffers_allocations--;
|
||||
}
|
||||
|
||||
VmaAllocation GPUallocator::createImage(const VkImageCreateInfo* iminfo, const VmaAllocationCreateInfo* vinfo, VkImage& image, const char* name) noexcept
|
||||
@@ -115,6 +121,7 @@ namespace mlx
|
||||
#ifdef DEBUG
|
||||
core::error::report(e_kind::message, "Graphics Allocator : created new image");
|
||||
#endif
|
||||
_active_images_allocations++;
|
||||
return allocation;
|
||||
}
|
||||
|
||||
@@ -125,6 +132,7 @@ namespace mlx
|
||||
#ifdef DEBUG
|
||||
core::error::report(e_kind::message, "Graphics Allocator : destroyed image");
|
||||
#endif
|
||||
_active_images_allocations--;
|
||||
}
|
||||
|
||||
void GPUallocator::mapMemory(VmaAllocation allocation, void** data) noexcept
|
||||
@@ -164,6 +172,10 @@ namespace mlx
|
||||
|
||||
void GPUallocator::destroy() noexcept
|
||||
{
|
||||
if(_active_images_allocations != 0)
|
||||
core::error::report(e_kind::error, "Graphics allocator : some user-dependant allocations were not freed before destroying the display (%d active allocations)", _active_images_allocations);
|
||||
else if(_active_buffers_allocations != 0)
|
||||
core::error::report(e_kind::error, "Graphics allocator : some MLX-dependant allocations were not freed before destroying the display (%d active allocations), please report, this should not happen", _active_buffers_allocations);
|
||||
vmaDestroyAllocator(_allocator);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
/* By: maldavid <kbz_8.dev@akel-engine.com> +#+ +:+ +#+ */
|
||||
/* +#+#+#+#+#+ +#+ */
|
||||
/* Created: 2023/10/20 02:13:03 by maldavid #+# #+# */
|
||||
/* Updated: 2023/12/08 19:07:34 by kbz_8 ### ########.fr */
|
||||
/* Updated: 2023/12/16 18:53:51 by maldavid ### ########.fr */
|
||||
/* */
|
||||
/* ************************************************************************** */
|
||||
|
||||
@@ -44,6 +44,8 @@ namespace mlx
|
||||
|
||||
private:
|
||||
VmaAllocator _allocator;
|
||||
uint32_t _active_buffers_allocations = 0;
|
||||
uint32_t _active_images_allocations = 0;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
/* By: maldavid <kbz_8.dev@akel-engine.com> +#+ +:+ +#+ */
|
||||
/* +#+#+#+#+#+ +#+ */
|
||||
/* Created: 2023/04/02 17:53:06 by maldavid #+# #+# */
|
||||
/* Updated: 2023/12/15 20:31:29 by maldavid ### ########.fr */
|
||||
/* Updated: 2023/12/16 18:47:36 by maldavid ### ########.fr */
|
||||
/* */
|
||||
/* ************************************************************************** */
|
||||
|
||||
@@ -23,7 +23,7 @@ namespace mlx
|
||||
|
||||
VkResult res;
|
||||
if((res = vkCreateFence(Render_Core::get().getDevice().get(), &fenceInfo, nullptr, &_fence)) != VK_SUCCESS)
|
||||
core::error::report(e_kind::fatal_error, "Vulkan : failed to create CPU synchronization object, %s", RCore::verbaliseResultVk(res));
|
||||
core::error::report(e_kind::fatal_error, "Vulkan : failed to create a synchronization object (fence), %s", RCore::verbaliseResultVk(res));
|
||||
#ifdef DEBUG
|
||||
core::error::report(e_kind::message, "Vulkan : created new fence");
|
||||
#endif
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
/* By: maldavid <kbz_8.dev@akel-engine.com> +#+ +:+ +#+ */
|
||||
/* +#+#+#+#+#+ +#+ */
|
||||
/* Created: 2023/04/02 17:52:09 by maldavid #+# #+# */
|
||||
/* Updated: 2023/12/15 20:31:25 by maldavid ### ########.fr */
|
||||
/* Updated: 2023/12/16 17:27:28 by maldavid ### ########.fr */
|
||||
/* */
|
||||
/* ************************************************************************** */
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
/* By: maldavid <kbz_8.dev@akel-engine.com> +#+ +:+ +#+ */
|
||||
/* +#+#+#+#+#+ +#+ */
|
||||
/* Created: 2022/10/08 19:01:08 by maldavid #+# #+# */
|
||||
/* Updated: 2023/12/12 15:51:37 by kbz_8 ### ########.fr */
|
||||
/* Updated: 2023/12/16 18:47:29 by maldavid ### ########.fr */
|
||||
/* */
|
||||
/* ************************************************************************** */
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace mlx
|
||||
VkResult res;
|
||||
if( (res = vkCreateSemaphore(Render_Core::get().getDevice().get(), &semaphoreInfo, nullptr, &_imageAvailableSemaphores)) != VK_SUCCESS ||
|
||||
(res = vkCreateSemaphore(Render_Core::get().getDevice().get(), &semaphoreInfo, nullptr, &_renderFinishedSemaphores)) != VK_SUCCESS)
|
||||
core::error::report(e_kind::fatal_error, "Vulkan : failed to create GPU synchronization object, %s", RCore::verbaliseResultVk(res));
|
||||
core::error::report(e_kind::fatal_error, "Vulkan : failed to create a synchronization object (semaphore), %s", RCore::verbaliseResultVk(res));
|
||||
#ifdef DEBUG
|
||||
core::error::report(e_kind::message, "Vulkan : created new semaphore");
|
||||
#endif
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
/* By: maldavid <kbz_8.dev@akel-engine.com> +#+ +:+ +#+ */
|
||||
/* +#+#+#+#+#+ +#+ */
|
||||
/* Created: 2023/01/25 11:59:07 by maldavid #+# #+# */
|
||||
/* Updated: 2023/11/18 17:21:14 by maldavid ### ########.fr */
|
||||
/* Updated: 2023/12/16 17:10:33 by maldavid ### ########.fr */
|
||||
/* */
|
||||
/* ************************************************************************** */
|
||||
|
||||
@@ -167,8 +167,6 @@ namespace mlx
|
||||
}
|
||||
|
||||
_allocation = Render_Core::get().getAllocator().createImage(&imageInfo, &alloc_info, _image, name);
|
||||
|
||||
_pool.init();
|
||||
}
|
||||
|
||||
void Image::createImageView(VkImageViewType type, VkImageAspectFlags aspectFlags) noexcept
|
||||
@@ -209,11 +207,8 @@ namespace mlx
|
||||
|
||||
void Image::copyFromBuffer(Buffer& buffer)
|
||||
{
|
||||
if(!_transfer_cmd.isInit())
|
||||
_transfer_cmd.init(&_pool);
|
||||
|
||||
_transfer_cmd.reset();
|
||||
_transfer_cmd.beginRecord();
|
||||
CmdBuffer& cmd = Render_Core::get().getSingleTimeCmdBuffer();
|
||||
cmd.beginRecord();
|
||||
|
||||
VkImageMemoryBarrier copy_barrier{};
|
||||
copy_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
|
||||
@@ -226,7 +221,7 @@ namespace mlx
|
||||
copy_barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
copy_barrier.subresourceRange.levelCount = 1;
|
||||
copy_barrier.subresourceRange.layerCount = 1;
|
||||
vkCmdPipelineBarrier(_transfer_cmd.get(), VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, ©_barrier);
|
||||
vkCmdPipelineBarrier(cmd.get(), VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, ©_barrier);
|
||||
|
||||
VkBufferImageCopy region{};
|
||||
region.bufferOffset = 0;
|
||||
@@ -239,7 +234,7 @@ namespace mlx
|
||||
region.imageOffset = { 0, 0, 0 };
|
||||
region.imageExtent = { _width, _height, 1 };
|
||||
|
||||
vkCmdCopyBufferToImage(_transfer_cmd.get(), buffer.get(), _image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion);
|
||||
vkCmdCopyBufferToImage(cmd.get(), buffer.get(), _image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion);
|
||||
|
||||
VkImageMemoryBarrier use_barrier{};
|
||||
use_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
|
||||
@@ -253,19 +248,16 @@ namespace mlx
|
||||
use_barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
use_barrier.subresourceRange.levelCount = 1;
|
||||
use_barrier.subresourceRange.layerCount = 1;
|
||||
vkCmdPipelineBarrier(_transfer_cmd.get(), VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0, nullptr, 0, nullptr, 1, &use_barrier);
|
||||
vkCmdPipelineBarrier(cmd.get(), VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0, nullptr, 0, nullptr, 1, &use_barrier);
|
||||
|
||||
_transfer_cmd.endRecord();
|
||||
_transfer_cmd.submitIdle();
|
||||
cmd.endRecord();
|
||||
cmd.submitIdle();
|
||||
}
|
||||
|
||||
void Image::copyToBuffer(Buffer& buffer)
|
||||
{
|
||||
if(!_transfer_cmd.isInit())
|
||||
_transfer_cmd.init(&_pool);
|
||||
|
||||
_transfer_cmd.reset();
|
||||
_transfer_cmd.beginRecord();
|
||||
CmdBuffer& cmd = Render_Core::get().getSingleTimeCmdBuffer();
|
||||
cmd.beginRecord();
|
||||
|
||||
VkImageMemoryBarrier copy_barrier{};
|
||||
copy_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
|
||||
@@ -278,7 +270,7 @@ namespace mlx
|
||||
copy_barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
copy_barrier.subresourceRange.levelCount = 1;
|
||||
copy_barrier.subresourceRange.layerCount = 1;
|
||||
vkCmdPipelineBarrier(_transfer_cmd.get(), VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, ©_barrier);
|
||||
vkCmdPipelineBarrier(cmd.get(), VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, ©_barrier);
|
||||
|
||||
VkBufferImageCopy region{};
|
||||
region.bufferOffset = 0;
|
||||
@@ -291,7 +283,7 @@ namespace mlx
|
||||
region.imageOffset = { 0, 0, 0 };
|
||||
region.imageExtent = { _width, _height, 1 };
|
||||
|
||||
vkCmdCopyImageToBuffer(_transfer_cmd.get(), _image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, buffer.get(), 1, ®ion);
|
||||
vkCmdCopyImageToBuffer(cmd.get(), _image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, buffer.get(), 1, ®ion);
|
||||
|
||||
VkImageMemoryBarrier use_barrier{};
|
||||
use_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
|
||||
@@ -305,10 +297,10 @@ namespace mlx
|
||||
use_barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
use_barrier.subresourceRange.levelCount = 1;
|
||||
use_barrier.subresourceRange.layerCount = 1;
|
||||
vkCmdPipelineBarrier(_transfer_cmd.get(), VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0, nullptr, 0, nullptr, 1, &use_barrier);
|
||||
vkCmdPipelineBarrier(cmd.get(), VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0, nullptr, 0, nullptr, 1, &use_barrier);
|
||||
|
||||
_transfer_cmd.endRecord();
|
||||
_transfer_cmd.submitIdle();
|
||||
cmd.endRecord();
|
||||
cmd.submitIdle();
|
||||
}
|
||||
|
||||
void Image::transitionLayout(VkImageLayout new_layout)
|
||||
@@ -316,10 +308,8 @@ namespace mlx
|
||||
if(new_layout == _layout)
|
||||
return;
|
||||
|
||||
if(!_transfer_cmd.isInit())
|
||||
_transfer_cmd.init(&_pool);
|
||||
_transfer_cmd.reset();
|
||||
_transfer_cmd.beginRecord();
|
||||
CmdBuffer& cmd = Render_Core::get().getSingleTimeCmdBuffer();
|
||||
cmd.beginRecord();
|
||||
|
||||
VkImageMemoryBarrier barrier{};
|
||||
barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
|
||||
@@ -354,10 +344,10 @@ namespace mlx
|
||||
else
|
||||
destinationStage = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
|
||||
|
||||
vkCmdPipelineBarrier(_transfer_cmd.get(), sourceStage, destinationStage, 0, 0, nullptr, 0, nullptr, 1, &barrier);
|
||||
vkCmdPipelineBarrier(cmd.get(), sourceStage, destinationStage, 0, 0, nullptr, 0, nullptr, 1, &barrier);
|
||||
|
||||
_transfer_cmd.endRecord();
|
||||
_transfer_cmd.submitIdle();
|
||||
cmd.endRecord();
|
||||
cmd.submitIdle();
|
||||
_layout = new_layout;
|
||||
}
|
||||
|
||||
@@ -379,7 +369,6 @@ namespace mlx
|
||||
{
|
||||
destroySampler();
|
||||
destroyImageView();
|
||||
destroyCmdPool();
|
||||
|
||||
if(_image != VK_NULL_HANDLE)
|
||||
Render_Core::get().getAllocator().destroyImage(_allocation, _image);
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
/* By: maldavid <kbz_8.dev@akel-engine.com> +#+ +:+ +#+ */
|
||||
/* +#+#+#+#+#+ +#+ */
|
||||
/* Created: 2023/01/25 11:54:21 by maldavid #+# #+# */
|
||||
/* Updated: 2023/12/15 21:07:34 by maldavid ### ########.fr */
|
||||
/* Updated: 2023/12/15 21:44:30 by maldavid ### ########.fr */
|
||||
/* */
|
||||
/* ************************************************************************** */
|
||||
|
||||
@@ -39,7 +39,6 @@ namespace mlx
|
||||
_width = width;
|
||||
_height = height;
|
||||
_layout = layout;
|
||||
_pool.init();
|
||||
}
|
||||
void create(uint32_t width, uint32_t height, VkFormat format, VkImageTiling tiling, VkImageUsageFlags usage, const char* name, bool decated_memory = false);
|
||||
void createImageView(VkImageViewType type, VkImageAspectFlags aspectFlags) noexcept;
|
||||
@@ -64,11 +63,8 @@ namespace mlx
|
||||
private:
|
||||
void destroySampler() noexcept;
|
||||
void destroyImageView() noexcept;
|
||||
inline void destroyCmdPool() noexcept { _transfer_cmd.destroy(); _pool.destroy(); }
|
||||
|
||||
private:
|
||||
CmdBuffer _transfer_cmd;
|
||||
CmdPool _pool;
|
||||
VmaAllocation _allocation;
|
||||
VkImage _image = VK_NULL_HANDLE;
|
||||
VkImageView _image_view = VK_NULL_HANDLE;
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
/* By: maldavid <kbz_8.dev@akel-engine.com> +#+ +:+ +#+ */
|
||||
/* +#+#+#+#+#+ +#+ */
|
||||
/* Created: 2022/10/06 18:22:28 by maldavid #+# #+# */
|
||||
/* Updated: 2023/12/10 22:32:54 by kbz_8 ### ########.fr */
|
||||
/* Updated: 2023/12/15 21:49:19 by maldavid ### ########.fr */
|
||||
/* */
|
||||
/* ************************************************************************** */
|
||||
|
||||
@@ -146,9 +146,6 @@ namespace mlx
|
||||
vkDestroySwapchainKHR(Render_Core::get().getDevice().get(), _swapChain, nullptr);
|
||||
_swapChain = VK_NULL_HANDLE;
|
||||
for(Image& img : _images)
|
||||
{
|
||||
img.destroyImageView();
|
||||
img.destroyCmdPool();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
/* By: maldavid <kbz_8.dev@akel-engine.com> +#+ +:+ +#+ */
|
||||
/* +#+#+#+#+#+ +#+ */
|
||||
/* Created: 2022/10/04 17:55:21 by maldavid #+# #+# */
|
||||
/* Updated: 2023/12/15 21:08:07 by maldavid ### ########.fr */
|
||||
/* Updated: 2023/12/16 19:14:56 by maldavid ### ########.fr */
|
||||
/* */
|
||||
/* ************************************************************************** */
|
||||
|
||||
|
||||
Reference in New Issue
Block a user