Laid the basis for rendering
This commit is contained in:
parent
e8ef4b59da
commit
7d117ce97c
|
|
@ -24,7 +24,6 @@ class NAZARA_API NzMemoryManager
|
|||
static void NextFree(const char* file, unsigned int line);
|
||||
|
||||
private:
|
||||
static void EnsureInitialization();
|
||||
static void Initialize();
|
||||
static char* TimeInfo();
|
||||
static void Uninitialize();
|
||||
|
|
|
|||
|
|
@ -39,7 +39,6 @@ template<typename T> class NzEulerAngles
|
|||
template<typename U> void Set(const NzEulerAngles<U>& angles);
|
||||
void SetZero();
|
||||
|
||||
//NzEulerAngles<T> ToEulerAngles() const;
|
||||
//NzMatrix3<T> ToRotationMatrix() const;
|
||||
NzQuaternion<T> ToQuaternion() const;
|
||||
NzString ToString() const;
|
||||
|
|
|
|||
|
|
@ -55,6 +55,9 @@ class NAZARA_API NzBuffer : public NzResource
|
|||
|
||||
bool Fill(const void* data, unsigned int offset, unsigned int length);
|
||||
|
||||
void* GetBufferPtr();
|
||||
const void* GetBufferPtr() const;
|
||||
NzBufferImpl* GetImpl() const;
|
||||
unsigned int GetLength() const;
|
||||
unsigned int GetSize() const;
|
||||
nzBufferStorage GetStorage() const;
|
||||
|
|
@ -67,7 +70,7 @@ class NAZARA_API NzBuffer : public NzResource
|
|||
void* Lock(nzBufferLock lock, unsigned int offset = 0, unsigned int length = 0);
|
||||
bool Unlock();
|
||||
|
||||
static bool IsHardwareSupported();
|
||||
static bool IsSupported(nzBufferStorage storage);
|
||||
|
||||
private:
|
||||
nzBufferStorage m_storage;
|
||||
|
|
|
|||
|
|
@ -21,6 +21,8 @@ class NAZARA_API NzIndexBuffer
|
|||
bool Fill(const void* data, unsigned int offset, unsigned int length);
|
||||
|
||||
NzBuffer* GetBuffer() const;
|
||||
void* GetBufferPtr();
|
||||
const void* GetBufferPtr() const;
|
||||
nzUInt8 GetIndexSize() const;
|
||||
unsigned int GetIndexCount() const;
|
||||
unsigned int GetStartIndex() const;
|
||||
|
|
|
|||
|
|
@ -8,19 +8,26 @@
|
|||
#define NAZARA_RENDERER_HPP
|
||||
|
||||
#include <Nazara/Prerequesites.hpp>
|
||||
#include <Nazara/Renderer/IndexBuffer.hpp>
|
||||
#include <Nazara/Renderer/RenderTarget.hpp>
|
||||
#include <Nazara/Renderer/Shader.hpp>
|
||||
#include <Nazara/Renderer/VertexBuffer.hpp>
|
||||
|
||||
#define NazaraRenderer NzRenderer::Instance()
|
||||
|
||||
enum nzPrimitiveType
|
||||
{
|
||||
nzPrimitiveType_LineList,
|
||||
nzPrimitiveType_LineStrip,
|
||||
nzPrimitiveType_PointList,
|
||||
nzPrimitiveType_TriangleList,
|
||||
nzPrimitiveType_TriangleStrip,
|
||||
nzPrimitiveType_TriangleFan
|
||||
};
|
||||
|
||||
enum nzRendererCap
|
||||
{
|
||||
nzRendererCap_AnisotropicFilter,
|
||||
nzRendererCap_FP64,
|
||||
nzRendererCap_HardwareBuffer,
|
||||
nzRendererCap_MultipleRenderTargets,
|
||||
nzRendererCap_SoftwareBuffer,
|
||||
nzRendererCap_Texture3D,
|
||||
nzRendererCap_TextureCubemap,
|
||||
nzRendererCap_TextureMulti,
|
||||
|
|
@ -36,6 +43,12 @@ enum nzRendererClear
|
|||
nzRendererClear_Stencil = 0x04
|
||||
};
|
||||
|
||||
class NzRenderTarget;
|
||||
class NzIndexBuffer;
|
||||
class NzShader;
|
||||
class NzVertexBuffer;
|
||||
class NzVertexDeclaration;
|
||||
|
||||
class NAZARA_API NzRenderer
|
||||
{
|
||||
public:
|
||||
|
|
@ -44,6 +57,9 @@ class NAZARA_API NzRenderer
|
|||
|
||||
void Clear(nzRendererClear flags);
|
||||
|
||||
void DrawIndexedPrimitives(nzPrimitiveType primitive, unsigned int firstIndex, unsigned int indexCount);
|
||||
void DrawPrimitives(nzPrimitiveType primitive, unsigned int firstVertex, unsigned int vertexCount);
|
||||
|
||||
NzShader* GetShader() const;
|
||||
NzRenderTarget* GetTarget() const;
|
||||
|
||||
|
|
@ -67,12 +83,15 @@ class NAZARA_API NzRenderer
|
|||
static NzRenderer* Instance();
|
||||
|
||||
private:
|
||||
bool UpdateVertexBuffer();
|
||||
|
||||
static NzRenderer* s_instance;
|
||||
|
||||
const NzIndexBuffer* m_indexBuffer;
|
||||
NzRenderTarget* m_target;
|
||||
NzShader* m_shader;
|
||||
const NzVertexBuffer* m_vertexBuffer;
|
||||
const NzVertexDeclaration* m_vertexDeclaration;
|
||||
bool m_capabilities[nzRendererCap_Count];
|
||||
bool m_vertexBufferUpdated;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -21,6 +21,8 @@ class NAZARA_API NzVertexBuffer
|
|||
bool Fill(const void* data, unsigned int offset, unsigned int length);
|
||||
|
||||
NzBuffer* GetBuffer() const;
|
||||
void* GetBufferPtr();
|
||||
const void* GetBufferPtr() const;
|
||||
unsigned int GetStartVertex() const;
|
||||
nzUInt8 GetTypeSize() const;
|
||||
unsigned int GetVertexCount() const;
|
||||
|
|
|
|||
|
|
@ -23,11 +23,11 @@ enum nzElementType
|
|||
|
||||
enum nzElementUsage
|
||||
{
|
||||
nzElementType_Diffuse,
|
||||
nzElementType_Normal,
|
||||
nzElementType_Position,
|
||||
nzElementType_Tangent,
|
||||
nzElementType_TexCoord
|
||||
nzElementUsage_Diffuse,
|
||||
nzElementUsage_Normal,
|
||||
nzElementUsage_Position,
|
||||
nzElementUsage_Tangent,
|
||||
nzElementUsage_TexCoord
|
||||
};
|
||||
|
||||
struct NzVertexElement
|
||||
|
|
|
|||
|
|
@ -50,7 +50,8 @@ NzMemoryManager::~NzMemoryManager()
|
|||
|
||||
void* NzMemoryManager::Allocate(std::size_t size, bool multi, const char* file, unsigned int line)
|
||||
{
|
||||
EnsureInitialization();
|
||||
if (!initialized)
|
||||
Initialize();
|
||||
|
||||
Block* ptr = reinterpret_cast<Block*>(std::malloc(size+sizeof(Block)));
|
||||
if (!ptr)
|
||||
|
|
@ -121,21 +122,6 @@ void NzMemoryManager::NextFree(const char* file, unsigned int line)
|
|||
nextFreeLine = line;
|
||||
}
|
||||
|
||||
void NzMemoryManager::EnsureInitialization()
|
||||
{
|
||||
if (!initialized)
|
||||
{
|
||||
Initialize();
|
||||
|
||||
if (std::atexit(Uninitialize) != 0)
|
||||
{
|
||||
static NzMemoryManager manager;
|
||||
}
|
||||
|
||||
initialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
void NzMemoryManager::Initialize()
|
||||
{
|
||||
char* time = TimeInfo();
|
||||
|
|
@ -147,6 +133,13 @@ void NzMemoryManager::Initialize()
|
|||
std::fclose(file);
|
||||
|
||||
std::free(time);
|
||||
|
||||
if (std::atexit(Uninitialize) != 0)
|
||||
{
|
||||
static NzMemoryManager manager;
|
||||
}
|
||||
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
char* NzMemoryManager::TimeInfo()
|
||||
|
|
|
|||
|
|
@ -11,6 +11,14 @@
|
|||
#include <stdexcept>
|
||||
#include <Nazara/Renderer/Debug.hpp>
|
||||
|
||||
namespace
|
||||
{
|
||||
nzRendererCap storageToCapability[] = {
|
||||
nzRendererCap_HardwareBuffer, // nzBufferStorage_Hardware
|
||||
nzRendererCap_SoftwareBuffer, // nzBufferStorage_Software
|
||||
};
|
||||
}
|
||||
|
||||
NzBuffer::NzBuffer(nzBufferType type) :
|
||||
m_type(type),
|
||||
m_typeSize(0),
|
||||
|
|
@ -25,7 +33,7 @@ m_impl(nullptr)
|
|||
{
|
||||
Create(length, typeSize, usage);
|
||||
|
||||
#if NAZARA_DEBUG
|
||||
#ifdef NAZARA_DEBUG
|
||||
if (!m_impl)
|
||||
{
|
||||
NazaraError("Failed to create buffer");
|
||||
|
|
@ -44,14 +52,14 @@ bool NzBuffer::CopyContent(NzBuffer& buffer)
|
|||
void* ptr = buffer.Lock(nzBufferLock_ReadOnly);
|
||||
if (!ptr)
|
||||
{
|
||||
NazaraError("Unable to lock buffer");
|
||||
NazaraError("Unable to lock source buffer");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool r = Fill(ptr, 0, m_length);
|
||||
|
||||
if (!buffer.Unlock())
|
||||
NazaraWarning("Unable to unlock buffer");
|
||||
NazaraWarning("Unable to unlock source buffer");
|
||||
|
||||
return r;
|
||||
}
|
||||
|
|
@ -64,7 +72,7 @@ bool NzBuffer::Create(unsigned int length, nzUInt8 typeSize, nzBufferUsage usage
|
|||
if (NazaraRenderer->HasCapability(nzRendererCap_HardwareBuffer))
|
||||
{
|
||||
m_impl = new NzHardwareBuffer(this, m_type);
|
||||
if (!m_impl->Create(length, typeSize, usage))
|
||||
if (!m_impl->Create(length*typeSize, usage))
|
||||
{
|
||||
NazaraWarning("Failed to create hardware buffer, trying to create software buffer...");
|
||||
|
||||
|
|
@ -76,7 +84,7 @@ bool NzBuffer::Create(unsigned int length, nzUInt8 typeSize, nzBufferUsage usage
|
|||
if (!m_impl)
|
||||
{
|
||||
m_impl = new NzSoftwareBuffer(this, m_type);
|
||||
if (!m_impl->Create(length, typeSize, usage))
|
||||
if (!m_impl->Create(length*typeSize, usage))
|
||||
{
|
||||
NazaraError("Failed to create software buffer");
|
||||
delete m_impl;
|
||||
|
|
@ -120,7 +128,38 @@ bool NzBuffer::Fill(const void* data, unsigned int offset, unsigned int length)
|
|||
}
|
||||
#endif
|
||||
|
||||
return m_impl->Fill(data, offset, length);
|
||||
return m_impl->Fill(data, offset*m_typeSize, length*m_typeSize);
|
||||
}
|
||||
|
||||
void* NzBuffer::GetBufferPtr()
|
||||
{
|
||||
#if NAZARA_RENDERER_SAFE
|
||||
if (!m_impl)
|
||||
{
|
||||
NazaraError("Buffer not created");
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
return m_impl->GetBufferPtr();
|
||||
}
|
||||
|
||||
const void* NzBuffer::GetBufferPtr() const
|
||||
{
|
||||
#if NAZARA_RENDERER_SAFE
|
||||
if (!m_impl)
|
||||
{
|
||||
NazaraError("Buffer not created");
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
return m_impl->GetBufferPtr();
|
||||
}
|
||||
|
||||
NzBufferImpl* NzBuffer::GetImpl() const
|
||||
{
|
||||
return m_impl;
|
||||
}
|
||||
|
||||
unsigned int NzBuffer::GetLength() const
|
||||
|
|
@ -174,7 +213,7 @@ void* NzBuffer::Lock(nzBufferLock lock, unsigned int offset, unsigned int length
|
|||
}
|
||||
#endif
|
||||
|
||||
return m_impl->Lock(lock, offset, length);
|
||||
return m_impl->Lock(lock, offset*m_typeSize, length*m_typeSize);
|
||||
}
|
||||
|
||||
bool NzBuffer::Unlock()
|
||||
|
|
@ -190,7 +229,7 @@ bool NzBuffer::Unlock()
|
|||
return m_impl->Unlock();
|
||||
}
|
||||
|
||||
bool NzBuffer::IsHardwareSupported()
|
||||
bool NzBuffer::IsSupported(nzBufferStorage storage)
|
||||
{
|
||||
return NazaraRenderer->HasCapability(nzRendererCap_HardwareBuffer);
|
||||
return NazaraRenderer->HasCapability(storageToCapability[storage]);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,5 +3,6 @@
|
|||
// For conditions of distribution and use, see copyright notice in Config.hpp
|
||||
|
||||
#include <Nazara/Renderer/BufferImpl.hpp>
|
||||
#include <Nazara/Renderer/Debug.hpp>
|
||||
|
||||
NzBufferImpl::~NzBufferImpl() = default;
|
||||
|
|
|
|||
|
|
@ -17,14 +17,16 @@ class NzBufferImpl
|
|||
|
||||
virtual void Bind() = 0;
|
||||
|
||||
virtual bool Create(unsigned int length, nzUInt8 typeSize, nzBufferUsage usage = nzBufferUsage_Static) = 0;
|
||||
virtual bool Create(unsigned int size, nzBufferUsage usage = nzBufferUsage_Static) = 0;
|
||||
virtual void Destroy() = 0;
|
||||
|
||||
virtual bool Fill(const void* data, unsigned int offset, unsigned int length) = 0;
|
||||
virtual bool Fill(const void* data, unsigned int offset, unsigned int size) = 0;
|
||||
|
||||
virtual void* GetBufferPtr() = 0;
|
||||
|
||||
virtual bool IsHardware() const = 0;
|
||||
|
||||
virtual void* Lock(nzBufferLock lock, unsigned int offset = 0, unsigned int length = 0) = 0;
|
||||
virtual void* Lock(nzBufferLock lock, unsigned int offset = 0, unsigned int size = 0) = 0;
|
||||
virtual bool Unlock() = 0;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -6,15 +6,53 @@
|
|||
#include <Nazara/Renderer/GLSLShader.hpp>
|
||||
#include <Nazara/Core/Error.hpp>
|
||||
#include <Nazara/Core/String.hpp>
|
||||
#include <Nazara/Renderer/BufferImpl.hpp>
|
||||
#include <Nazara/Renderer/VertexBuffer.hpp>
|
||||
#include <Nazara/Renderer/VertexDeclaration.hpp>
|
||||
#include <Nazara/Renderer/Debug.hpp>
|
||||
|
||||
namespace
|
||||
{
|
||||
GLenum shaderType[nzShaderType_Count] = {
|
||||
nzUInt8 attribIndex[] =
|
||||
{
|
||||
2, // nzElementUsage_Diffuse
|
||||
1, // nzElementUsage_Normal
|
||||
0, // nzElementUsage_Position
|
||||
3, // nzElementUsage_Tangent
|
||||
4 // nzElementUsage_TexCoord
|
||||
};
|
||||
|
||||
const GLenum shaderType[nzShaderType_Count] = {
|
||||
GL_FRAGMENT_SHADER, // nzShaderType_Fragment
|
||||
GL_GEOMETRY_SHADER, // nzShaderType_Geometry
|
||||
GL_VERTEX_SHADER // nzShaderType_Vertex
|
||||
};
|
||||
|
||||
const nzUInt8 size[] =
|
||||
{
|
||||
4, // nzElementType_Color
|
||||
1, // nzElementType_Double1
|
||||
2, // nzElementType_Double2
|
||||
3, // nzElementType_Double3
|
||||
4, // nzElementType_Double4
|
||||
1, // nzElementType_Float1
|
||||
2, // nzElementType_Float2
|
||||
3, // nzElementType_Float3
|
||||
4 // nzElementType_Float4
|
||||
};
|
||||
|
||||
const GLenum type[] =
|
||||
{
|
||||
GL_UNSIGNED_BYTE, // nzElementType_Color
|
||||
GL_DOUBLE, // nzElementType_Double1
|
||||
GL_DOUBLE, // nzElementType_Double2
|
||||
GL_DOUBLE, // nzElementType_Double3
|
||||
GL_DOUBLE, // nzElementType_Double4
|
||||
GL_FLOAT, // nzElementType_Float1
|
||||
GL_FLOAT, // nzElementType_Float2
|
||||
GL_FLOAT, // nzElementType_Float3
|
||||
GL_FLOAT // nzElementType_Float4
|
||||
};
|
||||
}
|
||||
|
||||
NzGLSLShader::NzGLSLShader(NzShader* parent) :
|
||||
|
|
@ -80,15 +118,16 @@ bool NzGLSLShader::Create()
|
|||
return false;
|
||||
}
|
||||
|
||||
glBindAttribLocation(m_program, 0, "Position");
|
||||
glBindAttribLocation(m_program, 1, "Normal");
|
||||
//glBindAttribLocation(m_program, 2, "Diffuse");
|
||||
glBindAttribLocation(m_program, 3, "Tangent");
|
||||
glBindAttribLocation(m_program, attribIndex[nzElementUsage_Position], "Position");
|
||||
glBindAttribLocation(m_program, attribIndex[nzElementUsage_Normal], "Normal");
|
||||
glBindAttribLocation(m_program, attribIndex[nzElementUsage_Diffuse], "Diffuse");
|
||||
glBindAttribLocation(m_program, attribIndex[nzElementUsage_Tangent], "Tangent");
|
||||
|
||||
NzString uniformName = "TexCoord0";
|
||||
for (unsigned int i = 0; i < 8; ++i)
|
||||
{
|
||||
NzString uniformName = "TexCoord" + NzString::Number(i);
|
||||
glBindAttribLocation(m_program, 4+i, uniformName.GetConstBuffer());
|
||||
uniformName[8] = '0'+i;
|
||||
glBindAttribLocation(m_program, attribIndex[nzElementUsage_TexCoord]+i, uniformName.GetConstBuffer());
|
||||
}
|
||||
|
||||
for (int i = 0; i < nzShaderType_Count; ++i)
|
||||
|
|
@ -252,3 +291,29 @@ void NzGLSLShader::Unbind()
|
|||
{
|
||||
glUseProgram(0);
|
||||
}
|
||||
|
||||
bool NzGLSLShader::UpdateVertexBuffer(const NzVertexBuffer* vertexBuffer, const NzVertexDeclaration* vertexDeclaration)
|
||||
{
|
||||
vertexBuffer->GetBuffer()->GetImpl()->Bind();
|
||||
const nzUInt8* buffer = reinterpret_cast<const nzUInt8*>(vertexBuffer->GetBufferPtr());
|
||||
|
||||
///FIXME: Améliorer les déclarations pour permettre de faire ça plus simplement
|
||||
for (int i = 0; i < 12; ++i) // Solution temporaire, à virer
|
||||
glDisableVertexAttribArray(i); // Chaque itération tue un chaton :(
|
||||
|
||||
unsigned int stride = vertexDeclaration->GetStride();
|
||||
unsigned int elementCount = vertexDeclaration->GetElementCount();
|
||||
for (unsigned int i = 0; i < elementCount; ++i)
|
||||
{
|
||||
const NzVertexDeclaration::Element* element = vertexDeclaration->GetElement(i);
|
||||
glEnableVertexAttribArray(attribIndex[element->usage]+element->usageIndex);
|
||||
glVertexAttribPointer(attribIndex[element->usage]+element->usageIndex,
|
||||
size[element->type],
|
||||
type[element->type],
|
||||
(element->type == nzElementType_Color) ? GL_TRUE : GL_FALSE,
|
||||
stride,
|
||||
&buffer[element->offset]);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,6 +45,8 @@ class NzGLSLShader : public NzShaderImpl
|
|||
void Unbind();
|
||||
|
||||
private:
|
||||
bool UpdateVertexBuffer(const NzVertexBuffer* vertexBuffer, const NzVertexDeclaration* vertexDeclaration);
|
||||
|
||||
mutable std::map<NzString, GLint> m_idCache;
|
||||
GLuint m_program;
|
||||
GLuint m_shaders[nzShaderType_Count];
|
||||
|
|
|
|||
|
|
@ -43,11 +43,11 @@ namespace
|
|||
GL_STATIC_DRAW // nzBufferUsage_Static
|
||||
};
|
||||
|
||||
typedef nzUInt8* (*LockRoutine)(nzBufferType type, nzBufferLock lock, unsigned int offset, unsigned int length);
|
||||
typedef nzUInt8* (*LockRoutine)(nzBufferType type, nzBufferLock lock, unsigned int offset, unsigned int size);
|
||||
|
||||
nzUInt8* LockBuffer(nzBufferType type, nzBufferLock lock, unsigned int offset, unsigned int length)
|
||||
nzUInt8* LockBuffer(nzBufferType type, nzBufferLock lock, unsigned int offset, unsigned int size)
|
||||
{
|
||||
NazaraUnused(length);
|
||||
NazaraUnused(size);
|
||||
|
||||
if (lock == nzBufferLock_DiscardAndWrite)
|
||||
{
|
||||
|
|
@ -68,22 +68,24 @@ namespace
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
nzUInt8* LockBufferRange(nzBufferType type, nzBufferLock lock, unsigned int offset, unsigned int length)
|
||||
nzUInt8* LockBufferRange(nzBufferType type, nzBufferLock lock, unsigned int offset, unsigned int size)
|
||||
{
|
||||
return reinterpret_cast<nzUInt8*>(glMapBufferRange(bufferTarget[type], offset, length, bufferLockRange[lock]));
|
||||
return reinterpret_cast<nzUInt8*>(glMapBufferRange(bufferTarget[type], offset, size, bufferLockRange[lock]));
|
||||
}
|
||||
|
||||
nzUInt8* LockBufferFirstRun(nzBufferType type, nzBufferLock lock, unsigned int offset, unsigned int length)
|
||||
nzUInt8* LockBufferFirstRun(nzBufferType type, nzBufferLock lock, unsigned int offset, unsigned int size);
|
||||
|
||||
LockRoutine lockBuffer = LockBufferFirstRun;
|
||||
|
||||
nzUInt8* LockBufferFirstRun(nzBufferType type, nzBufferLock lock, unsigned int offset, unsigned int size)
|
||||
{
|
||||
if (glMapBufferRange)
|
||||
lockBuffer = LockBufferRange;
|
||||
else
|
||||
lockBuffer = LockBuffer;
|
||||
|
||||
return lockBuffer(type, lock, offset, length);
|
||||
return lockBuffer(type, lock, offset, size);
|
||||
}
|
||||
|
||||
LockRoutine lockBuffer = LockBufferFirstRun;
|
||||
}
|
||||
|
||||
NzHardwareBuffer::NzHardwareBuffer(NzBuffer* parent, nzBufferType type) :
|
||||
|
|
@ -101,7 +103,7 @@ void NzHardwareBuffer::Bind()
|
|||
glBindBuffer(bufferTarget[m_type], m_buffer);
|
||||
}
|
||||
|
||||
bool NzHardwareBuffer::Create(unsigned int length, nzUInt8 typeSize, nzBufferUsage usage)
|
||||
bool NzHardwareBuffer::Create(unsigned int size, nzBufferUsage usage)
|
||||
{
|
||||
m_buffer = 0;
|
||||
glGenBuffers(1, &m_buffer);
|
||||
|
|
@ -116,7 +118,7 @@ bool NzHardwareBuffer::Create(unsigned int length, nzUInt8 typeSize, nzBufferUsa
|
|||
glGetIntegerv(bufferTargetBinding[m_type], &previous);
|
||||
|
||||
glBindBuffer(bufferTarget[m_type], m_buffer);
|
||||
glBufferData(bufferTarget[m_type], length*typeSize, nullptr, bufferUsage[usage]);
|
||||
glBufferData(bufferTarget[m_type], size, nullptr, bufferUsage[usage]);
|
||||
|
||||
// Pour ne pas perturber le rendu, on interfère pas avec le binding déjà présent
|
||||
if (previous != 0)
|
||||
|
|
@ -130,7 +132,7 @@ void NzHardwareBuffer::Destroy()
|
|||
glDeleteBuffers(1, &m_buffer);
|
||||
}
|
||||
|
||||
bool NzHardwareBuffer::Fill(const void* data, unsigned int offset, unsigned int length)
|
||||
bool NzHardwareBuffer::Fill(const void* data, unsigned int offset, unsigned int size)
|
||||
{
|
||||
GLuint previous;
|
||||
glGetIntegerv(bufferTargetBinding[m_type], reinterpret_cast<GLint*>(&previous));
|
||||
|
|
@ -139,26 +141,24 @@ bool NzHardwareBuffer::Fill(const void* data, unsigned int offset, unsigned int
|
|||
glBindBuffer(bufferTarget[m_type], m_buffer);
|
||||
|
||||
// Il semblerait que glBufferSubData soit plus performant que glMapBuffer(Range) en dessous d'un certain seuil
|
||||
if (length < 32*1024)
|
||||
/*if (size < 32*1024)
|
||||
{
|
||||
if (length == m_parent->GetLength())
|
||||
if (size == m_parent->GetLength())
|
||||
glBufferData(bufferTarget[m_type], m_parent->GetSize(), data, bufferUsage[m_parent->GetStorage()]);
|
||||
else
|
||||
{
|
||||
nzUInt8 typeSize = m_parent->GetTypeSize();
|
||||
glBufferSubData(bufferTarget[m_type], offset*typeSize, length*typeSize, data);
|
||||
}
|
||||
glBufferSubData(bufferTarget[m_type], offset, size, data);
|
||||
}
|
||||
else
|
||||
else*/
|
||||
{
|
||||
nzUInt8* ptr = lockBuffer(m_type, (length == m_parent->GetLength()) ? nzBufferLock_DiscardAndWrite : nzBufferLock_WriteOnly, offset, length);
|
||||
if (ptr)
|
||||
// La longueur que nous recevons est en fait la taille
|
||||
nzUInt8* ptr = lockBuffer(m_type, (size == m_parent->GetSize()) ? nzBufferLock_DiscardAndWrite : nzBufferLock_WriteOnly, offset, size);
|
||||
if (!ptr)
|
||||
{
|
||||
NazaraError("Failed to lock buffer");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::memcpy(ptr, data, length*m_parent->GetTypeSize());
|
||||
std::memcpy(ptr, data, size);
|
||||
|
||||
if (glUnmapBuffer(bufferTarget[m_type]) != GL_TRUE)
|
||||
{
|
||||
|
|
@ -178,6 +178,11 @@ bool NzHardwareBuffer::Fill(const void* data, unsigned int offset, unsigned int
|
|||
return true;
|
||||
}
|
||||
|
||||
void* NzHardwareBuffer::GetBufferPtr()
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool NzHardwareBuffer::IsHardware() const
|
||||
{
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -19,11 +19,13 @@ class NzHardwareBuffer : public NzBufferImpl
|
|||
|
||||
void Bind();
|
||||
|
||||
bool Create(unsigned int length, nzUInt8 typeSize, nzBufferUsage usage = nzBufferUsage_Static);
|
||||
bool Create(unsigned int size, nzBufferUsage usage = nzBufferUsage_Static);
|
||||
void Destroy();
|
||||
|
||||
bool Fill(const void* data, unsigned int offset, unsigned int length);
|
||||
|
||||
void* GetBufferPtr();
|
||||
|
||||
bool IsHardware() const;
|
||||
|
||||
void* Lock(nzBufferLock lock, unsigned int offset = 0, unsigned int length = 0);
|
||||
|
|
|
|||
|
|
@ -13,7 +13,20 @@ m_indexCount(indexCount),
|
|||
m_startIndex(startIndex)
|
||||
{
|
||||
if (m_buffer)
|
||||
{
|
||||
#ifdef NAZARA_DEBUG
|
||||
nzUInt8 indexSize = m_buffer->GetSize();
|
||||
if (indexSize != 1 && indexSize != 2 && indexSize != 4)
|
||||
{
|
||||
NazaraError("Invalid index size (" + NzString::Number(indexSize) + ')');
|
||||
m_buffer = nullptr;
|
||||
|
||||
throw std::runtime_error("Constructor failed");
|
||||
}
|
||||
#endif
|
||||
|
||||
m_buffer->AddResourceReference();
|
||||
}
|
||||
}
|
||||
|
||||
NzIndexBuffer::NzIndexBuffer(unsigned int length, nzUInt8 indexSize, nzBufferUsage usage) :
|
||||
|
|
@ -21,6 +34,16 @@ m_ownsBuffer(true),
|
|||
m_indexCount(length),
|
||||
m_startIndex(0)
|
||||
{
|
||||
#ifdef NAZARA_DEBUG
|
||||
if (indexSize != 1 && indexSize != 2 && indexSize != 4)
|
||||
{
|
||||
NazaraError("Invalid index size");
|
||||
m_buffer = nullptr;
|
||||
|
||||
throw std::runtime_error("Constructor failed");
|
||||
}
|
||||
#endif
|
||||
|
||||
m_buffer = new NzBuffer(nzBufferType_Index, length, indexSize, usage);
|
||||
m_buffer->AddResourceReference();
|
||||
m_buffer->SetPersistent(false);
|
||||
|
|
@ -80,6 +103,32 @@ NzBuffer* NzIndexBuffer::GetBuffer() const
|
|||
return m_buffer;
|
||||
}
|
||||
|
||||
void* NzIndexBuffer::GetBufferPtr()
|
||||
{
|
||||
#if NAZARA_RENDERER_SAFE
|
||||
if (!m_buffer)
|
||||
{
|
||||
NazaraError("Sequential index buffer: Buffer has no pointer");
|
||||
return nullptr;
|
||||
}
|
||||
#endif
|
||||
|
||||
return reinterpret_cast<nzUInt8*>(m_buffer->GetBufferPtr()) + m_startIndex*m_buffer->GetTypeSize();
|
||||
}
|
||||
|
||||
const void* NzIndexBuffer::GetBufferPtr() const
|
||||
{
|
||||
#if NAZARA_RENDERER_SAFE
|
||||
if (!m_buffer)
|
||||
{
|
||||
NazaraError("Sequential index buffer: Buffer has no pointer");
|
||||
return nullptr;
|
||||
}
|
||||
#endif
|
||||
|
||||
return reinterpret_cast<const nzUInt8*>(m_buffer->GetBufferPtr()) + m_startIndex*m_buffer->GetTypeSize();
|
||||
}
|
||||
|
||||
nzUInt8 NzIndexBuffer::GetIndexSize() const
|
||||
{
|
||||
#if NAZARA_RENDERER_SAFE
|
||||
|
|
|
|||
|
|
@ -6,14 +6,33 @@
|
|||
#include <Nazara/Renderer/Renderer.hpp>
|
||||
#include <Nazara/Core/Error.hpp>
|
||||
#include <Nazara/Renderer/BufferImpl.hpp>
|
||||
#include <Nazara/Renderer/IndexBuffer.hpp>
|
||||
#include <Nazara/Renderer/RenderTarget.hpp>
|
||||
#include <Nazara/Renderer/Shader.hpp>
|
||||
#include <Nazara/Renderer/ShaderImpl.hpp>
|
||||
#include <Nazara/Renderer/VertexBuffer.hpp>
|
||||
#include <stdexcept>
|
||||
#include <Nazara/Renderer/Debug.hpp>
|
||||
|
||||
namespace
|
||||
{
|
||||
GLenum openglPrimitive[] = {
|
||||
GL_LINES, // nzPrimitiveType_LineList,
|
||||
GL_LINE_STRIP, // nzPrimitiveType_LineStrip,
|
||||
GL_POINTS, // nzPrimitiveType_PointList,
|
||||
GL_TRIANGLES, // nzPrimitiveType_TriangleList,
|
||||
GL_TRIANGLE_STRIP, // nzPrimitiveType_TriangleStrip,
|
||||
GL_TRIANGLE_FAN // nzPrimitiveType_TriangleFan
|
||||
};
|
||||
}
|
||||
|
||||
NzRenderer::NzRenderer() :
|
||||
m_indexBuffer(nullptr),
|
||||
m_target(nullptr),
|
||||
m_shader(nullptr)
|
||||
m_shader(nullptr),
|
||||
m_vertexBuffer(nullptr),
|
||||
m_vertexDeclaration(nullptr),
|
||||
m_vertexBufferUpdated(false)
|
||||
{
|
||||
#if NAZARA_RENDERER_SAFE
|
||||
if (s_instance)
|
||||
|
|
@ -32,7 +51,7 @@ NzRenderer::~NzRenderer()
|
|||
|
||||
void NzRenderer::Clear(nzRendererClear flags)
|
||||
{
|
||||
#if NAZARA_DEBUG
|
||||
#ifdef NAZARA_DEBUG
|
||||
if (NzContext::GetCurrent() == nullptr)
|
||||
{
|
||||
NazaraError("No active context");
|
||||
|
|
@ -57,6 +76,64 @@ void NzRenderer::Clear(nzRendererClear flags)
|
|||
}
|
||||
}
|
||||
|
||||
void NzRenderer::DrawIndexedPrimitives(nzPrimitiveType primitive, unsigned int firstIndex, unsigned int indexCount)
|
||||
{
|
||||
#ifdef NAZARA_DEBUG
|
||||
if (!m_indexBuffer)
|
||||
{
|
||||
UngineError("No index buffer");
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!m_vertexBufferUpdated)
|
||||
{
|
||||
if (!UpdateVertexBuffer())
|
||||
{
|
||||
NazaraError("Failed to update vertex buffer");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
nzUInt8 indexSize = m_indexBuffer->GetIndexSize();
|
||||
|
||||
GLenum type;
|
||||
switch (indexSize)
|
||||
{
|
||||
case 1:
|
||||
type = GL_UNSIGNED_BYTE;
|
||||
break;
|
||||
|
||||
case 2:
|
||||
type = GL_UNSIGNED_SHORT;
|
||||
break;
|
||||
|
||||
case 4:
|
||||
type = GL_UNSIGNED_INT;
|
||||
break;
|
||||
|
||||
default:
|
||||
NazaraError("Invalid index size (" + NzString::Number(indexSize) + ')');
|
||||
return;
|
||||
}
|
||||
|
||||
glDrawElements(openglPrimitive[primitive], indexCount, type, reinterpret_cast<const nzUInt8*>(m_indexBuffer->GetBufferPtr()) + firstIndex*indexSize);
|
||||
}
|
||||
|
||||
void NzRenderer::DrawPrimitives(nzPrimitiveType primitive, unsigned int firstVertex, unsigned int vertexCount)
|
||||
{
|
||||
if (!m_vertexBufferUpdated)
|
||||
{
|
||||
if (!UpdateVertexBuffer())
|
||||
{
|
||||
NazaraError("Failed to update vertex buffer");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
glDrawArrays(openglPrimitive[primitive], firstVertex, vertexCount);
|
||||
}
|
||||
|
||||
NzShader* NzRenderer::GetShader() const
|
||||
{
|
||||
return m_shader;
|
||||
|
|
@ -80,6 +157,7 @@ bool NzRenderer::Initialize()
|
|||
m_capabilities[nzRendererCap_FP64] = NzOpenGL::IsSupported(NzOpenGL::FP64);
|
||||
m_capabilities[nzRendererCap_HardwareBuffer] = true; // Natif depuis OpenGL 2.0
|
||||
m_capabilities[nzRendererCap_MultipleRenderTargets] = true; // Natif depuis OpenGL 2.0
|
||||
m_capabilities[nzRendererCap_SoftwareBuffer] = NzOpenGL::GetVersion() <= 310; // Déprécié en OpenGL 3.1
|
||||
m_capabilities[nzRendererCap_Texture3D] = NzOpenGL::IsSupported(NzOpenGL::Texture3D);
|
||||
m_capabilities[nzRendererCap_TextureCubemap] = true; // Natif depuis OpenGL 1.3
|
||||
m_capabilities[nzRendererCap_TextureMulti] = true; // Natif depuis OpenGL 1.3
|
||||
|
|
@ -93,7 +171,7 @@ bool NzRenderer::Initialize()
|
|||
|
||||
void NzRenderer::SetClearColor(nzUInt8 r, nzUInt8 g, nzUInt8 b, nzUInt8 a)
|
||||
{
|
||||
#if NAZARA_DEBUG
|
||||
#ifdef NAZARA_DEBUG
|
||||
if (NzContext::GetCurrent() == nullptr)
|
||||
{
|
||||
NazaraError("No active context");
|
||||
|
|
@ -106,7 +184,7 @@ void NzRenderer::SetClearColor(nzUInt8 r, nzUInt8 g, nzUInt8 b, nzUInt8 a)
|
|||
|
||||
void NzRenderer::SetClearDepth(double depth)
|
||||
{
|
||||
#if NAZARA_DEBUG
|
||||
#ifdef NAZARA_DEBUG
|
||||
if (NzContext::GetCurrent() == nullptr)
|
||||
{
|
||||
NazaraError("No active context");
|
||||
|
|
@ -119,7 +197,7 @@ void NzRenderer::SetClearDepth(double depth)
|
|||
|
||||
void NzRenderer::SetClearStencil(unsigned int value)
|
||||
{
|
||||
#if NAZARA_DEBUG
|
||||
#ifdef NAZARA_DEBUG
|
||||
if (NzContext::GetCurrent() == nullptr)
|
||||
{
|
||||
NazaraError("No active context");
|
||||
|
|
@ -164,6 +242,7 @@ bool NzRenderer::SetShader(NzShader* shader)
|
|||
}
|
||||
|
||||
m_shader = shader;
|
||||
m_vertexBufferUpdated = false;
|
||||
}
|
||||
else if (m_shader)
|
||||
{
|
||||
|
|
@ -213,18 +292,20 @@ bool NzRenderer::SetVertexBuffer(const NzVertexBuffer* vertexBuffer)
|
|||
if (m_vertexBuffer == vertexBuffer)
|
||||
return true;
|
||||
|
||||
if (m_vertexBuffer && vertexBuffer)
|
||||
{
|
||||
// Si l'ancien buffer et le nouveau sont hardware, pas besoin de mettre à jour la déclaration
|
||||
if (!m_vertexBuffer->IsHardware() || !vertexBuffer->IsHardware())
|
||||
m_vertexBufferUpdated = false;
|
||||
}
|
||||
m_vertexBuffer = vertexBuffer;
|
||||
m_vertexBufferUpdated = false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NzRenderer::SetVertexDeclaration(const NzVertexBuffer* vertexBuffer)
|
||||
bool NzRenderer::SetVertexDeclaration(const NzVertexDeclaration* vertexDeclaration)
|
||||
{
|
||||
if (m_vertexBuffer == vertexBuffer)
|
||||
return true;
|
||||
|
||||
m_vertexBuffer = vertexBuffer;
|
||||
m_vertexDeclaration = vertexDeclaration;
|
||||
m_vertexBufferUpdated = false;
|
||||
|
||||
return true;
|
||||
|
|
@ -256,4 +337,34 @@ NzRenderer* NzRenderer::Instance()
|
|||
return s_instance;
|
||||
}
|
||||
|
||||
bool NzRenderer::UpdateVertexBuffer()
|
||||
{
|
||||
#if NAZARA_RENDERER_SAFE
|
||||
if (!m_shader)
|
||||
{
|
||||
NazaraError("No shader");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!m_vertexBuffer)
|
||||
{
|
||||
NazaraError("No vertex buffer");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!m_vertexDeclaration)
|
||||
{
|
||||
NazaraError("No vertex declaration");
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!m_shader->m_impl->UpdateVertexBuffer(m_vertexBuffer, m_vertexDeclaration))
|
||||
return false;
|
||||
|
||||
m_vertexBufferUpdated = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
NzRenderer* NzRenderer::s_instance = nullptr;
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ m_compiled(false)
|
|||
{
|
||||
Create(language);
|
||||
|
||||
#if NAZARA_DEBUG
|
||||
#ifdef NAZARA_DEBUG
|
||||
if (!m_impl)
|
||||
{
|
||||
NazaraError("Failed to create shader");
|
||||
|
|
|
|||
|
|
@ -3,5 +3,6 @@
|
|||
// For conditions of distribution and use, see copyright notice in Config.hpp
|
||||
|
||||
#include <Nazara/Renderer/ShaderImpl.hpp>
|
||||
#include <Nazara/Renderer/Debug.hpp>
|
||||
|
||||
NzShaderImpl::~NzShaderImpl() = default;
|
||||
|
|
|
|||
|
|
@ -9,8 +9,14 @@
|
|||
|
||||
#include <Nazara/Renderer/Shader.hpp>
|
||||
|
||||
class NzRenderer;
|
||||
class NzVertexBuffer;
|
||||
class NzVertexDeclaration;
|
||||
|
||||
class NzShaderImpl
|
||||
{
|
||||
friend class NzRenderer;
|
||||
|
||||
public:
|
||||
NzShaderImpl() = default;
|
||||
virtual ~NzShaderImpl();
|
||||
|
|
@ -38,6 +44,9 @@ class NzShaderImpl
|
|||
virtual bool SendMatrix(const NzString& name, const NzMatrix4f& matrix) = 0;
|
||||
|
||||
virtual void Unbind() = 0;
|
||||
|
||||
protected:
|
||||
virtual bool UpdateVertexBuffer(const NzVertexBuffer* vertexBuffer, const NzVertexDeclaration* vertexDeclaration) = 0;
|
||||
};
|
||||
|
||||
#endif // NAZARA_SHADERIMPL_HPP
|
||||
|
|
|
|||
|
|
@ -32,14 +32,14 @@ void NzSoftwareBuffer::Bind()
|
|||
glBindBuffer(bufferTarget[m_type], 0);
|
||||
}
|
||||
|
||||
bool NzSoftwareBuffer::Create(unsigned int length, nzUInt8 typeSize, nzBufferUsage usage)
|
||||
bool NzSoftwareBuffer::Create(unsigned int size, nzBufferUsage usage)
|
||||
{
|
||||
NazaraUnused(usage);
|
||||
|
||||
// Cette allocation est protégée car sa taille dépend directement de paramètres utilisateurs
|
||||
try
|
||||
{
|
||||
m_buffer = new nzUInt8[length*typeSize];
|
||||
m_buffer = new nzUInt8[size];
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
|
|
@ -47,9 +47,7 @@ bool NzSoftwareBuffer::Create(unsigned int length, nzUInt8 typeSize, nzBufferUsa
|
|||
return false;
|
||||
}
|
||||
|
||||
m_length = length;
|
||||
m_locked = false;
|
||||
m_typeSize = typeSize;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
@ -59,7 +57,7 @@ void NzSoftwareBuffer::Destroy()
|
|||
delete[] m_buffer;
|
||||
}
|
||||
|
||||
bool NzSoftwareBuffer::Fill(const void* data, unsigned int offset, unsigned int length)
|
||||
bool NzSoftwareBuffer::Fill(const void* data, unsigned int offset, unsigned int size)
|
||||
{
|
||||
#if NAZARA_RENDERER_SAFE
|
||||
if (m_locked)
|
||||
|
|
@ -69,20 +67,25 @@ bool NzSoftwareBuffer::Fill(const void* data, unsigned int offset, unsigned int
|
|||
}
|
||||
#endif
|
||||
|
||||
std::memcpy(&m_buffer[offset*m_typeSize], data, length*m_typeSize);
|
||||
std::memcpy(&m_buffer[offset], data, size);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void* NzSoftwareBuffer::GetBufferPtr()
|
||||
{
|
||||
return m_buffer;
|
||||
}
|
||||
|
||||
bool NzSoftwareBuffer::IsHardware() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
void* NzSoftwareBuffer::Lock(nzBufferLock lock, unsigned int offset, unsigned int length)
|
||||
void* NzSoftwareBuffer::Lock(nzBufferLock lock, unsigned int offset, unsigned int size)
|
||||
{
|
||||
NazaraUnused(length);
|
||||
NazaraUnused(lock);
|
||||
NazaraUnused(size);
|
||||
|
||||
#if NAZARA_RENDERER_SAFE
|
||||
if (m_locked)
|
||||
|
|
@ -94,7 +97,7 @@ void* NzSoftwareBuffer::Lock(nzBufferLock lock, unsigned int offset, unsigned in
|
|||
|
||||
m_locked = true;
|
||||
|
||||
return &m_buffer[offset*m_typeSize];
|
||||
return &m_buffer[offset];
|
||||
}
|
||||
|
||||
bool NzSoftwareBuffer::Unlock()
|
||||
|
|
|
|||
|
|
@ -18,11 +18,13 @@ class NzSoftwareBuffer : public NzBufferImpl
|
|||
|
||||
void Bind();
|
||||
|
||||
bool Create(unsigned int length, nzUInt8 typeSize, nzBufferUsage usage = nzBufferUsage_Static);
|
||||
bool Create(unsigned int size, nzBufferUsage usage = nzBufferUsage_Static);
|
||||
void Destroy();
|
||||
|
||||
bool Fill(const void* data, unsigned int offset, unsigned int length);
|
||||
|
||||
void* GetBufferPtr();
|
||||
|
||||
bool IsHardware() const;
|
||||
|
||||
void* Lock(nzBufferLock lock, unsigned int offset = 0, unsigned int length = 0);
|
||||
|
|
@ -30,10 +32,8 @@ class NzSoftwareBuffer : public NzBufferImpl
|
|||
|
||||
private:
|
||||
nzBufferType m_type;
|
||||
nzUInt8 m_typeSize;
|
||||
nzUInt8* m_buffer;
|
||||
bool m_locked;
|
||||
unsigned int m_length;
|
||||
};
|
||||
|
||||
#endif // NAZARA_SOFTWAREBUFFER_HPP
|
||||
|
|
|
|||
|
|
@ -67,6 +67,16 @@ NzBuffer* NzVertexBuffer::GetBuffer() const
|
|||
return m_buffer;
|
||||
}
|
||||
|
||||
void* NzVertexBuffer::GetBufferPtr()
|
||||
{
|
||||
return reinterpret_cast<nzUInt8*>(m_buffer->GetBufferPtr()) + m_startVertex*m_buffer->GetTypeSize();
|
||||
}
|
||||
|
||||
const void* NzVertexBuffer::GetBufferPtr() const
|
||||
{
|
||||
return reinterpret_cast<const nzUInt8*>(m_buffer->GetBufferPtr()) + m_startVertex*m_buffer->GetTypeSize();
|
||||
}
|
||||
|
||||
unsigned int NzVertexBuffer::GetStartVertex() const
|
||||
{
|
||||
return m_startVertex;
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
#include <Nazara/Renderer/Context.hpp>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <Nazara/Renderer/Debug.hpp>
|
||||
|
||||
NzContextImpl::NzContextImpl()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ m_impl(nullptr)
|
|||
{
|
||||
Create(handle);
|
||||
|
||||
#if NAZARA_DEBUG
|
||||
#ifdef NAZARA_DEBUG
|
||||
if (!m_impl)
|
||||
{
|
||||
NazaraError("Failed to create window");
|
||||
|
|
|
|||
Loading…
Reference in New Issue