Fixed various errors in swapchain creation and created depth image
This commit is contained in:
parent
cc49f1b704
commit
758c577161
5
.gitignore
vendored
5
.gitignore
vendored
@ -9,10 +9,12 @@
|
||||
*.user
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
.vscode/
|
||||
|
||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||
*.userprefs
|
||||
|
||||
|
||||
# Mono auto generated files
|
||||
mono_crash.*
|
||||
|
||||
@ -34,6 +36,9 @@ bld/
|
||||
[Ll]ogs/
|
||||
[Ll]ib/
|
||||
|
||||
# Cmake directory
|
||||
[Bb]uild/
|
||||
|
||||
# Visual Studio 2015/2017 cache/options directory
|
||||
.vs/
|
||||
# Uncomment if you have tasks that create the project's static files in wwwroot
|
||||
|
||||
@ -3,16 +3,21 @@
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
set(CMAKE_CXX_FLAGS "-DDEBUG")
|
||||
set(CMAKE_CXX_FLAGS "-fsanitize=address -g -DDEBUG -D_DEBUG")
|
||||
|
||||
project(Pleascach)
|
||||
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake")
|
||||
if(WIN32)
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake")
|
||||
endif()
|
||||
|
||||
file(GLOB SOURCES
|
||||
pléascach.cpp
|
||||
*/*.cpp
|
||||
)
|
||||
|
||||
add_executable (Pleascach ${SOURCES})
|
||||
add_executable (pleascach ${SOURCES})
|
||||
|
||||
find_package(glfw3 3.3 REQUIRED)
|
||||
find_package(Vulkan REQUIRED)
|
||||
@ -21,7 +26,8 @@ if(UNIX)
|
||||
set(GLFW3_LIBRARY glfw)
|
||||
endif()
|
||||
|
||||
|
||||
include_directories(${CMAKE_CURRENT_LIST_DIR} include ${GLFW3_INCLUDE_DIR})
|
||||
|
||||
target_link_libraries(Pleascach ${GLFW3_LIBRARY} Vulkan::Vulkan)
|
||||
target_link_libraries(pleascach ${GLFW3_LIBRARY} Vulkan::Vulkan)
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
#include <GLFW/glfw3.h>
|
||||
#define INPUT_PTR GLFWwindow*
|
||||
|
||||
#include <input/input.hpp>
|
||||
#include <Input/Input.hpp>
|
||||
|
||||
#include <util/log.hpp>
|
||||
|
||||
@ -32,6 +32,7 @@ constexpr u32 operator ~ (InputModifierBit a) {
|
||||
|
||||
struct InputEvent {
|
||||
enum Tag {
|
||||
EXIT,
|
||||
RESIZE,
|
||||
CURSOR,
|
||||
KEY,
|
||||
@ -1,9 +1,12 @@
|
||||
#include <memory/memory.hpp>
|
||||
#include <Memory/Memory.hpp>
|
||||
#include "Memory.hpp"
|
||||
|
||||
namespace mem {
|
||||
u32 choose_heap(vk::PhysicalDevice& phys_dev, const vk::MemoryRequirements& requirements, vk::MemoryPropertyFlags req_flags, vk::MemoryPropertyFlags pref_flags) {
|
||||
auto dev_props = phys_dev.getMemoryProperties();
|
||||
|
||||
if(static_cast<int>(pref_flags.operator VkImageCreateFlags()) != -1) {
|
||||
|
||||
/* try to find an exact match for preferred flags first */
|
||||
for (u32 memory_type = 0; memory_type < 32; memory_type++) {
|
||||
if (requirements.memoryTypeBits & (1 << memory_type)) {
|
||||
@ -13,6 +16,7 @@ namespace mem {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (u32 memory_type = 0; memory_type < 32; memory_type++) {
|
||||
if (requirements.memoryTypeBits & (1 << memory_type)) {
|
||||
@ -22,5 +26,7 @@ namespace mem {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
@ -5,5 +5,5 @@
|
||||
#include <util/int.hpp>
|
||||
|
||||
namespace mem {
|
||||
u32 choose_heap(vk::PhysicalDevice& phys_dev, const vk::MemoryRequirements& requirements, vk::MemoryPropertyFlags req_flags, vk::MemoryPropertyFlags pref_flags);
|
||||
u32 choose_heap(vk::PhysicalDevice& phys_dev, const vk::MemoryRequirements& requirements, vk::MemoryPropertyFlags req_flags, vk::MemoryPropertyFlags pref_flags = vk::MemoryPropertyFlagBits(-1));
|
||||
}
|
||||
@ -2,4 +2,5 @@
|
||||
|
||||
|
||||
## Long Term Improvements
|
||||
- Properly query surface to find supported formats for surfaces
|
||||
- Add pipeline caching
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
#include <renderer/command_buffer.hpp>
|
||||
#include <Renderer/CommandBuffer.hpp>
|
||||
#include <Renderer/Pipeline.hpp>
|
||||
|
||||
|
||||
CommandBuffer::CommandBuffer(vk::Device dev, u32 queue_family) {
|
||||
/* (For now) allow command buffers to be individually recycled */
|
||||
@ -17,7 +19,8 @@ CommandBuffer::CommandBuffer(vk::Device dev, u32 queue_family) {
|
||||
.commandBufferCount = 1,
|
||||
};
|
||||
|
||||
dev.allocateCommandBuffers(alloc_info);
|
||||
|
||||
command_buffer = dev.allocateCommandBuffers(alloc_info)[0];
|
||||
}
|
||||
|
||||
void CommandBuffer::begin() {
|
||||
@ -33,6 +36,14 @@ void CommandBuffer::copy(vk::Buffer in, vk::Buffer out, vk::ArrayProxy<const vk:
|
||||
command_buffer.copyBuffer(in, out, regions);
|
||||
}
|
||||
|
||||
void CommandBuffer::bind(const GraphicsPipeline& pipeline) {
|
||||
command_buffer.bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline.pipeline);
|
||||
}
|
||||
|
||||
void CommandBuffer::bind(const ComputePipeline& pipeline) {
|
||||
command_buffer.bindPipeline(vk::PipelineBindPoint::eCompute, pipeline.pipeline);
|
||||
}
|
||||
|
||||
void CommandBuffer::end() {
|
||||
command_buffer.end();
|
||||
}
|
||||
@ -42,6 +53,5 @@ void CommandBuffer::recycle() {
|
||||
}
|
||||
|
||||
void CommandBuffer::cleanup(vk::Device dev) {
|
||||
dev.freeCommandBuffers(command_pool, command_buffer);
|
||||
dev.destroyCommandPool(command_pool);
|
||||
}
|
||||
@ -5,6 +5,9 @@
|
||||
|
||||
#include <util/int.hpp>
|
||||
|
||||
struct GraphicsPipeline;
|
||||
struct ComputePipeline;
|
||||
|
||||
struct CommandBuffer {
|
||||
CommandBuffer(vk::Device dev, u32 queue_family);
|
||||
|
||||
@ -16,6 +19,9 @@ struct CommandBuffer {
|
||||
/* copy between buffer */
|
||||
void copy(vk::Buffer in, vk::Buffer out, vk::ArrayProxy<const vk::BufferCopy> regions);
|
||||
|
||||
void bind(const GraphicsPipeline& pipeline);
|
||||
void bind(const ComputePipeline& pipeline);
|
||||
|
||||
/* stop recording commands */
|
||||
void end();
|
||||
|
||||
19
Renderer/Pipeline.cpp
Normal file
19
Renderer/Pipeline.cpp
Normal file
@ -0,0 +1,19 @@
|
||||
#include <Renderer/Pipeline.hpp>
|
||||
|
||||
#include <Renderer/Shader.hpp>
|
||||
|
||||
ComputePipeline::ComputePipeline(vk::Device dev, const Shader& shader) {
|
||||
auto shader_info = vk::PipelineShaderStageCreateInfo {
|
||||
.stage = vk::ShaderStageFlagBits::eCompute,
|
||||
.module = shader,
|
||||
.pName = "main",
|
||||
};
|
||||
|
||||
auto layout = vk::PipelineLayoutCreateInfo {
|
||||
|
||||
};
|
||||
|
||||
auto create_info = vk::ComputePipelineCreateInfo {
|
||||
.stage = shader_info,
|
||||
};
|
||||
}
|
||||
27
Renderer/Pipeline.hpp
Normal file
27
Renderer/Pipeline.hpp
Normal file
@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
|
||||
#define VULKAN_HPP_NO_STRUCT_CONSTRUCTORS
|
||||
#include <vulkan/vulkan.hpp>
|
||||
|
||||
struct Shader;
|
||||
|
||||
struct ComputePipeline {
|
||||
ComputePipeline(vk::Device dev, const Shader& shader);
|
||||
|
||||
vk::Pipeline pipeline;
|
||||
inline operator vk::Pipeline&() {
|
||||
return pipeline;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
struct GraphicsPipeline {
|
||||
GraphicsPipeline(vk::Device dev);
|
||||
vk::Pipeline pipeline;
|
||||
|
||||
inline operator vk::Pipeline&() {
|
||||
return pipeline;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -1,18 +1,20 @@
|
||||
#include <renderer/renderer.hpp>
|
||||
#include <window/window.hpp>
|
||||
#include <Renderer/Renderer.hpp>
|
||||
#include <Window/Window.hpp>
|
||||
|
||||
#include <util/log.hpp>
|
||||
|
||||
#include <Memory/Memory.hpp>
|
||||
|
||||
using namespace std::string_literals;
|
||||
|
||||
Renderer::Renderer(Window& win) : win(win) {
|
||||
/* Create Instance object */
|
||||
auto app_info = vk::ApplicationInfo {
|
||||
.pApplicationName = "Pléascach Demo",
|
||||
.pApplicationName = "Pl<EFBFBD>ascach Demo",
|
||||
.applicationVersion = VK_MAKE_API_VERSION(0, 0, 1, 0),
|
||||
.pEngineName = "Pléascach",
|
||||
.pEngineName = "Pl<EFBFBD>ascach",
|
||||
.engineVersion = VK_MAKE_API_VERSION(0, 0, 1, 0),
|
||||
.apiVersion = VK_API_VERSION_1_0,
|
||||
.apiVersion = VK_API_VERSION_1_1,
|
||||
};
|
||||
|
||||
const auto req_extensions = win.requiredExtensions();
|
||||
@ -29,7 +31,7 @@ Renderer::Renderer(Window& win) : win(win) {
|
||||
}
|
||||
|
||||
/* query and enable available layers if in DEBUG mode */
|
||||
#ifdef _DEBUG
|
||||
#ifdef DEBUG
|
||||
|
||||
auto layers = vk::enumerateInstanceLayerProperties();
|
||||
Log::info("%zu available instance layers\n", layers.size());
|
||||
@ -46,8 +48,8 @@ Renderer::Renderer(Window& win) : win(win) {
|
||||
.pApplicationInfo = &app_info,
|
||||
.enabledLayerCount = std::size(my_layers),
|
||||
.ppEnabledLayerNames = my_layers,
|
||||
.enabledExtensionCount = static_cast<u32>(extensions.size()),
|
||||
.ppEnabledExtensionNames = extension_names.data(),
|
||||
.enabledExtensionCount = static_cast<u32>(req_extensions.size()),
|
||||
.ppEnabledExtensionNames = req_extensions.data(),
|
||||
};
|
||||
|
||||
#else
|
||||
@ -55,8 +57,8 @@ Renderer::Renderer(Window& win) : win(win) {
|
||||
.pApplicationInfo = &app_info,
|
||||
.enabledLayerCount = 0,
|
||||
.ppEnabledLayerNames = nullptr,
|
||||
.enabledExtensionCount = static_cast<u32>(extensions.size()),
|
||||
.ppEnabledExtensionNames = extensions.data(),
|
||||
.enabledExtensionCount = static_cast<u32>(req_extensions.size()),
|
||||
.ppEnabledExtensionNames = req_extensions.data(),
|
||||
};
|
||||
#endif
|
||||
|
||||
@ -108,11 +110,11 @@ Renderer::Renderer(Window& win) : win(win) {
|
||||
|
||||
/* enumerate available device features */
|
||||
std::vector<const char*> required_extensions;
|
||||
required_extensions.push_back("VK_KHR_swapchain");
|
||||
auto dev_extentions = phys_dev.enumerateDeviceExtensionProperties();
|
||||
Log::info("%zu available device extensions\n", dev_extentions.size());
|
||||
for (const auto& ext : dev_extentions) {
|
||||
Log::info("\t\"%s\"\n", ext.extensionName.data());
|
||||
required_extensions.push_back(ext.extensionName);
|
||||
}
|
||||
|
||||
auto dev_layers = phys_dev.enumerateDeviceLayerProperties();
|
||||
@ -124,7 +126,6 @@ Renderer::Renderer(Window& win) : win(win) {
|
||||
"VK_LAYER_KHRONOS_validation"
|
||||
};
|
||||
|
||||
#pragma message("TODO: MAKE THIS NO LONGER EVERY SINGLE EXTENTION NAME")
|
||||
auto dev_info = vk::DeviceCreateInfo{
|
||||
.flags = vk::DeviceCreateFlagBits(0),
|
||||
.queueCreateInfoCount = 1,
|
||||
@ -147,6 +148,66 @@ Renderer::Renderer(Window& win) : win(win) {
|
||||
swapchain = std::make_unique<Swapchain>(dev, surface, win.getDimensions());
|
||||
|
||||
queue = dev.getQueue(queue_family, 0);
|
||||
|
||||
|
||||
/* depth image */
|
||||
/* 1. create an Image to be the buffer
|
||||
* 2. find memory req's
|
||||
* 3. allocate
|
||||
* 4. set layout
|
||||
* 5. create attachment view
|
||||
*/
|
||||
auto extent = win.getDimensions();
|
||||
auto depth_image_info = vk::ImageCreateInfo {
|
||||
.imageType = vk::ImageType::e2D,
|
||||
.format = vk::Format::eD16Unorm,
|
||||
.extent = {
|
||||
.width = extent.width,
|
||||
.height = extent.height,
|
||||
.depth = 1,
|
||||
},
|
||||
.mipLevels = 1,
|
||||
.arrayLayers = 1,
|
||||
.samples = vk::SampleCountFlagBits::e1,
|
||||
.usage = vk::ImageUsageFlagBits::eDepthStencilAttachment,
|
||||
.sharingMode = vk::SharingMode::eExclusive,
|
||||
.queueFamilyIndexCount = 0,
|
||||
.pQueueFamilyIndices = NULL,
|
||||
.initialLayout = vk::ImageLayout::eUndefined,
|
||||
};
|
||||
|
||||
depth_image = dev.createImage(depth_image_info);
|
||||
|
||||
auto depth_mem_reqs = dev.getImageMemoryRequirements(depth_image);
|
||||
|
||||
auto depth_alloc_info = vk::MemoryAllocateInfo {
|
||||
.allocationSize = depth_mem_reqs.size,
|
||||
.memoryTypeIndex = mem::choose_heap(phys_dev, depth_mem_reqs, vk::MemoryPropertyFlagBits::eDeviceLocal),
|
||||
};
|
||||
|
||||
auto depth_alloc = dev.allocateMemory(depth_alloc_info);
|
||||
dev.bindImageMemory(depth_image, depth_alloc, 0);
|
||||
|
||||
auto depth_view_info = vk::ImageViewCreateInfo {
|
||||
.image = depth_image,
|
||||
.viewType = vk::ImageViewType::e2D,
|
||||
.format = vk::Format::eD16Unorm,
|
||||
.components = {
|
||||
.r = vk::ComponentSwizzle::eR,
|
||||
.g = vk::ComponentSwizzle::eG,
|
||||
.b = vk::ComponentSwizzle::eB,
|
||||
.a = vk::ComponentSwizzle::eA,
|
||||
},
|
||||
.subresourceRange = {
|
||||
.aspectMask = vk::ImageAspectFlagBits::eDepth,
|
||||
.baseMipLevel = 0,
|
||||
.levelCount = 1,
|
||||
.baseArrayLayer = 0,
|
||||
.layerCount = 1,
|
||||
},
|
||||
};
|
||||
|
||||
depth_image_view = dev.createImageView(depth_view_info);
|
||||
}
|
||||
|
||||
void Renderer::draw() {
|
||||
@ -161,6 +222,8 @@ void Renderer::draw() {
|
||||
command_buffer->recycle();
|
||||
command_buffer->begin();
|
||||
|
||||
|
||||
|
||||
command_buffer->end();
|
||||
}
|
||||
|
||||
@ -185,7 +248,9 @@ void Renderer::present() {
|
||||
}
|
||||
|
||||
Renderer::~Renderer() {
|
||||
command_buffer->cleanup(dev);
|
||||
dev.destroyImage(depth_image);
|
||||
dev.destroyImageView(depth_image_view);
|
||||
|
||||
swapchain.reset();
|
||||
dev.waitIdle();
|
||||
dev.destroy();
|
||||
|
||||
@ -5,8 +5,8 @@
|
||||
#define VULKAN_HPP_NO_STRUCT_CONSTRUCTORS
|
||||
#include <vulkan/vulkan.hpp>
|
||||
|
||||
#include <renderer/swapchain.hpp>
|
||||
#include <renderer/command_buffer.hpp>
|
||||
#include <Renderer/Swapchain.hpp>
|
||||
#include <Renderer/CommandBuffer.hpp>
|
||||
|
||||
struct Window;
|
||||
|
||||
@ -19,12 +19,19 @@ struct Renderer {
|
||||
void present();
|
||||
|
||||
Window& win;
|
||||
|
||||
vk::Instance instance;
|
||||
vk::Device dev;
|
||||
|
||||
vk::SurfaceKHR surface;
|
||||
std::unique_ptr<Swapchain> swapchain;
|
||||
|
||||
int queue_family;
|
||||
vk::Queue queue;
|
||||
|
||||
std::unique_ptr<CommandBuffer> command_buffer;
|
||||
uint32_t current_image_idx;
|
||||
|
||||
vk::Image depth_image;
|
||||
vk::ImageView depth_image_view;
|
||||
};
|
||||
@ -1,4 +1,4 @@
|
||||
#include <renderer/shader.hpp>
|
||||
#include <Renderer/Shader.hpp>
|
||||
|
||||
#include <util/log.hpp>
|
||||
|
||||
|
||||
@ -9,7 +9,9 @@ struct Shader {
|
||||
Shader(vk::Device dev, const std::string& fname);
|
||||
void cleanup(vk::Device dev);
|
||||
|
||||
|
||||
inline operator vk::ShaderModule() const {
|
||||
return module;
|
||||
}
|
||||
|
||||
inline operator vk::ShaderModule& () {
|
||||
return module;
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
#include <renderer/swapchain.hpp>
|
||||
#include <Renderer/Swapchain.hpp>
|
||||
|
||||
Swapchain::Swapchain(vk::Device& dev, const vk::SurfaceKHR& surface, const vk::Extent2D& extent) : dev(dev), surface(surface) {
|
||||
create(extent);
|
||||
@ -6,11 +6,13 @@ Swapchain::Swapchain(vk::Device& dev, const vk::SurfaceKHR& surface, const vk::E
|
||||
|
||||
void Swapchain::create(const vk::Extent2D& extent, vk::SwapchainKHR old_swapchain) {
|
||||
|
||||
auto format = vk::Format::eB8G8R8A8Unorm;
|
||||
|
||||
auto swap_info = vk::SwapchainCreateInfoKHR{
|
||||
.surface = surface,
|
||||
/* at least double-buffered */
|
||||
.minImageCount = 3,
|
||||
.imageFormat = vk::Format::eR8G8B8A8Unorm,
|
||||
.imageFormat = format,
|
||||
.imageColorSpace = vk::ColorSpaceKHR::eSrgbNonlinear,
|
||||
.imageExtent = extent,
|
||||
.imageArrayLayers = 1,
|
||||
@ -30,6 +32,29 @@ void Swapchain::create(const vk::Extent2D& extent, vk::SwapchainKHR old_swapchai
|
||||
swapchain = dev.createSwapchainKHR(swap_info);
|
||||
|
||||
images = dev.getSwapchainImagesKHR(swapchain);
|
||||
views.resize(images.size());
|
||||
for(size_t i = 0; i < views.size(); i++) {
|
||||
auto color_image_info = vk::ImageViewCreateInfo {
|
||||
.image = images[i],
|
||||
.viewType = vk::ImageViewType::e2D,
|
||||
.format = format,
|
||||
.components = {
|
||||
.r = vk::ComponentSwizzle::eR,
|
||||
.g = vk::ComponentSwizzle::eG,
|
||||
.b = vk::ComponentSwizzle::eB,
|
||||
.a = vk::ComponentSwizzle::eA,
|
||||
},
|
||||
.subresourceRange = {
|
||||
.aspectMask = vk::ImageAspectFlagBits::eColor,
|
||||
.baseMipLevel = 0,
|
||||
.levelCount = 1,
|
||||
.baseArrayLayer = 0,
|
||||
.layerCount = 1,
|
||||
}
|
||||
};
|
||||
|
||||
views[i] = dev.createImageView(color_image_info);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -13,6 +13,7 @@ struct Swapchain {
|
||||
vk::Device& dev;
|
||||
vk::SurfaceKHR surface;
|
||||
std::vector<vk::Image> images;
|
||||
std::vector<vk::ImageView> views;
|
||||
|
||||
void create(const vk::Extent2D& extent, vk::SwapchainKHR old_swapchain = nullptr);
|
||||
void recreate(const vk::Extent2D& extent);
|
||||
|
||||
@ -4,8 +4,8 @@
|
||||
|
||||
#define WINDOW_PTR GLFWwindow*
|
||||
#define INPUT_PTR GLFWwindow*
|
||||
#include <window/window.hpp>
|
||||
#include <input/input.hpp>
|
||||
#include <Window/Window.hpp>
|
||||
#include <Input/Input.hpp>
|
||||
|
||||
#include <util/log.hpp>
|
||||
|
||||
|
||||
@ -5,7 +5,9 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <input/input.hpp>
|
||||
#include <memory>
|
||||
|
||||
#include <Input/Input.hpp>
|
||||
|
||||
#ifndef WINDOW_PTR
|
||||
#define WINDOW_PTR void*
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
#include <window/window.hpp>
|
||||
#include <input/input.hpp>
|
||||
#include <renderer/renderer.hpp>
|
||||
#include <Window/Window.hpp>
|
||||
#include <Input/Input.hpp>
|
||||
#include <Renderer/Renderer.hpp>
|
||||
|
||||
#include <util/log.hpp>
|
||||
|
||||
@ -30,6 +30,8 @@ int main() {
|
||||
break;
|
||||
case InputEvent::Tag::KEY:
|
||||
Log::info("Event Processed: Button 0x%x %d\n", event.key.key, event.key.state);
|
||||
break;
|
||||
case InputEvent::Tag::EXIT:
|
||||
win.close();
|
||||
break;
|
||||
}
|
||||
|
||||
@ -1,15 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#define VULKAN_HPP_NO_STRUCT_CONSTRUCTORS
|
||||
#include <vulkan/vulkan.hpp>
|
||||
|
||||
struct ComputePipeline {
|
||||
ComputePipeline(vk::Device dev) {
|
||||
}
|
||||
|
||||
vk::Pipeline pipeline;
|
||||
inline operator vk::Pipeline&() {
|
||||
return pipeline;
|
||||
}
|
||||
};
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user