mirror of
https://github.com/bailwillharr/engine.git
synced 2024-09-21 04:51:18 +00:00
Test fullscreen exclusive support
This commit is contained in:
parent
010ecb1641
commit
e1fd364027
@ -28,7 +28,7 @@ set(SRC_FILES
|
||||
"src/systems/collisions.cpp"
|
||||
"src/systems/custom_behaviour.cpp"
|
||||
"src/systems/mesh_render_system.cpp"
|
||||
"src/systems/text_render_system.cpp"
|
||||
"src/systems/ui_render_system.cpp"
|
||||
"src/systems/transform.cpp"
|
||||
"src/util/files.cpp"
|
||||
"src/util/model_loader.cpp"
|
||||
@ -48,7 +48,7 @@ set(INCLUDE_FILES
|
||||
"include/components/collider.h"
|
||||
"include/components/custom.h"
|
||||
"include/components/mesh_renderable.h"
|
||||
"include/components/text_renderable.h"
|
||||
"include/components/ui_renderable.h"
|
||||
"include/components/transform.h"
|
||||
"include/ecs_system.h"
|
||||
"include/engine_api.h"
|
||||
@ -72,7 +72,7 @@ set(INCLUDE_FILES
|
||||
"include/systems/collisions.h"
|
||||
"include/systems/custom_behaviour.h"
|
||||
"include/systems/mesh_render_system.h"
|
||||
"include/systems/text_render_system.h"
|
||||
"include/systems/ui_render_system.h"
|
||||
"include/systems/transform.h"
|
||||
"include/util.h"
|
||||
"include/util/files.h"
|
||||
|
2
dependencies/VulkanMemoryAllocator
vendored
2
dependencies/VulkanMemoryAllocator
vendored
@ -1 +1 @@
|
||||
Subproject commit 0e89587db3ebee4d463f191bd296374c5fafc8ea
|
||||
Subproject commit c351692490513cdb0e5a2c925aaf7ea4a9b672f4
|
@ -8,7 +8,7 @@
|
||||
|
||||
namespace engine {
|
||||
|
||||
struct RenderableComponent {
|
||||
struct MeshRenderableComponent {
|
||||
std::shared_ptr<resources::Mesh> mesh;
|
||||
std::shared_ptr<resources::Material> material;
|
||||
};
|
||||
|
@ -1,8 +0,0 @@
|
||||
#ifndef ENGINE_INCLUDE_COMPONENTS_TEXT_RENDERABLE_H_
|
||||
#define ENGINE_INCLUDE_COMPONENTS_TEXT_RENDERABLE_H_
|
||||
|
||||
namespace engine {
|
||||
|
||||
} // namespace engine
|
||||
|
||||
#endif
|
9
include/components/ui_renderable.h
Normal file
9
include/components/ui_renderable.h
Normal file
@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
namespace engine {
|
||||
|
||||
struct UIRenderableComponent {
|
||||
int x;
|
||||
};
|
||||
|
||||
} // namespace engine
|
@ -1,8 +0,0 @@
|
||||
#ifndef ENGINE_INCLUDE_SYSTEMS_TEXT_RENDER_SYSTEM_H_
|
||||
#define ENGINE_INCLUDE_SYSTEMS_TEXT_RENDER_SYSTEM_H_
|
||||
|
||||
namespace engine {
|
||||
|
||||
} // namespace engine
|
||||
|
||||
#endif
|
23
include/systems/ui_render_system.h
Normal file
23
include/systems/ui_render_system.h
Normal file
@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
#include <glm/mat4x4.hpp>
|
||||
|
||||
#include "ecs_system.h"
|
||||
#include "scene.h"
|
||||
#include "gfx.h"
|
||||
|
||||
namespace engine {
|
||||
|
||||
class UIRenderSystem : public System {
|
||||
public:
|
||||
UIRenderSystem(Scene* scene);
|
||||
~UIRenderSystem();
|
||||
|
||||
void OnComponentInsert(uint32_t entity) override;
|
||||
void OnUpdate(float ts) override;
|
||||
|
||||
private:
|
||||
|
||||
};
|
||||
|
||||
} // namespace engine
|
@ -271,7 +271,7 @@ static VkShaderModule compileShader(VkDevice device, shaderc_shader_kind kind,
|
||||
VkShaderModuleCreateInfo createInfo{};
|
||||
createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
|
||||
createInfo.codeSize = shaderBytecode.size() * sizeof(uint32_t);
|
||||
createInfo.pCode = compiledShader.cbegin();
|
||||
createInfo.pCode = compiledShader.cbegin();
|
||||
|
||||
VkShaderModule shaderModule;
|
||||
if (vkCreateShaderModule(device, &createInfo, nullptr, &shaderModule) !=
|
||||
@ -398,6 +398,8 @@ GFXDevice::GFXDevice(const char* appName, const char* appVersion,
|
||||
DeviceRequirements deviceRequirements{};
|
||||
deviceRequirements.requiredExtensions.push_back(
|
||||
VK_KHR_SWAPCHAIN_EXTENSION_NAME);
|
||||
deviceRequirements.requiredExtensions.push_back(
|
||||
VK_EXT_FULL_SCREEN_EXCLUSIVE_EXTENSION_NAME);
|
||||
deviceRequirements.optionalExtensions.push_back(
|
||||
VK_EXT_MEMORY_PRIORITY_EXTENSION_NAME);
|
||||
deviceRequirements.optionalExtensions.push_back(
|
||||
@ -856,6 +858,8 @@ void GFXDevice::CmdPushConstants(gfx::DrawBuffer* drawBuffer,
|
||||
const gfx::Pipeline* pipeline, uint32_t offset,
|
||||
uint32_t size, const void* data) {
|
||||
assert(drawBuffer != nullptr);
|
||||
assert(pipeline != nullptr);
|
||||
assert(data != nullptr);
|
||||
vkCmdPushConstants(drawBuffer->frameData.drawBuf, pipeline->layout,
|
||||
VK_SHADER_STAGE_VERTEX_BIT, offset, size, data);
|
||||
}
|
||||
@ -864,6 +868,9 @@ void GFXDevice::CmdBindDescriptorSet(gfx::DrawBuffer* drawBuffer,
|
||||
const gfx::Pipeline* pipeline,
|
||||
const gfx::DescriptorSet* set,
|
||||
uint32_t setNumber) {
|
||||
assert(drawBuffer != nullptr);
|
||||
assert(pipeline != nullptr);
|
||||
assert(set != nullptr);
|
||||
vkCmdBindDescriptorSets(
|
||||
drawBuffer->frameData.drawBuf, VK_PIPELINE_BIND_POINT_GRAPHICS,
|
||||
pipeline->layout, setNumber, 1, &set->sets[drawBuffer->currentFrameIndex],
|
||||
@ -1092,9 +1099,9 @@ gfx::Pipeline* GFXDevice::CreatePipeline(const gfx::PipelineInfo& info) {
|
||||
}
|
||||
|
||||
void GFXDevice::DestroyPipeline(const gfx::Pipeline* pipeline) {
|
||||
assert(pipeline != nullptr);
|
||||
vkDestroyPipeline(pimpl->device.device, pipeline->handle, nullptr);
|
||||
vkDestroyPipelineLayout(pimpl->device.device, pipeline->layout, nullptr);
|
||||
|
||||
delete pipeline;
|
||||
}
|
||||
|
||||
@ -1138,6 +1145,8 @@ void GFXDevice::DestroyDescriptorSetLayout(
|
||||
|
||||
gfx::DescriptorSet* GFXDevice::AllocateDescriptorSet(
|
||||
const gfx::DescriptorSetLayout* layout) {
|
||||
assert(layout != nullptr);
|
||||
|
||||
gfx::DescriptorSet* set = new gfx::DescriptorSet{};
|
||||
|
||||
for (uint32_t i = 0; i < FRAMES_IN_FLIGHT; i++) {
|
||||
@ -1161,14 +1170,18 @@ gfx::DescriptorSet* GFXDevice::AllocateDescriptorSet(
|
||||
}
|
||||
|
||||
void GFXDevice::FreeDescriptorSet(const gfx::DescriptorSet* set) {
|
||||
assert(set != nullptr);
|
||||
VKCHECK(vkFreeDescriptorSets(pimpl->device.device, pimpl->descriptorPool,
|
||||
set->sets.size(), set->sets.data()));
|
||||
static_cast<uint32_t>(set->sets.size()), set->sets.data()));
|
||||
}
|
||||
|
||||
void GFXDevice::UpdateDescriptorUniformBuffer(const gfx::DescriptorSet* set,
|
||||
uint32_t binding,
|
||||
const gfx::UniformBuffer* buffer,
|
||||
size_t offset, size_t range) {
|
||||
assert(set != nullptr);
|
||||
assert(buffer != nullptr);
|
||||
|
||||
assert(pimpl->FRAMECOUNT == 0);
|
||||
|
||||
for (uint32_t i = 0; i < FRAMES_IN_FLIGHT; i++) {
|
||||
@ -1194,7 +1207,11 @@ void GFXDevice::UpdateDescriptorUniformBuffer(const gfx::DescriptorSet* set,
|
||||
void GFXDevice::UpdateDescriptorCombinedImageSampler(
|
||||
const gfx::DescriptorSet* set, uint32_t binding, const gfx::Image* image,
|
||||
const gfx::Sampler* sampler) {
|
||||
if (pimpl->FRAMECOUNT != 0) abort();
|
||||
assert(set != nullptr);
|
||||
assert(image != nullptr);
|
||||
assert(sampler != nullptr);
|
||||
|
||||
if (pimpl->FRAMECOUNT != 0) abort(); // TODO. This is annoying
|
||||
|
||||
VkDescriptorImageInfo imageInfo{};
|
||||
imageInfo.sampler = sampler->sampler;
|
||||
@ -1220,6 +1237,8 @@ void GFXDevice::UpdateDescriptorCombinedImageSampler(
|
||||
|
||||
gfx::UniformBuffer* GFXDevice::CreateUniformBuffer(uint64_t size,
|
||||
const void* initialData) {
|
||||
assert(initialData != nullptr);
|
||||
|
||||
gfx::UniformBuffer* out = new gfx::UniformBuffer{};
|
||||
|
||||
/* first make staging buffer */
|
||||
@ -1283,6 +1302,8 @@ gfx::UniformBuffer* GFXDevice::CreateUniformBuffer(uint64_t size,
|
||||
}
|
||||
|
||||
void GFXDevice::DestroyUniformBuffer(const gfx::UniformBuffer* uniformBuffer) {
|
||||
assert(uniformBuffer != nullptr);
|
||||
|
||||
for (uint32_t i = 0; i < FRAMES_IN_FLIGHT; i++) {
|
||||
vmaDestroyBuffer(pimpl->allocator, uniformBuffer->gpuBuffers[i].buffer,
|
||||
uniformBuffer->gpuBuffers[i].allocation);
|
||||
@ -1296,6 +1317,9 @@ void GFXDevice::DestroyUniformBuffer(const gfx::UniformBuffer* uniformBuffer) {
|
||||
|
||||
void GFXDevice::WriteUniformBuffer(gfx::UniformBuffer* buffer, uint64_t offset,
|
||||
uint64_t size, const void* data) {
|
||||
assert(buffer != nullptr);
|
||||
assert(data != nullptr);
|
||||
|
||||
assert(offset + size <= buffer->stagingBuffer.size);
|
||||
|
||||
/* first update the staging buffer */
|
||||
@ -1314,7 +1338,7 @@ void GFXDevice::WriteUniformBuffer(gfx::UniformBuffer* buffer, uint64_t offset,
|
||||
|
||||
gfx::Buffer* GFXDevice::CreateBuffer(gfx::BufferType type, uint64_t size,
|
||||
const void* data) {
|
||||
[[maybe_unused]] VkResult res;
|
||||
assert(data != nullptr);
|
||||
|
||||
auto out = new gfx::Buffer{};
|
||||
out->size = size;
|
||||
@ -1379,6 +1403,7 @@ gfx::Buffer* GFXDevice::CreateBuffer(gfx::BufferType type, uint64_t size,
|
||||
}
|
||||
|
||||
void GFXDevice::DestroyBuffer(const gfx::Buffer* buffer) {
|
||||
assert(buffer != nullptr);
|
||||
vmaDestroyBuffer(pimpl->allocator, buffer->buffer, buffer->allocation);
|
||||
delete buffer;
|
||||
}
|
||||
@ -1387,9 +1412,8 @@ void GFXDevice::DestroyBuffer(const gfx::Buffer* buffer) {
|
||||
gfx::Image* GFXDevice::CreateImage(uint32_t w, uint32_t h,
|
||||
const void* imageData) {
|
||||
assert(imageData != nullptr);
|
||||
if (pimpl->FRAMECOUNT != 0) {
|
||||
throw std::runtime_error("Framecount must be 0 when creating a texture");
|
||||
}
|
||||
|
||||
if (pimpl->FRAMECOUNT != 0) abort(); // TODO. This is annoying
|
||||
|
||||
gfx::Image* out = new gfx::Image{};
|
||||
|
||||
@ -1660,6 +1684,7 @@ gfx::Image* GFXDevice::CreateImage(uint32_t w, uint32_t h,
|
||||
}
|
||||
|
||||
void GFXDevice::DestroyImage(const gfx::Image* image) {
|
||||
assert(image != nullptr);
|
||||
vkDestroyImageView(pimpl->device.device, image->view, nullptr);
|
||||
vmaDestroyImage(pimpl->allocator, image->image, image->allocation);
|
||||
delete image;
|
||||
@ -1691,6 +1716,7 @@ const gfx::Sampler* GFXDevice::CreateSampler(const gfx::SamplerInfo& info) {
|
||||
}
|
||||
|
||||
void GFXDevice::DestroySampler(const gfx::Sampler* sampler) {
|
||||
assert(sampler != nullptr);
|
||||
vkDestroySampler(pimpl->device.device, sampler->sampler, nullptr);
|
||||
delete sampler;
|
||||
}
|
||||
|
@ -1,11 +1,13 @@
|
||||
#include "scene.h"
|
||||
|
||||
#include "components/transform.h"
|
||||
#include "components/mesh_renderable.h"
|
||||
#include "components/collider.h"
|
||||
#include "components/custom.h"
|
||||
#include "components/mesh_renderable.h"
|
||||
#include "components/ui_renderable.h"
|
||||
#include "systems/transform.h"
|
||||
#include "systems/mesh_render_system.h"
|
||||
#include "systems/ui_render_system.h"
|
||||
#include "systems/collisions.h"
|
||||
#include "systems/custom_behaviour.h"
|
||||
|
||||
@ -18,15 +20,17 @@ Scene::Scene(Application* app) : app_(app) {
|
||||
// ecs configuration:
|
||||
|
||||
RegisterComponent<TransformComponent>();
|
||||
RegisterComponent<RenderableComponent>();
|
||||
RegisterComponent<ColliderComponent>();
|
||||
RegisterComponent<CustomComponent>();
|
||||
RegisterComponent<MeshRenderableComponent>();
|
||||
RegisterComponent<UIRenderableComponent>();
|
||||
|
||||
// Order here matters:
|
||||
RegisterSystem<TransformSystem>();
|
||||
RegisterSystem<PhysicsSystem>();
|
||||
RegisterSystem<CustomBehaviourSystem>();
|
||||
RegisterSystem<MeshRenderSystem>();
|
||||
RegisterSystem<UIRenderSystem>();
|
||||
}
|
||||
|
||||
Scene::~Scene() {}
|
||||
|
@ -10,7 +10,7 @@ namespace engine {
|
||||
|
||||
MeshRenderSystem::MeshRenderSystem(Scene* scene)
|
||||
: System(scene, {typeid(TransformComponent).hash_code(),
|
||||
typeid(RenderableComponent).hash_code()}) {}
|
||||
typeid(MeshRenderableComponent).hash_code()}) {}
|
||||
|
||||
MeshRenderSystem::~MeshRenderSystem() {}
|
||||
|
||||
@ -47,7 +47,7 @@ void MeshRenderSystem::BuildRenderList(RenderList& render_list,
|
||||
|
||||
if (transform->is_static != with_static_entities) continue;
|
||||
|
||||
auto renderable = scene_->GetComponent<engine::RenderableComponent>(entity);
|
||||
auto renderable = scene_->GetComponent<engine::MeshRenderableComponent>(entity);
|
||||
|
||||
const gfx::Pipeline* pipeline =
|
||||
renderable->material->GetShader()->GetPipeline();
|
||||
|
@ -1,5 +0,0 @@
|
||||
#include "systems/text_render_system.h"
|
||||
|
||||
namespace engine {
|
||||
|
||||
} // namespace engine
|
22
src/systems/ui_render_system.cpp
Normal file
22
src/systems/ui_render_system.cpp
Normal file
@ -0,0 +1,22 @@
|
||||
#include "systems/ui_render_system.h"
|
||||
|
||||
#include "components/transform.h"
|
||||
#include "components/ui_renderable.h"
|
||||
|
||||
namespace engine {
|
||||
|
||||
UIRenderSystem::UIRenderSystem(Scene* scene)
|
||||
: System(scene, { typeid(TransformComponent).hash_code(),
|
||||
typeid(UIRenderableComponent).hash_code() }) {}
|
||||
|
||||
UIRenderSystem::~UIRenderSystem() {}
|
||||
|
||||
void UIRenderSystem::OnComponentInsert(uint32_t entity) {
|
||||
(void)entity;
|
||||
}
|
||||
|
||||
void UIRenderSystem::OnUpdate(float ts) {
|
||||
(void)ts;
|
||||
}
|
||||
|
||||
} // namespace engine
|
@ -77,7 +77,7 @@ namespace engine::util {
|
||||
// create child node for each mesh
|
||||
auto child = scene->CreateEntity("_mesh" + std::to_string(i), parentObj);
|
||||
scene->GetComponent<TransformComponent>(child)->is_static = is_static;
|
||||
auto childRenderer = scene->AddComponent<RenderableComponent>(child);
|
||||
auto childRenderer = scene->AddComponent<MeshRenderableComponent>(child);
|
||||
childRenderer->mesh = meshes[parentNode->mMeshes[i]];
|
||||
childRenderer->material = std::make_shared<resources::Material>(scene->app()->GetResource<resources::Shader>("builtin.standard"));
|
||||
if (textures.contains(meshTextureIndices[parentNode->mMeshes[i]])) {
|
||||
|
@ -147,6 +147,8 @@ namespace engine {
|
||||
const std::vector<const char*> windowExtensions = getWindowExtensions(window);
|
||||
std::vector<const char*> instanceExtensionsToUse = windowExtensions;
|
||||
|
||||
instanceExtensionsToUse.push_back(VK_KHR_GET_SURFACE_CAPABILITIES_2_EXTENSION_NAME);
|
||||
|
||||
const char* validationLayer = nullptr;
|
||||
if (useValidation) {
|
||||
validationLayer = getValidationLayer();
|
||||
|
@ -9,6 +9,7 @@
|
||||
#include "log.h"
|
||||
|
||||
#include "swapchain.h"
|
||||
#include <SDL_syswm.h>
|
||||
|
||||
namespace engine {
|
||||
|
||||
@ -116,9 +117,14 @@ namespace engine {
|
||||
|
||||
/* create swapchain */
|
||||
|
||||
VkSurfaceFullScreenExclusiveInfoEXT fullscreenInfo{};
|
||||
fullscreenInfo.sType = VK_STRUCTURE_TYPE_SURFACE_FULL_SCREEN_EXCLUSIVE_INFO_EXT;
|
||||
fullscreenInfo.pNext = nullptr;
|
||||
fullscreenInfo.fullScreenExclusive = VK_FULL_SCREEN_EXCLUSIVE_ALLOWED_EXT;
|
||||
|
||||
VkSwapchainCreateInfoKHR scInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR,
|
||||
.pNext = nullptr,
|
||||
.pNext = &fullscreenInfo,
|
||||
.flags = 0,
|
||||
.surface = info.surface,
|
||||
.minImageCount = minImageCount,
|
||||
|
BIN
test/res/models/terrain.dae
(Stored with Git LFS)
Normal file
BIN
test/res/models/terrain.dae
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
test/res/textures/grass_sRGB_scene-linear Rec.709-sRGB.jpg.tx
Normal file
BIN
test/res/textures/grass_sRGB_scene-linear Rec.709-sRGB.jpg.tx
Normal file
Binary file not shown.
@ -92,12 +92,11 @@ void PlayGame(GameSettings settings) {
|
||||
uint32_t skybox = my_scene->CreateEntity("skybox");
|
||||
|
||||
auto skybox_renderable =
|
||||
my_scene->AddComponent<engine::RenderableComponent>(skybox);
|
||||
my_scene->AddComponent<engine::MeshRenderableComponent>(skybox);
|
||||
skybox_renderable->material =
|
||||
std::make_unique<engine::resources::Material>(
|
||||
app.GetResource<engine::resources::Shader>("builtin.skybox"));
|
||||
skybox_renderable->material->texture_ =
|
||||
space_texture;
|
||||
skybox_renderable->material->texture_ = space_texture;
|
||||
skybox_renderable->mesh = GenCuboidMesh(app.renderer()->GetDevice(),
|
||||
10.0f, 10.0f, 10.0f, 1.0f, true);
|
||||
|
||||
@ -109,34 +108,23 @@ void PlayGame(GameSettings settings) {
|
||||
|
||||
/* floor */
|
||||
{
|
||||
uint32_t floor = my_scene->CreateEntity("floor");
|
||||
uint32_t floor = engine::util::LoadMeshFromFile(
|
||||
my_scene, app.GetResourcePath("models/terrain.dae"), true);
|
||||
|
||||
auto floor_transform =
|
||||
my_scene->GetComponent<engine::TransformComponent>(floor);
|
||||
floor_transform->is_static = true;
|
||||
floor_transform->position = glm::vec3{-50.0f, -0.1f, -50.0f};
|
||||
//floor_transform->position = glm::vec3{-50.0f, -0.1f, -50.0f};
|
||||
|
||||
auto floor_renderable =
|
||||
my_scene->AddComponent<engine::RenderableComponent>(floor);
|
||||
floor_renderable->material =
|
||||
std::make_shared<engine::resources::Material>(
|
||||
app.GetResource<engine::resources::Shader>("builtin.standard"));
|
||||
floor_renderable->material->texture_ =
|
||||
grass_texture;
|
||||
floor_renderable->mesh = GenCuboidMesh(app.renderer()->GetDevice(),
|
||||
100.0f, 0.1f, 100.0f, 100.0f);
|
||||
|
||||
auto floor_collider =
|
||||
my_scene->AddComponent<engine::ColliderComponent>(floor);
|
||||
floor_collider->is_static = true;
|
||||
floor_collider->aabb = {{0.0f, 0.0f, 0.0f}, {100.0f, 0.1f, 100.0f}};
|
||||
|
||||
}
|
||||
|
||||
// engine::util::LoadMeshFromFile(my_scene,
|
||||
// app.GetResourcePath("models/test_scene.dae"));
|
||||
|
||||
auto cobbleHouse = engine::util::LoadMeshFromFile(
|
||||
my_scene, app.GetResourcePath("models/cobble_house/cobble_house.dae"), false);
|
||||
my_scene, app.GetResourcePath("models/cobble_house/cobble_house.dae"),
|
||||
false);
|
||||
my_scene->GetComponent<engine::TransformComponent>(cobbleHouse)->position +=
|
||||
glm::vec3{33.0f, 0.1f, 35.0f};
|
||||
auto cobbleCustom =
|
||||
@ -149,7 +137,8 @@ void PlayGame(GameSettings settings) {
|
||||
my_scene->GetComponent<engine::TransformComponent>(cobbleHouse);
|
||||
t->rotation *= glm::angleAxis(ts, glm::vec3{0.0f, 0.0f, 1.0f});
|
||||
if (app.window()->GetKeyPress(engine::inputs::Key::K_F)) {
|
||||
my_scene->GetSystem<engine::MeshRenderSystem>()->RebuildStaticRenderList();
|
||||
my_scene->GetSystem<engine::MeshRenderSystem>()
|
||||
->RebuildStaticRenderList();
|
||||
}
|
||||
};
|
||||
|
||||
@ -168,7 +157,7 @@ void PlayGame(GameSettings settings) {
|
||||
time_elapsed += ts;
|
||||
if (time_elapsed >= 1.0f) {
|
||||
time_elapsed = 0.0f;
|
||||
//LOG_INFO("COMPONENT UPDATE");
|
||||
// LOG_INFO("COMPONENT UPDATE");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user