wip: practica1
This commit is contained in:
@@ -210,7 +210,7 @@ typedef _W64 int ptrdiff_t;
|
|||||||
#else /* _UNIX */
|
#else /* _UNIX */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Needed for ptrdiff_t in turn needed by VBO. This is defined by ISO
|
* Needed for ptrdiff_t in turn needed by vbo_. This is defined by ISO
|
||||||
* C. On my system, this amounts to _3 lines_ of included code, all of
|
* C. On my system, this amounts to _3 lines_ of included code, all of
|
||||||
* them pretty much harmless. If you know of a way of detecting 32 vs
|
* them pretty much harmless. If you know of a way of detecting 32 vs
|
||||||
* 64 _targets_ at compile time you are free to replace this with
|
* 64 _targets_ at compile time you are free to replace this with
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,9 +1,3 @@
|
|||||||
buffer.cpp
|
main.cpp
|
||||||
main.cpp
|
LINK : warning LNK4098: defaultlib 'MSVCRT' conflicts with use of other libs; use /NODEFAULTLIB:library
|
||||||
Generating Code...
|
ugine3d.vcxproj -> C:\Users\rabil\git\utad-programacion3d\project\x64\Debug\Practica3D.exe
|
||||||
C:\Users\rabil\git\utad-programacion3d\src\main.cpp(84,9): error C2660: 'Buffer::Draw': function does not take 0 arguments
|
|
||||||
C:\Users\rabil\git\utad-programacion3d\src\buffer.h(15,7):
|
|
||||||
see declaration of 'Buffer::Draw'
|
|
||||||
C:\Users\rabil\git\utad-programacion3d\src\main.cpp(84,9):
|
|
||||||
while trying to match the argument list '()'
|
|
||||||
|
|
||||||
|
|||||||
@@ -9,23 +9,36 @@
|
|||||||
|
|
||||||
Buffer::Buffer(const std::vector<Vertex>& vertices, const std::vector<uint16_t>& indices)
|
Buffer::Buffer(const std::vector<Vertex>& vertices, const std::vector<uint16_t>& indices)
|
||||||
{
|
{
|
||||||
glGenVertexArrays(1, &VAO);
|
index_count_ = indices.size();
|
||||||
glGenBuffers(1, &VBO);
|
|
||||||
glGenBuffers(1, &EBO);
|
|
||||||
|
|
||||||
glBindVertexArray(VAO);
|
glGenVertexArrays(1, &vao_);
|
||||||
glBindBuffer(GL_ARRAY_BUFFER, VBO);
|
glGenBuffers(1, &vbo_);
|
||||||
|
glGenBuffers(1, &ebo_);
|
||||||
|
|
||||||
|
glBindVertexArray(vao_);
|
||||||
|
|
||||||
|
glBindBuffer(GL_ARRAY_BUFFER, vbo_);
|
||||||
glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(Vertex), vertices.data(), GL_STATIC_DRAW);
|
glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(Vertex), vertices.data(), GL_STATIC_DRAW);
|
||||||
|
|
||||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
|
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo_);
|
||||||
glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(uint16_t), indices.data(), GL_STATIC_DRAW);
|
glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(uint16_t), indices.data(), GL_STATIC_DRAW);
|
||||||
|
|
||||||
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)0);
|
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), nullptr);
|
||||||
glEnableVertexAttribArray(0);
|
glEnableVertexAttribArray(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Buffer::~Buffer()
|
||||||
|
{
|
||||||
|
glDeleteVertexArrays(1, &vao_);
|
||||||
|
glDeleteBuffers(1, &vbo_);
|
||||||
|
glDeleteBuffers(1, &ebo_);
|
||||||
|
}
|
||||||
|
|
||||||
void Buffer::Draw(const Shader& shader) const
|
void Buffer::Draw(const Shader& shader) const
|
||||||
{
|
{
|
||||||
glBindVertexArray(VAO);
|
glBindVertexArray(vao_);
|
||||||
glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_SHORT, 0);
|
glBindBuffer(GL_ARRAY_BUFFER, vbo_);
|
||||||
|
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo_);
|
||||||
|
shader.SetupAttribs();
|
||||||
|
glDrawElements(GL_TRIANGLES, index_count_, GL_UNSIGNED_SHORT, nullptr);
|
||||||
}
|
}
|
||||||
@@ -11,11 +11,13 @@
|
|||||||
class Buffer {
|
class Buffer {
|
||||||
public:
|
public:
|
||||||
Buffer(const std::vector<Vertex>& vertices, const std::vector<uint16_t>& indices);
|
Buffer(const std::vector<Vertex>& vertices, const std::vector<uint16_t>& indices);
|
||||||
|
~Buffer();
|
||||||
|
|
||||||
void Draw(const Shader& shader) const;
|
void Draw(const Shader& shader) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
uint32_t VAO, VBO, EBO;
|
uint32_t vao_, vbo_, ebo_;
|
||||||
|
GLsizei index_count_;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // BUFFER_H_
|
#endif // BUFFER_H_
|
||||||
77
src/main.cpp
77
src/main.cpp
@@ -1,6 +1,6 @@
|
|||||||
#ifdef _MSC_VER
|
/*#ifdef _MSC_VER
|
||||||
#pragma comment(linker, "/SUBSYSTEM:windows /ENTRY:mainCRTStartup")
|
#pragma comment(linker, "/SUBSYSTEM:windows /ENTRY:mainCRTStartup")
|
||||||
#endif
|
#endif*/
|
||||||
|
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
@@ -19,79 +19,96 @@
|
|||||||
#define SCREEN_HEIGHT 600
|
#define SCREEN_HEIGHT 600
|
||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
// init glfw
|
// Initialize OpenGL
|
||||||
if (!glfwInit()) {
|
if (!glfwInit()) {
|
||||||
std::cerr << "Failed to initialize glfw\n";
|
std::cerr << "Failed to initialize glfw\n";
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// create window
|
glfwWindowHint(GLFW_RESIZABLE, false);
|
||||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
|
|
||||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
|
|
||||||
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
|
|
||||||
//glfwWindowHint(GLFW_RESIZABLE, false);
|
|
||||||
glfwWindowHint(GLFW_SAMPLES, 8);
|
glfwWindowHint(GLFW_SAMPLES, 8);
|
||||||
|
|
||||||
|
//glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
|
||||||
|
//glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
|
||||||
|
//glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
|
||||||
|
|
||||||
GLFWwindow* win = glfwCreateWindow(SCREEN_WIDTH, SCREEN_HEIGHT, "Daniel Poveda", nullptr, nullptr);
|
GLFWwindow* win = glfwCreateWindow(SCREEN_WIDTH, SCREEN_HEIGHT, "Daniel Poveda", nullptr, nullptr);
|
||||||
if (!win) {
|
if (win == nullptr) {
|
||||||
std::cerr << "Failed to create opengl window\n";
|
std::cerr << "Failed to create opengl window\n";
|
||||||
glfwTerminate();
|
glfwTerminate();
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
glfwMakeContextCurrent(win);
|
glfwMakeContextCurrent(win);
|
||||||
|
|
||||||
|
glEnable(GL_DEPTH_TEST);
|
||||||
|
glEnable(GL_SCISSOR_TEST);
|
||||||
|
|
||||||
|
std::cout << "OpenGL initialized, version: " << glGetString(GL_VERSION) << "\n";
|
||||||
|
|
||||||
|
// Initialize GLEW
|
||||||
glewExperimental = GL_TRUE;
|
glewExperimental = GL_TRUE;
|
||||||
if (glewInit() != GLEW_OK) {
|
GLenum err = glewInit();
|
||||||
std::cerr << "Failed to initialize GLEW\n";
|
if (err != GLEW_OK) {
|
||||||
|
std::cerr << "Failed to initialize GLEW: " << glewGetErrorString(err) << "\n";
|
||||||
glfwTerminate();
|
glfwTerminate();
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
glEnable(GL_DEPTH_TEST);
|
// Logic
|
||||||
Shader shader("data/vertex.glsl", "data/fragment.glsl");
|
|
||||||
|
|
||||||
std::vector<Vertex> vertices = {
|
std::vector<Vertex> vertices = {
|
||||||
{{-0.5f, -0.5f, 0.0f}},
|
{{-0.5f, -0.5f, 0.0f}},
|
||||||
{{0.5f, -0.5f, 0.0f}},
|
{{0.5f, -0.5f, 0.0f}},
|
||||||
{{0.0f, 0.5f, 0.0f}}
|
{{0.0f, 0.5f, 0.0f}}
|
||||||
};
|
};
|
||||||
std::vector<uint16_t> indices = { 0, 1, 2 };
|
std::vector<uint16_t> indices = { 0, 1, 2 };
|
||||||
|
|
||||||
Buffer buffer(vertices, indices);
|
Buffer buffer(vertices, indices);
|
||||||
|
Shader shader("data/vertex.glsl", "data/fragment.glsl");
|
||||||
|
if (std::strlen(shader.getError()) > 0) {
|
||||||
|
std::cerr << "Failed to initialize shaders: " << shader.getError() << "\n";
|
||||||
|
}
|
||||||
|
|
||||||
glm::mat4 projection = glm::perspective(glm::radians(45.0f), static_cast<float>(SCREEN_WIDTH) / SCREEN_HEIGHT, 0.1f, 100.0f);
|
glm::mat4 projection = glm::perspective(glm::radians(45.0f), static_cast<float>(SCREEN_WIDTH) / SCREEN_HEIGHT, 0.1f, 100.0f);
|
||||||
glm::mat4 view = glm::translate(glm::mat4(1.0f), glm::vec3(0, 0, 6));
|
glm::mat4 view = glm::translate(glm::mat4(1.0f), glm::vec3(0, 0, 6));
|
||||||
|
|
||||||
// main loop
|
// main loop
|
||||||
float angle = 0;
|
double angle = 0;
|
||||||
double lastTime = glfwGetTime();
|
double lastTime = glfwGetTime();
|
||||||
while ( !glfwWindowShouldClose(win) && !glfwGetKey(win, GLFW_KEY_ESCAPE) ) {
|
while (!glfwWindowShouldClose(win) && !glfwGetKey(win, GLFW_KEY_ESCAPE)) {
|
||||||
// get delta time
|
// Delta
|
||||||
float delta = static_cast<float>(glfwGetTime() - lastTime);
|
double delta_time = glfwGetTime() - lastTime;
|
||||||
|
|
||||||
// get window size
|
// Reset window
|
||||||
//int screenWidth, screenHeight;
|
int screenWidth, screenHeight;
|
||||||
//glfwGetWindowSize(win, &screenWidth, &screenHeight);
|
glfwGetWindowSize(win, &screenWidth, &screenHeight);
|
||||||
|
glViewport(0, 0, screenWidth, screenHeight);
|
||||||
|
|
||||||
|
glClearColor(0.3f, 0.3f, 0.3f, 1.0f);
|
||||||
|
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||||
|
|
||||||
|
shader.Use();
|
||||||
|
|
||||||
// Logic
|
// Logic
|
||||||
glm::mat4 model = glm::rotate(glm::mat4(1.0f), glm::radians(delta * 32.0f), glm::vec3(0, 1, 0));
|
angle += 32.0 * delta_time;
|
||||||
|
|
||||||
|
glm::mat4 model = glm::translate(glm::mat4(1.0), glm::vec3(-3.0f + 0.0f * 3.0f, 0.0f, -0.0f * 3.0f));
|
||||||
|
model = glm::rotate(model, glm::radians(static_cast<float>(angle)), glm::vec3(0, 1, 0));
|
||||||
|
|
||||||
glm::mat4 mvp = projection * view * model;
|
glm::mat4 mvp = projection * view * model;
|
||||||
|
|
||||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
Shader::setMat4(shader.getLocation("mvp"), mvp);
|
||||||
//glClearColor(0, 255, 0, 255);
|
|
||||||
shader.use();
|
|
||||||
uint32_t mvpLoc = glGetUniformLocation(shader.getId(), "MVP");
|
|
||||||
glUniformMatrix4fv(mvpLoc, 1, GL_FALSE, &mvp[0][0]);
|
|
||||||
buffer.Draw(shader);
|
buffer.Draw(shader);
|
||||||
|
|
||||||
// refresh screen
|
// Refresh screen
|
||||||
glfwSwapBuffers(win);
|
glfwSwapBuffers(win);
|
||||||
glfwPollEvents();
|
glfwPollEvents();
|
||||||
|
|
||||||
// Set last time
|
// Delta
|
||||||
lastTime = glfwGetTime();
|
lastTime = glfwGetTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
// shutdown
|
// Shutdown
|
||||||
glfwTerminate();
|
glfwTerminate();
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|||||||
147
src/shader.cpp
147
src/shader.cpp
@@ -3,41 +3,134 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
#include "../lib/glew/GL/glew.h"
|
#include "../lib/glew/GL/glew.h"
|
||||||
#include "../lib/glfw/glfw3.h"
|
#include "../lib/glfw/glfw3.h"
|
||||||
|
|
||||||
namespace {
|
#include "vertex.h"
|
||||||
std::string readShaderFile(const std::string& filename) {
|
|
||||||
std::ifstream file(filename);
|
|
||||||
if (!file)
|
|
||||||
throw std::runtime_error("Failed to open shader file: " + filename);
|
|
||||||
std::stringstream buffer;
|
|
||||||
buffer << file.rdbuf();
|
|
||||||
return buffer.str();
|
|
||||||
}
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
Shader::Shader(const std::string& vertexPath, const std::string& fragmentPath)
|
Shader::Shader(const std::string& vertexPath, const std::string& fragmentPath)
|
||||||
{
|
{
|
||||||
std::string vertexCode = readShaderFile(vertexPath);
|
const uint32_t vshader = CompileShader(GL_VERTEX_SHADER, vertexPath);
|
||||||
std::string fragmentCode = readShaderFile(fragmentPath);
|
const uint32_t fshader = CompileShader(GL_FRAGMENT_SHADER, fragmentPath);
|
||||||
const char* vShaderCode = vertexCode.c_str();
|
|
||||||
const char* fShaderCode = fragmentCode.c_str();
|
|
||||||
|
|
||||||
const uint32_t vertex = glCreateShader(GL_VERTEX_SHADER);
|
if (vshader == 0 || fshader == 0) {
|
||||||
glShaderSource(vertex, 1, &vShaderCode, NULL);
|
program_id_ = 0;
|
||||||
glCompileShader(vertex);
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const uint32_t fragment = glCreateShader(GL_FRAGMENT_SHADER);
|
program_id_ = glCreateProgram();
|
||||||
glShaderSource(fragment, 1, &fShaderCode, NULL);
|
glAttachShader(program_id_, vshader);
|
||||||
glCompileShader(fragment);
|
glAttachShader(program_id_, fshader);
|
||||||
|
glLinkProgram(program_id_);
|
||||||
|
|
||||||
program = glCreateProgram();
|
// Error handling
|
||||||
glAttachShader(program, vertex);
|
int ret;
|
||||||
glAttachShader(program, fragment);
|
glGetProgramiv(program_id_, GL_LINK_STATUS, &ret);
|
||||||
glLinkProgram(program);
|
if (ret == GL_FALSE) {
|
||||||
|
char buffer[1024];
|
||||||
|
glGetProgramInfoLog(program_id_, sizeof(buffer), nullptr, buffer);
|
||||||
|
error_ = buffer;
|
||||||
|
glDeleteProgram(program_id_);
|
||||||
|
program_id_ = 0;
|
||||||
|
}
|
||||||
|
|
||||||
glDeleteShader(vertex);
|
glDeleteShader(vshader);
|
||||||
glDeleteShader(fragment);
|
glDeleteShader(fshader);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Shader::Use() const
|
||||||
|
{
|
||||||
|
if (program_id_ != 0)
|
||||||
|
glUseProgram(program_id_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Shader::SetupAttribs() const
|
||||||
|
{
|
||||||
|
int loc = glGetAttribLocation(program_id_, "vpos");
|
||||||
|
if (loc != -1) {
|
||||||
|
glEnableVertexAttribArray(loc);
|
||||||
|
glVertexAttribPointer(loc, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), reinterpret_cast<const void*>(offsetof(Vertex, position)));
|
||||||
|
}
|
||||||
|
|
||||||
|
loc = glGetAttribLocation(program_id_, "vcolor");
|
||||||
|
if (loc != -1) {
|
||||||
|
glEnableVertexAttribArray(loc);
|
||||||
|
glVertexAttribPointer(loc, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), reinterpret_cast<const void*>(offsetof(Vertex, color)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int Shader::getLocation(const char* key) const
|
||||||
|
{
|
||||||
|
return glGetUniformLocation(program_id_, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Shader::setInt(int loc, int value)
|
||||||
|
{
|
||||||
|
if (loc != -1)
|
||||||
|
glUniform1i(loc, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Shader::setFloat(int loc, float value)
|
||||||
|
{
|
||||||
|
if (loc != -1)
|
||||||
|
glUniform1f(loc, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Shader::setVec3(int loc, const glm::vec3& value)
|
||||||
|
{
|
||||||
|
if (loc != -1)
|
||||||
|
glUniform3fv(loc, 1, &value[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Shader::setVec4(int loc, const glm::vec4& value)
|
||||||
|
{
|
||||||
|
if (loc != -1)
|
||||||
|
glUniform4fv(loc, 1, &value[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Shader::setMat4(int loc, const glm::mat4& value)
|
||||||
|
{
|
||||||
|
if (loc != -1)
|
||||||
|
glUniformMatrix4fv(loc, 1, GL_FALSE, &value[0][0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string Shader::ReadShaderFile(const std::string& filename)
|
||||||
|
{
|
||||||
|
std::ifstream file(filename);
|
||||||
|
if (!file) {
|
||||||
|
error_ = "Failed to open shader file: " + filename + "\n";
|
||||||
|
return std::string{0};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::stringstream buffer;
|
||||||
|
buffer << file.rdbuf();
|
||||||
|
return buffer.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t Shader::CompileShader(uint32_t type, const std::string& source_path)
|
||||||
|
{
|
||||||
|
std::string source = ReadShaderFile(source_path);
|
||||||
|
if (source.empty())
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
const uint32_t shader = glCreateShader(type);
|
||||||
|
const char* shader_code = source.c_str();
|
||||||
|
|
||||||
|
glShaderSource(shader, 1, &shader_code, nullptr);
|
||||||
|
glCompileShader(shader);
|
||||||
|
|
||||||
|
// Error handling
|
||||||
|
int ret;
|
||||||
|
glGetShaderiv(shader, GL_COMPILE_STATUS, &ret);
|
||||||
|
if (ret == GL_FALSE) {
|
||||||
|
char buffer[1024];
|
||||||
|
glGetShaderInfoLog(shader, sizeof(buffer), nullptr, buffer);
|
||||||
|
error_ = buffer;
|
||||||
|
glDeleteShader(shader);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return shader;
|
||||||
|
}
|
||||||
|
|||||||
23
src/shader.h
23
src/shader.h
@@ -9,13 +9,28 @@
|
|||||||
|
|
||||||
class Shader {
|
class Shader {
|
||||||
public:
|
public:
|
||||||
Shader(const std::string& vertexPath, const std::string& fragmentPath);
|
Shader(const std::string& vertex_path, const std::string& fragment_path);
|
||||||
|
|
||||||
void use() const { glUseProgram(program); }
|
void Use() const;
|
||||||
uint32_t getId() const { return program; }
|
void SetupAttribs() const;
|
||||||
|
|
||||||
|
uint32_t getId() const { return program_id_; }
|
||||||
|
const char* getError() const { return error_.c_str(); }
|
||||||
|
|
||||||
|
int getLocation(const char* key) const;
|
||||||
|
|
||||||
|
static void setInt(int loc, int value);
|
||||||
|
static void setFloat(int loc, float value);
|
||||||
|
static void setVec3(int loc, const glm::vec3& value);
|
||||||
|
static void setVec4(int loc, const glm::vec4& value);
|
||||||
|
static void setMat4(int loc, const glm::mat4& value);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
uint32_t program;
|
uint32_t program_id_;
|
||||||
|
std::string error_;
|
||||||
|
|
||||||
|
std::string ReadShaderFile(const std::string& filename);
|
||||||
|
uint32_t CompileShader(uint32_t type, const std::string& source_path);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
struct Vertex {
|
struct Vertex {
|
||||||
glm::vec3 position{ 0.0f, 0.0f, 0.0f };
|
glm::vec3 position{ 0.0f, 0.0f, 0.0f };
|
||||||
|
glm::vec3 color{ 0.0f, 0.0f, 0.0f };
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // VERTEX_H_
|
#endif // VERTEX_H_
|
||||||
Reference in New Issue
Block a user