Merge branch 'main' into SDL3

This commit is contained in:
Sasha Szpakowski
2024-03-24 18:53:08 -03:00
616 changed files with 4577 additions and 4003 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2006-2023 LOVE Development Team
* Copyright (c) 2006-2024 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
+1 -1
View File
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2006-2023 LOVE Development Team
* Copyright (c) 2006-2024 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
+1 -1
View File
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2006-2023 LOVE Development Team
* Copyright (c) 2006-2024 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
+1 -1
View File
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2006-2023 LOVE Development Team
* Copyright (c) 2006-2024 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
+1 -1
View File
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2006-2023 LOVE Development Team
* Copyright (c) 2006-2024 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
+1 -1
View File
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2006-2023 LOVE Development Team
* Copyright (c) 2006-2024 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
+1 -1
View File
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2006-2023 LOVE Development Team
* Copyright (c) 2006-2024 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
+1 -1
View File
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2006-2023 LOVE Development Team
* Copyright (c) 2006-2024 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
+54 -56
View File
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2006-2023 LOVE Development Team
* Copyright (c) 2006-2024 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
@@ -180,8 +180,9 @@ Graphics::DisplayState::DisplayState()
defaultSamplerState.mipmapFilter = SamplerState::MIPMAP_FILTER_LINEAR;
}
Graphics::Graphics()
: width(0)
Graphics::Graphics(const char *name)
: Module(M_GRAPHICS, name)
, width(0)
, height(0)
, pixelWidth(0)
, pixelHeight(0)
@@ -1202,15 +1203,16 @@ bool Graphics::isRenderTargetActive() const
bool Graphics::isRenderTargetActive(Texture *texture) const
{
Texture *roottexture = texture->getRootViewInfo().texture;
const auto &rts = states.back().renderTargets;
for (const auto &rt : rts.colors)
{
if (rt.texture.get() == texture)
if (rt.texture.get() && rt.texture->getRootViewInfo().texture == roottexture)
return true;
}
if (rts.depthStencil.texture.get() == texture)
if (rts.depthStencil.texture.get() && rts.depthStencil.texture->getRootViewInfo().texture == roottexture)
return true;
return false;
@@ -1218,16 +1220,27 @@ bool Graphics::isRenderTargetActive(Texture *texture) const
bool Graphics::isRenderTargetActive(Texture *texture, int slice) const
{
const auto &rootinfo = texture->getRootViewInfo();
slice += rootinfo.startLayer;
const auto &rts = states.back().renderTargets;
for (const auto &rt : rts.colors)
{
if (rt.texture.get() == texture && rt.slice == slice)
return true;
if (rt.texture.get())
{
const auto &info = rt.texture->getRootViewInfo();
if (rootinfo.texture == info.texture && rt.slice + info.startLayer == slice)
return true;
}
}
if (rts.depthStencil.texture.get() == texture && rts.depthStencil.slice == slice)
return true;
if (rts.depthStencil.texture.get())
{
const auto &info = rts.depthStencil.texture->getRootViewInfo();
if (rootinfo.texture == info.texture && rts.depthStencil.slice + info.startLayer == slice)
return true;
}
return false;
}
@@ -2576,21 +2589,42 @@ void Graphics::polygon(DrawMode mode, const Vector2 *coords, size_t count, bool
BatchedDrawCommand cmd;
cmd.formats[0] = getSinglePositionFormat(is2D);
cmd.formats[1] = CommonFormat::RGBAub;
cmd.formats[1] = CommonFormat::STf_RGBAub;
cmd.indexMode = TRIANGLEINDEX_FAN;
cmd.vertexCount = (int)count - (skipLastFilledVertex ? 1 : 0);
BatchedVertexData data = requestBatchedDraw(cmd);
if (is2D)
t.transformXY((Vector2 *) data.stream[0], coords, cmd.vertexCount);
else
t.transformXY0((Vector3 *) data.stream[0], coords, cmd.vertexCount);
// Compute texture coordinates.
constexpr float inf = std::numeric_limits<float>::infinity();
Vector2 mincoord(inf, inf);
Vector2 maxcoord(-inf, -inf);
for (int i = 0; i < cmd.vertexCount; i++)
{
Vector2 v = coords[i];
mincoord.x = std::min(mincoord.x, v.x);
mincoord.y = std::min(mincoord.y, v.y);
maxcoord.x = std::max(maxcoord.x, v.x);
maxcoord.y = std::max(maxcoord.y, v.y);
}
Vector2 invsize(1.0f / (maxcoord.x - mincoord.x), 1.0f / (maxcoord.y - mincoord.y));
Vector2 start(mincoord.x * invsize.x, mincoord.y * invsize.y);
Color32 c = toColor32(getColor());
Color32 *colordata = (Color32 *) data.stream[1];
STf_RGBAub *attributes = (STf_RGBAub *) data.stream[1];
for (int i = 0; i < cmd.vertexCount; i++)
colordata[i] = c;
{
attributes[i].s = coords[i].x * invsize.x - start.x;
attributes[i].t = coords[i].y * invsize.y - start.y;
attributes[i].color = c;
}
if (is2D)
t.transformXY((Vector2*)data.stream[0], coords, cmd.vertexCount);
else
t.transformXY0((Vector3*)data.stream[0], coords, cmd.vertexCount);
}
}
@@ -2768,27 +2802,6 @@ Vector2 Graphics::inverseTransformPoint(Vector2 point)
return p;
}
void Graphics::setOrthoProjection(float w, float h, float near, float far)
{
if (near >= far)
throw love::Exception("Orthographic projection Z far value must be greater than the Z near value.");
Matrix4 m = Matrix4::ortho(0.0f, w, 0.0f, h, near, far);
setCustomProjection(m);
}
void Graphics::setPerspectiveProjection(float verticalfov, float aspect, float near, float far)
{
if (near <= 0.0f)
throw love::Exception("Perspective projection Z near value must be greater than 0.");
if (near >= far)
throw love::Exception("Perspective projection Z far value must be greater than the Z near value.");
Matrix4 m = Matrix4::perspective(verticalfov, aspect, near, far);
setCustomProjection(m);
}
void Graphics::setCustomProjection(const Matrix4 &m)
{
flushBatchedDraws();
@@ -2818,29 +2831,14 @@ void Graphics::resetProjection()
state.useCustomProjection = false;
updateDeviceProjection(Matrix4::ortho(0.0f, w, 0.0f, h, -10.0f, 10.0f));
// NDC is y-up. The ortho() parameter names assume that as well. We want
// a y-down projection, so we set bottom to h and top to 0.
updateDeviceProjection(Matrix4::ortho(0.0f, w, h, 0.0f, -10.0f, 10.0f));
}
void Graphics::updateDeviceProjection(const Matrix4 &projection)
{
// Note: graphics implementations define computeDeviceProjection.
deviceProjectionMatrix = computeDeviceProjection(projection, isRenderTargetActive());
}
Matrix4 Graphics::calculateDeviceProjection(const Matrix4 &projection, uint32 flags) const
{
Matrix4 m = projection;
bool reverseZ = (flags & DEVICE_PROJECTION_REVERSE_Z) != 0;
if (flags & DEVICE_PROJECTION_FLIP_Y)
m.setRow(1, -m.getRow(1));
if (flags & DEVICE_PROJECTION_Z_01) // Go from Z [-1, 1] to Z [0, 1].
m.setRow(2, m.getRow(2) * (reverseZ ? -0.5f : 0.5f) + m.getRow(3));
else if (reverseZ)
m.setRow(2, -m.getRow(2));
return m;
deviceProjectionMatrix = projection;
}
STRINGMAP_CLASS_BEGIN(Graphics, Graphics::DrawMode, Graphics::DRAW_MAX_ENUM, drawMode)
+6 -18
View File
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2006-2023 LOVE Development Team
* Copyright (c) 2006-2024 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
@@ -451,13 +451,11 @@ public:
}
};
Graphics();
Graphics(const char *name);
virtual ~Graphics();
// Implements Module.
virtual ModuleType getModuleType() const { return M_GRAPHICS; }
virtual Texture *newTexture(const Texture::Settings &settings, const Texture::Slices *data = nullptr) = 0;
virtual Texture *newTextureView(Texture *base, const Texture::ViewSettings &viewsettings) = 0;
Quad *newQuad(Quad::Viewport v, double sw, double sh);
Font *newFont(love::font::Rasterizer *data);
@@ -629,6 +627,9 @@ public:
void setMeshCullMode(CullMode cull);
CullMode getMeshCullMode() const;
// Note: These are meant to be relative to the y-down default projection,
// which may be flipped compared to device NDC. Implementations may have
// to flip the winding internally.
virtual void setFrontFaceWinding(Winding winding) = 0;
Winding getFrontFaceWinding() const;
@@ -877,13 +878,9 @@ public:
Vector2 transformPoint(Vector2 point);
Vector2 inverseTransformPoint(Vector2 point);
void setOrthoProjection(float w, float h, float near, float far);
void setPerspectiveProjection(float verticalfov, float aspect, float near, float far);
void setCustomProjection(const Matrix4 &m);
void resetProjection();
virtual Matrix4 computeDeviceProjection(const Matrix4 &projection, bool rendertotexture) const = 0;
virtual void draw(const DrawCommand &cmd) = 0;
virtual void draw(const DrawIndexedCommand &cmd) = 0;
virtual void drawQuads(int start, int count, const VertexAttributes &attributes, const BufferBindings &buffers, Texture *texture) = 0;
@@ -926,14 +923,6 @@ public:
protected:
enum DeviceProjectionFlags
{
DEVICE_PROJECTION_DEFAULT = 0,
DEVICE_PROJECTION_FLIP_Y = (1 << 0),
DEVICE_PROJECTION_Z_01 = (1 << 1),
DEVICE_PROJECTION_REVERSE_Z = (1 << 2),
};
struct DisplayState
{
DisplayState();
@@ -1062,7 +1051,6 @@ protected:
void popTransform();
void updateDeviceProjection(const Matrix4 &projection);
Matrix4 calculateDeviceProjection(const Matrix4 &projection, uint32 flags) const;
int width;
int height;
+2 -1
View File
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2006-2023 LOVE Development Team
* Copyright (c) 2006-2024 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
@@ -192,6 +192,7 @@ GraphicsReadback::Status GraphicsReadback::readbackBuffer(Buffer *buffer, size_t
if (imageData.get())
{
// Always lock the mutex since the user can't know when to do it.
love::thread::Lock lock(imageData->getMutex());
if (imageData->getWidth() != rect.w)
+1 -1
View File
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2006-2023 LOVE Development Team
* Copyright (c) 2006-2024 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
+1 -1
View File
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2006-2023 LOVE Development Team
* Copyright (c) 2006-2024 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
+1 -1
View File
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2006-2023 LOVE Development Team
* Copyright (c) 2006-2024 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
+1 -1
View File
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2006-2023 LOVE Development Team
* Copyright (c) 2006-2024 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
+1 -1
View File
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2006-2023 LOVE Development Team
* Copyright (c) 2006-2024 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
+20 -10
View File
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2006-2023 LOVE Development Team
* Copyright (c) 2006-2024 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
@@ -424,7 +424,7 @@ void Polyline::draw(love::graphics::Graphics *gfx)
Graphics::BatchedDrawCommand cmd;
cmd.formats[0] = getSinglePositionFormat(is2D);
cmd.formats[1] = CommonFormat::RGBAub;
cmd.formats[1] = CommonFormat::STf_RGBAub;
cmd.indexMode = triangle_mode;
cmd.vertexCount = std::min(maxvertices, total_vertex_count - vertex_start);
@@ -435,13 +435,19 @@ void Polyline::draw(love::graphics::Graphics *gfx)
else
t.transformXY0((Vector3 *) data.stream[0], verts, cmd.vertexCount);
Color32 *colordata = (Color32 *) data.stream[1];
STf_RGBAub *attributes = (STf_RGBAub *) data.stream[1];
int draw_rough_count = std::min(cmd.vertexCount, (int) vertex_count - vertex_start);
// Constant vertex color up to the overdraw vertices.
// Texture coordinates are a constant value, we only have them to keep auto-batching
// when drawing filled and line polygons together.
for (int i = 0; i < draw_rough_count; i++)
colordata[i] = curcolor;
{
attributes[i].s = 0.0f;
attributes[i].t = 0.0f;
attributes[i].color = curcolor;
}
if (overdraw)
{
@@ -456,30 +462,34 @@ void Polyline::draw(love::graphics::Graphics *gfx)
if (draw_overdraw_count > 0)
{
Color32 *colors = colordata + draw_overdraw_begin;
fill_color_array(curcolor, colors, draw_overdraw_count);
STf_RGBAub *c = attributes + draw_overdraw_begin;
fill_color_array(curcolor, c, draw_overdraw_count);
}
}
}
}
void Polyline::fill_color_array(Color32 constant_color, Color32 *colors, int count)
void Polyline::fill_color_array(Color32 constant_color, STf_RGBAub *attributes, int count)
{
for (int i = 0; i < count; ++i)
{
Color32 c = constant_color;
c.a *= (i+1) % 2; // avoids branching. equiv to if (i%2 == 1) c.a = 0;
colors[i] = c;
attributes[i].s = 0.0f;
attributes[i].t = 0.0f;
attributes[i].color = c;
}
}
void NoneJoinPolyline::fill_color_array(Color32 constant_color, Color32 *colors, int count)
void NoneJoinPolyline::fill_color_array(Color32 constant_color, STf_RGBAub *attributes, int count)
{
for (int i = 0; i < count; ++i)
{
Color32 c = constant_color;
c.a *= (i & 3) < 2; // if (i % 4 == 2 || i % 4 == 3) c.a = 0
colors[i] = c;
attributes[i].s = 0.0f;
attributes[i].t = 0.0f;
attributes[i].color = c;
}
}
+3 -3
View File
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2006-2023 LOVE Development Team
* Copyright (c) 2006-2024 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
@@ -73,7 +73,7 @@ protected:
virtual void calc_overdraw_vertex_count(bool is_looping);
virtual void render_overdraw(const std::vector<Vector2> &normals, float pixel_size, bool is_looping);
virtual void fill_color_array(Color32 constant_color, Color32 *colors, int count);
virtual void fill_color_array(Color32 constant_color, STf_RGBAub *attributes, int count);
/** Calculate line boundary points.
*
@@ -133,7 +133,7 @@ protected:
void calc_overdraw_vertex_count(bool is_looping) override;
void render_overdraw(const std::vector<Vector2> &normals, float pixel_size, bool is_looping) override;
void fill_color_array(Color32 constant_color, Color32 *colors, int count) override;
void fill_color_array(Color32 constant_color, STf_RGBAub *attributes, int count) override;
void renderEdge(std::vector<Vector2> &anchors, std::vector<Vector2> &normals,
Vector2 &s, float &len_s, Vector2 &ns, const Vector2 &q,
const Vector2 &r, float hw) override;
+1 -1
View File
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2006-2023 LOVE Development Team
* Copyright (c) 2006-2024 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
+1 -1
View File
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2006-2023 LOVE Development Team
* Copyright (c) 2006-2024 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
+1 -1
View File
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2006-2023 LOVE Development Team
* Copyright (c) 2006-2024 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
+305 -81
View File
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2006-2023 LOVE Development Team
* Copyright (c) 2006-2024 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
@@ -26,6 +26,7 @@
// glslang
#include "libraries/glslang/glslang/Public/ShaderLang.h"
#include "libraries/glslang/glslang/Public/ResourceLimits.h"
// Needed for reflection information.
#include "libraries/glslang/glslang/Include/Types.h"
@@ -91,10 +92,10 @@ static const char render_uniforms[] = R"(
// but we can't guarantee that highp is always supported in fragment shaders...
// We *really* don't want to use mediump for these in vertex shaders though.
#ifdef LOVE_SPLIT_UNIFORMS_PER_DRAW
uniform LOVE_HIGHP_OR_MEDIUMP vec4 love_UniformsPerDraw[12];
uniform LOVE_HIGHP_OR_MEDIUMP vec4 love_UniformsPerDraw[13];
uniform LOVE_HIGHP_OR_MEDIUMP vec4 love_UniformsPerDraw2[1];
#else
uniform LOVE_HIGHP_OR_MEDIUMP vec4 love_UniformsPerDraw[13];
uniform LOVE_HIGHP_OR_MEDIUMP vec4 love_UniformsPerDraw[14];
#endif
// Older GLSL doesn't support preprocessor line continuations...
@@ -106,12 +107,15 @@ uniform LOVE_HIGHP_OR_MEDIUMP vec4 love_UniformsPerDraw[13];
#define CurrentDPIScale (love_UniformsPerDraw[8].w)
#define ConstantPointSize (love_UniformsPerDraw[9].w)
#define ConstantColor (love_UniformsPerDraw[11])
#define love_ClipSpaceParams (love_UniformsPerDraw[11])
#define ConstantColor (love_UniformsPerDraw[12])
#ifdef LOVE_SPLIT_UNIFORMS_PER_DRAW
#define love_ScreenSize (love_UniformsPerDraw2[0])
#else
#define love_ScreenSize (love_UniformsPerDraw[12])
#define love_ScreenSize (love_UniformsPerDraw[13])
#endif
// Alternate names
@@ -123,16 +127,17 @@ uniform LOVE_HIGHP_OR_MEDIUMP vec4 love_UniformsPerDraw[13];
static const char global_functions[] = R"(
#ifdef GL_ES
precision mediump sampler2D;
#if __VERSION__ >= 300 || defined(LOVE_EXT_TEXTURE_ARRAY_ENABLED)
precision lowp sampler2DArray;
precision mediump sampler2DArray;
#endif
#if __VERSION__ >= 300 || defined(GL_OES_texture_3D)
precision lowp sampler3D;
precision mediump sampler3D;
#endif
#if __VERSION__ >= 300 && !defined(LOVE_GLSL1_ON_GLSL3)
precision lowp sampler2DShadow;
precision lowp samplerCubeShadow;
precision lowp sampler2DArrayShadow;
precision mediump sampler2DShadow;
precision mediump samplerCubeShadow;
precision mediump sampler2DArrayShadow;
#endif
#endif
@@ -246,7 +251,13 @@ static const char vertex_header[] = R"(
#endif
)";
static const char vertex_functions[] = R"()";
static const char vertex_functions[] = R"(
vec4 love_clipSpaceTransform(vec4 clipPosition) {
clipPosition.y *= love_ClipSpaceParams.x;
clipPosition.z = (love_ClipSpaceParams.y * clipPosition.z + love_ClipSpaceParams.z * clipPosition.w) * love_ClipSpaceParams.w;
return clipPosition;
}
)";
static const char vertex_main[] = R"(
LOVE_IO_LOCATION(0) attribute vec4 VertexPosition;
@@ -262,6 +273,7 @@ void main() {
VaryingTexCoord = VertexTexCoord;
VaryingColor = gammaCorrectColor(VertexColor) * ConstantColor;
love_Position = position(ClipSpaceFromLocal, VertexPosition);
love_Position = love_clipSpaceTransform(love_Position);
}
)";
@@ -270,6 +282,7 @@ void vertexmain();
void main() {
vertexmain();
love_Position = love_clipSpaceTransform(love_Position);
}
)";
@@ -586,7 +599,7 @@ static Shader::EntryPoint getComputeEntryPoint(const std::string &src, const std
} // glsl
static_assert(sizeof(Shader::BuiltinUniformData) == sizeof(float) * 4 * 13, "Update the array in wrap_GraphicsShader.lua if this changes.");
static_assert(sizeof(Shader::BuiltinUniformData) == sizeof(float) * 4 * 14, "Update the array in wrap_GraphicsShader.lua if this changes.");
love::Type Shader::type("Shader", &Object::type);
@@ -691,9 +704,48 @@ Shader::Shader(StrongRef<ShaderStage> _stages[], const CompileOptions &options)
, debugName(options.debugName)
{
std::string err;
if (!validateInternal(_stages, err, validationReflection))
if (!validateInternal(_stages, err, reflection))
throw love::Exception("%s", err.c_str());
activeTextures.resize(reflection.textureCount);
activeBuffers.resize(reflection.bufferCount);
auto gfx = Module::getInstance<Graphics>(Module::M_GRAPHICS);
// Default bindings for read-only resources.
for (const auto &kvp : reflection.allUniforms)
{
const auto &u = *kvp.second;
if (u.resourceIndex < 0)
continue;
if ((u.access & ACCESS_WRITE) != 0)
continue;
if (u.baseType == UNIFORM_SAMPLER || u.baseType == UNIFORM_STORAGETEXTURE)
{
auto tex = gfx->getDefaultTexture(u.textureType, u.dataBaseType);
for (int i = 0; i < u.count; i++)
{
tex->retain();
activeTextures[u.resourceIndex + i] = tex;
}
}
else if (u.baseType == UNIFORM_TEXELBUFFER || u.baseType == UNIFORM_STORAGEBUFFER)
{
auto buffer = u.baseType == UNIFORM_TEXELBUFFER
? gfx->getDefaultTexelBuffer(u.dataBaseType)
: gfx->getDefaultStorageBuffer();
for (int i = 0; i < u.count; i++)
{
buffer->retain();
activeBuffers[u.resourceIndex + i] = buffer;
}
}
}
for (int i = 0; i < SHADERSTAGE_MAX_ENUM; i++)
stages[i] = _stages[i];
}
@@ -708,6 +760,18 @@ Shader::~Shader()
if (current == this)
attachDefault(STANDARD_DEFAULT);
for (Texture *tex : activeTextures)
{
if (tex)
tex->release();
}
for (Buffer *buffer : activeBuffers)
{
if (buffer)
buffer->release();
}
}
bool Shader::hasStage(ShaderStageType stage)
@@ -740,6 +804,40 @@ bool Shader::isDefaultActive()
return false;
}
Vector4 Shader::computeClipSpaceParams(uint32 clipSpaceTransformFlags)
{
// See the love_clipSpaceTransform vertex shader function.
Vector4 params(1.0f, 1.0f, 0.0f, 1.0f);
if (clipSpaceTransformFlags & CLIP_TRANSFORM_FLIP_Y)
params.x = -1.0f;
if (clipSpaceTransformFlags & CLIP_TRANSFORM_Z_NEG1_1_TO_0_1)
{
params.z = 1.0f;
params.w = 0.5f;
}
else if (clipSpaceTransformFlags & CLIP_TRANSFORM_Z_0_1_TO_NEG1_1)
{
params.y = 2.0f;
params.z = -1.0f;
}
return params;
}
const Shader::UniformInfo *Shader::getUniformInfo(const std::string &name) const
{
const auto it = reflection.allUniforms.find(name);
return it != reflection.allUniforms.end() ? it->second : nullptr;
}
bool Shader::hasUniform(const std::string &name) const
{
const auto it = reflection.allUniforms.find(name);
return it != reflection.allUniforms.end() && it->second->active;
}
const Shader::UniformInfo *Shader::getMainTextureInfo() const
{
return getUniformInfo(BUILTIN_TEXTURE_MAIN);
@@ -781,9 +879,9 @@ bool Shader::isResourceBaseTypeCompatible(DataBaseType a, DataBaseType b)
void Shader::validateDrawState(PrimitiveType primtype, Texture *maintex) const
{
if ((primtype == PRIMITIVE_POINTS) != validationReflection.usesPointSize)
if ((primtype == PRIMITIVE_POINTS) != reflection.usesPointSize)
{
if (validationReflection.usesPointSize)
if (reflection.usesPointSize)
throw love::Exception("The active shader can only be used to draw points.");
else
throw love::Exception("The gl_PointSize variable must be set in a vertex shader when drawing points.");
@@ -825,17 +923,29 @@ void Shader::validateDrawState(PrimitiveType primtype, Texture *maintex) const
void Shader::getLocalThreadgroupSize(int *x, int *y, int *z)
{
*x = validationReflection.localThreadgroupSize[0];
*y = validationReflection.localThreadgroupSize[1];
*z = validationReflection.localThreadgroupSize[2];
*x = reflection.localThreadgroupSize[0];
*y = reflection.localThreadgroupSize[1];
*z = reflection.localThreadgroupSize[2];
}
bool Shader::validate(StrongRef<ShaderStage> stages[], std::string& err)
{
ValidationReflection reflection;
Reflection reflection;
return validateInternal(stages, err, reflection);
}
static DataBaseType getBaseType(glslang::TBasicType basictype)
{
switch (basictype)
{
case glslang::EbtInt: return DATA_BASETYPE_INT;
case glslang::EbtUint: return DATA_BASETYPE_UINT;
case glslang::EbtFloat: return DATA_BASETYPE_FLOAT;
case glslang::EbtBool: return DATA_BASETYPE_BOOL;
default: return DATA_BASETYPE_FLOAT;
}
}
static PixelFormat getPixelFormat(glslang::TLayoutFormat format)
{
using namespace glslang;
@@ -885,6 +995,30 @@ static PixelFormat getPixelFormat(glslang::TLayoutFormat format)
}
}
static TextureType getTextureType(const glslang::TSampler &sampler)
{
if (sampler.is2D())
return sampler.isArrayed() ? TEXTURE_2D_ARRAY : TEXTURE_2D;
else if (sampler.dim == glslang::EsdCube)
return sampler.isArrayed() ? TEXTURE_MAX_ENUM : TEXTURE_CUBE;
else if (sampler.dim == glslang::Esd3D)
return TEXTURE_VOLUME;
else
return TEXTURE_MAX_ENUM;
}
static uint32 getStageMask(EShLanguageMask mask)
{
uint32 m = 0;
if (mask & EShLangVertexMask)
m |= SHADERSTAGEMASK_VERTEX;
if (mask & EShLangFragmentMask)
m |= SHADERSTAGEMASK_PIXEL;
if (mask & EShLangComputeMask)
m |= SHADERSTAGEMASK_COMPUTE;
return m;
}
template <typename T>
static T convertData(const glslang::TConstUnion &data)
{
@@ -903,7 +1037,7 @@ static T convertData(const glslang::TConstUnion &data)
}
}
bool Shader::validateInternal(StrongRef<ShaderStage> stages[], std::string &err, ValidationReflection &reflection)
bool Shader::validateInternal(StrongRef<ShaderStage> stages[], std::string &err, Reflection &reflection)
{
glslang::TProgram program;
@@ -946,6 +1080,9 @@ bool Shader::validateInternal(StrongRef<ShaderStage> stages[], std::string &err,
}
}
reflection.textureCount = 0;
reflection.bufferCount = 0;
for (int i = 0; i < program.getNumUniformVariables(); i++)
{
const glslang::TObjectReflection &info = program.getUniform(i);
@@ -955,9 +1092,40 @@ bool Shader::validateInternal(StrongRef<ShaderStage> stages[], std::string &err,
const glslang::TQualifier &qualifiers = type->getQualifier();
if (type->isImage())
UniformInfo u = {};
u.name = canonicaliizeUniformName(info.name);
u.location = -1;
u.access = ACCESS_READ;
u.stageMask = getStageMask(info.stages);
u.components = 1;
u.resourceIndex = -1;
if (type->isSizedArray())
u.count = type->getArraySizes()->getCumulativeSize();
else
u.count = 1;
const auto &sampler = type->getSampler();
if (type->isTexture() && type->getSampler().isCombined() && !sampler.isBuffer())
{
if ((info.stages & EShLangComputeMask) == 0)
u.baseType = UNIFORM_SAMPLER;
u.dataBaseType = getBaseType(sampler.getBasicType());
u.isDepthSampler = sampler.isShadow();
u.textureType = getTextureType(sampler);
if (u.textureType == TEXTURE_MAX_ENUM)
continue;
u.resourceIndex = reflection.textureCount;
reflection.textureCount += u.count;
reflection.sampledTextures[u.name] = u;
}
else if (type->isImage())
{
if ((info.stages & (~EShLangComputeMask)) != 0)
{
err = "Shader validation error:\nStorage Texture uniform variables (image2D, etc) are only allowed in compute shaders.";
return false;
@@ -965,36 +1133,63 @@ bool Shader::validateInternal(StrongRef<ShaderStage> stages[], std::string &err,
if (!qualifiers.hasFormat())
{
err = "Shader validation error:\nStorage Texture '" + info.name + "' must have an explicit format set in its layout declaration.";
err = "Shader validation error:\nStorage Texture '" + u.name + "' must have an explicit format set in its layout declaration.";
return false;
}
StorageTextureReflection texreflection = {};
u.baseType = UNIFORM_STORAGETEXTURE;
u.storageTextureFormat = getPixelFormat(qualifiers.getFormat());
u.dataBaseType = getDataBaseType(u.storageTextureFormat);
u.textureType = getTextureType(sampler);
texreflection.format = getPixelFormat(qualifiers.getFormat());
if (u.textureType == TEXTURE_MAX_ENUM)
continue;
u.resourceIndex = reflection.textureCount;
reflection.textureCount += u.count;
if (qualifiers.isReadOnly())
texreflection.access = ACCESS_READ;
u.access = ACCESS_READ;
else if (qualifiers.isWriteOnly())
texreflection.access = ACCESS_WRITE;
u.access = ACCESS_WRITE;
else
texreflection.access = (Access)(ACCESS_READ | ACCESS_WRITE);
u.access = (Access)(ACCESS_READ | ACCESS_WRITE);
reflection.storageTextures[info.name] = texreflection;
reflection.storageTextures[u.name] = u;
}
else if (type->getBasicType() == glslang::EbtSampler && sampler.isBuffer())
{
u.baseType = UNIFORM_TEXELBUFFER;
u.dataBaseType = getBaseType(sampler.getBasicType());
u.resourceIndex = reflection.bufferCount;
reflection.bufferCount += u.count;
reflection.texelBuffers[u.name] = u;
}
else if (!type->isOpaque())
{
LocalUniform u = {};
auto &values = u.initializerValues;
std::vector<LocalUniformValue> values;
const glslang::TConstUnionArray *constarray = info.getConstArray();
if (type->isMatrix())
{
u.matrix.rows = type->getMatrixRows();
u.matrix.columns = type->getMatrixCols();
}
else
{
u.components = type->getVectorSize();
}
// Store initializer values for local uniforms. Some love graphics
// backends strip these out of the shader so we need to be able to
// access them (to re-send them) by getting them here.
switch (type->getBasicType())
{
case glslang::EbtFloat:
u.dataType = DATA_BASETYPE_FLOAT;
u.baseType = type->isMatrix() ? UNIFORM_MATRIX : UNIFORM_FLOAT;
u.dataBaseType = DATA_BASETYPE_FLOAT;
if (constarray != nullptr)
{
values.resize(constarray->size());
@@ -1003,7 +1198,8 @@ bool Shader::validateInternal(StrongRef<ShaderStage> stages[], std::string &err,
}
break;
case glslang::EbtUint:
u.dataType = DATA_BASETYPE_UINT;
u.baseType = UNIFORM_UINT;
u.dataBaseType = DATA_BASETYPE_UINT;
if (constarray != nullptr)
{
values.resize(constarray->size());
@@ -1012,7 +1208,8 @@ bool Shader::validateInternal(StrongRef<ShaderStage> stages[], std::string &err,
}
break;
case glslang::EbtBool:
u.dataType = DATA_BASETYPE_BOOL;
u.baseType = UNIFORM_BOOL;
u.dataBaseType = DATA_BASETYPE_BOOL;
if (constarray != nullptr)
{
values.resize(constarray->size());
@@ -1022,7 +1219,8 @@ bool Shader::validateInternal(StrongRef<ShaderStage> stages[], std::string &err,
break;
case glslang::EbtInt:
default:
u.dataType = DATA_BASETYPE_INT;
u.baseType = UNIFORM_INT;
u.dataBaseType = DATA_BASETYPE_INT;
if (constarray != nullptr)
{
values.resize(constarray->size());
@@ -1032,7 +1230,8 @@ bool Shader::validateInternal(StrongRef<ShaderStage> stages[], std::string &err,
break;
}
reflection.localUniforms[info.name] = u;
reflection.localUniforms[u.name] = u;
reflection.localUniformInitializerValues[u.name] = values;
}
}
@@ -1044,7 +1243,7 @@ bool Shader::validateInternal(StrongRef<ShaderStage> stages[], std::string &err,
{
const glslang::TQualifier &qualifiers = type->getQualifier();
if ((!qualifiers.isReadOnly() || qualifiers.isWriteOnly()) && (info.stages & EShLangComputeMask) == 0)
if ((!qualifiers.isReadOnly() || qualifiers.isWriteOnly()) && ((info.stages & (~EShLangComputeMask)) != 0))
{
err = "Shader validation error:\nStorage Buffer block '" + info.name + "' must be marked as readonly in vertex and pixel shaders.";
return false;
@@ -1070,18 +1269,32 @@ bool Shader::validateInternal(StrongRef<ShaderStage> stages[], std::string &err,
return false;
}
BufferReflection bufferReflection = {};
bufferReflection.stride = (size_t) info.size;
bufferReflection.memberCount = (size_t) info.numMembers;
UniformInfo u = {};
u.name = canonicaliizeUniformName(info.name);
u.location = -1;
u.stageMask = getStageMask(info.stages);
u.components = 1;
u.baseType = UNIFORM_STORAGEBUFFER;
if (type->isSizedArray())
u.count = type->getArraySizes()->getCumulativeSize();
else
u.count = 1;
u.bufferStride = (size_t) info.size;
u.bufferMemberCount = (size_t) info.numMembers;
u.resourceIndex = reflection.bufferCount;
reflection.bufferCount += u.count;
if (qualifiers.isReadOnly())
bufferReflection.access = ACCESS_READ;
u.access = ACCESS_READ;
else if (qualifiers.isWriteOnly())
bufferReflection.access = ACCESS_WRITE;
u.access = ACCESS_WRITE;
else
bufferReflection.access = (Access)(ACCESS_READ | ACCESS_WRITE);
u.access = (Access)(ACCESS_READ | ACCESS_WRITE);
reflection.storageBuffers[info.name] = bufferReflection;
reflection.storageBuffers[u.name] = u;
}
else
{
@@ -1090,6 +1303,21 @@ bool Shader::validateInternal(StrongRef<ShaderStage> stages[], std::string &err,
}
}
for (auto &kvp : reflection.texelBuffers)
reflection.allUniforms[kvp.first] = &kvp.second;
for (auto &kvp : reflection.storageBuffers)
reflection.allUniforms[kvp.first] = &kvp.second;
for (auto &kvp : reflection.sampledTextures)
reflection.allUniforms[kvp.first] = &kvp.second;
for (auto &kvp : reflection.storageTextures)
reflection.allUniforms[kvp.first] = &kvp.second;
for (auto &kvp : reflection.localUniforms)
reflection.allUniforms[kvp.first] = &kvp.second;
return true;
}
@@ -1141,7 +1369,7 @@ bool Shader::validateTexture(const UniformInfo *info, Texture *tex, bool interna
else
throw love::Exception("Texture must be created with the computewrite flag set to true in order to be used with a storage texture (image2D etc) shader uniform variable.");
}
else if (isstoragetex && info->storageTextureFormat != getLinearPixelFormat(tex->getPixelFormat()))
else if (isstoragetex && info->storageTextureFormat != tex->getPixelFormat())
{
if (internalUpdate)
return false;
@@ -1216,41 +1444,6 @@ bool Shader::validateBuffer(const UniformInfo *info, Buffer *buffer, bool intern
return true;
}
bool Shader::fillUniformReflectionData(UniformInfo &u)
{
const auto &r = validationReflection;
if (u.baseType == UNIFORM_STORAGETEXTURE)
{
const auto reflectionit = r.storageTextures.find(u.name);
if (reflectionit != r.storageTextures.end())
{
u.storageTextureFormat = reflectionit->second.format;
u.access = reflectionit->second.access;
return true;
}
// No reflection info - maybe glslang was better at detecting dead code
// than the driver's compiler?
return false;
}
else if (u.baseType == UNIFORM_STORAGEBUFFER)
{
const auto reflectionit = r.storageBuffers.find(u.name);
if (reflectionit != r.storageBuffers.end())
{
u.bufferStride = reflectionit->second.stride;
u.bufferMemberCount = reflectionit->second.memberCount;
u.access = reflectionit->second.access;
return true;
}
return false;
}
return true;
}
std::string Shader::getShaderStageDebugName(ShaderStageType stage) const
{
std::string name = debugName;
@@ -1265,9 +1458,40 @@ std::string Shader::getShaderStageDebugName(ShaderStageType stage) const
return name;
}
std::string Shader::canonicaliizeUniformName(const std::string &n)
{
std::string name(n);
// Some drivers/compilers append "[0]" to the end of array uniform names.
if (name.length() > 3)
{
size_t findpos = name.rfind("[0]");
if (findpos != std::string::npos && findpos == name.length() - 3)
name.erase(name.length() - 3);
}
return name;
}
void Shader::handleUnknownUniformName(const char */*name*/)
{
// TODO: do something here?
}
bool Shader::initialize()
{
return glslang::InitializeProcess();
bool success = glslang::InitializeProcess();
if (!success)
return false;
TBuiltInResource *resources = GetResources();
*resources = *GetDefaultResources();
// This is 32 in the default resource struct, which is too high for Metal.
// TODO: Set this based on what the system actually supports?
resources->maxDrawBuffers = 8;
return true;
}
void Shader::deinitialize()
+49 -34
View File
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2006-2023 LOVE Development Team
* Copyright (c) 2006-2024 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
@@ -108,6 +108,14 @@ public:
ACCESS_WRITE = (1 << 1),
};
enum ClipSpaceTransformFlags
{
CLIP_TRANSFORM_NONE = 0,
CLIP_TRANSFORM_FLIP_Y = 1 << 0,
CLIP_TRANSFORM_Z_NEG1_1_TO_0_1 = 1 << 1,
CLIP_TRANSFORM_Z_0_1_TO_NEG1_1 = 1 << 2,
};
struct CompileOptions
{
std::map<std::string, std::string> defines;
@@ -129,6 +137,10 @@ public:
struct UniformInfo
{
UniformType baseType;
uint32 stageMask;
bool active;
int location;
int count;
@@ -138,7 +150,6 @@ public:
MatrixSize matrix;
};
UniformType baseType;
DataBaseType dataBaseType;
TextureType textureType;
Access access;
@@ -148,6 +159,8 @@ public:
size_t bufferMemberCount;
std::string name;
int resourceIndex;
union
{
void *data;
@@ -157,12 +170,6 @@ public:
};
size_t dataSize;
union
{
Texture **textures;
Buffer **buffers;
};
};
union LocalUniformValue
@@ -178,6 +185,7 @@ public:
Matrix4 transformMatrix;
Matrix4 projectionMatrix;
Vector4 normalMatrix[3]; // 3x3 matrix padded to an array of 3 vector4s.
Vector4 clipSpaceParams;
Colorf constantColor;
// Pixel shader-centric variables past this point.
@@ -213,6 +221,18 @@ public:
**/
static bool isDefaultActive();
/**
* Used for transforming standardized post-projection clip space positions
* into the backend's current clip space.
* Right now, the standard is:
* NDC y is [-1, 1] starting at the bottom (y-up).
* NDC z is [-1, 1].
* Pixel coordinates are y-down.
* Pixel (0, 0) in a texture is the top-left.
* Aside from NDC z, this matches Metal and D3D12.
*/
static Vector4 computeClipSpaceParams(uint32 clipSpaceTransformFlags);
/**
* Returns any warnings this Shader may have generated.
**/
@@ -222,7 +242,7 @@ public:
virtual int getVertexAttributeIndex(const std::string &name) = 0;
virtual const UniformInfo *getUniformInfo(const std::string &name) const = 0;
const UniformInfo *getUniformInfo(const std::string &name) const;
virtual const UniformInfo *getUniformInfo(BuiltinUniform builtin) const = 0;
virtual void updateUniform(const UniformInfo *info, int count) = 0;
@@ -234,7 +254,7 @@ public:
* Gets whether a uniform with the specified name exists and is actively
* used in the shader.
**/
virtual bool hasUniform(const std::string &name) const = 0;
bool hasUniform(const std::string &name) const;
/**
* Sets the textures used when rendering a video. For internal use only.
@@ -264,39 +284,31 @@ public:
protected:
struct BufferReflection
struct Reflection
{
size_t stride;
size_t memberCount;
Access access;
};
std::map<std::string, UniformInfo> texelBuffers;
std::map<std::string, UniformInfo> storageBuffers;
std::map<std::string, UniformInfo> sampledTextures;
std::map<std::string, UniformInfo> storageTextures;
std::map<std::string, UniformInfo> localUniforms;
struct StorageTextureReflection
{
PixelFormat format;
Access access;
};
std::map<std::string, UniformInfo *> allUniforms;
struct LocalUniform
{
DataBaseType dataType;
std::vector<LocalUniformValue> initializerValues;
};
std::map<std::string, std::vector<LocalUniformValue>> localUniformInitializerValues;
int textureCount;
int bufferCount;
struct ValidationReflection
{
std::map<std::string, BufferReflection> storageBuffers;
std::map<std::string, StorageTextureReflection> storageTextures;
std::map<std::string, LocalUniform> localUniforms;
int localThreadgroupSize[3];
bool usesPointSize;
};
bool fillUniformReflectionData(UniformInfo &u);
std::string getShaderStageDebugName(ShaderStageType stage) const;
static bool validateInternal(StrongRef<ShaderStage> stages[], std::string& err, ValidationReflection &reflection);
void handleUnknownUniformName(const char *name);
static std::string canonicaliizeUniformName(const std::string &name);
static bool validateInternal(StrongRef<ShaderStage> stages[], std::string& err, Reflection &reflection);
static DataBaseType getDataBaseType(PixelFormat format);
static bool isResourceBaseTypeCompatible(DataBaseType a, DataBaseType b);
@@ -305,7 +317,10 @@ protected:
StrongRef<ShaderStage> stages[SHADERSTAGE_MAX_ENUM];
ValidationReflection validationReflection;
Reflection reflection;
std::vector<Texture *> activeTextures;
std::vector<Buffer *> activeBuffers;
std::string debugName;
+2 -2
View File
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2006-2023 LOVE Development Team
* Copyright (c) 2006-2024 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
@@ -62,7 +62,7 @@ ShaderStage::ShaderStage(Graphics *gfx, ShaderStageType stage, const std::string
bool forwardcompat = supportsGLSL3 && !forcedefault;
if (!glslangShader->parse(GetDefaultResources(), defaultversion, defaultprofile, forcedefault, forwardcompat, EShMsgSuppressWarnings))
if (!glslangShader->parse(GetResources(), defaultversion, defaultprofile, forcedefault, forwardcompat, EShMsgSuppressWarnings))
{
const char *stagename = "unknown";
getConstant(stage, stagename);
+9 -1
View File
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2006-2023 LOVE Development Team
* Copyright (c) 2006-2024 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
@@ -48,6 +48,14 @@ enum ShaderStageType
SHADERSTAGE_MAX_ENUM
};
enum ShaderStageMask
{
SHADERSTAGEMASK_NONE = 0,
SHADERSTAGEMASK_VERTEX = 1 << SHADERSTAGE_VERTEX,
SHADERSTAGEMASK_PIXEL = 1 << SHADERSTAGE_PIXEL,
SHADERSTAGEMASK_COMPUTE = 1 << SHADERSTAGE_COMPUTE,
};
class ShaderStage : public love::Object
{
public:
+1 -1
View File
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2006-2023 LOVE Development Team
* Copyright (c) 2006-2024 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
+1 -1
View File
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2006-2023 LOVE Development Team
* Copyright (c) 2006-2024 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
+1 -1
View File
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2006-2023 LOVE Development Team
* Copyright (c) 2006-2024 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
+3 -1
View File
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2006-2023 LOVE Development Team
* Copyright (c) 2006-2024 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
@@ -57,6 +57,8 @@ public:
BufferUsage getMode() const { return mode; }
size_t getUsableSize() const { return bufferSize - frameGPUReadOffset; }
virtual size_t getGPUReadOffset() const = 0;
virtual MapInfo map(size_t minsize) = 0;
virtual size_t unmap(size_t usedsize) = 0;
virtual void markUsed(size_t usedsize) = 0;
+1 -1
View File
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2006-2023 LOVE Development Team
* Copyright (c) 2006-2024 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
+1 -1
View File
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2006-2023 LOVE Development Team
* Copyright (c) 2006-2024 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
+209 -80
View File
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2006-2023 LOVE Development Team
* Copyright (c) 2006-2024 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
@@ -167,6 +167,7 @@ Texture::Texture(Graphics *gfx, const Settings &settings, const Slices *slices)
, renderTarget(settings.renderTarget)
, computeWrite(settings.computeWrite)
, readable(true)
, viewFormats(settings.viewFormats)
, mipmapsMode(settings.mipmaps)
, width(settings.width)
, height(settings.height)
@@ -179,6 +180,8 @@ Texture::Texture(Graphics *gfx, const Settings &settings, const Slices *slices)
, samplerState()
, graphicsMemorySize(0)
, debugName(settings.debugName)
, rootView({this, 0, 0})
, parentView({this, 0, 0})
{
const auto &caps = gfx->getCapabilities();
int requestedMipmapCount = settings.mipmapCount;
@@ -284,32 +287,30 @@ Texture::Texture(Graphics *gfx, const Settings &settings, const Slices *slices)
if (isCompressed() && renderTarget)
throw love::Exception("Compressed textures cannot be render targets.");
uint32 usage = PIXELFORMATUSAGEFLAGS_NONE;
if (renderTarget)
usage |= PIXELFORMATUSAGEFLAGS_RENDERTARGET;
if (readable)
usage |= PIXELFORMATUSAGEFLAGS_SAMPLE;
if (computeWrite)
usage |= PIXELFORMATUSAGEFLAGS_COMPUTEWRITE;
if (!gfx->isPixelFormatSupported(format, (PixelFormatUsageFlags) usage))
for (PixelFormat viewformat : viewFormats)
{
const char *fstr = "unknown";
love::getConstant(format, fstr);
if (getLinearPixelFormat(viewformat) == getLinearPixelFormat(format))
continue;
const char *readablestr = "";
if (readable != !isPixelFormatDepthStencil(format))
readablestr = readable ? " readable" : " non-readable";
if (isPixelFormatCompressed(format) || isPixelFormatCompressed(viewformat))
throw love::Exception("Compressed textures cannot use different pixel formats for texture views, aside from sRGB versus linear variants of the same pixel format.");
const char *rtstr = "";
if (computeWrite)
rtstr = " as a compute shader-writable texture";
else if (renderTarget)
rtstr = " as a render target";
if (isPixelFormatColor(viewformat) != isPixelFormatColor(format))
throw love::Exception("Color-format textures cannot use depth/stencil pixel formats and vice versa, in texture views.");
throw love::Exception("The %s%s pixel format is not supported%s on this system.", fstr, readablestr, rtstr);
// TODO: depth[24|32f]_stencil8 -> stencil8 can work.
if (isPixelFormatDepthStencil(viewformat))
throw love::Exception("Using different pixel formats for texture views is not currently supported for depth or stencil formats.");
size_t viewbytes = getPixelFormatBlockSize(viewformat);
size_t basebytes = getPixelFormatBlockSize(format);
if (viewbytes != basebytes)
throw love::Exception("Texture view pixel formats must have the same bits per pixel as the base texture's pixel format.");
}
validatePixelFormat(gfx);
if (!caps.textureTypes[texType])
{
const char *textypestr = "unknown";
@@ -330,10 +331,131 @@ Texture::Texture(Graphics *gfx, const Settings &settings, const Slices *slices)
++textureCount;
}
Texture::Texture(Graphics *gfx, Texture *base, const ViewSettings &viewsettings)
: texType(viewsettings.type.get(base->getTextureType()))
, format(viewsettings.format.get(base->getPixelFormat()))
, renderTarget(base->renderTarget)
, computeWrite(base->computeWrite)
, readable(base->readable)
, viewFormats(base->viewFormats)
, mipmapsMode(base->mipmapsMode)
, width(1)
, height(1)
, depth(1)
, layers(1)
, mipmapCount(1)
, pixelWidth(1)
, pixelHeight(1)
, requestedMSAA(base->requestedMSAA)
, samplerState(base->samplerState)
, quad(base->quad)
, graphicsMemorySize(0)
, debugName(viewsettings.debugName)
, rootView({base->rootView.texture, 0, 0})
, parentView({base, viewsettings.mipmapStart.get(0), viewsettings.layerStart.get(0)})
{
width = base->getWidth(parentView.startMipmap);
height = base->getHeight(parentView.startMipmap);
if (texType == TEXTURE_VOLUME)
depth = base->getDepth(parentView.startMipmap);
if (texType == TEXTURE_2D_ARRAY)
{
int baselayers = base->getTextureType() == TEXTURE_CUBE ? 6 : base->getLayerCount();
layers = viewsettings.layerCount.get(baselayers - parentView.startLayer);
}
mipmapCount = viewsettings.mipmapCount.get(base->getMipmapCount() - parentView.startMipmap);
pixelWidth = base->getPixelWidth(parentView.startMipmap);
pixelHeight = base->getPixelHeight(parentView.startMipmap);
if (parentView.startMipmap < 0)
throw love::Exception("Invalid mipmap start value for texture view (out of range).");
if (mipmapCount < 0 || parentView.startMipmap + mipmapCount > base->getMipmapCount())
throw love::Exception("Invalid mipmap start or count value for texture view (out of range).");
if (parentView.startLayer < 0)
throw love::Exception("Invalid layer start value for texture view (out of range).");
int baseLayerCount = base->getTextureType() == TEXTURE_CUBE ? 6 : base->getLayerCount();
if (layers < 0 || parentView.startLayer + layers > baseLayerCount)
throw love::Exception("Invalid layer start or count value for texture view (out of range).");
if (texType == TEXTURE_CUBE && parentView.startLayer + 6 > baseLayerCount)
throw love::Exception("Cube texture view cannot fit in the base texture's layers with the given start layer.");
ViewInfo nextView = { this, 0, 0 };
while (nextView.texture != rootView.texture)
{
nextView = nextView.texture->parentView;
rootView.startMipmap += nextView.startMipmap;
rootView.startLayer += nextView.startLayer;
}
const auto &caps = gfx->getCapabilities();
if (!caps.features[Graphics::FEATURE_GLSL4])
throw love::Exception("Texture views are not supported on this system (GLSL 4 support is necessary.)");
validatePixelFormat(gfx);
if (!caps.textureTypes[texType])
{
const char *textypestr = "unknown";
Texture::getConstant(texType, textypestr);
throw love::Exception("%s textures are not supported on this system.", textypestr);
}
if (!readable)
throw love::Exception("Texture views are not supported for non-readable textures.");
if (base->getTextureType() == TEXTURE_2D)
{
if (texType != TEXTURE_2D && texType != TEXTURE_2D_ARRAY)
throw love::Exception("Texture views created from a 2D texture must use the 2d or array texture type.");
}
else if (base->getTextureType() == TEXTURE_2D_ARRAY || base->getTextureType() == TEXTURE_CUBE)
{
if (texType != TEXTURE_2D && texType != TEXTURE_2D_ARRAY && texType != TEXTURE_CUBE)
throw love::Exception("Texture views created from an array or cube texture must use the 2d, array, or cube texture type.");
}
else if (base->getTextureType() == TEXTURE_VOLUME)
{
if (texType != TEXTURE_VOLUME)
throw love::Exception("Texture views created from a volume texture must use the volume texture type.");
}
else
{
throw love::Exception("Unknown texture type.");
}
if (format != base->getPixelFormat())
{
if (std::find(viewFormats.begin(), viewFormats.end(), format) == viewFormats.end())
throw love::Exception("Using a different pixel format in a texture view requires the original texture to be created with a 'viewformats' setting that includes the given format in its list.");
}
const char *miperr = nullptr;
if (mipmapsMode == MIPMAPS_AUTO && !supportsGenerateMipmaps(miperr))
mipmapsMode = MIPMAPS_MANUAL;
rootView.texture->retain();
parentView.texture->retain();
}
Texture::~Texture()
{
--textureCount;
setGraphicsMemorySize(0);
if (this == rootView.texture)
--textureCount;
if (rootView.texture != this && rootView.texture != nullptr)
rootView.texture->release();
if (parentView.texture != this && parentView.texture != nullptr)
parentView.texture->release();
}
void Texture::setGraphicsMemorySize(int64 bytes)
@@ -352,18 +474,18 @@ void Texture::draw(Graphics *gfx, const Matrix4 &m)
void Texture::draw(Graphics *gfx, Quad *q, const Matrix4 &localTransform)
{
if (!readable)
throw love::Exception("Textures with non-readable formats cannot be drawn.");
if (renderTarget && gfx->isRenderTargetActive(this))
throw love::Exception("Cannot render a Texture to itself.");
if (texType == TEXTURE_2D_ARRAY)
{
drawLayer(gfx, q->getLayer(), q, localTransform);
return;
}
if (!readable)
throw love::Exception("Textures with non-readable formats cannot be drawn.");
if (renderTarget && gfx->isRenderTargetActive(this))
throw love::Exception("Cannot render a Texture to itself.");
const Matrix4 &tm = gfx->getTransform();
bool is2D = tm.isAffine2DTransform();
@@ -451,14 +573,8 @@ void Texture::drawLayer(Graphics *gfx, int layer, Quad *q, const Matrix4 &m)
void Texture::uploadImageData(love::image::ImageDataBase *d, int level, int slice, int x, int y)
{
love::image::ImageData *id = dynamic_cast<love::image::ImageData *>(d);
love::thread::EmptyLock lock;
if (id != nullptr)
lock.setLock(id->getMutex());
Rect rect = {x, y, d->getWidth(), d->getHeight()};
uploadByteData(d->getFormat(), d->getData(), d->getSize(), level, slice, rect);
uploadByteData(d->getData(), d->getSize(), level, slice, rect);
}
void Texture::replacePixels(love::image::ImageDataBase *d, int slice, int mipmap, int x, int y, bool reloadmipmaps)
@@ -477,7 +593,9 @@ void Texture::replacePixels(love::image::ImageDataBase *d, int slice, int mipmap
if (getHandle() == 0)
return;
if (d->getFormat() != getPixelFormat())
// ImageData format might be linear but intended to be used as sRGB, so we
// don't error if only the sRGBness is different.
if (getLinearPixelFormat(d->getFormat()) != getLinearPixelFormat(getPixelFormat()))
throw love::Exception("Pixel formats must match.");
if (mipmap < 0 || mipmap >= getMipmapCount())
@@ -533,7 +651,7 @@ void Texture::replacePixels(const void *data, size_t size, int slice, int mipmap
Graphics::flushBatchedDrawsGlobal();
uploadByteData(format, data, size, mipmap, slice, rect);
uploadByteData(data, size, mipmap, slice, rect);
if (reloadmipmaps && mipmap == 0 && getMipmapCount() > 1)
generateMipmaps();
@@ -586,36 +704,6 @@ void Texture::generateMipmaps()
generateMipmapsInternal();
}
TextureType Texture::getTextureType() const
{
return texType;
}
PixelFormat Texture::getPixelFormat() const
{
return format;
}
Texture::MipmapsMode Texture::getMipmapsMode() const
{
return mipmapsMode;
}
bool Texture::isRenderTarget() const
{
return renderTarget;
}
bool Texture::isComputeWritable() const
{
return computeWrite;
}
bool Texture::isReadable() const
{
return readable;
}
bool Texture::isCompressed() const
{
return isPixelFormatCompressed(format);
@@ -689,28 +777,39 @@ int Texture::getRequestedMSAA() const
return requestedMSAA;
}
void Texture::setSamplerState(const SamplerState &s)
const SamplerState &Texture::getSamplerState() const
{
return samplerState;
}
SamplerState Texture::validateSamplerState(SamplerState s) const
{
if (!readable)
return;
return s;
if (s.depthSampleMode.hasValue && !isPixelFormatDepth(format))
throw love::Exception("Only depth textures can have a depth sample compare mode.");
Graphics::flushBatchedDrawsGlobal();
samplerState = s;
if (samplerState.mipmapFilter != SamplerState::MIPMAP_FILTER_NONE && getMipmapCount() == 1)
samplerState.mipmapFilter = SamplerState::MIPMAP_FILTER_NONE;
if (s.mipmapFilter != SamplerState::MIPMAP_FILTER_NONE && getMipmapCount() == 1)
s.mipmapFilter = SamplerState::MIPMAP_FILTER_NONE;
if (texType == TEXTURE_CUBE)
samplerState.wrapU = samplerState.wrapV = samplerState.wrapW = SamplerState::WRAP_CLAMP;
}
s.wrapU = s.wrapV = s.wrapW = SamplerState::WRAP_CLAMP;
const SamplerState &Texture::getSamplerState() const
{
return samplerState;
if (s.minFilter == SamplerState::FILTER_LINEAR || s.magFilter == SamplerState::FILTER_LINEAR || s.mipmapFilter == SamplerState::MIPMAP_FILTER_LINEAR)
{
auto gfx = Module::getInstance<Graphics>(Module::M_GRAPHICS);
if (!gfx->isPixelFormatSupported(format, PIXELFORMATUSAGEFLAGS_LINEAR))
{
s.minFilter = s.magFilter = SamplerState::FILTER_NEAREST;
if (s.mipmapFilter == SamplerState::MIPMAP_FILTER_LINEAR)
s.mipmapFilter = SamplerState::MIPMAP_FILTER_NEAREST;
}
}
Graphics::flushBatchedDrawsGlobal();
return s;
}
Quad *Texture::getQuad() const
@@ -785,6 +884,35 @@ bool Texture::validateDimensions(bool throwException) const
return success;
}
void Texture::validatePixelFormat(Graphics *gfx) const
{
uint32 usage = PIXELFORMATUSAGEFLAGS_NONE;
if (renderTarget)
usage |= PIXELFORMATUSAGEFLAGS_RENDERTARGET;
if (readable)
usage |= PIXELFORMATUSAGEFLAGS_SAMPLE;
if (computeWrite)
usage |= PIXELFORMATUSAGEFLAGS_COMPUTEWRITE;
if (!gfx->isPixelFormatSupported(format, (PixelFormatUsageFlags) usage))
{
const char *fstr = "unknown";
love::getConstant(format, fstr);
const char *readablestr = "";
if (readable != !isPixelFormatDepthStencil(format))
readablestr = readable ? " readable" : " non-readable";
const char *rtstr = "";
if (computeWrite)
rtstr = " as a compute shader-writable texture";
else if (renderTarget)
rtstr = " as a render target";
throw love::Exception("The %s%s pixel format is not supported%s on this system.", fstr, readablestr, rtstr);
}
}
Texture::Slices::Slices(TextureType textype)
: textureType(textype)
{
@@ -975,6 +1103,7 @@ static StringMap<Texture::SettingType, Texture::SETTING_MAX_ENUM>::Entry setting
{ "msaa", Texture::SETTING_MSAA },
{ "canvas", Texture::SETTING_RENDER_TARGET },
{ "computewrite", Texture::SETTING_COMPUTE_WRITE },
{ "viewformats", Texture::SETTING_VIEW_FORMATS },
{ "readable", Texture::SETTING_READABLE },
{ "debugname", Texture::SETTING_DEBUGNAME },
};
+47 -12
View File
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2006-2023 LOVE Development Team
* Copyright (c) 2006-2024 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
@@ -39,6 +39,7 @@
// C
#include <stddef.h>
#include <vector>
namespace love
{
@@ -174,6 +175,7 @@ public:
SETTING_MSAA,
SETTING_RENDER_TARGET,
SETTING_COMPUTE_WRITE,
SETTING_VIEW_FORMATS,
SETTING_READABLE,
SETTING_DEBUGNAME,
SETTING_MAX_ENUM
@@ -194,10 +196,22 @@ public:
int msaa = 1;
bool renderTarget = false;
bool computeWrite = false;
std::vector<PixelFormat> viewFormats;
OptionalBool readable;
std::string debugName;
};
struct ViewSettings
{
Optional<PixelFormat> format;
Optional<TextureType> type;
OptionalInt mipmapStart;
OptionalInt mipmapCount;
OptionalInt layerStart;
OptionalInt layerCount;
std::string debugName;
};
struct Slices
{
public:
@@ -228,10 +242,14 @@ public:
}; // Slices
static int64 totalGraphicsMemory;
struct ViewInfo
{
Texture *texture;
int startMipmap;
int startLayer;
};
Texture(Graphics *gfx, const Settings &settings, const Slices *slices);
virtual ~Texture();
static int64 totalGraphicsMemory;
// Drawable.
void draw(Graphics *gfx, const Matrix4 &m) override;
@@ -255,13 +273,15 @@ public:
virtual ptrdiff_t getRenderTargetHandle() const = 0;
virtual ptrdiff_t getSamplerHandle() const = 0;
TextureType getTextureType() const;
PixelFormat getPixelFormat() const;
MipmapsMode getMipmapsMode() const;
TextureType getTextureType() const { return texType; }
PixelFormat getPixelFormat() const { return format; }
MipmapsMode getMipmapsMode() const { return mipmapsMode; }
bool isRenderTarget() const;
bool isComputeWritable() const;
bool isReadable() const;
bool isRenderTarget() const { return renderTarget; }
bool isComputeWritable() const { return computeWrite; }
bool isReadable() const { return readable; }
const std::vector<PixelFormat> &getViewFormats() const { return viewFormats; }
bool isCompressed() const;
bool isFormatLinear() const;
@@ -285,11 +305,14 @@ public:
int getRequestedMSAA() const;
virtual int getMSAA() const = 0;
virtual void setSamplerState(const SamplerState &s);
virtual void setSamplerState(const SamplerState &s) = 0;
const SamplerState &getSamplerState() const;
Quad *getQuad() const;
const ViewInfo &getRootViewInfo() const { return rootView; }
const ViewInfo &getParentViewInfo() const { return parentView; }
const std::string &getDebugName() const { return debugName; }
static int getTotalMipmapCount(int w, int h);
@@ -310,15 +333,22 @@ public:
protected:
Texture(Graphics *gfx, const Settings &settings, const Slices *slices);
Texture(Graphics *gfx, Texture *base, const ViewSettings &viewsettings);
virtual ~Texture();
void setGraphicsMemorySize(int64 size);
void uploadImageData(love::image::ImageDataBase *d, int level, int slice, int x, int y);
virtual void uploadByteData(PixelFormat pixelformat, const void *data, size_t size, int level, int slice, const Rect &r) = 0;
virtual void uploadByteData(const void *data, size_t size, int level, int slice, const Rect &r) = 0;
bool supportsGenerateMipmaps(const char *&outReason) const;
virtual void generateMipmapsInternal() = 0;
SamplerState validateSamplerState(SamplerState s) const;
bool validateDimensions(bool throwException) const;
void validatePixelFormat(Graphics *gfx) const;
TextureType texType;
@@ -327,6 +357,8 @@ protected:
bool computeWrite;
bool readable;
std::vector<PixelFormat> viewFormats;
MipmapsMode mipmapsMode;
int width;
@@ -349,6 +381,9 @@ protected:
std::string debugName;
ViewInfo rootView;
ViewInfo parentView;
}; // Texture
} // graphics
+1 -1
View File
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2006-2023 LOVE Development Team
* Copyright (c) 2006-2024 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
+1 -1
View File
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2006-2023 LOVE Development Team
* Copyright (c) 2006-2024 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
+1 -1
View File
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2006-2023 LOVE Development Team
* Copyright (c) 2006-2024 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
+1 -1
View File
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2006-2023 LOVE Development Team
* Copyright (c) 2006-2024 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
+1 -1
View File
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2006-2023 LOVE Development Team
* Copyright (c) 2006-2024 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
+1 -1
View File
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2006-2023 LOVE Development Team
* Copyright (c) 2006-2024 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
+3 -6
View File
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2006-2023 LOVE Development Team
* Copyright (c) 2006-2024 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
@@ -60,14 +60,10 @@ public:
Graphics();
virtual ~Graphics();
// Implements Module.
const char *getName() const override { return "love.graphics.metal"; }
love::graphics::Texture *newTexture(const Texture::Settings &settings, const Texture::Slices *data = nullptr) override;
love::graphics::Texture *newTextureView(love::graphics::Texture *base, const Texture::ViewSettings &viewsettings) override;
love::graphics::Buffer *newBuffer(const Buffer::Settings &settings, const std::vector<Buffer::DataDeclaration> &format, const void *data, size_t size, size_t arraylength) override;
Matrix4 computeDeviceProjection(const Matrix4 &projection, bool rendertotexture) const override;
void backbufferChanged(int width, int height, int pixelwidth, int pixelheight, bool backbufferstencil, bool backbufferdepth, int msaa) override;
bool setMode(void *context, int width, int height, int pixelwidth, int pixelheight, bool backbufferstencil, bool backbufferdepth, int msaa) override;
void unSetMode() override;
@@ -244,6 +240,7 @@ private:
StreamBuffer *uniformBuffer;
StreamBuffer::MapInfo uniformBufferData;
size_t uniformBufferOffset;
size_t uniformBufferGPUStart;
Buffer *defaultAttributesBuffer;
+34 -19
View File
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2006-2023 LOVE Development Team
* Copyright (c) 2006-2024 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
@@ -263,7 +263,8 @@ struct DefaultVertexAttributes
Graphics *Graphics::graphicsInstance = nullptr;
Graphics::Graphics()
: device(nil)
: love::graphics::Graphics("love.graphics.metal")
, device(nil)
, commandQueue(nil)
, commandBuffer(nil)
, renderEncoder(nil)
@@ -279,6 +280,7 @@ Graphics::Graphics()
, attachmentStoreActions()
, renderBindings()
, uniformBufferOffset(0)
, uniformBufferGPUStart(0)
, defaultAttributesBuffer(nullptr)
, families()
{ @autoreleasepool {
@@ -340,6 +342,7 @@ Graphics::Graphics()
};
Buffer::Settings attribsettings(BUFFERUSAGEFLAG_VERTEX, BUFFERDATAUSAGE_STATIC);
attribsettings.debugName = "Default Vertex Attributes";
defaultAttributesBuffer = newBuffer(attribsettings, dataformat, &defaults, sizeof(DefaultVertexAttributes), 0);
}
@@ -432,6 +435,11 @@ love::graphics::Texture *Graphics::newTexture(const Texture::Settings &settings,
return new Texture(this, device, settings, data);
}
love::graphics::Texture *Graphics::newTextureView(love::graphics::Texture *base, const Texture::ViewSettings &viewsettings)
{
return new Texture(this, device, base, viewsettings);
}
love::graphics::ShaderStage *Graphics::newShaderStageInternal(ShaderStageType stage, const std::string &cachekey, const std::string &source, bool gles)
{
return new ShaderStage(this, stage, source, gles, cachekey);
@@ -457,12 +465,6 @@ love::graphics::GraphicsReadback *Graphics::newReadbackInternal(ReadbackMethod m
return new GraphicsReadback(this, method, texture, slice, mipmap, rect, dest, destx, desty);
}
Matrix4 Graphics::computeDeviceProjection(const Matrix4 &projection, bool /*rendertotexture*/) const
{
uint32 flags = DEVICE_PROJECTION_FLIP_Y;
return calculateDeviceProjection(projection, flags);
}
void Graphics::backbufferChanged(int width, int height, int pixelwidth, int pixelheight, bool backbufferstencil, bool backbufferdepth, int msaa)
{
bool sizechanged = width != this->width || height != this->height
@@ -913,8 +915,8 @@ void Graphics::applyRenderState(id<MTLRenderCommandEncoder> encoder, const Verte
const auto &rt = state.renderTargets.getFirstTarget();
if (rt.texture.get())
{
rtw = rt.texture->getPixelWidth();
rth = rt.texture->getPixelHeight();
rtw = rt.texture->getPixelWidth(rt.mipmap);
rth = rt.texture->getPixelHeight(rt.mipmap);
}
else
{
@@ -1028,14 +1030,19 @@ bool Graphics::applyShaderUniforms(id<MTLComputeCommandEncoder> encoder, love::g
if (uniformBuffer->getSize() < uniformBufferOffset + size)
{
size_t newsize = uniformBuffer->getSize() * 2;
if (uniformBufferOffset > 0)
uniformBuffer->nextFrame();
uniformBuffer->release();
uniformBuffer = CreateStreamBuffer(device, BUFFERUSAGE_VERTEX, newsize);
uniformBuffer = CreateStreamBuffer(device, BUFFERUSAGE_UNIFORM, newsize);
uniformBufferData = {};
uniformBufferOffset = 0;
}
if (uniformBufferData.data == nullptr)
{
uniformBufferData = uniformBuffer->map(uniformBuffer->getSize());
uniformBufferGPUStart = uniformBuffer->getGPUReadOffset();
}
memcpy(uniformBufferData.data + uniformBufferOffset, bufferdata, size);
@@ -1043,7 +1050,7 @@ bool Graphics::applyShaderUniforms(id<MTLComputeCommandEncoder> encoder, love::g
int uniformindex = Shader::getUniformBufferBinding();
auto &bindings = renderBindings;
setBuffer(encoder, bindings, uniformindex, buffer, uniformBufferOffset);
setBuffer(encoder, bindings, uniformindex, buffer, uniformBufferGPUStart + uniformBufferOffset);
uniformBufferOffset += alignUp(size, alignment);
@@ -1120,12 +1127,15 @@ void Graphics::applyShaderUniforms(id<MTLRenderCommandEncoder> renderEncoder, lo
// Same with point size.
builtins->normalMatrix[1].w = getPointSize();
uint32 flags = Shader::CLIP_TRANSFORM_Z_NEG1_1_TO_0_1;
builtins->clipSpaceParams = Shader::computeClipSpaceParams(flags);
builtins->screenSizeParams = Vector4(getPixelWidth(), getPixelHeight(), 1.0f, 0.0f);
auto rt = states.back().renderTargets.getFirstTarget().texture.get();
if (rt != nullptr)
auto rt = states.back().renderTargets.getFirstTarget();
if (rt.texture.get())
{
builtins->screenSizeParams.x = rt->getPixelWidth();
builtins->screenSizeParams.y = rt->getPixelHeight();
builtins->screenSizeParams.x = rt.texture->getPixelWidth(rt.mipmap);
builtins->screenSizeParams.y = rt.texture->getPixelHeight(rt.mipmap);
}
builtins->constantColor = getColor();
@@ -1134,14 +1144,19 @@ void Graphics::applyShaderUniforms(id<MTLRenderCommandEncoder> renderEncoder, lo
if (uniformBuffer->getSize() < uniformBufferOffset + size)
{
size_t newsize = uniformBuffer->getSize() * 2;
if (uniformBufferOffset > 0)
uniformBuffer->nextFrame();
uniformBuffer->release();
uniformBuffer = CreateStreamBuffer(device, BUFFERUSAGE_VERTEX, newsize);
uniformBuffer = CreateStreamBuffer(device, BUFFERUSAGE_UNIFORM, newsize);
uniformBufferData = {};
uniformBufferOffset = 0;
}
if (uniformBufferData.data == nullptr)
{
uniformBufferData = uniformBuffer->map(uniformBuffer->getSize());
uniformBufferGPUStart = uniformBuffer->getGPUReadOffset();
}
memcpy(uniformBufferData.data + uniformBufferOffset, bufferdata, size);
@@ -1149,8 +1164,8 @@ void Graphics::applyShaderUniforms(id<MTLRenderCommandEncoder> renderEncoder, lo
int uniformindex = Shader::getUniformBufferBinding();
auto &bindings = renderBindings;
setBuffer(renderEncoder, bindings, SHADERSTAGE_VERTEX, uniformindex, buffer, uniformBufferOffset);
setBuffer(renderEncoder, bindings, SHADERSTAGE_PIXEL, uniformindex, buffer, uniformBufferOffset);
setBuffer(renderEncoder, bindings, SHADERSTAGE_VERTEX, uniformindex, buffer, uniformBufferGPUStart + uniformBufferOffset);
setBuffer(renderEncoder, bindings, SHADERSTAGE_PIXEL, uniformindex, buffer, uniformBufferGPUStart + uniformBufferOffset);
uniformBufferOffset += alignUp(size, alignment);
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2006-2023 LOVE Development Team
* Copyright (c) 2006-2024 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2006-2023 LOVE Development Team
* Copyright (c) 2006-2024 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
+1 -1
View File
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2006-2023 LOVE Development Team
* Copyright (c) 2006-2024 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
+1 -1
View File
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2006-2023 LOVE Development Team
* Copyright (c) 2006-2024 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
+1 -5
View File
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2006-2023 LOVE Development Team
* Copyright (c) 2006-2024 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
@@ -107,12 +107,10 @@ public:
void attach() override;
std::string getWarnings() const override { return ""; }
int getVertexAttributeIndex(const std::string &name) override;
const UniformInfo *getUniformInfo(const std::string &name) const override;
const UniformInfo *getUniformInfo(BuiltinUniform builtin) const override;
void updateUniform(const UniformInfo *info, int count) override;
void sendTextures(const UniformInfo *info, love::graphics::Texture **textures, int count) override;
void sendBuffers(const UniformInfo *info, love::graphics::Buffer **buffers, int count) override;
bool hasUniform(const std::string &name) const override;
ptrdiff_t getHandle() const override { return 0; }
void setVideoTextures(love::graphics::Texture *ytexture, love::graphics::Texture *cbtexture, love::graphics::Texture *crtexture) override;
@@ -140,13 +138,11 @@ private:
};
void buildLocalUniforms(const spirv_cross::CompilerMSL &msl, const spirv_cross::SPIRType &type, size_t baseoffset, const std::string &basename);
void addImage(const spirv_cross::CompilerMSL &msl, const spirv_cross::Resource &resource, UniformType baseType);
void compileFromGLSLang(id<MTLDevice> device, const glslang::TProgram &program);
id<MTLFunction> functions[SHADERSTAGE_MAX_ENUM];
UniformInfo *builtinUniformInfo[BUILTIN_MAX_ENUM];
std::map<std::string, UniformInfo> uniforms;
uint8 *localUniformStagingData;
uint8 *localUniformBufferData;
+93 -218
View File
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2006-2023 LOVE Development Team
* Copyright (c) 2006-2024 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
@@ -208,7 +208,7 @@ Shader::Shader(id<MTLDevice> device, StrongRef<love::graphics::ShaderStage> stag
forcedefault = true;
#endif
if (!tshader->parse(GetDefaultResources(), defaultversion, defaultprofile, forcedefault, forwardcompat, EShMsgSuppressWarnings))
if (!tshader->parse(GetResources(), defaultversion, defaultprofile, forcedefault, forwardcompat, EShMsgSuppressWarnings))
{
const char *stagename = "unknown";
ShaderStage::getConstant(stage, stagename);
@@ -299,134 +299,47 @@ void Shader::buildLocalUniforms(const spirv_cross::CompilerMSL &msl, const spirv
continue;
}
name = canonicaliizeUniformName(name);
if (offset + membersize > localUniformBufferSize)
throw love::Exception("Invalid uniform offset + size for '%s' (offset=%d, size=%d, buffer size=%d)", name.c_str(), (int)offset, (int)membersize, (int)localUniformBufferSize);
UniformInfo u = {};
u.name = name;
auto uniformit = reflection.allUniforms.find(name);
if (uniformit == reflection.allUniforms.end())
{
handleUnknownUniformName(name.c_str());
continue;
}
UniformInfo &u = *(uniformit->second);
u.active = true;
if (u.dataSize > 0)
continue;
u.dataSize = membersize;
u.count = membertype.array.empty() ? 1 : membertype.array[0];
u.components = 1;
u.data = localUniformStagingData + offset;
if (membertype.columns == 1)
{
if (membertype.basetype == SPIRType::Int)
u.baseType = UNIFORM_INT;
else if (membertype.basetype == SPIRType::UInt)
u.baseType = UNIFORM_UINT;
else
u.baseType = UNIFORM_FLOAT;
u.components = membertype.vecsize;
}
else
{
u.baseType = UNIFORM_MATRIX;
u.matrix.rows = membertype.vecsize;
u.matrix.columns = membertype.columns;
}
const auto &reflectionit = validationReflection.localUniforms.find(u.name);
if (reflectionit != validationReflection.localUniforms.end())
const auto &reflectionit = reflection.localUniformInitializerValues.find(u.name);
if (reflectionit != reflection.localUniformInitializerValues.end())
{
const auto &localuniform = reflectionit->second;
const auto &values = localuniform.initializerValues;
const auto &values = reflectionit->second;
if (!values.empty())
memcpy(u.data, values.data(), std::min(u.dataSize, values.size() * sizeof(LocalUniformValue)));
}
uniforms[u.name] = u;
BuiltinUniform builtin = BUILTIN_MAX_ENUM;
if (getConstant(u.name.c_str(), builtin))
{
if (builtin == BUILTIN_UNIFORMS_PER_DRAW)
builtinUniformDataOffset = offset;
builtinUniformInfo[builtin] = &uniforms[u.name];
builtinUniformInfo[builtin] = &u;
}
updateUniform(&u, u.count);
}
}
void Shader::addImage(const spirv_cross::CompilerMSL &msl, const spirv_cross::Resource &resource, UniformType baseType)
{
using namespace spirv_cross;
const SPIRType &basetype = msl.get_type(resource.base_type_id);
const SPIRType &type = msl.get_type(resource.type_id);
const SPIRType &imagetype = msl.get_type(basetype.image.type);
UniformInfo u = {};
u.baseType = baseType;
u.name = resource.name;
u.count = type.array.empty() ? 1 : type.array[0];
u.isDepthSampler = type.image.depth;
u.components = 1;
auto it = uniforms.find(u.name);
if (it != uniforms.end())
return;
if (!fillUniformReflectionData(u))
return;
switch (imagetype.basetype)
{
case SPIRType::Float:
u.dataBaseType = DATA_BASETYPE_FLOAT;
break;
case SPIRType::Int:
u.dataBaseType = DATA_BASETYPE_INT;
break;
case SPIRType::UInt:
u.dataBaseType = DATA_BASETYPE_UINT;
break;
default:
break;
}
switch (basetype.image.dim)
{
case spv::Dim2D:
u.textureType = basetype.image.arrayed ? TEXTURE_2D_ARRAY : TEXTURE_2D;
u.textures = new love::graphics::Texture*[u.count];
memset(u.textures, 0, sizeof(love::graphics::Texture *) * u.count);
break;
case spv::Dim3D:
u.textureType = TEXTURE_VOLUME;
u.textures = new love::graphics::Texture*[u.count];
memset(u.textures, 0, sizeof(love::graphics::Texture *) * u.count);
break;
case spv::DimCube:
if (basetype.image.arrayed)
throw love::Exception("Cubemap Arrays are not currently supported.");
u.textureType = TEXTURE_CUBE;
u.textures = new love::graphics::Texture*[u.count];
memset(u.textures, 0, sizeof(love::graphics::Texture *) * u.count);
break;
case spv::DimBuffer:
u.baseType = UNIFORM_TEXELBUFFER;
u.buffers = new love::graphics::Buffer*[u.count];
memset(u.buffers, 0, sizeof(love::graphics::Buffer *) * u.count);
break;
default:
// TODO: error? continue?
break;
}
u.dataSize = sizeof(int) * u.count;
u.data = malloc(u.dataSize);
for (int i = 0; i < u.count; i++)
u.ints[i] = -1; // Initialized below, after compiling.
uniforms[u.name] = u;
BuiltinUniform builtin;
if (getConstant(resource.name.c_str(), builtin))
builtinUniformInfo[builtin] = &uniforms[u.name];
}
void Shader::compileFromGLSLang(id<MTLDevice> device, const glslang::TProgram &program)
{
using namespace glslang;
@@ -473,21 +386,10 @@ void Shader::compileFromGLSLang(id<MTLDevice> device, const glslang::TProgram &p
auto &msl = *mslpointer;
auto interfacevars = msl.get_active_interface_variables();
ShaderResources resources = msl.get_shader_resources(interfacevars);
msl.set_enabled_interface_variables(interfacevars);
ShaderResources resources = msl.get_shader_resources();
for (const auto &resource : resources.storage_images)
{
addImage(msl, resource, UNIFORM_STORAGETEXTURE);
}
for (const auto &resource : resources.sampled_images)
{
addImage(msl, resource, UNIFORM_SAMPLER);
}
for (const auto &resource : resources.uniform_buffers)
{
MSLResourceBinding binding;
@@ -535,33 +437,6 @@ void Shader::compileFromGLSLang(id<MTLDevice> device, const glslang::TProgram &p
binding.desc_set = msl.get_decoration(resource.id, spv::DecorationDescriptorSet);
binding.msl_buffer = metalBufferIndices[stageindex]++;
msl.add_msl_resource_binding(binding);
auto it = uniforms.find(resource.name);
if (it != uniforms.end())
continue;
const SPIRType &type = msl.get_type(resource.type_id);
UniformInfo u = {};
u.baseType = UNIFORM_STORAGEBUFFER;
u.components = 1;
u.name = resource.name;
u.count = type.array.empty() ? 1 : type.array[0];
if (!fillUniformReflectionData(u))
continue;
u.buffers = new love::graphics::Buffer*[u.count];
u.dataSize = sizeof(int) * u.count;
u.data = malloc(u.dataSize);
for (int i = 0; i < u.count; i++)
{
u.ints[i] = -1; // Initialized below, after compiling.
u.buffers[i] = nullptr;
}
uniforms[u.name] = u;
}
if (stageindex == SHADERSTAGE_VERTEX)
@@ -634,7 +509,8 @@ void Shader::compileFromGLSLang(id<MTLDevice> device, const glslang::TProgram &p
if (library == nil && err != nil)
{
NSLog(@"errors: %@", err);
throw love::Exception("Error compiling converted Metal shader code");
NSString *errorstr = err.localizedDescription;
throw love::Exception("Error compiling converted Metal shader code:\n\n%s", errorstr.UTF8String);
}
functions[stageindex] = [library newFunctionWithName:library.functionNames[0]];
@@ -645,11 +521,15 @@ void Shader::compileFromGLSLang(id<MTLDevice> device, const glslang::TProgram &p
auto setTextureBinding = [this](CompilerMSL &msl, int stageindex, const spirv_cross::Resource &resource) -> void
{
auto it = uniforms.find(resource.name);
if (it == uniforms.end())
std::string name = canonicaliizeUniformName(resource.name);
auto it = reflection.allUniforms.find(name);
if (it == reflection.allUniforms.end())
{
handleUnknownUniformName(name.c_str());
return;
}
UniformInfo &u = it->second;
UniformInfo &u = *(it->second);
uint32 texturebinding = msl.get_automatic_msl_resource_binding(resource.id);
uint32 samplerbinding = msl.get_automatic_msl_resource_binding_secondary(resource.id);
@@ -657,15 +537,16 @@ void Shader::compileFromGLSLang(id<MTLDevice> device, const glslang::TProgram &p
if (texturebinding == (uint32)-1)
{
// No valid binding, the uniform was likely optimized out because it's not used.
uniforms.erase(resource.name);
return;
}
for (int i = 0; i < u.count; i++)
u.active = true;
if (u.location < 0)
{
if (u.ints[i] == -1)
u.location = (int)textureBindings.size();
for (int i = 0; i < u.count; i++)
{
u.ints[i] = (int)textureBindings.size();
TextureBinding b = {};
b.access = u.access;
@@ -683,11 +564,18 @@ void Shader::compileFromGLSLang(id<MTLDevice> device, const glslang::TProgram &p
textureBindings.push_back(b);
}
}
auto &b = textureBindings[u.ints[i]];
for (int i = 0; i < u.count; i++)
{
auto &b = textureBindings[u.location + i];
b.textureStages[stageindex] = (uint8) texturebinding;
b.samplerStages[stageindex] = (uint8) samplerbinding;
}
BuiltinUniform builtin;
if (getConstant(name.c_str(), builtin))
builtinUniformInfo[builtin] = &u;
};
for (const auto &resource : resources.sampled_images)
@@ -702,9 +590,13 @@ void Shader::compileFromGLSLang(id<MTLDevice> device, const glslang::TProgram &p
for (const auto &resource : resources.storage_buffers)
{
auto it = uniforms.find(resource.name);
if (it == uniforms.end())
std::string name = canonicaliizeUniformName(resource.name);
auto it = reflection.storageBuffers.find(name);
if (it == reflection.storageBuffers.end())
{
handleUnknownUniformName(name.c_str());
continue;
}
UniformInfo &u = it->second;
@@ -712,15 +604,16 @@ void Shader::compileFromGLSLang(id<MTLDevice> device, const glslang::TProgram &p
if (bufferbinding == (uint32)-1)
{
// No valid binding, the uniform was likely optimized out because it's not used.
uniforms.erase(resource.name);
continue;
}
for (int i = 0; i < u.count; i++)
u.active = true;
if (u.location < 0)
{
if (u.ints[i] == -1)
u.location = (int)bufferBindings.size();
for (int i = 0; i < u.count; i++)
{
u.ints[i] = (int)bufferBindings.size();
BufferBinding b = {};
b.access = u.access;
@@ -729,25 +622,26 @@ void Shader::compileFromGLSLang(id<MTLDevice> device, const glslang::TProgram &p
bufferBindings.push_back(b);
}
bufferBindings[u.ints[i]].stages[stageindex] = (uint8) bufferbinding;
}
for (int i = 0; i < u.count; i++)
bufferBindings[u.location + i].stages[stageindex] = (uint8) bufferbinding;
}
}
// Initialize default resource bindings.
for (auto &kvp : uniforms)
for (const auto &kvp : reflection.allUniforms)
{
UniformInfo &info = kvp.second;
switch (info.baseType)
const UniformInfo *info = kvp.second;
switch (info->baseType)
{
case UNIFORM_SAMPLER:
case UNIFORM_STORAGETEXTURE:
sendTextures(&info, info.textures, info.count);
sendTextures(info, &activeTextures[info->resourceIndex], info->count);
break;
case UNIFORM_TEXELBUFFER:
case UNIFORM_STORAGEBUFFER:
sendBuffers(&info, info.buffers, info.count);
sendBuffers(info, &activeBuffers[info->resourceIndex], info->count);
break;
default:
break;
@@ -767,31 +661,6 @@ Shader::~Shader()
cachedRenderPipelines.clear();
for (const auto &it : uniforms)
{
const auto &u = it.second;
if (u.baseType == UNIFORM_SAMPLER || u.baseType == UNIFORM_STORAGETEXTURE)
{
free(u.data);
for (int i = 0; i < u.count; i++)
{
if (u.textures[i] != nullptr)
u.textures[i]->release();
}
delete[] u.textures;
}
else if (u.baseType == UNIFORM_TEXELBUFFER || u.baseType == UNIFORM_STORAGEBUFFER)
{
free(u.data);
for (int i = 0; i < u.count; i++)
{
if (u.buffers[i] != nullptr)
u.buffers[i]->release();
}
delete[] u.buffers;
}
}
delete[] localUniformStagingData;
delete[] localUniformBufferData;
}}
@@ -813,12 +682,6 @@ int Shader::getVertexAttributeIndex(const std::string &name)
return it != attributes.end() ? it->second : -1;
}
const Shader::UniformInfo *Shader::getUniformInfo(const std::string &name) const
{
const auto it = uniforms.find(name);
return it != uniforms.end() ? &(it->second) : nullptr;
}
const Shader::UniformInfo *Shader::getUniformInfo(BuiltinUniform builtin) const
{
return builtinUniformInfo[(int)builtin];
@@ -826,6 +689,9 @@ const Shader::UniformInfo *Shader::getUniformInfo(BuiltinUniform builtin) const
void Shader::updateUniform(const UniformInfo *info, int count)
{
if (info->dataSize == 0)
return;
if (current == this)
Graphics::flushBatchedDrawsGlobal();
@@ -895,12 +761,21 @@ void Shader::sendTextures(const UniformInfo *info, love::graphics::Texture **tex
tex->retain();
if (info->textures[i] != nullptr)
info->textures[i]->release();
int resourceindex = info->resourceIndex + i;
info->textures[i] = tex;
if (activeTextures[resourceindex] != nullptr)
activeTextures[resourceindex]->release();
auto &binding = textureBindings[info->ints[i]];
activeTextures[resourceindex] = tex;
if (info->location < 0)
continue;
int bindingindex = info->location + i;
if (bindingindex < 0)
continue;
auto &binding = textureBindings[bindingindex];
if (isdefault && (binding.access & ACCESS_WRITE) != 0)
{
binding.texture = nil;
@@ -927,7 +802,6 @@ void Shader::sendBuffers(const UniformInfo *info, love::graphics::Buffer **buffe
count = std::min(count, info->count);
// Bind the textures to the texture units.
for (int i = 0; i < count; i++)
{
love::graphics::Buffer *buffer = buffers[i];
@@ -949,18 +823,24 @@ void Shader::sendBuffers(const UniformInfo *info, love::graphics::Buffer **buffe
buffer->retain();
if (info->buffers[i] != nullptr)
info->buffers[i]->release();
int resourceindex = info->resourceIndex + i;
info->buffers[i] = buffer;
if (activeBuffers[resourceindex] != nullptr)
activeBuffers[resourceindex]->release();
if (texelbinding)
activeBuffers[resourceindex] = buffer;
if (info->location < 0)
continue;
int bindingindex = info->location + i;
if (texelbinding && bindingindex >= 0)
{
textureBindings[info->ints[i]].texture = getMTLTexture(buffer);
textureBindings[bindingindex].texture = getMTLTexture(buffer);
}
else if (storagebinding)
else if (storagebinding && bindingindex >= 0)
{
auto &binding = bufferBindings[info->ints[i]];
auto &binding = bufferBindings[bindingindex];
if (isdefault && (binding.access & ACCESS_WRITE) != 0)
binding.buffer = nil;
else
@@ -987,11 +867,6 @@ void Shader::setVideoTextures(love::graphics::Texture *ytexture, love::graphics:
}
}
bool Shader::hasUniform(const std::string &name) const
{
return uniforms.find(name) != uniforms.end();
}
id<MTLRenderPipelineState> Shader::getCachedRenderPipeline(const RenderPipelineKey &key)
{
auto it = cachedRenderPipelines.find(key);
@@ -1073,7 +948,7 @@ id<MTLRenderPipelineState> Shader::getCachedRenderPipeline(const RenderPipelineK
const auto &attrib = attributes.attribs[i];
int metalBufferIndex = firstVertexBufferBinding + attrib.bufferIndex;
vertdesc.attributes[i].format = getMTLVertexFormat(attrib.format);
vertdesc.attributes[i].format = getMTLVertexFormat(attrib.getFormat());
vertdesc.attributes[i].offset = attrib.offsetFromVertex;
vertdesc.attributes[i].bufferIndex = metalBufferIndex;
+1 -1
View File
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2006-2023 LOVE Development Team
* Copyright (c) 2006-2024 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
+1 -1
View File
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2006-2023 LOVE Development Team
* Copyright (c) 2006-2024 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
+1 -1
View File
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2006-2023 LOVE Development Team
* Copyright (c) 2006-2024 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
+8 -1
View File
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2006-2023 LOVE Development Team
* Copyright (c) 2006-2024 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
@@ -48,6 +48,8 @@ public:
if (buffer == nil)
throw love::Exception("Out of graphics memory.");
buffer.label = [NSString stringWithFormat:@"StreamBuffer (usage: %d, size: %ld)", usage, size];
data = (uint8 *) buffer.contents;
for (int i = 0; i < BUFFER_FRAMES; i++)
@@ -65,6 +67,11 @@ public:
}
}}
size_t getGPUReadOffset() const override
{
return (frameIndex * bufferSize) + frameGPUReadOffset;
}
MapInfo map(size_t /*minsize*/) override
{
// Make sure this frame's section of the buffer is done being used.
+7 -6
View File
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2006-2023 LOVE Development Team
* Copyright (c) 2006-2024 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
@@ -38,6 +38,7 @@ class Texture final : public love::graphics::Texture
public:
Texture(love::graphics::Graphics *gfx, id<MTLDevice> device, const Settings &settings, const Slices *data);
Texture(love::graphics::Graphics *gfx, id<MTLDevice> device, love::graphics::Texture *base, const Texture::ViewSettings &viewsettings);
virtual ~Texture();
void copyFromBuffer(love::graphics::Buffer *source, size_t sourceoffset, int sourcewidth, size_t size, int slice, int mipmap, const Rect &rect) override;
@@ -55,14 +56,14 @@ public:
private:
void uploadByteData(PixelFormat pixelformat, const void *data, size_t size, int level, int slice, const Rect &r) override;
void uploadByteData(const void *data, size_t size, int level, int slice, const Rect &r) override;
void generateMipmapsInternal() override;
id<MTLTexture> texture;
id<MTLTexture> msaaTexture;
id<MTLSamplerState> sampler;
id<MTLTexture> texture = nil;
id<MTLTexture> msaaTexture = nil;
id<MTLSamplerState> sampler = nil;
int actualMSAASamples;
int actualMSAASamples = 1;
}; // Texture
+50 -14
View File
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2006-2023 LOVE Development Team
* Copyright (c) 2006-2024 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
@@ -43,10 +43,6 @@ static MTLTextureType getMTLTextureType(TextureType type, int msaa)
Texture::Texture(love::graphics::Graphics *gfxbase, id<MTLDevice> device, const Settings &settings, const Slices *data)
: love::graphics::Texture(gfxbase, settings, data)
, texture(nil)
, msaaTexture(nil)
, sampler(nil)
, actualMSAASamples(1)
{ @autoreleasepool {
auto gfx = (Graphics *) gfxbase;
@@ -81,6 +77,15 @@ Texture::Texture(love::graphics::Graphics *gfxbase, id<MTLDevice> device, const
if (computeWrite)
desc.usage |= MTLTextureUsageShaderWrite;
for (PixelFormat viewformat : viewFormats)
{
if (getLinearPixelFormat(viewformat) != getLinearPixelFormat(format))
{
desc.usage |= MTLTextureUsagePixelFormatView;
break;
}
}
texture = [device newTextureWithDescriptor:desc];
if (texture == nil)
@@ -149,7 +154,7 @@ Texture::Texture(love::graphics::Graphics *gfxbase, id<MTLDevice> device, const
emptydata.resize(getPixelFormatSliceSize(format, w, h));
Rect r = {0, 0, getPixelWidth(mip), getPixelHeight(mip)};
uploadByteData(format, emptydata.data(), emptydata.size(), mip, slice, r);
uploadByteData(emptydata.data(), emptydata.size(), mip, slice, r);
}
else if (isRenderTarget())
{
@@ -212,6 +217,41 @@ Texture::Texture(love::graphics::Graphics *gfxbase, id<MTLDevice> device, const
setSamplerState(samplerState);
}}
Texture::Texture(love::graphics::Graphics *gfx, id<MTLDevice> device, love::graphics::Texture *base, const Texture::ViewSettings &viewsettings)
: love::graphics::Texture(gfx, base, viewsettings)
{
id<MTLTexture> basetex = ((Texture *) base)->texture;
auto formatdesc = Metal::convertPixelFormat(device, format);
int slices = texType == TEXTURE_CUBE ? 6 : getLayerCount();
if (formatdesc.swizzled)
{
if (@available(macOS 10.15, iOS 13, *))
{
texture = [basetex newTextureViewWithPixelFormat:formatdesc.format
textureType:getMTLTextureType(texType, 1)
levels:NSMakeRange(parentView.startMipmap, mipmapCount)
slices:NSMakeRange(parentView.startLayer, slices)
swizzle:formatdesc.swizzle];
}
}
else
{
texture = [basetex newTextureViewWithPixelFormat:formatdesc.format
textureType:getMTLTextureType(texType, 1)
levels:NSMakeRange(parentView.startMipmap, mipmapCount)
slices:NSMakeRange(parentView.startLayer, slices)];
}
if (texture == nil)
throw love::Exception("Could not create Metal texture view.");
if (!debugName.empty())
texture.label = @(debugName.c_str());
setSamplerState(samplerState);
}
Texture::~Texture()
{ @autoreleasepool {
texture = nil;
@@ -219,15 +259,13 @@ Texture::~Texture()
sampler = nil;
}}
void Texture::uploadByteData(PixelFormat pixelformat, const void *data, size_t size, int level, int slice, const Rect &r)
void Texture::uploadByteData(const void *data, size_t size, int level, int slice, const Rect &r)
{ @autoreleasepool {
auto gfx = Graphics::getInstance();
id<MTLBuffer> buffer = [gfx->device newBufferWithBytes:data
length:size
options:MTLResourceStorageModeShared];
memcpy(buffer.contents, data, size);
id<MTLBlitCommandEncoder> encoder = gfx->useBlitEncoder();
int z = 0;
@@ -239,7 +277,7 @@ void Texture::uploadByteData(PixelFormat pixelformat, const void *data, size_t s
MTLBlitOption options = MTLBlitOptionNone;
switch (pixelformat)
switch (format)
{
case PIXELFORMAT_PVR1_RGB2_UNORM:
case PIXELFORMAT_PVR1_RGB4_UNORM:
@@ -342,10 +380,8 @@ void Texture::setSamplerState(const SamplerState &s)
if (s.depthSampleMode.hasValue && !Graphics::getInstance()->isDepthCompareSamplerSupported())
throw love::Exception("Depth comparison sampling in shaders is not supported on this system.");
// Base class does common validation and assigns samplerState.
love::graphics::Texture::setSamplerState(s);
sampler = Graphics::getInstance()->getCachedSampler(s);
samplerState = validateSamplerState(s);
sampler = Graphics::getInstance()->getCachedSampler(samplerState);
}}
} // metal
+1 -1
View File
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2006-2023 LOVE Development Team
* Copyright (c) 2006-2024 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
+1 -1
View File
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2006-2023 LOVE Development Team
* Copyright (c) 2006-2024 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
+1 -1
View File
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2006-2023 LOVE Development Team
* Copyright (c) 2006-2024 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
+1 -1
View File
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2006-2023 LOVE Development Team
* Copyright (c) 2006-2024 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
+28 -25
View File
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2006-2023 LOVE Development Team
* Copyright (c) 2006-2024 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
@@ -106,7 +106,8 @@ love::graphics::Graphics *createInstance()
}
Graphics::Graphics()
: windowHasStencil(false)
: love::graphics::Graphics("love.graphics.opengl")
, windowHasStencil(false)
, mainVAO(0)
, internalBackbufferFBO(0)
, requestedBackbufferMSAA(0)
@@ -147,11 +148,6 @@ Graphics::~Graphics()
delete[] bufferMapMemory;
}
const char *Graphics::getName() const
{
return "love.graphics.opengl";
}
love::graphics::StreamBuffer *Graphics::newStreamBuffer(BufferUsage type, size_t size)
{
return CreateStreamBuffer(type, size);
@@ -162,6 +158,11 @@ love::graphics::Texture *Graphics::newTexture(const Texture::Settings &settings,
return new Texture(this, settings, data);
}
love::graphics::Texture *Graphics::newTextureView(love::graphics::Texture *base, const Texture::ViewSettings &viewsettings)
{
return new Texture(this, base, viewsettings);
}
love::graphics::ShaderStage *Graphics::newShaderStageInternal(ShaderStageType stage, const std::string &cachekey, const std::string &source, bool gles)
{
return new ShaderStage(this, stage, source, gles, cachekey);
@@ -187,18 +188,6 @@ love::graphics::GraphicsReadback *Graphics::newReadbackInternal(ReadbackMethod m
return new GraphicsReadback(this, method, texture, slice, mipmap, rect, dest, destx, desty);
}
Matrix4 Graphics::computeDeviceProjection(const Matrix4 &projection, bool rendertotexture) const
{
uint32 flags = DEVICE_PROJECTION_DEFAULT;
// The projection matrix is flipped compared to rendering to a texture, due
// to OpenGL considering (0,0) bottom-left instead of top-left.
if (!rendertotexture)
flags |= DEVICE_PROJECTION_FLIP_Y;
return calculateDeviceProjection(projection, flags);
}
void Graphics::backbufferChanged(int width, int height, int pixelwidth, int pixelheight, bool backbufferstencil, bool backbufferdepth, int msaa)
{
bool changed = width != this->width || height != this->height
@@ -707,24 +696,27 @@ void Graphics::drawQuads(int start, int count, const VertexAttributes &attribute
static void APIENTRY debugCB(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei /*len*/, const GLchar *msg, const GLvoid* /*usr*/)
{
if (severity == GL_DEBUG_SEVERITY_NOTIFICATION)
return;
// Human-readable strings for the debug info.
const char *sourceStr = OpenGL::debugSourceString(source);
const char *typeStr = OpenGL::debugTypeString(type);
const char *severityStr = OpenGL::debugSeverityString(severity);
const char *fmt = "OpenGL: %s [source=%s, type=%s, severity=%s, id=%d]\n";
printf(fmt, msg, sourceStr, typeStr, severityStr, id);
const char *fmt = "OpenGL: [source=%s, type=%s, severity=%s, id=%d]: %s\n";
printf(fmt, sourceStr, typeStr, severityStr, id, msg);
}
void Graphics::setDebug(bool enable)
{
// Make sure debug output is supported. The AMD ext. is a bit different
// so we don't make use of it, since AMD drivers now support KHR_debug.
if (!(GLAD_VERSION_4_3 || GLAD_KHR_debug || GLAD_ARB_debug_output))
if (!(GLAD_VERSION_4_3 || GLAD_ES_VERSION_3_2 || GLAD_KHR_debug || GLAD_ARB_debug_output))
return;
// TODO: We don't support GL_KHR_debug in GLES yet.
if (GLAD_ES_VERSION_2_0)
if (GLAD_ES_VERSION_2_0 && !GLAD_ES_VERSION_3_2)
return;
// Ugly hack to reduce code duplication.
@@ -740,7 +732,7 @@ void Graphics::setDebug(bool enable)
glDebugMessageCallback(nullptr, nullptr);
// We can disable debug output entirely with KHR_debug.
if (GLAD_VERSION_4_3 || GLAD_KHR_debug)
if (GLAD_VERSION_4_3 || GLAD_ES_VERSION_3_2 || GLAD_KHR_debug)
glDisable(GL_DEBUG_OUTPUT);
return;
@@ -758,7 +750,7 @@ void Graphics::setDebug(bool enable)
glDebugMessageControl(GL_DEBUG_SOURCE_API, GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR, GL_DONT_CARE, 0, 0, GL_FALSE);
glDebugMessageControl(GL_DEBUG_SOURCE_SHADER_COMPILER, GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR, GL_DONT_CARE, 0, 0, GL_FALSE);
if (GLAD_VERSION_4_3 || GLAD_KHR_debug)
if (GLAD_VERSION_4_3 || GLAD_ES_VERSION_3_2 || GLAD_KHR_debug)
glEnable(GL_DEBUG_OUTPUT);
::printf("OpenGL debug output enabled (LOVE_GRAPHICS_DEBUG=1)\n");
@@ -1689,6 +1681,17 @@ void Graphics::initCapabilities()
pixelFormatUsage[i][0] = computePixelFormatUsage(format, false);
pixelFormatUsage[i][1] = computePixelFormatUsage(format, true);
}
#ifdef LOVE_ANDROID
// This can't be done in initContext with the rest of the bug checks because
// isPixelFormatSupported relies on state initialized here / after init.
if (GLAD_ES_VERSION_3_0 && !isPixelFormatSupported(PIXELFORMAT_R8_UNORM, PIXELFORMATUSAGEFLAGS_SAMPLE | PIXELFORMATUSAGEFLAGS_RENDERTARGET))
{
gl.bugs.brokenR8PixelFormat = true;
pixelFormatUsage[PIXELFORMAT_R8_UNORM][0] = computePixelFormatUsage(PIXELFORMAT_R8_UNORM, false);
pixelFormatUsage[PIXELFORMAT_R8_UNORM][1] = computePixelFormatUsage(PIXELFORMAT_R8_UNORM, true);
}
#endif
}
uint32 Graphics::computePixelFormatUsage(PixelFormat format, bool readable)
+2 -6
View File
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2006-2023 LOVE Development Team
* Copyright (c) 2006-2024 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
@@ -56,14 +56,10 @@ public:
Graphics();
virtual ~Graphics();
// Implements Module.
const char *getName() const override;
love::graphics::Texture *newTexture(const Texture::Settings &settings, const Texture::Slices *data = nullptr) override;
love::graphics::Texture *newTextureView(love::graphics::Texture *base, const Texture::ViewSettings &viewsettings) override;
love::graphics::Buffer *newBuffer(const Buffer::Settings &settings, const std::vector<Buffer::DataDeclaration> &format, const void *data, size_t size, size_t arraylength) override;
Matrix4 computeDeviceProjection(const Matrix4 &projection, bool rendertotexture) const override;
void backbufferChanged(int width, int height, int pixelwidth, int pixelheight, bool backbufferstencil, bool backbufferdepth, int msaa) override;
bool setMode(void *context, int width, int height, int pixelwidth, int pixelheight, bool backbufferstencil, bool backbufferdepth, int msaa) override;
void unSetMode() override;
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2006-2023 LOVE Development Team
* Copyright (c) 2006-2024 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
@@ -68,8 +68,6 @@ GraphicsReadback::GraphicsReadback(love::graphics::Graphics *gfx, ReadbackMethod
{
void *dest = prepareReadbackDest(size);
love::thread::Lock lock(imageData->getMutex());
// Direct readback without copying avoids the need for a staging buffer,
// and lowers the system requirements of immediate RT readback.
Texture *t = (Texture *) texture;
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2006-2023 LOVE Development Team
* Copyright (c) 2006-2024 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
+24 -16
View File
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2006-2023 LOVE Development Team
* Copyright (c) 2006-2024 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
@@ -324,14 +324,6 @@ void OpenGL::setupContext()
setColorWriteMask(state.colorWriteMask);
contextInitialized = true;
#ifdef LOVE_ANDROID
// This can't be done in initContext with the rest of the bug checks because
// isPixelFormatSupported relies on state initialized here / after init.
auto gfx = Module::getInstance<Graphics>(Module::M_GRAPHICS);
if (GLAD_ES_VERSION_3_0 && gfx != nullptr && !gfx->isPixelFormatSupported(PIXELFORMAT_R8_UNORM, PIXELFORMATUSAGEFLAGS_SAMPLE | PIXELFORMATUSAGEFLAGS_RENDERTARGET))
bugs.brokenR8PixelFormat = true;
#endif
}
void OpenGL::deInitContext()
@@ -870,7 +862,7 @@ void OpenGL::setVertexAttributes(const VertexAttributes &attributes, const Buffe
int components = 0;
GLboolean normalized = GL_FALSE;
bool intformat = false;
GLenum gltype = getGLVertexDataType(attrib.format, components, normalized, intformat);
GLenum gltype = getGLVertexDataType(attrib.getFormat(), components, normalized, intformat);
const void *offsetpointer = reinterpret_cast<void*>(bufferinfo.offset + attrib.offsetFromVertex);
@@ -1344,7 +1336,17 @@ bool OpenGL::rawTexStorage(TextureType target, int levels, PixelFormat pixelform
glTexParameteri(gltarget, GL_TEXTURE_SWIZZLE_A, fmt.swizzle[3]);
}
if (isTexStorageSupported())
bool usetexstorage = isTexStorageSupported();
// The fallback for bugs.brokenR8PixelFormat is GL_LUMINANCE, which doesn't have a sized
// version in ES3 so it can't be used with glTexStorage.
if (pixelformat == PIXELFORMAT_R8_UNORM && bugs.brokenR8PixelFormat && GLAD_ES_VERSION_3_0)
{
usetexstorage = false;
fmt.internalformat = fmt.externalformat;
}
if (usetexstorage)
{
if (target == TEXTURE_2D || target == TEXTURE_CUBE)
glTexStorage2D(gltarget, levels, fmt.internalformat, width, height);
@@ -2162,8 +2164,6 @@ uint32 OpenGL::getPixelFormatUsageFlags(PixelFormat pixelformat)
if (GLAD_ES_VERSION_3_0 || GLAD_VERSION_3_0
|| ((GLAD_ARB_framebuffer_sRGB || GLAD_EXT_framebuffer_sRGB) && (GLAD_VERSION_2_1 || GLAD_EXT_texture_sRGB)))
flags |= commonrender;
if (GLAD_VERSION_4_3 || GLAD_ES_VERSION_3_1)
flags |= computewrite;
break;
case PIXELFORMAT_BGRA8_UNORM:
case PIXELFORMAT_BGRA8_sRGB:
@@ -2190,7 +2190,7 @@ uint32 OpenGL::getPixelFormatUsageFlags(PixelFormat pixelformat)
flags |= commonsample | commonrender;
if (GLAD_ES_VERSION_3_0 || (GLAD_OES_texture_half_float && GLAD_EXT_texture_rg))
flags |= commonsample;
if (GLAD_EXT_color_buffer_half_float && (GLAD_ES_VERSION_3_0 || GLAD_EXT_texture_rg))
if ((GLAD_EXT_color_buffer_half_float || GLAD_EXT_color_buffer_float) && (GLAD_ES_VERSION_3_0 || GLAD_EXT_texture_rg))
flags |= commonrender;
if (!(GLAD_VERSION_1_1 || GLAD_ES_VERSION_3_0 || GLAD_OES_texture_half_float_linear))
flags &= ~PIXELFORMATUSAGEFLAGS_LINEAR;
@@ -2202,7 +2202,7 @@ uint32 OpenGL::getPixelFormatUsageFlags(PixelFormat pixelformat)
flags |= commonsample | commonrender;
if (GLAD_ES_VERSION_3_0 || GLAD_OES_texture_half_float)
flags |= commonsample;
if (GLAD_EXT_color_buffer_half_float)
if (GLAD_EXT_color_buffer_half_float || GLAD_EXT_color_buffer_float)
flags |= commonrender;
if (!(GLAD_VERSION_1_1 || GLAD_ES_VERSION_3_0 || GLAD_OES_texture_half_float_linear))
flags &= ~PIXELFORMATUSAGEFLAGS_LINEAR;
@@ -2218,6 +2218,8 @@ uint32 OpenGL::getPixelFormatUsageFlags(PixelFormat pixelformat)
flags |= commonsample | commonrender;
if (GLAD_ES_VERSION_3_0 || (GLAD_OES_texture_float && GLAD_EXT_texture_rg))
flags |= commonsample;
if (GLAD_EXT_color_buffer_float)
flags |= commonrender;
if (!(GLAD_VERSION_1_1 || GLAD_ES_VERSION_3_0 || GLAD_OES_texture_half_float_linear))
flags &= ~PIXELFORMATUSAGEFLAGS_LINEAR;
if (GLAD_VERSION_4_3)
@@ -2228,6 +2230,8 @@ uint32 OpenGL::getPixelFormatUsageFlags(PixelFormat pixelformat)
flags |= commonsample | commonrender;
if (GLAD_ES_VERSION_3_0 || GLAD_OES_texture_float)
flags |= commonsample;
if (GLAD_EXT_color_buffer_float)
flags |= commonrender;
if (!(GLAD_VERSION_1_1 || GLAD_OES_texture_float_linear))
flags &= ~PIXELFORMATUSAGEFLAGS_LINEAR;
if (GLAD_VERSION_4_3 || GLAD_ES_VERSION_3_1)
@@ -2295,10 +2299,12 @@ uint32 OpenGL::getPixelFormatUsageFlags(PixelFormat pixelformat)
flags |= computewrite;
break;
case PIXELFORMAT_RG11B10_FLOAT:
if (GLAD_VERSION_3_0 || GLAD_EXT_packed_float || GLAD_APPLE_texture_packed_float)
if (GLAD_ES_VERSION_3_1 || GLAD_VERSION_3_0 || GLAD_EXT_packed_float || GLAD_APPLE_texture_packed_float)
flags |= commonsample;
if (GLAD_VERSION_3_0 || GLAD_EXT_packed_float || GLAD_APPLE_color_buffer_packed_float)
flags |= commonrender;
if (GLAD_EXT_color_buffer_float)
flags |= commonrender;
if (GLAD_VERSION_4_3)
flags |= computewrite;
break;
@@ -2506,6 +2512,8 @@ const char *OpenGL::debugSeverityString(GLenum severity)
return "medium";
case GL_DEBUG_SEVERITY_LOW:
return "low";
case GL_DEBUG_SEVERITY_NOTIFICATION:
return "notification";
default:
return "unknown";
}
+1 -1
View File
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2006-2023 LOVE Development Team
* Copyright (c) 2006-2024 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
+118 -595
View File
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2006-2023 LOVE Development Team
* Copyright (c) 2006-2024 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
@@ -38,11 +38,6 @@ namespace graphics
namespace opengl
{
static bool isBuffer(Shader::UniformType utype)
{
return utype == Shader::UNIFORM_TEXELBUFFER || utype == Shader::UNIFORM_STORAGEBUFFER;
}
Shader::Shader(StrongRef<love::graphics::ShaderStage> stages[SHADERSTAGE_MAX_ENUM], const CompileOptions &options)
: love::graphics::Shader(stages, options)
, program(0)
@@ -58,32 +53,11 @@ Shader::~Shader()
{
unloadVolatile();
for (const auto &p : uniforms)
for (const auto &p : reflection.allUniforms)
{
// Allocated with malloc().
if (p.second.data != nullptr)
free(p.second.data);
if (p.second.baseType == UNIFORM_SAMPLER || p.second.baseType == UNIFORM_STORAGETEXTURE)
{
for (int i = 0; i < p.second.count; i++)
{
if (p.second.textures[i] != nullptr)
p.second.textures[i]->release();
}
delete[] p.second.textures;
}
else if (isBuffer(p.second.baseType))
{
for (int i = 0; i < p.second.count; i++)
{
if (p.second.buffers[i] != nullptr)
p.second.buffers[i]->release();
}
delete[] p.second.buffers;
}
if (p.second->data != nullptr)
free(p.second->data);
}
}
@@ -96,6 +70,26 @@ void Shader::mapActiveUniforms()
builtinUniformInfo[i] = nullptr;
}
// Make sure all stored resources have their Volatiles loaded before
// the sendTextures/sendBuffers calls below, since they call getHandle().
for (love::graphics::Texture *tex : activeTextures)
{
if (tex == nullptr)
continue;
Volatile *v = dynamic_cast<Volatile *>(tex);
if (v != nullptr)
v->loadVolatile();
}
for (love::graphics::Buffer *buffer : activeBuffers)
{
if (buffer == nullptr)
continue;
Volatile *v = dynamic_cast<Volatile *>(buffer);
if (v != nullptr)
v->loadVolatile();
}
GLint activeprogram = 0;
glGetIntegerv(GL_CURRENT_PROGRAM, &activeprogram);
@@ -107,43 +101,39 @@ void Shader::mapActiveUniforms()
GLchar cname[256];
const GLint bufsize = (GLint) (sizeof(cname) / sizeof(GLchar));
std::map<std::string, UniformInfo> olduniforms = uniforms;
uniforms.clear();
auto gfx = Module::getInstance<love::graphics::Graphics>(Module::M_GRAPHICS);
for (int uindex = 0; uindex < numuniforms; uindex++)
{
GLsizei namelen = 0;
GLenum gltype = 0;
UniformInfo u = {};
int count = 0;
glGetActiveUniform(program, (GLuint) uindex, bufsize, &namelen, &u.count, &gltype, cname);
glGetActiveUniform(program, (GLuint) uindex, bufsize, &namelen, &count, &gltype, cname);
u.name = std::string(cname, (size_t) namelen);
u.location = glGetUniformLocation(program, u.name.c_str());
u.access = ACCESS_READ;
computeUniformTypeInfo(gltype, u);
std::string name(cname, (size_t) namelen);
int location = glGetUniformLocation(program, name.c_str());
// glGetActiveUniform appends "[0]" to the end of array uniform names...
if (u.name.length() > 3)
if (location == -1)
continue;
name = canonicaliizeUniformName(name);
const auto &uniformit = reflection.allUniforms.find(name);
if (uniformit == reflection.allUniforms.end())
{
size_t findpos = u.name.find("[0]");
if (findpos != std::string::npos && findpos == u.name.length() - 3)
u.name.erase(u.name.length() - 3);
handleUnknownUniformName(name.c_str());
continue;
}
UniformInfo &u = *uniformit->second;
u.active = true;
u.location = location;
// If this is a built-in (LOVE-created) uniform, store the location.
BuiltinUniform builtin = BUILTIN_MAX_ENUM;
if (getConstant(u.name.c_str(), builtin))
builtinUniforms[int(builtin)] = u.location;
if (u.location == -1)
continue;
if (!fillUniformReflectionData(u))
continue;
if ((u.baseType == UNIFORM_SAMPLER && builtin != BUILTIN_TEXTURE_MAIN) || u.baseType == UNIFORM_TEXELBUFFER)
{
TextureUnit unit;
@@ -175,183 +165,51 @@ void Shader::mapActiveUniforms()
storageTextureBindings.push_back(binding);
}
// Make sure previously set uniform data is preserved, and shader-
// initialized values are retrieved.
auto oldu = olduniforms.find(u.name);
if (oldu != olduniforms.end())
if (u.dataSize == 0)
{
u.data = oldu->second.data;
u.dataSize = oldu->second.dataSize;
u.textures = oldu->second.textures;
if (u.baseType == UNIFORM_MATRIX)
u.dataSize = sizeof(uint32) * u.matrix.rows * u.matrix.columns * u.count;
else
u.dataSize = sizeof(uint32) * u.components * u.count;
updateUniform(&u, u.count, true);
u.data = malloc(u.dataSize);
memset(u.data, 0, u.dataSize);
const auto &valuesit = reflection.localUniformInitializerValues.find(u.name);
if (valuesit != reflection.localUniformInitializerValues.end())
{
const auto &values = valuesit->second;
if (!values.empty())
memcpy(u.data, values.data(), std::min(u.dataSize, sizeof(LocalUniformValue) * values.size()));
}
}
else
if (u.baseType == UNIFORM_SAMPLER || u.baseType == UNIFORM_TEXELBUFFER)
{
u.dataSize = 0;
int startunit = (int) textureUnits.size() - u.count;
switch (u.baseType)
{
case UNIFORM_FLOAT:
u.dataSize = sizeof(float) * u.components * u.count;
u.data = malloc(u.dataSize);
break;
case UNIFORM_INT:
case UNIFORM_BOOL:
case UNIFORM_SAMPLER:
case UNIFORM_STORAGETEXTURE:
case UNIFORM_TEXELBUFFER:
u.dataSize = sizeof(int) * u.components * u.count;
u.data = malloc(u.dataSize);
break;
case UNIFORM_UINT:
u.dataSize = sizeof(unsigned int) * u.components * u.count;
u.data = malloc(u.dataSize);
break;
case UNIFORM_MATRIX:
u.dataSize = sizeof(float) * ((size_t)u.matrix.rows * u.matrix.columns) * u.count;
u.data = malloc(u.dataSize);
break;
default:
break;
}
if (builtin == BUILTIN_TEXTURE_MAIN)
startunit = 0;
if (u.dataSize > 0)
{
memset(u.data, 0, u.dataSize);
if (u.baseType == UNIFORM_SAMPLER || u.baseType == UNIFORM_TEXELBUFFER)
{
int startunit = (int) textureUnits.size() - u.count;
if (builtin == BUILTIN_TEXTURE_MAIN)
startunit = 0;
for (int i = 0; i < u.count; i++)
u.ints[i] = startunit + i;
glUniform1iv(u.location, u.count, u.ints);
if (u.baseType == UNIFORM_TEXELBUFFER)
{
u.buffers = new love::graphics::Buffer*[u.count];
memset(u.buffers, 0, sizeof(Buffer *) * u.count);
}
else
{
u.textures = new love::graphics::Texture*[u.count];
auto *tex = gfx->getDefaultTexture(u.textureType, u.dataBaseType);
for (int i = 0; i < u.count; i++)
{
tex->retain();
u.textures[i] = tex;
}
}
}
else if (u.baseType == UNIFORM_STORAGETEXTURE)
{
int startbinding = (int) storageTextureBindings.size() - u.count;
for (int i = 0; i < u.count; i++)
u.ints[i] = startbinding + i;
glUniform1iv(u.location, u.count, u.ints);
u.textures = new love::graphics::Texture*[u.count];
if ((u.access & ACCESS_WRITE) != 0)
{
memset(u.textures, 0, sizeof(Texture *) * u.count);
}
else
{
auto *tex = gfx->getDefaultTexture(u.textureType, u.dataBaseType);
for (int i = 0; i < u.count; i++)
{
tex->retain();
u.textures[i] = tex;
}
}
}
}
size_t offset = 0;
// Store any shader-initialized values in our own memory.
for (int i = 0; i < u.count; i++)
{
GLint location = u.location;
if (u.count > 1)
{
std::ostringstream ss;
ss << i;
std::string indexname = u.name + "[" + ss.str() + "]";
location = glGetUniformLocation(program, indexname.c_str());
}
if (location == -1)
continue;
switch (u.baseType)
{
case UNIFORM_FLOAT:
glGetUniformfv(program, location, &u.floats[offset]);
offset += u.components;
break;
case UNIFORM_INT:
case UNIFORM_BOOL:
glGetUniformiv(program, location, &u.ints[offset]);
offset += u.components;
break;
case UNIFORM_UINT:
glGetUniformuiv(program, location, &u.uints[offset]);
offset += u.components;
break;
case UNIFORM_MATRIX:
glGetUniformfv(program, location, &u.floats[offset]);
offset += (size_t)u.matrix.rows * u.matrix.columns;
break;
default:
break;
}
}
u.ints[i] = startunit + i;
}
else if (u.baseType == UNIFORM_STORAGETEXTURE)
{
int startbinding = (int) storageTextureBindings.size() - u.count;
for (int i = 0; i < u.count; i++)
u.ints[i] = startbinding + i;
}
uniforms[u.name] = u;
updateUniform(&u, u.count, true);
if (builtin != BUILTIN_MAX_ENUM)
builtinUniformInfo[(int)builtin] = &uniforms[u.name];
builtinUniformInfo[(int)builtin] = &u;
if (u.baseType == UNIFORM_SAMPLER || u.baseType == UNIFORM_STORAGETEXTURE)
{
// Make sure all stored textures have their Volatiles loaded before
// the sendTextures call, since it calls getHandle().
for (int i = 0; i < u.count; i++)
{
if (u.textures[i] == nullptr)
continue;
Volatile *v = dynamic_cast<Volatile *>(u.textures[i]);
if (v != nullptr)
v->loadVolatile();
}
sendTextures(&u, u.textures, u.count, true);
}
sendTextures(&u, &activeTextures[u.resourceIndex], u.count, true);
else if (u.baseType == UNIFORM_TEXELBUFFER)
{
for (int i = 0; i < u.count; i++)
{
if (u.buffers[i] == nullptr)
continue;
Volatile *v = dynamic_cast<Volatile *>(u.buffers[i]);
if (v != nullptr)
v->loadVolatile();
}
sendBuffers(&u, u.buffers, u.count, true);
}
sendBuffers(&u, &activeBuffers[u.resourceIndex], u.count, true);
}
if (gl.isBufferUsageSupported(BUFFERUSAGE_SHADER_STORAGE))
@@ -364,37 +222,28 @@ void Shader::mapActiveUniforms()
for (int sindex = 0; sindex < numstoragebuffers; sindex++)
{
UniformInfo u = {};
u.baseType = UNIFORM_STORAGEBUFFER;
u.access = ACCESS_READ;
GLsizei namelength = 0;
glGetProgramResourceName(program, GL_SHADER_STORAGE_BLOCK, sindex, 2048, &namelength, namebuffer);
u.name = std::string(namebuffer, namelength);
u.count = 1;
std::string name = canonicaliizeUniformName(std::string(namebuffer, namelength));
if (!fillUniformReflectionData(u))
const auto &uniformit = reflection.storageBuffers.find(name);
if (uniformit == reflection.storageBuffers.end())
{
handleUnknownUniformName(name.c_str());
continue;
// Make sure previously set uniform data is preserved, and shader-
// initialized values are retrieved.
auto oldu = olduniforms.find(u.name);
if (oldu != olduniforms.end())
{
u.data = oldu->second.data;
u.dataSize = oldu->second.dataSize;
u.buffers = oldu->second.buffers;
}
else
UniformInfo &u = uniformit->second;
u.active = true;
if (u.dataSize == 0)
{
u.dataSize = sizeof(int) * 1;
u.dataSize = sizeof(int) * u.count;
u.data = malloc(u.dataSize);
u.ints[0] = -1;
u.buffers = new love::graphics::Buffer * [u.count];
memset(u.buffers, 0, sizeof(Buffer*)* u.count);
for (int i = 0; i < u.count; i++)
u.ints[i] = -1;
}
// Unlike local uniforms and attributes, OpenGL doesn't auto-assign storage
@@ -417,56 +266,13 @@ void Shader::mapActiveUniforms()
if (u.access & ACCESS_WRITE)
{
p.second = (int)activeWritableStorageBuffers.size();
activeWritableStorageBuffers.push_back(u.buffers[0]);
activeWritableStorageBuffers.push_back(activeBuffers[u.resourceIndex]);
}
storageBufferBindingIndexToActiveBinding[binding.bindingindex] = p;
}
uniforms[u.name] = u;
for (int i = 0; i < u.count; i++)
{
if (u.buffers[i] == nullptr)
continue;
Volatile* v = dynamic_cast<Volatile*>(u.buffers[i]);
if (v != nullptr)
v->loadVolatile();
}
sendBuffers(&u, u.buffers, u.count, true);
}
}
// Make sure uniforms that existed before but don't exist anymore are
// cleaned up. This theoretically shouldn't happen, but...
for (const auto &p : olduniforms)
{
if (uniforms.find(p.first) == uniforms.end())
{
if (p.second.data != nullptr)
free(p.second.data);
if (p.second.baseType == UNIFORM_SAMPLER || p.second.baseType == UNIFORM_STORAGETEXTURE)
{
for (int i = 0; i < p.second.count; i++)
{
if (p.second.textures[i] != nullptr)
p.second.textures[i]->release();
}
delete[] p.second.textures;
}
else if (isBuffer(p.second.baseType))
{
for (int i = 0; i < p.second.count; i++)
{
if (p.second.buffers[i] != nullptr)
p.second.buffers[i]->release();
}
delete[] p.second.buffers;
}
sendBuffers(&u, &activeBuffers[u.resourceIndex], u.count, true);
}
}
@@ -600,7 +406,7 @@ std::string Shader::getWarnings() const
const std::string &stagewarnings = stage->getWarnings();
if (ShaderStage::getConstant(stage->getStageType(), stagestr))
if (!stagewarnings.empty() && ShaderStage::getConstant(stage->getStageType(), stagestr))
warnings += std::string(stagestr) + std::string(" shader:\n") + stagewarnings;
}
@@ -649,16 +455,6 @@ void Shader::attach()
}
}
const Shader::UniformInfo *Shader::getUniformInfo(const std::string &name) const
{
const auto it = uniforms.find(name);
if (it == uniforms.end())
return nullptr;
return &(it->second);
}
const Shader::UniformInfo *Shader::getUniformInfo(BuiltinUniform builtin) const
{
return builtinUniformInfo[(int)builtin];
@@ -802,10 +598,12 @@ void Shader::sendTextures(const UniformInfo *info, love::graphics::Texture **tex
tex->retain();
if (info->textures[i] != nullptr)
info->textures[i]->release();
int resourceindex = info->resourceIndex + i;
info->textures[i] = tex;
if (activeTextures[resourceindex] != nullptr)
activeTextures[resourceindex]->release();
activeTextures[resourceindex] = tex;
if (isstoragetex)
{
@@ -885,10 +683,12 @@ void Shader::sendBuffers(const UniformInfo *info, love::graphics::Buffer **buffe
buffer->retain();
if (info->buffers[i] != nullptr)
info->buffers[i]->release();
int resourceindex = info->resourceIndex + i;
info->buffers[i] = buffer;
if (activeBuffers[resourceindex] != nullptr)
activeBuffers[resourceindex]->release();
activeBuffers[resourceindex] = buffer;
if (texelbinding)
{
@@ -926,11 +726,6 @@ void Shader::flushBatchedDraws() const
Graphics::flushBatchedDrawsGlobal();
}
bool Shader::hasUniform(const std::string &name) const
{
return uniforms.find(name) != uniforms.end();
}
ptrdiff_t Shader::getHandle() const
{
return program;
@@ -972,6 +767,8 @@ void Shader::updateBuiltinUniforms(love::graphics::Graphics *gfx, int viewportW,
if (current != this)
return;
bool rt = gfx->isRenderTargetActive();
BuiltinUniformData data;
data.transformMatrix = gfx->getTransform();
@@ -997,13 +794,26 @@ void Shader::updateBuiltinUniforms(love::graphics::Graphics *gfx, int viewportW,
// Same with point size.
data.normalMatrix[1].w = gfx->getPointSize();
// Users expect to work with y-up NDC, y-down pixel coordinates and textures
// (see graphics/Shader.h).
// OpenGL has y-up NDC and y-up pixel coordinates and textures. If we just flip
// NDC y when rendering to a texture, it's enough to make (0, 0) on the texture
// match what we expect when sampling from it - so it's the same as if textures
// are y-down with y-up NDC.
// Windowing systems treat (0, 0) on the backbuffer texture as the bottom left,
// so we don't need to do that there.
uint32 clipflags = 0;
if (rt)
clipflags |= CLIP_TRANSFORM_FLIP_Y;
data.clipSpaceParams = computeClipSpaceParams(clipflags);
data.screenSizeParams.x = viewportW;
data.screenSizeParams.y = viewportH;
// The shader does pixcoord.y = gl_FragCoord.y * params.z + params.w.
// This lets us flip pixcoord.y when needed, to be consistent (drawing
// with no RT active makes the pixel coordinates y-flipped.)
if (gfx->isRenderTargetActive())
if (rt)
{
// No flipping: pixcoord.y = gl_FragCoord.y * 1.0 + 0.0.
data.screenSizeParams.z = 1.0f;
@@ -1030,7 +840,7 @@ void Shader::updateBuiltinUniforms(love::graphics::Graphics *gfx, int viewportW,
{
GLint location = builtinUniforms[BUILTIN_UNIFORMS_PER_DRAW];
if (location >= 0)
glUniform4fv(location, 12, (const GLfloat *) &data);
glUniform4fv(location, 13, (const GLfloat *) &data);
GLint location2 = builtinUniforms[BUILTIN_UNIFORMS_PER_DRAW_2];
if (location2 >= 0)
glUniform4fv(location2, 1, (const GLfloat *) &data.screenSizeParams);
@@ -1039,294 +849,7 @@ void Shader::updateBuiltinUniforms(love::graphics::Graphics *gfx, int viewportW,
{
GLint location = builtinUniforms[BUILTIN_UNIFORMS_PER_DRAW];
if (location >= 0)
glUniform4fv(location, 13, (const GLfloat *) &data);
}
}
int Shader::getUniformTypeComponents(GLenum type) const
{
switch (type)
{
case GL_INT:
case GL_UNSIGNED_INT:
case GL_FLOAT:
case GL_BOOL:
return 1;
case GL_INT_VEC2:
case GL_UNSIGNED_INT_VEC2:
case GL_FLOAT_VEC2:
case GL_FLOAT_MAT2:
case GL_BOOL_VEC2:
return 2;
case GL_INT_VEC3:
case GL_UNSIGNED_INT_VEC3:
case GL_FLOAT_VEC3:
case GL_FLOAT_MAT3:
case GL_BOOL_VEC3:
return 3;
case GL_INT_VEC4:
case GL_UNSIGNED_INT_VEC4:
case GL_FLOAT_VEC4:
case GL_FLOAT_MAT4:
case GL_BOOL_VEC4:
return 4;
default:
return 1;
}
}
Shader::MatrixSize Shader::getMatrixSize(GLenum type) const
{
MatrixSize m;
switch (type)
{
case GL_FLOAT_MAT2:
m.columns = m.rows = 2;
break;
case GL_FLOAT_MAT3:
m.columns = m.rows = 3;
break;
case GL_FLOAT_MAT4:
m.columns = m.rows = 4;
break;
case GL_FLOAT_MAT2x3:
m.columns = 2;
m.rows = 3;
break;
case GL_FLOAT_MAT2x4:
m.columns = 2;
m.rows = 4;
break;
case GL_FLOAT_MAT3x2:
m.columns = 3;
m.rows = 2;
break;
case GL_FLOAT_MAT3x4:
m.columns = 3;
m.rows = 4;
break;
case GL_FLOAT_MAT4x2:
m.columns = 4;
m.rows = 2;
break;
case GL_FLOAT_MAT4x3:
m.columns = 4;
m.rows = 3;
break;
default:
m.columns = m.rows = 0;
break;
}
return m;
}
void Shader::computeUniformTypeInfo(GLenum type, UniformInfo &u)
{
u.isDepthSampler = false;
u.components = getUniformTypeComponents(type);
u.baseType = UNIFORM_UNKNOWN;
switch (type)
{
case GL_INT:
case GL_INT_VEC2:
case GL_INT_VEC3:
case GL_INT_VEC4:
u.baseType = UNIFORM_INT;
u.dataBaseType = DATA_BASETYPE_INT;
break;
case GL_UNSIGNED_INT:
case GL_UNSIGNED_INT_VEC2:
case GL_UNSIGNED_INT_VEC3:
case GL_UNSIGNED_INT_VEC4:
u.baseType = UNIFORM_UINT;
u.dataBaseType = DATA_BASETYPE_UINT;
break;
case GL_FLOAT:
case GL_FLOAT_VEC2:
case GL_FLOAT_VEC3:
case GL_FLOAT_VEC4:
u.baseType = UNIFORM_FLOAT;
u.dataBaseType = DATA_BASETYPE_FLOAT;
break;
case GL_FLOAT_MAT2:
case GL_FLOAT_MAT3:
case GL_FLOAT_MAT4:
case GL_FLOAT_MAT2x3:
case GL_FLOAT_MAT2x4:
case GL_FLOAT_MAT3x2:
case GL_FLOAT_MAT3x4:
case GL_FLOAT_MAT4x2:
case GL_FLOAT_MAT4x3:
u.baseType = UNIFORM_MATRIX;
u.dataBaseType = DATA_BASETYPE_FLOAT;
u.matrix = getMatrixSize(type);
break;
case GL_BOOL:
case GL_BOOL_VEC2:
case GL_BOOL_VEC3:
case GL_BOOL_VEC4:
u.baseType = UNIFORM_BOOL;
u.dataBaseType = DATA_BASETYPE_BOOL;
break;
case GL_SAMPLER_2D:
u.baseType = UNIFORM_SAMPLER;
u.dataBaseType = DATA_BASETYPE_FLOAT;
u.textureType = TEXTURE_2D;
break;
case GL_SAMPLER_2D_SHADOW:
u.baseType = UNIFORM_SAMPLER;
u.dataBaseType = DATA_BASETYPE_FLOAT;
u.textureType = TEXTURE_2D;
u.isDepthSampler = true;
break;
case GL_INT_SAMPLER_2D:
u.baseType = UNIFORM_SAMPLER;
u.dataBaseType = DATA_BASETYPE_INT;
u.textureType = TEXTURE_2D;
break;
case GL_UNSIGNED_INT_SAMPLER_2D:
u.baseType = UNIFORM_SAMPLER;
u.dataBaseType = DATA_BASETYPE_UINT;
u.textureType = TEXTURE_2D;
break;
case GL_SAMPLER_2D_ARRAY:
u.baseType = UNIFORM_SAMPLER;
u.dataBaseType = DATA_BASETYPE_FLOAT;
u.textureType = TEXTURE_2D_ARRAY;
break;
case GL_SAMPLER_2D_ARRAY_SHADOW:
u.baseType = UNIFORM_SAMPLER;
u.dataBaseType = DATA_BASETYPE_FLOAT;
u.textureType = TEXTURE_2D_ARRAY;
u.isDepthSampler = true;
break;
case GL_INT_SAMPLER_2D_ARRAY:
u.baseType = UNIFORM_SAMPLER;
u.dataBaseType = DATA_BASETYPE_INT;
u.textureType = TEXTURE_2D_ARRAY;
break;
case GL_UNSIGNED_INT_SAMPLER_2D_ARRAY:
u.baseType = UNIFORM_SAMPLER;
u.dataBaseType = DATA_BASETYPE_UINT;
u.textureType = TEXTURE_2D_ARRAY;
break;
case GL_SAMPLER_3D:
u.baseType = UNIFORM_SAMPLER;
u.dataBaseType = DATA_BASETYPE_FLOAT;
u.textureType = TEXTURE_VOLUME;
break;
case GL_INT_SAMPLER_3D:
u.baseType = UNIFORM_SAMPLER;
u.dataBaseType = DATA_BASETYPE_INT;
u.textureType = TEXTURE_VOLUME;
break;
case GL_UNSIGNED_INT_SAMPLER_3D:
u.baseType = UNIFORM_SAMPLER;
u.dataBaseType = DATA_BASETYPE_UINT;
u.textureType = TEXTURE_VOLUME;
break;
case GL_SAMPLER_CUBE:
u.baseType = UNIFORM_SAMPLER;
u.dataBaseType = DATA_BASETYPE_FLOAT;
u.textureType = TEXTURE_CUBE;
break;
case GL_SAMPLER_CUBE_SHADOW:
u.baseType = UNIFORM_SAMPLER;
u.dataBaseType = DATA_BASETYPE_FLOAT;
u.textureType = TEXTURE_CUBE;
u.isDepthSampler = true;
break;
case GL_INT_SAMPLER_CUBE:
u.baseType = UNIFORM_SAMPLER;
u.dataBaseType = DATA_BASETYPE_INT;
u.textureType = TEXTURE_CUBE;
break;
case GL_UNSIGNED_INT_SAMPLER_CUBE:
u.baseType = UNIFORM_SAMPLER;
u.dataBaseType = DATA_BASETYPE_UINT;
u.textureType = TEXTURE_CUBE;
break;
case GL_SAMPLER_BUFFER:
u.baseType = UNIFORM_TEXELBUFFER;
u.dataBaseType = DATA_BASETYPE_FLOAT;
break;
case GL_INT_SAMPLER_BUFFER:
u.baseType = UNIFORM_TEXELBUFFER;
u.dataBaseType = DATA_BASETYPE_INT;
break;
case GL_UNSIGNED_INT_SAMPLER_BUFFER:
u.baseType = UNIFORM_TEXELBUFFER;
u.dataBaseType = DATA_BASETYPE_UINT;
break;
case GL_IMAGE_2D:
u.baseType = UNIFORM_STORAGETEXTURE;
u.dataBaseType = DATA_BASETYPE_FLOAT;
u.textureType = TEXTURE_2D;
break;
case GL_INT_IMAGE_2D:
u.baseType = UNIFORM_STORAGETEXTURE;
u.dataBaseType = DATA_BASETYPE_INT;
u.textureType = TEXTURE_2D;
break;
case GL_UNSIGNED_INT_IMAGE_2D:
u.baseType = UNIFORM_STORAGETEXTURE;
u.dataBaseType = DATA_BASETYPE_UINT;
u.textureType = TEXTURE_2D;
break;
case GL_IMAGE_2D_ARRAY:
u.baseType = UNIFORM_STORAGETEXTURE;
u.dataBaseType = DATA_BASETYPE_FLOAT;
u.textureType = TEXTURE_2D_ARRAY;
break;
case GL_INT_IMAGE_2D_ARRAY:
u.baseType = UNIFORM_STORAGETEXTURE;
u.dataBaseType = DATA_BASETYPE_INT;
u.textureType = TEXTURE_2D_ARRAY;
break;
case GL_UNSIGNED_INT_IMAGE_2D_ARRAY:
u.baseType = UNIFORM_STORAGETEXTURE;
u.dataBaseType = DATA_BASETYPE_UINT;
u.textureType = TEXTURE_2D_ARRAY;
break;
case GL_IMAGE_3D:
u.baseType = UNIFORM_STORAGETEXTURE;
u.dataBaseType = DATA_BASETYPE_FLOAT;
u.textureType = TEXTURE_VOLUME;
break;
case GL_INT_IMAGE_3D:
u.baseType = UNIFORM_STORAGETEXTURE;
u.dataBaseType = DATA_BASETYPE_INT;
u.textureType = TEXTURE_VOLUME;
break;
case GL_UNSIGNED_INT_IMAGE_3D:
u.baseType = UNIFORM_STORAGETEXTURE;
u.dataBaseType = DATA_BASETYPE_UINT;
u.textureType = TEXTURE_VOLUME;
break;
case GL_IMAGE_CUBE:
u.baseType = UNIFORM_STORAGETEXTURE;
u.dataBaseType = DATA_BASETYPE_FLOAT;
u.textureType = TEXTURE_CUBE;
break;
case GL_INT_IMAGE_CUBE:
u.baseType = UNIFORM_STORAGETEXTURE;
u.dataBaseType = DATA_BASETYPE_INT;
u.textureType = TEXTURE_CUBE;
break;
case GL_UNSIGNED_INT_IMAGE_CUBE:
u.baseType = UNIFORM_STORAGETEXTURE;
u.dataBaseType = DATA_BASETYPE_UINT;
u.textureType = TEXTURE_CUBE;
break;
default:
break;
glUniform4fv(location, 14, (const GLfloat *) &data);
}
}
+1 -10
View File
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2006-2023 LOVE Development Team
* Copyright (c) 2006-2024 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
@@ -63,12 +63,10 @@ public:
void attach() override;
std::string getWarnings() const override;
int getVertexAttributeIndex(const std::string &name) override;
const UniformInfo *getUniformInfo(const std::string &name) const override;
const UniformInfo *getUniformInfo(BuiltinUniform builtin) const override;
void updateUniform(const UniformInfo *info, int count) override;
void sendTextures(const UniformInfo *info, love::graphics::Texture **textures, int count) override;
void sendBuffers(const UniformInfo *info, love::graphics::Buffer **buffers, int count) override;
bool hasUniform(const std::string &name) const override;
ptrdiff_t getHandle() const override;
void setVideoTextures(love::graphics::Texture *ytexture, love::graphics::Texture *cbtexture, love::graphics::Texture *crtexture) override;
@@ -100,10 +98,6 @@ private:
void sendTextures(const UniformInfo *info, love::graphics::Texture **textures, int count, bool internalupdate);
void sendBuffers(const UniformInfo *info, love::graphics::Buffer **buffers, int count, bool internalupdate);
int getUniformTypeComponents(GLenum type) const;
void computeUniformTypeInfo(GLenum type, UniformInfo &u);
MatrixSize getMatrixSize(GLenum type) const;
void flushBatchedDraws() const;
// Get any warnings or errors generated only by the shader program object.
@@ -120,9 +114,6 @@ private:
std::map<std::string, GLint> attributes;
// Uniform location buffer map
std::map<std::string, UniformInfo> uniforms;
// Texture unit pool for setting textures
std::vector<TextureUnit> textureUnits;
+1 -1
View File
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2006-2023 LOVE Development Team
* Copyright (c) 2006-2024 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
+1 -1
View File
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2006-2023 LOVE Development Team
* Copyright (c) 2006-2024 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
+16 -1
View File
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2006-2023 LOVE Development Team
* Copyright (c) 2006-2024 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
@@ -63,6 +63,11 @@ public:
delete[] data;
}
size_t getGPUReadOffset() const override
{
return (size_t) data;
}
MapInfo map(size_t /*minsize*/) override
{
return MapInfo(data, bufferSize);
@@ -111,6 +116,11 @@ public:
delete[] data;
}
size_t getGPUReadOffset() const override
{
return frameGPUReadOffset;
}
MapInfo map(size_t /*minsize*/) override
{
if (orphan)
@@ -192,6 +202,11 @@ public:
virtual ~StreamBufferSync() {}
size_t getGPUReadOffset() const override
{
return (frameIndex * bufferSize) + frameGPUReadOffset;
}
void nextFrame() override
{
// Insert a GPU fence for this frame's section of the data, we'll wait
+1 -1
View File
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2006-2023 LOVE Development Team
* Copyright (c) 2006-2024 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
+49 -20
View File
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2006-2023 LOVE Development Team
* Copyright (c) 2006-2024 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
@@ -244,6 +244,25 @@ Texture::Texture(love::graphics::Graphics *gfx, const Settings &settings, const
slices.clear();
}
Texture::Texture(love::graphics::Graphics *gfx, love::graphics::Texture *base, const Texture::ViewSettings &viewsettings)
: love::graphics::Texture(gfx, base, viewsettings)
, slices(viewsettings.type.get(base->getTextureType()))
, fbo(0)
, texture(0)
, renderbuffer(0)
, framebufferStatus(GL_FRAMEBUFFER_COMPLETE)
, textureGLError(GL_NO_ERROR)
, actualSamples(1)
{
if (!loadVolatile())
{
if (framebufferStatus != GL_FRAMEBUFFER_COMPLETE)
throw love::Exception("Cannot create texture view (OpenGL framebuffer error: %s)", OpenGL::framebufferStatusString(framebufferStatus));
if (textureGLError != GL_NO_ERROR)
throw love::Exception("Cannot create texture view (OpenGL error: %s)", OpenGL::errorString(textureGLError));
}
}
Texture::~Texture()
{
unloadVolatile();
@@ -254,11 +273,26 @@ void Texture::createTexture()
// The base class handles some validation. For example, if ImageData is
// given then it must exist for all mip levels, a render target can't use
// a compressed format, etc.
glGenTextures(1, &texture);
GLenum gltype = OpenGL::getGLTextureType(texType);
if (parentView.texture != this)
{
OpenGL::TextureFormat fmt = gl.convertPixelFormat(format, false);
Texture *basetex = (Texture *) parentView.texture;
int layers = texType == TEXTURE_CUBE ? 6 : getLayerCount();
glTextureView(texture, gltype, basetex->texture, fmt.internalformat,
parentView.startMipmap, getMipmapCount(),
parentView.startLayer, layers);
gl.bindTextureToUnit(this, 0, false);
setSamplerState(samplerState);
return;
}
gl.bindTextureToUnit(this, 0, false);
GLenum gltype = OpenGL::getGLTextureType(texType);
if (renderTarget && GLAD_ANGLE_texture_usage)
glTexParameteri(gltype, GL_TEXTURE_USAGE_ANGLE, GL_FRAMEBUFFER_ATTACHMENT_ANGLE);
@@ -356,7 +390,7 @@ void Texture::createTexture()
int slices = texType == TEXTURE_VOLUME ? getDepth(mip) : layers;
slices = texType == TEXTURE_CUBE ? 6 : slices;
for (int i = 0; i < slices; i++)
uploadByteData(format, emptydata.data(), emptydata.size(), mip, i, r);
uploadByteData(emptydata.data(), emptydata.size(), mip, i, r);
}
}
@@ -371,6 +405,12 @@ bool Texture::loadVolatile()
if (texture != 0 || renderbuffer != 0)
return true;
if (parentView.texture != this)
{
Texture *basetex = (Texture *) parentView.texture;
basetex->loadVolatile();
}
OpenGL::TempDebugGroup debuggroup("Texture load");
// NPOT textures don't support mipmapping without full NPOT support.
@@ -466,19 +506,19 @@ void Texture::unloadVolatile()
setGraphicsMemorySize(0);
}
void Texture::uploadByteData(PixelFormat pixelformat, const void *data, size_t size, int level, int slice, const Rect &r)
void Texture::uploadByteData(const void *data, size_t size, int level, int slice, const Rect &r)
{
OpenGL::TempDebugGroup debuggroup("Texture data upload");
gl.bindTextureToUnit(this, 0, false);
OpenGL::TextureFormat fmt = OpenGL::convertPixelFormat(pixelformat, false);
OpenGL::TextureFormat fmt = OpenGL::convertPixelFormat(format, false);
GLenum gltarget = OpenGL::getGLTextureType(texType);
if (texType == TEXTURE_CUBE)
gltarget = GL_TEXTURE_CUBE_MAP_POSITIVE_X + slice;
if (isPixelFormatCompressed(pixelformat))
if (isPixelFormatCompressed(format))
{
if (texType == TEXTURE_2D || texType == TEXTURE_CUBE)
{
@@ -566,7 +606,7 @@ void Texture::copyFromBuffer(love::graphics::Buffer *source, size_t sourceoffset
// glTexSubImage and friends copy from the active pixel_unpack_buffer by
// treating the pointer as a byte offset.
const uint8 *byteoffset = (const uint8 *)(ptrdiff_t)sourceoffset;
uploadByteData(format, byteoffset, size, mipmap, slice, rect);
uploadByteData(byteoffset, size, mipmap, slice, rect);
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
@@ -593,18 +633,7 @@ void Texture::setSamplerState(const SamplerState &s)
if (s.depthSampleMode.hasValue && !gl.isDepthCompareSampleSupported())
throw love::Exception("Depth comparison sampling in shaders is not supported on this system.");
// Base class does common validation and assigns samplerState.
love::graphics::Texture::setSamplerState(s);
auto supportedflags = OpenGL::getPixelFormatUsageFlags(getPixelFormat());
if ((supportedflags & PIXELFORMATUSAGEFLAGS_LINEAR) == 0)
{
samplerState.magFilter = samplerState.minFilter = SamplerState::FILTER_NEAREST;
if (samplerState.mipmapFilter == SamplerState::MIPMAP_FILTER_LINEAR)
samplerState.mipmapFilter = SamplerState::MIPMAP_FILTER_NEAREST;
}
samplerState = validateSamplerState(s);
// If we only have limited NPOT support then the wrap mode must be CLAMP.
if ((GLAD_ES_VERSION_2_0 && !(GLAD_ES_VERSION_3_0 || GLAD_OES_texture_npot))
+4 -2
View File
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2006-2023 LOVE Development Team
* Copyright (c) 2006-2024 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
@@ -39,6 +39,7 @@ class Texture final : public love::graphics::Texture, public Volatile
public:
Texture(love::graphics::Graphics *gfx, const Settings &settings, const Slices *data);
Texture(love::graphics::Graphics *gfx, love::graphics::Texture *base, const Texture::ViewSettings &viewsettings);
virtual ~Texture();
@@ -61,9 +62,10 @@ public:
void readbackInternal(int slice, int mipmap, const Rect &rect, int destwidth, size_t size, void *dest);
private:
void createTexture();
void uploadByteData(PixelFormat pixelformat, const void *data, size_t size, int level, int slice, const Rect &r) override;
void uploadByteData(const void *data, size_t size, int level, int slice, const Rect &r) override;
void generateMipmapsInternal() override;
+1 -1
View File
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2006-2023 LOVE Development Team
* Copyright (c) 2006-2024 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
+1 -1
View File
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2006-2023 LOVE Development Team
* Copyright (c) 2006-2024 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
+4 -2
View File
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2006-2023 LOVE Development Team
* Copyright (c) 2006-2024 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
@@ -26,6 +26,8 @@ namespace love
namespace graphics
{
static_assert(sizeof(VertexAttributeInfo) == 4, "Unexpected sizeof(VertexAttributeInfo)");
static_assert(sizeof(Color32) == 4, "sizeof(Color32) incorrect!");
static_assert(sizeof(STf_RGBAub) == sizeof(float)*2 + sizeof(Color32), "sizeof(STf_RGBAub) incorrect!");
static_assert(sizeof(STPf_RGBAub) == sizeof(float)*3 + sizeof(Color32), "sizeof(STPf_RGBAub) incorrect!");
@@ -336,7 +338,7 @@ bool VertexAttributes::operator == (const VertexAttributes &other) const
{
const auto &a = attribs[i];
const auto &b = other.attribs[i];
if (a.bufferIndex != b.bufferIndex || a.format != b.format || a.offsetFromVertex != b.offsetFromVertex)
if (a.bufferIndex != b.bufferIndex || a.packedFormat != b.packedFormat || a.offsetFromVertex != b.offsetFromVertex)
return false;
if (bufferLayouts[a.bufferIndex].stride != other.bufferLayouts[a.bufferIndex].stride)
+7 -4
View File
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2006-2023 LOVE Development Team
* Copyright (c) 2006-2024 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
@@ -298,9 +298,12 @@ struct BufferBindings
struct VertexAttributeInfo
{
uint8 bufferIndex;
DataFormat format : 8;
uint16 offsetFromVertex;
uint8 packedFormat;
uint8 bufferIndex;
void setFormat(DataFormat format) { packedFormat = (uint8)format; }
DataFormat getFormat() const { return (DataFormat)packedFormat; }
};
struct VertexBufferLayout
@@ -335,7 +338,7 @@ struct VertexAttributes
enableBits |= (1u << index);
attribs[index].bufferIndex = bufferindex;
attribs[index].format = format;
attribs[index].setFormat(format);
attribs[index].offsetFromVertex = offsetfromvertex;
}
+1 -1
View File
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2006-2023 LOVE Development Team
* Copyright (c) 2006-2024 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
+1 -1
View File
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2006-2023 LOVE Development Team
* Copyright (c) 2006-2024 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
+224 -169
View File
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2006-2023 LOVE Development Team
* Copyright (c) 2006-2024 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
@@ -21,6 +21,7 @@
#include "common/Exception.h"
#include "common/pixelformat.h"
#include "common/version.h"
#include "common/memory.h"
#include "window/Window.h"
#include "Buffer.h"
#include "Graphics.h"
@@ -60,11 +61,6 @@ static const std::vector<const char*> deviceExtensions = {
constexpr uint32_t USAGES_POLL_INTERVAL = 5000;
const char *Graphics::getName() const
{
return "love.graphics.vulkan";
}
VkDevice Graphics::getDevice() const
{
return device;
@@ -95,6 +91,7 @@ static void checkOptionalInstanceExtensions(OptionalInstanceExtensions& ext)
}
Graphics::Graphics()
: love::graphics::Graphics("love.graphics.vulkan")
{
if (SDL_Vulkan_LoadLibrary(nullptr))
throw love::Exception("could not find vulkan");
@@ -157,6 +154,7 @@ Graphics::~Graphics()
{
defaultConstantTexCoord.set(nullptr);
defaultConstantColor.set(nullptr);
localUniformBuffer.set(nullptr);
Volatile::unloadAll();
cleanup();
@@ -172,6 +170,11 @@ love::graphics::Texture *Graphics::newTexture(const love::graphics::Texture::Set
return new Texture(this, settings, data);
}
love::graphics::Texture *Graphics::newTextureView(love::graphics::Texture *base, const Texture::ViewSettings &viewsettings)
{
return new Texture(this, base, viewsettings);
}
love::graphics::Buffer *Graphics::newBuffer(const love::graphics::Buffer::Settings &settings, const std::vector<love::graphics::Buffer::DataDeclaration> &format, const void *data, size_t size, size_t arraylength)
{
return new Buffer(this, settings, format, data, size, arraylength);
@@ -383,63 +386,50 @@ void Graphics::submitGpuCommands(SubmitMode submitMode, void *screenshotCallback
if (renderPassState.active)
endRenderPass();
VkBuffer screenshotBuffer = VK_NULL_HANDLE;
VmaAllocation screenshotAllocation = VK_NULL_HANDLE;
VmaAllocationInfo screenshotAllocationInfo = {};
if (submitMode == SUBMIT_PRESENT)
{
if (pendingScreenshotCallbacks.empty())
Vulkan::cmdTransitionImageLayout(
commandBuffers.at(currentFrame),
swapChainImages.at(imageIndex),
swapChainPixelFormat,
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
VK_IMAGE_LAYOUT_PRESENT_SRC_KHR);
else
{
VkBufferCreateInfo bufferInfo{};
bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
bufferInfo.size = 4ll * swapChainExtent.width * swapChainExtent.height;
bufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT;
bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
VmaAllocationCreateInfo allocCreateInfo{};
allocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO;
allocCreateInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT;
auto result = vmaCreateBuffer(
vmaAllocator,
&bufferInfo,
&allocCreateInfo,
&screenshotBuffer,
&screenshotAllocation,
&screenshotAllocationInfo);
if (result != VK_SUCCESS)
throw love::Exception("failed to create screenshot readback buffer");
// TODO: swap chain images aren't guaranteed to support TRANSFER_SRC_BIT usage flags.
Vulkan::cmdTransitionImageLayout(
commandBuffers.at(currentFrame),
swapChainImages.at(imageIndex),
swapChainPixelFormat,
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);
Vulkan::cmdTransitionImageLayout(
commandBuffers.at(currentFrame),
screenshotReadbackBuffers.at(currentFrame).image,
VK_IMAGE_LAYOUT_UNDEFINED,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
VkImageBlit blit{};
blit.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
blit.srcSubresource.layerCount = 1;
blit.srcOffsets[1] = {
static_cast<int>(swapChainExtent.width),
static_cast<int>(swapChainExtent.height),
1
};
blit.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
blit.dstSubresource.layerCount = 1;
blit.dstOffsets[1] = {
static_cast<int>(swapChainExtent.width),
static_cast<int>(swapChainExtent.height),
1
};
vkCmdBlitImage(
commandBuffers.at(currentFrame),
swapChainImages.at(imageIndex), VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
screenshotReadbackBuffers.at(currentFrame).image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
1, &blit,
VK_FILTER_NEAREST);
Vulkan::cmdTransitionImageLayout(
commandBuffers.at(currentFrame),
swapChainImages.at(imageIndex),
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
VK_IMAGE_LAYOUT_PRESENT_SRC_KHR);
Vulkan::cmdTransitionImageLayout(
commandBuffers.at(currentFrame),
screenshotReadbackBuffers.at(currentFrame).image,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);
VkBufferImageCopy region{};
region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
region.imageSubresource.layerCount = 1;
@@ -451,32 +441,17 @@ void Graphics::submitGpuCommands(SubmitMode submitMode, void *screenshotCallback
vkCmdCopyImageToBuffer(
commandBuffers.at(currentFrame),
screenshotReadbackBuffers.at(currentFrame).image,
swapChainImages.at(imageIndex),
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
screenshotReadbackBuffers.at(currentFrame).buffer,
screenshotBuffer,
1, &region);
addReadbackCallback([
w = swapChainExtent.width,
h = swapChainExtent.height,
pendingScreenshotCallbacks = pendingScreenshotCallbacks,
screenShotReadbackBuffer = screenshotReadbackBuffers.at(currentFrame),
screenshotCallbackData = screenshotCallbackData]() {
auto imageModule = Module::getInstance<love::image::Image>(M_IMAGE);
for (const auto &info : pendingScreenshotCallbacks)
{
image::ImageData *img = imageModule->newImageData(
w,
h,
PIXELFORMAT_RGBA8_UNORM,
screenShotReadbackBuffer.allocationInfo.pMappedData);
info.callback(&info, img, screenshotCallbackData);
img->release();
}
});
pendingScreenshotCallbacks.clear();
Vulkan::cmdTransitionImageLayout(
commandBuffers.at(currentFrame),
swapChainImages.at(imageIndex),
swapChainPixelFormat,
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
VK_IMAGE_LAYOUT_PRESENT_SRC_KHR);
}
}
@@ -521,7 +496,7 @@ void Graphics::submitGpuCommands(SubmitMode submitMode, void *screenshotCallback
if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, fence) != VK_SUCCESS)
throw love::Exception("failed to submit draw command buffer");
if (submitMode == SUBMIT_NOPRESENT || submitMode == SUBMIT_RESTART)
if (submitMode == SUBMIT_NOPRESENT || submitMode == SUBMIT_RESTART || screenshotBuffer != VK_NULL_HANDLE)
{
vkQueueWaitIdle(graphicsQueue);
@@ -532,6 +507,64 @@ void Graphics::submitGpuCommands(SubmitMode submitMode, void *screenshotCallback
callbacks.clear();
}
if (screenshotBuffer != VK_NULL_HANDLE)
{
auto imageModule = Module::getInstance<love::image::Image>(M_IMAGE);
for (int i = 0; i < (int)pendingScreenshotCallbacks.size(); i++)
{
const auto &info = pendingScreenshotCallbacks[i];
image::ImageData *img = nullptr;
try
{
img = imageModule->newImageData(
swapChainExtent.width,
swapChainExtent.height,
PIXELFORMAT_RGBA8_UNORM,
screenshotAllocationInfo.pMappedData);
}
catch (love::Exception &)
{
info.callback(&info, nullptr, nullptr);
for (int j = i + 1; j < (int)pendingScreenshotCallbacks.size(); j++)
{
const auto& ninfo = pendingScreenshotCallbacks[j];
ninfo.callback(&ninfo, nullptr, nullptr);
}
vmaDestroyBuffer(vmaAllocator, screenshotBuffer, screenshotAllocation);
pendingScreenshotCallbacks.clear();
throw;
}
uint8 *screenshot = (uint8*)img->getData();
if (swapChainImageFormat == VK_FORMAT_B8G8R8A8_UNORM || swapChainImageFormat == VK_FORMAT_B8G8R8A8_SRGB)
{
// Convert from BGRA to RGBA and replace alpha with full opacity.
for (size_t i = 0; i < img->getSize(); i += 4)
{
uint8 r = screenshot[i + 2];
screenshot[i + 2] = screenshot[i + 0];
screenshot[i + 0] = r;
screenshot[i + 3] = 255;
}
}
else
{
// Replace alpha with full opacity.
for (size_t i = 0; i < img->getSize(); i += 4)
screenshot[i + 3] = 255;
}
info.callback(&info, img, screenshotCallbackData);
img->release();
}
vmaDestroyBuffer(vmaAllocator, screenshotBuffer, screenshotAllocation);
pendingScreenshotCallbacks.clear();
}
if (submitMode == SUBMIT_RESTART)
startRecordingGraphicsCommands();
}
@@ -637,7 +670,6 @@ bool Graphics::setMode(void *context, int width, int height, int pixelwidth, int
createSwapChain();
createImageViews();
createScreenshotCallbackBuffers();
createColorResources();
createDepthResources();
transitionColorDepthLayouts = true;
@@ -649,6 +681,9 @@ bool Graphics::setMode(void *context, int width, int height, int pixelwidth, int
createSyncObjects();
}
if (localUniformBuffer == nullptr)
localUniformBuffer.set(new StreamBuffer(this, BUFFERUSAGE_UNIFORM, 1024 * 512 * 1), Acquire::NORETAIN);
beginFrame();
if (createBaseObjects)
@@ -1057,6 +1092,22 @@ bool Graphics::isPixelFormatSupported(PixelFormat format, uint32 usage)
{
format = getSizedFormat(format);
switch (format)
{
case PIXELFORMAT_PVR1_RGB2_UNORM:
case PIXELFORMAT_PVR1_RGB2_sRGB:
case PIXELFORMAT_PVR1_RGB4_UNORM:
case PIXELFORMAT_PVR1_RGB4_sRGB:
case PIXELFORMAT_PVR1_RGBA2_UNORM:
case PIXELFORMAT_PVR1_RGBA2_sRGB:
case PIXELFORMAT_PVR1_RGBA4_UNORM:
case PIXELFORMAT_PVR1_RGBA4_sRGB:
// Lets not support these in Vulkan - they're deprecated.
return false;
default:
break;
}
auto vulkanFormat = Vulkan::getTextureFormat(format);
VkFormatProperties formatProperties;
@@ -1188,12 +1239,6 @@ bool Graphics::dispatch(love::graphics::Shader *shader, love::graphics::Buffer *
return true;
}
Matrix4 Graphics::computeDeviceProjection(const Matrix4 &projection, bool rendertotexture) const
{
uint32 flags = DEVICE_PROJECTION_DEFAULT;
return calculateDeviceProjection(projection, flags);
}
void Graphics::setRenderTargetsInternal(const RenderTargets &rts, int pixelw, int pixelh, bool hasSRGBtexture)
{
if (renderPassState.active)
@@ -1277,6 +1322,7 @@ void Graphics::beginFrame()
Vulkan::cmdTransitionImageLayout(
commandBuffers.at(currentFrame),
swapChainImages[imageIndex],
swapChainPixelFormat,
VK_IMAGE_LAYOUT_UNDEFINED,
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
@@ -1286,6 +1332,7 @@ void Graphics::beginFrame()
Vulkan::cmdTransitionImageLayout(
commandBuffers.at(currentFrame),
depthImage,
depthStencilPixelFormat,
VK_IMAGE_LAYOUT_UNDEFINED,
VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL);
@@ -1293,6 +1340,7 @@ void Graphics::beginFrame()
Vulkan::cmdTransitionImageLayout(
commandBuffers.at(currentFrame),
colorImage,
swapChainPixelFormat,
VK_IMAGE_LAYOUT_UNDEFINED,
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
@@ -1301,9 +1349,11 @@ void Graphics::beginFrame()
Vulkan::resetShaderSwitches();
for (const auto shader : usedShadersInFrame)
for (const auto &shader : usedShadersInFrame)
shader->newFrame();
usedShadersInFrame.clear();
localUniformBuffer->nextFrame();
}
void Graphics::startRecordingGraphicsCommands()
@@ -1329,11 +1379,6 @@ void Graphics::endRecordingGraphicsCommands() {
throw love::Exception("failed to record command buffer");
}
const VkDeviceSize Graphics::getMinUniformBufferOffsetAlignment() const
{
return minUniformBufferOffsetAlignment;
}
VkCommandBuffer Graphics::getCommandBufferForDataTransfer()
{
if (renderPassState.active)
@@ -1379,8 +1424,22 @@ graphics::Shader::BuiltinUniformData Graphics::getCurrentBuiltinUniformData()
// Same with point size.
data.normalMatrix[1].w = getPointSize();
data.screenSizeParams.x = static_cast<float>(swapChainExtent.width);
data.screenSizeParams.y = static_cast<float>(swapChainExtent.height);
// Flip y to convert input y-up [-1, 1] to vulkan's y-down [-1, 1].
// Convert input z [-1, 1] to vulkan [0, 1].
uint32 flags = Shader::CLIP_TRANSFORM_FLIP_Y | Shader::CLIP_TRANSFORM_Z_NEG1_1_TO_0_1;
data.clipSpaceParams = Shader::computeClipSpaceParams(flags);
const auto &rt = states.back().renderTargets.getFirstTarget();
if (rt.texture != nullptr)
{
data.screenSizeParams.x = rt.texture->getPixelWidth(rt.mipmap);
data.screenSizeParams.y = rt.texture->getPixelHeight(rt.mipmap);
}
else
{
data.screenSizeParams.x = getPixelWidth();
data.screenSizeParams.y = getPixelHeight();
}
data.screenSizeParams.z = 1.0f;
data.screenSizeParams.w = 0.0f;
@@ -1459,6 +1518,18 @@ void Graphics::pickPhysicalDevice()
deviceApiVersion = properties.apiVersion;
depthStencilFormat = findDepthFormat();
switch (depthStencilFormat)
{
case VK_FORMAT_D32_SFLOAT_S8_UINT:
depthStencilPixelFormat = PIXELFORMAT_DEPTH32_FLOAT_STENCIL8;
break;
case VK_FORMAT_D24_UNORM_S8_UINT:
depthStencilPixelFormat = PIXELFORMAT_DEPTH24_UNORM_STENCIL8;
break;
default:
throw love::Exception("Failed to convert vulkan depth/stencil swapchain pixel format %d to love PixelFormat.", depthStencilFormat);
break;
}
}
bool Graphics::checkDeviceExtensionSupport(VkPhysicalDevice device)
@@ -1848,6 +1919,25 @@ void Graphics::createSwapChain()
swapChainImageFormat = surfaceFormat.format;
swapChainExtent = extent;
preTransform = swapChainSupport.capabilities.currentTransform;
switch (swapChainImageFormat)
{
case VK_FORMAT_B8G8R8A8_SRGB:
swapChainPixelFormat = PIXELFORMAT_BGRA8_sRGB;
break;
case VK_FORMAT_B8G8R8A8_UNORM:
swapChainPixelFormat = PIXELFORMAT_BGRA8_UNORM;
break;
case VK_FORMAT_R8G8B8A8_SRGB:
swapChainPixelFormat = PIXELFORMAT_RGBA8_sRGB;
break;
case VK_FORMAT_R8G8B8A8_UNORM:
swapChainPixelFormat = PIXELFORMAT_RGBA8_UNORM;
break;
default:
throw love::Exception("Failed to convert vulkan depth/stencil swapchain image format %d to love PixelFormat.", swapChainImageFormat);
break;
}
}
VkSurfaceFormatKHR Graphics::chooseSwapSurfaceFormat(const std::vector<VkSurfaceFormatKHR> &availableFormats)
@@ -1983,65 +2073,6 @@ void Graphics::createImageViews()
}
}
void Graphics::createScreenshotCallbackBuffers()
{
screenshotReadbackBuffers.resize(MAX_FRAMES_IN_FLIGHT);
for (uint32_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++)
{
VkBufferCreateInfo bufferInfo{};
bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
bufferInfo.size = 4ll * swapChainExtent.width * swapChainExtent.height;
bufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT;
bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
VmaAllocationCreateInfo allocCreateInfo{};
allocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO;
allocCreateInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT;
auto result = vmaCreateBuffer(
vmaAllocator,
&bufferInfo,
&allocCreateInfo,
&screenshotReadbackBuffers.at(i).buffer,
&screenshotReadbackBuffers.at(i).allocation,
&screenshotReadbackBuffers.at(i).allocationInfo);
if (result != VK_SUCCESS)
throw love::Exception("failed to create screenshot readback buffer");
VkImageCreateInfo imageInfo{};
imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
imageInfo.imageType = VK_IMAGE_TYPE_2D;
imageInfo.format = VK_FORMAT_R8G8B8A8_SRGB;
imageInfo.extent = {
swapChainExtent.width,
swapChainExtent.height,
1
};
imageInfo.mipLevels = 1;
imageInfo.arrayLayers = 1;
imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
imageInfo.usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
VmaAllocationCreateInfo imageAllocCreateInfo{};
result = vmaCreateImage(
vmaAllocator,
&imageInfo,
&imageAllocCreateInfo,
&screenshotReadbackBuffers.at(i).image,
&screenshotReadbackBuffers.at(i).imageAllocation,
nullptr);
if (result != VK_SUCCESS)
throw love::Exception("failed to create screenshot readback image");
}
}
VkFramebuffer Graphics::createFramebuffer(FramebufferConfiguration &configuration)
{
std::vector<VkImageView> attachments;
@@ -2272,7 +2303,7 @@ void Graphics::createVulkanVertexFormat(
attributeDescription.location = i;
attributeDescription.binding = bufferBinding;
attributeDescription.offset = attrib.offsetFromVertex;
attributeDescription.format = Vulkan::getVulkanVertexFormat(attrib.format);
attributeDescription.format = Vulkan::getVulkanVertexFormat(attrib.getFormat());
attributeDescriptions.push_back(attributeDescription);
}
@@ -2353,28 +2384,39 @@ void Graphics::prepareDraw(const VertexAttributes &attributes, const BufferBindi
configuration.dynamicState.cullmode = cullmode;
}
std::vector<VkBuffer> bufferVector;
std::vector<VkDeviceSize> offsets;
VkBuffer vkbuffers[VertexAttributes::MAX + 2];
VkDeviceSize vkoffsets[VertexAttributes::MAX + 2];
int buffercount = 0;
for (uint32_t i = 0; i < VertexAttributes::MAX; i++)
uint32 allbits = buffers.useBits;
uint32 i = 0;
while (allbits)
{
if (buffers.useBits & (1u << i))
uint32 bit = 1u << i;
if (buffers.useBits & bit)
{
bufferVector.push_back((VkBuffer)buffers.info[i].buffer->getHandle());
offsets.push_back((VkDeviceSize)buffers.info[i].offset);
vkbuffers[buffercount] = (VkBuffer)buffers.info[i].buffer->getHandle();
vkoffsets[buffercount] = (VkDeviceSize)buffers.info[i].offset;
buffercount++;
}
i++;
allbits >>= 1;
}
if (!(attributes.enableBits & (1u << ATTRIB_TEXCOORD)))
{
bufferVector.push_back((VkBuffer)defaultConstantTexCoord->getHandle());
offsets.push_back((VkDeviceSize)0);
vkbuffers[buffercount] = (VkBuffer)defaultConstantTexCoord->getHandle();
vkoffsets[buffercount] = (VkDeviceSize)0;
buffercount++;
}
if (!(attributes.enableBits & (1u << ATTRIB_COLOR)))
{
bufferVector.push_back((VkBuffer)defaultConstantColor->getHandle());
offsets.push_back((VkDeviceSize)0);
vkbuffers[buffercount] = (VkBuffer)defaultConstantColor->getHandle();
vkoffsets[buffercount] = (VkDeviceSize)0;
buffercount++;
}
configuration.shader->setMainTex(texture);
@@ -2382,7 +2424,9 @@ void Graphics::prepareDraw(const VertexAttributes &attributes, const BufferBindi
ensureGraphicsPipelineConfiguration(configuration);
configuration.shader->cmdPushDescriptorSets(commandBuffers.at(currentFrame), VK_PIPELINE_BIND_POINT_GRAPHICS);
vkCmdBindVertexBuffers(commandBuffers.at(currentFrame), 0, static_cast<uint32_t>(bufferVector.size()), bufferVector.data(), offsets.data());
if (buffercount > 0)
vkCmdBindVertexBuffers(commandBuffers.at(currentFrame), 0, static_cast<uint32_t>(buffercount), vkbuffers, vkoffsets);
}
void Graphics::setDefaultRenderPass()
@@ -2455,12 +2499,12 @@ void Graphics::setRenderPass(const RenderTargets &rts, int pixelw, int pixelh, b
FramebufferConfiguration configuration{};
std::vector<VkImage> transitionImages;
std::vector<std::tuple<VkImage, PixelFormat>> transitionImages;
for (const auto &color : rts.colors)
{
configuration.colorViews.push_back(dynamic_cast<Texture*>(color.texture)->getRenderTargetView(color.mipmap, color.slice));
transitionImages.push_back((VkImage) color.texture->getHandle());
transitionImages.push_back({ (VkImage)color.texture->getHandle(), color.texture->getPixelFormat() });
}
if (rts.depthStencil.texture != nullptr)
configuration.staticData.depthView = dynamic_cast<Texture*>(rts.depthStencil.texture)->getRenderTargetView(rts.depthStencil.mipmap, rts.depthStencil.slice);
@@ -2513,8 +2557,8 @@ void Graphics::startRenderPass()
renderPassState.framebufferConfiguration.staticData.renderPass = renderPassState.beginInfo.renderPass;
renderPassState.beginInfo.framebuffer = getFramebuffer(renderPassState.framebufferConfiguration);
for (const auto &image : renderPassState.transitionImages)
Vulkan::cmdTransitionImageLayout(commandBuffers.at(currentFrame), image, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
for (const auto &[image, format] : renderPassState.transitionImages)
Vulkan::cmdTransitionImageLayout(commandBuffers.at(currentFrame), image, format, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
vkCmdBeginRenderPass(commandBuffers.at(currentFrame), &renderPassState.beginInfo, VK_SUBPASS_CONTENTS_INLINE);
}
@@ -2525,8 +2569,8 @@ void Graphics::endRenderPass()
vkCmdEndRenderPass(commandBuffers.at(currentFrame));
for (const auto &image : renderPassState.transitionImages)
Vulkan::cmdTransitionImageLayout(commandBuffers.at(currentFrame), image, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
for (const auto &[image, format] : renderPassState.transitionImages)
Vulkan::cmdTransitionImageLayout(commandBuffers.at(currentFrame), image, format, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
for (auto &colorAttachment : renderPassState.renderPassConfiguration.colorAttachments)
colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
@@ -2855,6 +2899,23 @@ int Graphics::getVsync() const
return vsync;
}
void Graphics::mapLocalUniformData(void *data, size_t size, VkDescriptorBufferInfo &bufferInfo)
{
size_t alignedSize = alignUp(size, minUniformBufferOffsetAlignment);
if (localUniformBuffer->getUsableSize() < alignedSize)
localUniformBuffer.set(new StreamBuffer(this, BUFFERUSAGE_UNIFORM, localUniformBuffer->getSize() * 2), Acquire::NORETAIN);
auto mapInfo = localUniformBuffer->map(size);
memcpy(mapInfo.data, data, size);
bufferInfo.buffer = (VkBuffer)localUniformBuffer->getHandle();
bufferInfo.offset = localUniformBuffer->unmap(size);
bufferInfo.range = size;
localUniformBuffer->markUsed(alignedSize);
}
void Graphics::createColorResources()
{
if (msaaSamples & VK_SAMPLE_COUNT_1_BIT)
@@ -3073,11 +3134,6 @@ void Graphics::cleanup()
void Graphics::cleanupSwapChain()
{
for (const auto &readbackBuffer : screenshotReadbackBuffers)
{
vmaDestroyBuffer(vmaAllocator, readbackBuffer.buffer, readbackBuffer.allocation);
vmaDestroyImage(vmaAllocator, readbackBuffer.image, readbackBuffer.imageAllocation);
}
if (colorImage)
{
vkDestroyImageView(device, colorImageView, nullptr);
@@ -3104,7 +3160,6 @@ void Graphics::recreateSwapChain()
createSwapChain();
createImageViews();
createScreenshotCallbackBuffers();
createColorResources();
createDepthResources();
+11 -19
View File
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2006-2023 LOVE Development Team
* Copyright (c) 2006-2024 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
@@ -37,7 +37,7 @@
#include <memory>
#include <functional>
#include <set>
#include <tuple>
namespace love
{
@@ -236,7 +236,7 @@ struct RenderpassState
RenderPassConfiguration renderPassConfiguration{};
FramebufferConfiguration framebufferConfiguration{};
VkPipeline pipeline = VK_NULL_HANDLE;
std::vector<VkImage> transitionImages;
std::vector<std::tuple<VkImage, PixelFormat>> transitionImages;
uint32_t numColorAttachments = 0;
float width = 0.0f;
float height = 0.0f;
@@ -249,16 +249,6 @@ struct RenderpassState
OptionalInt mainWindowClearStencilValue;
};
struct ScreenshotReadbackBuffer
{
VkBuffer buffer;
VmaAllocation allocation;
VmaAllocationInfo allocationInfo;
VkImage image;
VmaAllocation imageAllocation;
};
enum SubmitMode
{
SUBMIT_PRESENT,
@@ -274,14 +264,13 @@ public:
~Graphics();
// implementation for virtual functions
const char *getName() const override;
love::graphics::Texture *newTexture(const love::graphics::Texture::Settings &settings, const love::graphics::Texture::Slices *data) override;
love::graphics::Texture *newTextureView(love::graphics::Texture *base, const Texture::ViewSettings &viewsettings) override;
love::graphics::Buffer *newBuffer(const love::graphics::Buffer::Settings &settings, const std::vector<love::graphics::Buffer::DataDeclaration>& format, const void *data, size_t size, size_t arraylength) override;
graphics::GraphicsReadback *newReadbackInternal(ReadbackMethod method, love::graphics::Buffer *buffer, size_t offset, size_t size, data::ByteData *dest, size_t destoffset) override;
graphics::GraphicsReadback *newReadbackInternal(ReadbackMethod method, love::graphics::Texture *texture, int slice, int mipmap, const Rect &rect, image::ImageData *dest, int destx, int desty) override;
void clear(OptionalColorD color, OptionalInt stencil, OptionalDouble depth) override;
void clear(const std::vector<OptionalColorD> &colors, OptionalInt stencil, OptionalDouble depth) override;
Matrix4 computeDeviceProjection(const Matrix4 &projection, bool rendertotexture) const override;
void discard(const std::vector<bool>& colorbuffers, bool depthstencil) override;
void present(void *screenshotCallbackdata) override;
void backbufferChanged(int width, int height, int pixelwidth, int pixelheight, bool backbufferstencil, bool backbufferdepth, int msaa) override;
@@ -316,7 +305,6 @@ public:
void queueCleanUp(std::function<void()> cleanUp);
void addReadbackCallback(std::function<void()> callback);
void submitGpuCommands(SubmitMode, void *screenshotCallbackData = nullptr);
const VkDeviceSize getMinUniformBufferOffsetAlignment() const;
VkSampler getCachedSampler(const SamplerState &sampler);
void setComputeShader(Shader *computeShader);
graphics::Shader::BuiltinUniformData getCurrentBuiltinUniformData();
@@ -325,6 +313,9 @@ public:
VkSampleCountFlagBits getMsaaCount(int requestedMsaa) const;
void setVsync(int vsync);
int getVsync() const;
void mapLocalUniformData(void *data, size_t size, VkDescriptorBufferInfo &bufferInfo);
uint32 getDeviceApiVersion() const { return deviceApiVersion; }
protected:
graphics::ShaderStage *newShaderStageInternal(ShaderStageType stage, const std::string &cachekey, const std::string &source, bool gles) override;
@@ -353,7 +344,6 @@ private:
VkCompositeAlphaFlagBitsKHR chooseCompositeAlpha(const VkSurfaceCapabilitiesKHR &capabilities);
void createSwapChain();
void createImageViews();
void createScreenshotCallbackBuffers();
VkFramebuffer createFramebuffer(FramebufferConfiguration &configuration);
VkFramebuffer getFramebuffer(FramebufferConfiguration &configuration);
void createDefaultShaders();
@@ -406,7 +396,9 @@ private:
Matrix4 displayRotation;
std::vector<VkImage> swapChainImages;
VkFormat swapChainImageFormat = VK_FORMAT_UNDEFINED;
PixelFormat swapChainPixelFormat = PIXELFORMAT_UNKNOWN;
VkFormat depthStencilFormat = VK_FORMAT_UNDEFINED;
PixelFormat depthStencilPixelFormat = PIXELFORMAT_UNKNOWN;
VkExtent2D swapChainExtent = VkExtent2D();
std::vector<VkImageView> swapChainImageViews;
VkSampleCountFlagBits msaaSamples = VK_SAMPLE_COUNT_1_BIT;
@@ -442,12 +434,12 @@ private:
VmaAllocator vmaAllocator = VK_NULL_HANDLE;
StrongRef<love::graphics::Buffer> defaultConstantColor;
StrongRef<love::graphics::Buffer> defaultConstantTexCoord;
StrongRef<StreamBuffer> localUniformBuffer;
// functions that need to be called to cleanup objects that were needed for rendering a frame.
// We need a vector for each frame in flight.
std::vector<std::vector<std::function<void()>>> cleanUpFunctions;
std::vector<std::vector<std::function<void()>>> readbackCallbacks;
std::vector<ScreenshotReadbackBuffer> screenshotReadbackBuffers;
std::set<Shader*> usedShadersInFrame;
std::set<StrongRef<Shader>> usedShadersInFrame;
RenderpassState renderPassState;
};
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2006-2023 LOVE Development Team
* Copyright (c) 2006-2024 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2006-2023 LOVE Development Team
* Copyright (c) 2006-2024 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
+304 -375
View File
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2006-2023 LOVE Development Team
* Copyright (c) 2006-2024 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
@@ -21,6 +21,7 @@
#include "graphics/vertex.h"
#include "Shader.h"
#include "Graphics.h"
#include "common/Range.h"
#include "libraries/glslang/glslang/Public/ShaderLang.h"
#include "libraries/glslang/glslang/Public/ResourceLimits.h"
@@ -35,65 +36,65 @@ namespace graphics
namespace vulkan
{
static const uint32_t STREAMBUFFER_DEFAULT_SIZE = 16;
static const uint32_t DESCRIPTOR_POOL_SIZE = 1000;
class BindingMapper
{
public:
uint32_t operator()(spirv_cross::CompilerGLSL &comp, std::vector<uint32_t> &spirv, const std::string &name, const spirv_cross::ID &id)
uint32_t operator()(spirv_cross::CompilerGLSL &comp, std::vector<uint32_t> &spirv, const std::string &name, int count, const spirv_cross::ID &id)
{
auto it = bindingMappings.find(name);
if (it == bindingMappings.end())
{
auto binding = comp.get_decoration(id, spv::DecorationBinding);
if (isFreeBinding(binding))
if (isFreeBinding(binding, count))
{
bindingMappings[name] = binding;
bindingMappings[name] = Range(binding, count);
return binding;
}
else
{
uint32_t freeBinding = getFreeBinding();
uint32_t freeBinding = getFreeBinding(count);
uint32_t binaryBindingOffset;
if (!comp.get_binary_offset_for_decoration(id, spv::DecorationBinding, binaryBindingOffset))
throw love::Exception("could not get binary offset for binding");
throw love::Exception("could not get binary offset for uniform %s binding", name.c_str());
spirv[binaryBindingOffset] = freeBinding;
bindingMappings[name] = freeBinding;
bindingMappings[name] = Range(freeBinding, count);
return freeBinding;
}
}
else
return it->second;
return (uint32_t)it->second.getOffset();
};
private:
uint32_t getFreeBinding()
uint32_t getFreeBinding(int count)
{
for (uint32_t i = 0;; i++)
{
if (isFreeBinding(i))
if (isFreeBinding(i, count))
return i;
}
}
bool isFreeBinding(uint32_t binding)
bool isFreeBinding(uint32_t binding, int count)
{
Range r(binding, count);
for (const auto &entry : bindingMappings)
{
if (entry.second == binding)
if (entry.second.intersects(r))
return false;
}
return true;
}
std::map<std::string, uint32_t> bindingMappings;
std::map<std::string, Range> bindingMappings;
};
@@ -155,14 +156,11 @@ bool Shader::loadVolatile()
builtinUniformInfo[i] = nullptr;
compileShaders();
calculateUniformBufferSizeAligned();
createDescriptorSetLayout();
createPipelineLayout();
createDescriptorPoolSizes();
createStreamBuffers();
descriptorPools.resize(MAX_FRAMES_IN_FLIGHT);
currentFrame = 0;
currentUsedUniformStreamBuffersCount = 0;
newFrame();
return true;
@@ -173,31 +171,6 @@ void Shader::unloadVolatile()
if (shaderModules.empty())
return;
for (const auto &uniform : uniformInfos)
{
switch (uniform.second.baseType)
{
case UNIFORM_SAMPLER:
case UNIFORM_STORAGETEXTURE:
for (int i = 0; i < uniform.second.count; i++)
{
if (uniform.second.textures[i] != nullptr)
uniform.second.textures[i]->release();
}
delete[] uniform.second.textures;
break;
case UNIFORM_TEXELBUFFER:
case UNIFORM_STORAGEBUFFER:
for (int i = 0; i < uniform.second.count; i++)
{
if (uniform.second.buffers[i] != nullptr)
uniform.second.buffers[i]->release();
}
delete[] uniform.second.buffers;
break;
}
}
vgfx->queueCleanUp([shaderModules = std::move(shaderModules), device = device, descriptorSetLayout = descriptorSetLayout, pipelineLayout = pipelineLayout, descriptorPools = descriptorPools, computePipeline = computePipeline](){
for (const auto &pools : descriptorPools)
{
@@ -212,12 +185,8 @@ void Shader::unloadVolatile()
vkDestroyPipeline(device, computePipeline, nullptr);
});
for (const auto streamBuffer : streamBuffers)
streamBuffer->release();
shaderModules.clear();
shaderStages.clear();
streamBuffers.clear();
descriptorPools.clear();
}
@@ -240,51 +209,20 @@ void Shader::newFrame()
{
currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT;
currentUsedUniformStreamBuffersCount = 0;
currentDescriptorPool = 0;
if (streamBuffers.size() > 1)
{
size_t newSize = 0;
for (auto streamBuffer : streamBuffers)
{
newSize += streamBuffer->getSize();
streamBuffer->release();
}
streamBuffers.clear();
streamBuffers.push_back(new StreamBuffer(vgfx, BUFFERUSAGE_UNIFORM, newSize));
}
else
streamBuffers.at(0)->nextFrame();
for (VkDescriptorPool pool : descriptorPools[currentFrame])
vkResetDescriptorPool(device, pool, 0);
}
void Shader::cmdPushDescriptorSets(VkCommandBuffer commandBuffer, VkPipelineBindPoint bindPoint)
{
VkDescriptorSet currentDescriptorSet = allocateDescriptorSet();
std::vector<VkDescriptorBufferInfo> bufferInfos;
bufferInfos.reserve(numBuffers);
std::vector<VkDescriptorImageInfo> imageInfos;
imageInfos.reserve(numTextures);
std::vector<VkBufferView> bufferViews;
bufferViews.reserve(numBufferViews);
std::vector<VkWriteDescriptorSet> descriptorWrites;
int imageIndex = 0;
int bufferIndex = 0;
int bufferViewIndex = 0;
if (!localUniformData.empty())
{
auto usedStreamBufferMemory = currentUsedUniformStreamBuffersCount * uniformBufferSizeAligned;
if (usedStreamBufferMemory >= streamBuffers.back()->getSize())
{
streamBuffers.push_back(new StreamBuffer(vgfx, BUFFERUSAGE_UNIFORM, STREAMBUFFER_DEFAULT_SIZE * uniformBufferSizeAligned));
currentUsedUniformStreamBuffersCount = 0;
}
if (builtinUniformDataOffset.hasValue)
{
auto builtinData = vgfx->getCurrentBuiltinUniformData();
@@ -292,127 +230,93 @@ void Shader::cmdPushDescriptorSets(VkCommandBuffer commandBuffer, VkPipelineBind
memcpy(dst, &builtinData, sizeof(builtinData));
}
auto currentStreamBuffer = streamBuffers.back();
auto mapInfo = currentStreamBuffer->map(uniformBufferSizeAligned);
memcpy(mapInfo.data, localUniformData.data(), localUniformData.size());
auto offset = currentStreamBuffer->unmap(uniformBufferSizeAligned);
currentStreamBuffer->markUsed(uniformBufferSizeAligned);
VkDescriptorBufferInfo bufferInfo{};
bufferInfo.buffer = (VkBuffer)currentStreamBuffer->getHandle();
bufferInfo.offset = offset;
bufferInfo.range = localUniformData.size();
bufferInfos.push_back(bufferInfo);
VkWriteDescriptorSet uniformWrite{};
uniformWrite.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
uniformWrite.dstSet = currentDescriptorSet;
uniformWrite.dstBinding = localUniformLocation;
uniformWrite.dstArrayElement = 0;
uniformWrite.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
uniformWrite.descriptorCount = 1;
uniformWrite.pBufferInfo = &bufferInfos[bufferInfos.size() - 1];
descriptorWrites.push_back(uniformWrite);
currentUsedUniformStreamBuffersCount++;
vgfx->mapLocalUniformData(localUniformData.data(), localUniformData.size(), descriptorBuffers[bufferIndex++]);
}
for (const auto &u : uniformInfos)
// TODO: iteration order must match the order at the end of compileShaders right now.
// TODO: We can store data via setTextures and setBuffers instead of iterating over
// everything here.
for (const auto &u : reflection.sampledTextures)
{
auto &info = u.second;
if (usesLocalUniformData(&info))
const auto &info = u.second;
if (!info.active)
continue;
if (info.baseType == UNIFORM_SAMPLER || info.baseType == UNIFORM_STORAGETEXTURE)
for (int i = 0; i < info.count; i++)
{
bool isSampler = info.baseType == UNIFORM_SAMPLER;
auto vkTexture = dynamic_cast<Texture*>(activeTextures[info.resourceIndex + i]);
for (int i = 0; i < info.count; i++)
{
auto vkTexture = dynamic_cast<Texture*>(info.textures[i]);
if (vkTexture == nullptr)
throw love::Exception("uniform variable %s is not set.", info.name.c_str());
if (vkTexture == nullptr)
throw love::Exception("uniform variable %s is not set.", info.name.c_str());
VkDescriptorImageInfo &imageInfo = descriptorImages[imageIndex++];
VkDescriptorImageInfo imageInfo{};
imageInfo.imageLayout = vkTexture->getImageLayout();
imageInfo.imageView = (VkImageView)vkTexture->getRenderTargetHandle();
if (isSampler)
imageInfo.sampler = (VkSampler)vkTexture->getSamplerHandle();
imageInfos.push_back(imageInfo);
}
VkWriteDescriptorSet write{};
write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
write.dstSet = currentDescriptorSet;
write.dstBinding = info.location;
write.dstArrayElement = 0;
if (isSampler)
write.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
else
write.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE;
write.descriptorCount = static_cast<uint32_t>(info.count);
write.pImageInfo = &imageInfos[imageInfos.size() - info.count];
descriptorWrites.push_back(write);
}
if (info.baseType == UNIFORM_STORAGEBUFFER)
{
VkWriteDescriptorSet write{};
write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
write.dstSet = currentDescriptorSet;
write.dstBinding = info.location;
write.dstArrayElement = 0;
write.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
write.descriptorCount = info.count;
for (int i = 0; i < info.count; i++)
{
if (info.buffers[i] == nullptr)
throw love::Exception("uniform variable %s is not set.", info.name.c_str());
VkDescriptorBufferInfo bufferInfo{};
bufferInfo.buffer = (VkBuffer)info.buffers[i]->getHandle();;
bufferInfo.offset = 0;
bufferInfo.range = info.buffers[i]->getSize();
bufferInfos.push_back(bufferInfo);
}
write.pBufferInfo = &bufferInfos[bufferInfos.size() - info.count];
descriptorWrites.push_back(write);
}
if (info.baseType == UNIFORM_TEXELBUFFER)
{
VkWriteDescriptorSet write{};
write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
write.dstSet = currentDescriptorSet;
write.dstBinding = info.location;
write.dstArrayElement = 0;
write.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER;
write.descriptorCount = info.count;
for (int i = 0; i < info.count; i++)
{
if (info.buffers[i] == nullptr)
throw love::Exception("uniform variable %s is not set.", info.name.c_str());
bufferViews.push_back((VkBufferView)info.buffers[i]->getTexelBufferHandle());
}
write.pTexelBufferView = &bufferViews[bufferViews.size() - info.count];
descriptorWrites.push_back(write);
imageInfo.imageLayout = vkTexture->getImageLayout();
imageInfo.imageView = (VkImageView)vkTexture->getRenderTargetHandle();
imageInfo.sampler = (VkSampler)vkTexture->getSamplerHandle();
}
}
for (const auto &u : reflection.storageTextures)
{
const auto &info = u.second;
if (!info.active)
continue;
for (int i = 0; i < info.count; i++)
{
auto vkTexture = dynamic_cast<Texture*>(activeTextures[info.resourceIndex + i]);
if (vkTexture == nullptr)
throw love::Exception("uniform variable %s is not set.", info.name.c_str());
VkDescriptorImageInfo &imageInfo = descriptorImages[imageIndex++];
imageInfo.imageLayout = vkTexture->getImageLayout();
imageInfo.imageView = (VkImageView)vkTexture->getRenderTargetHandle();
}
}
for (const auto &u : reflection.texelBuffers)
{
const auto &info = u.second;
if (!info.active)
continue;
for (int i = 0; i < info.count; i++)
{
auto b = activeBuffers[info.resourceIndex + i];
if (b == nullptr)
throw love::Exception("uniform variable %s is not set.", info.name.c_str());
descriptorBufferViews[bufferViewIndex++] = (VkBufferView)b->getTexelBufferHandle();
}
}
for (const auto &u : reflection.storageBuffers)
{
const auto &info = u.second;
if (!info.active)
continue;
for (int i = 0; i < info.count; i++)
{
auto b = activeBuffers[info.resourceIndex + i];
if (b == nullptr)
throw love::Exception("uniform variable %s is not set.", info.name.c_str());
VkDescriptorBufferInfo &bufferInfo = descriptorBuffers[bufferIndex++];
bufferInfo.buffer = (VkBuffer)b->getHandle();
bufferInfo.offset = 0;
bufferInfo.range = b->getSize();
}
}
VkDescriptorSet currentDescriptorSet = allocateDescriptorSet();
for (auto &write : descriptorWrites)
write.dstSet = currentDescriptorSet;
vkUpdateDescriptorSets(device, descriptorWrites.size(), descriptorWrites.data(), 0, nullptr);
vkCmdBindDescriptorSets(commandBuffer, bindPoint, pipelineLayout, 0, 1, &currentDescriptorSet, 0, nullptr);
@@ -444,12 +348,6 @@ int Shader::getVertexAttributeIndex(const std::string &name)
return it == attributes.end() ? -1 : it->second;
}
const Shader::UniformInfo *Shader::getUniformInfo(const std::string &name) const
{
const auto it = uniformInfos.find(name);
return it != uniformInfos.end() ? &(it->second) : nullptr;
}
const Shader::UniformInfo *Shader::getUniformInfo(BuiltinUniform builtin) const
{
return builtinUniformInfo[builtin];
@@ -466,11 +364,15 @@ void Shader::updateUniform(const UniformInfo *info, int count)
void Shader::sendTextures(const UniformInfo *info, graphics::Texture **textures, int count)
{
if (current == this)
Graphics::flushBatchedDrawsGlobal();
for (int i = 0; i < count; i++)
{
auto oldTexture = info->textures[i];
info->textures[i] = textures[i];
info->textures[i]->retain();
int resourceindex = info->resourceIndex + i;
auto oldTexture = activeTextures[resourceindex];
activeTextures[resourceindex] = textures[i];
activeTextures[resourceindex]->retain();
if (oldTexture)
oldTexture->release();
}
@@ -478,24 +380,20 @@ void Shader::sendTextures(const UniformInfo *info, graphics::Texture **textures,
void Shader::sendBuffers(const UniformInfo *info, love::graphics::Buffer **buffers, int count)
{
if (current == this)
Graphics::flushBatchedDrawsGlobal();
for (int i = 0; i < count; i++)
{
auto oldBuffer = info->buffers[i];
info->buffers[i] = buffers[i];
info->buffers[i]->retain();
int resourceindex = info->resourceIndex + i;
auto oldBuffer = activeBuffers[resourceindex];
activeBuffers[resourceindex] = buffers[i];
activeBuffers[resourceindex]->retain();
if (oldBuffer)
oldBuffer->release();
}
}
void Shader::calculateUniformBufferSizeAligned()
{
auto minAlignment = vgfx->getMinUniformBufferOffsetAlignment();
size_t size = localUniformStagingData.size();
auto factor = static_cast<VkDeviceSize>(std::ceil(static_cast<float>(size) / static_cast<float>(minAlignment)));
uniformBufferSizeAligned = factor * minAlignment;
}
void Shader::buildLocalUniforms(spirv_cross::Compiler &comp, const spirv_cross::SPIRType &type, size_t baseoff, const std::string &basename)
{
using namespace spirv_cross;
@@ -524,38 +422,25 @@ void Shader::buildLocalUniforms(spirv_cross::Compiler &comp, const spirv_cross::
continue;
}
UniformInfo u{};
u.name = name;
name = canonicaliizeUniformName(name);
auto uniformit = reflection.allUniforms.find(name);
if (uniformit == reflection.allUniforms.end())
{
handleUnknownUniformName(name.c_str());
continue;
}
UniformInfo &u = *(uniformit->second);
u.active = true;
u.dataSize = memberSize;
u.count = memberType.array.empty() ? 1 : memberType.array[0];
u.components = 1;
u.data = localUniformStagingData.data() + offset;
if (memberType.columns == 1)
const auto &valuesit = reflection.localUniformInitializerValues.find(name);
if (valuesit != reflection.localUniformInitializerValues.end())
{
if (memberType.basetype == SPIRType::Int)
u.baseType = UNIFORM_INT;
else if (memberType.basetype == SPIRType::UInt)
u.baseType = UNIFORM_UINT;
else
u.baseType = UNIFORM_FLOAT;
u.components = memberType.vecsize;
}
else
{
u.baseType = UNIFORM_MATRIX;
u.matrix.rows = memberType.vecsize;
u.matrix.columns = memberType.columns;
}
const auto &reflectionIt = validationReflection.localUniforms.find(u.name);
if (reflectionIt != validationReflection.localUniforms.end())
{
const auto &localUniform = reflectionIt->second;
if (localUniform.dataType == DATA_BASETYPE_BOOL)
u.baseType = UNIFORM_BOOL;
const auto &values = localUniform.initializerValues;
const auto &values = valuesit->second;
if (!values.empty())
memcpy(
u.data,
@@ -563,14 +448,12 @@ void Shader::buildLocalUniforms(spirv_cross::Compiler &comp, const spirv_cross::
std::min(u.dataSize, values.size() * sizeof(LocalUniformValue)));
}
uniformInfos[u.name] = u;
BuiltinUniform builtin = BUILTIN_MAX_ENUM;
if (getConstant(u.name.c_str(), builtin))
{
if (builtin == BUILTIN_UNIFORMS_PER_DRAW)
builtinUniformDataOffset = offset;
builtinUniformInfo[builtin] = &uniformInfos[u.name];
builtinUniformInfo[builtin] = &u;
}
}
}
@@ -621,7 +504,7 @@ void Shader::compileShaders()
bool forceDefault = false;
bool forwardCompat = true;
if (!tshader->parse(GetDefaultResources(), defaultVersion, defaultProfile, forceDefault, forwardCompat, EShMsgSuppressWarnings))
if (!tshader->parse(GetResources(), defaultVersion, defaultProfile, forceDefault, forwardCompat, EShMsgSuppressWarnings))
{
const char *stageName = "unknown";
ShaderStage::getConstant(stage, stageName);
@@ -643,8 +526,6 @@ void Shader::compileShaders()
if (!program->mapIO())
throw love::Exception("mapIO failed");
uniformInfos.clear();
BindingMapper bindingMapper;
for (int i = 0; i < SHADERSTAGE_MAX_ENUM; i++)
@@ -679,7 +560,7 @@ void Shader::compileShaders()
localUniformStagingData.resize(defaultUniformBlockSize);
localUniformData.resize(defaultUniformBlockSize);
localUniformLocation = bindingMapper(comp, spirv, resource.name, resource.id);
localUniformLocation = bindingMapper(comp, spirv, resource.name, 1, resource.id);
memset(localUniformStagingData.data(), 0, defaultUniformBlockSize);
memset(localUniformData.data(), 0, defaultUniformBlockSize);
@@ -695,119 +576,51 @@ void Shader::compileShaders()
for (const auto &r : shaderResources.sampled_images)
{
const SPIRType &basetype = comp.get_type(r.base_type_id);
const SPIRType &type = comp.get_type(r.type_id);
const SPIRType &imagetype = comp.get_type(basetype.image.type);
graphics::Shader::UniformInfo info;
info.location = bindingMapper(comp, spirv, r.name, r.id);
info.baseType = UNIFORM_SAMPLER;
info.name = r.name;
info.count = type.array.empty() ? 1 : type.array[0];
info.isDepthSampler = type.image.depth;
info.components = 1;
switch (imagetype.basetype)
std::string name = canonicaliizeUniformName(r.name);
auto uniformit = reflection.allUniforms.find(name);
if (uniformit == reflection.allUniforms.end())
{
case SPIRType::Float:
info.dataBaseType = DATA_BASETYPE_FLOAT;
break;
case SPIRType::Int:
info.dataBaseType = DATA_BASETYPE_INT;
break;
case SPIRType::UInt:
info.dataBaseType = DATA_BASETYPE_UINT;
break;
default:
break;
handleUnknownUniformName(name.c_str());
continue;
}
switch (basetype.image.dim)
{
case spv::Dim2D:
info.textureType = basetype.image.arrayed ? TEXTURE_2D_ARRAY : TEXTURE_2D;
info.textures = new love::graphics::Texture *[info.count];
break;
case spv::Dim3D:
info.textureType = TEXTURE_VOLUME;
info.textures = new love::graphics::Texture *[info.count];
break;
case spv::DimCube:
if (basetype.image.arrayed) {
throw love::Exception("cubemap arrays are not currently supported");
}
info.textureType = TEXTURE_CUBE;
info.textures = new love::graphics::Texture *[info.count];
break;
case spv::DimBuffer:
info.baseType = UNIFORM_TEXELBUFFER;
info.buffers = new love::graphics::Buffer *[info.count];
break;
default:
throw love::Exception("unknown dim");
}
UniformInfo &u = *(uniformit->second);
u.active = true;
u.location = bindingMapper(comp, spirv, name, u.count, r.id);
if (info.baseType == UNIFORM_TEXELBUFFER)
{
for (int i = 0; i < info.count; i++)
info.buffers[i] = nullptr;
}
else
{
for (int i = 0; i < info.count; i++)
{
info.textures[i] = nullptr;
}
}
uniformInfos[r.name] = info;
BuiltinUniform builtin;
if (getConstant(r.name.c_str(), builtin))
builtinUniformInfo[builtin] = &uniformInfos[info.name];
if (getConstant(name.c_str(), builtin))
builtinUniformInfo[builtin] = &u;
}
for (const auto &r : shaderResources.storage_buffers)
{
const auto &type = comp.get_type(r.type_id);
UniformInfo u{};
u.baseType = UNIFORM_STORAGEBUFFER;
u.components = 1;
u.name = r.name;
u.count = type.array.empty() ? 1 : type.array[0];
if (!fillUniformReflectionData(u))
std::string name = canonicaliizeUniformName(r.name);
const auto &uniformit = reflection.storageBuffers.find(name);
if (uniformit == reflection.storageBuffers.end())
{
handleUnknownUniformName(name.c_str());
continue;
}
u.location = bindingMapper(comp, spirv, r.name, r.id);
u.buffers = new love::graphics::Buffer *[u.count];
for (int i = 0; i < u.count; i++)
u.buffers[i] = nullptr;
uniformInfos[u.name] = u;
UniformInfo &u = uniformit->second;
u.active = true;
u.location = bindingMapper(comp, spirv, name, u.count, r.id);
}
for (const auto &r : shaderResources.storage_images)
{
const auto &type = comp.get_type(r.type_id);
UniformInfo u{};
u.baseType = UNIFORM_STORAGETEXTURE;
u.components = 1;
u.name = r.name;
u.count = type.array.empty() ? 1 : type.array[0];
if (!fillUniformReflectionData(u))
std::string name = canonicaliizeUniformName(r.name);
const auto &uniformit = reflection.storageTextures.find(name);
if (uniformit == reflection.storageTextures.end())
{
handleUnknownUniformName(name.c_str());
continue;
}
u.textures = new love::graphics::Texture *[u.count];
u.location = bindingMapper(comp, spirv, r.name, r.id);
for (int i = 0; i < u.count; i++)
u.textures[i] = nullptr;
uniformInfos[u.name] = u;
UniformInfo &u = uniformit->second;
u.active = true;
u.location = bindingMapper(comp, spirv, name, u.count, r.id);
}
if (shaderStage == SHADERSTAGE_VERTEX)
@@ -826,7 +639,7 @@ void Shader::compileShaders()
uint32_t locationOffset;
if (!comp.get_binary_offset_for_decoration(r.id, spv::DecorationLocation, locationOffset))
throw love::Exception("could not get binary offset for location");
throw love::Exception("could not get binary offset for vertex attribute %s location", r.name.c_str());
spirv[locationOffset] = (uint32_t)index;
@@ -868,31 +681,151 @@ void Shader::compileShaders()
shaderStages.push_back(shaderStageInfo);
}
numBuffers = 0;
numTextures = 0;
numBufferViews = 0;
int numBuffers = 0;
int numTextures = 0;
int numBufferViews = 0;
if (localUniformData.size() > 0)
numBuffers++;
for (const auto &u : uniformInfos)
for (const auto kvp : reflection.allUniforms)
{
switch (u.second.baseType)
if (!kvp.second->active)
continue;
switch (kvp.second->baseType)
{
case UNIFORM_SAMPLER:
case UNIFORM_STORAGETEXTURE:
numTextures++;
numTextures += kvp.second->count;
break;
case UNIFORM_STORAGEBUFFER:
numBuffers++;
numBuffers += kvp.second->count;
break;
case UNIFORM_TEXELBUFFER:
numBufferViews++;
numBufferViews += kvp.second->count;
break;
default:
continue;
}
}
descriptorWrites.clear();
descriptorBuffers.clear();
descriptorBuffers.reserve(numBuffers);
descriptorImages.clear();
descriptorImages.reserve(numTextures);
descriptorBufferViews.clear();
descriptorBufferViews.reserve(numBufferViews);
if (localUniformData.size() > 0)
{
VkDescriptorBufferInfo bufferInfo{};
bufferInfo.range = localUniformData.size();
descriptorBuffers.push_back(bufferInfo);
VkWriteDescriptorSet write{};
write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
write.dstBinding = localUniformLocation;
write.dstArrayElement = 0;
write.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
write.descriptorCount = 1;
write.pBufferInfo = &descriptorBuffers.back();
descriptorWrites.push_back(write);
}
for (const auto &u : reflection.sampledTextures)
{
const UniformInfo &info = u.second;
if (!info.active)
continue;
for (int i = 0; i < info.count; i++)
{
VkDescriptorImageInfo imageInfo{};
descriptorImages.push_back(imageInfo);
}
VkWriteDescriptorSet write{};
write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
write.dstBinding = info.location;
write.dstArrayElement = 0;
write.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
write.descriptorCount = static_cast<uint32_t>(info.count);
write.pImageInfo = &descriptorImages[descriptorImages.size() - info.count];
descriptorWrites.push_back(write);
}
for (const auto &u : reflection.storageTextures)
{
const UniformInfo &info = u.second;
if (!info.active)
continue;
for (int i = 0; i < info.count; i++)
{
VkDescriptorImageInfo imageInfo{};
descriptorImages.push_back(imageInfo);
}
VkWriteDescriptorSet write{};
write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
write.dstBinding = info.location;
write.dstArrayElement = 0;
write.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE;
write.descriptorCount = static_cast<uint32_t>(info.count);
write.pImageInfo = &descriptorImages[descriptorImages.size() - info.count];
descriptorWrites.push_back(write);
}
for (const auto &u : reflection.texelBuffers)
{
const UniformInfo &info = u.second;
if (!info.active)
continue;
for (int i = 0; i < info.count; i++)
descriptorBufferViews.push_back(VK_NULL_HANDLE);
VkWriteDescriptorSet write{};
write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
write.dstBinding = info.location;
write.dstArrayElement = 0;
write.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER;
write.descriptorCount = info.count;
write.pTexelBufferView = &descriptorBufferViews[descriptorBufferViews.size() - info.count];
descriptorWrites.push_back(write);
}
for (const auto &u : reflection.storageBuffers)
{
const UniformInfo &info = u.second;
if (!info.active)
continue;
for (int i = 0; i < info.count; i++)
{
VkDescriptorBufferInfo bufferInfo{};
descriptorBuffers.push_back(bufferInfo);
}
VkWriteDescriptorSet write{};
write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
write.dstBinding = info.location;
write.dstArrayElement = 0;
write.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
write.descriptorCount = info.count;
write.pBufferInfo = &descriptorBuffers[descriptorBuffers.size() - info.count];
descriptorWrites.push_back(write);
}
}
void Shader::createDescriptorSetLayout()
@@ -905,16 +838,19 @@ void Shader::createDescriptorSetLayout()
else
stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT;
for (auto const &entry : uniformInfos)
for (auto const &entry : reflection.allUniforms)
{
auto type = Vulkan::getDescriptorType(entry.second.baseType);
if (!entry.second->active)
continue;
auto type = Vulkan::getDescriptorType(entry.second->baseType);
if (type != VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER)
{
VkDescriptorSetLayoutBinding layoutBinding{};
layoutBinding.binding = entry.second.location;
layoutBinding.binding = entry.second->location;
layoutBinding.descriptorType = type;
layoutBinding.descriptorCount = entry.second.count;
layoutBinding.descriptorCount = entry.second->count;
layoutBinding.stageFlags = stageFlags;
bindings.push_back(layoutBinding);
@@ -976,10 +912,13 @@ void Shader::createDescriptorPoolSizes()
descriptorPoolSizes.push_back(size);
}
for (const auto &entry : uniformInfos)
for (const auto &entry : reflection.allUniforms)
{
if (entry.second->location < 0)
continue;
VkDescriptorPoolSize size{};
auto type = Vulkan::getDescriptorType(entry.second.baseType);
auto type = Vulkan::getDescriptorType(entry.second->baseType);
if (type == VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER)
continue;
@@ -989,13 +928,6 @@ void Shader::createDescriptorPoolSizes()
}
}
void Shader::createStreamBuffers()
{
size_t size = STREAMBUFFER_DEFAULT_SIZE * uniformBufferSizeAligned;
if (size > 0)
streamBuffers.push_back(new StreamBuffer(vgfx, BUFFERUSAGE_UNIFORM, size));
}
void Shader::setVideoTextures(graphics::Texture *ytexture, graphics::Texture *cbtexture, graphics::Texture *crtexture)
{
std::array<graphics::Texture*, 3> textures = {
@@ -1012,29 +944,26 @@ void Shader::setVideoTextures(graphics::Texture *ytexture, graphics::Texture *cb
for (size_t i = 0; i < textures.size(); i++)
{
if (builtinUniformInfo[builtIns[i]] != nullptr)
const UniformInfo *u = builtinUniformInfo[builtIns[i]];
if (u != nullptr)
{
textures[i]->retain();
if (builtinUniformInfo[builtIns[i]]->textures[0])
builtinUniformInfo[builtIns[i]]->textures[0]->release();
builtinUniformInfo[builtIns[i]]->textures[0] = textures[i];
if (activeTextures[u->resourceIndex])
activeTextures[u->resourceIndex]->release();
activeTextures[u->resourceIndex] = textures[i];
}
}
}
bool Shader::hasUniform(const std::string &name) const
{
return uniformInfos.find(name) != uniformInfos.end();
}
void Shader::setMainTex(graphics::Texture *texture)
{
if (builtinUniformInfo[BUILTIN_TEXTURE_MAIN] != nullptr)
const UniformInfo *u = builtinUniformInfo[BUILTIN_TEXTURE_MAIN];
if (u != nullptr)
{
texture->retain();
if (builtinUniformInfo[BUILTIN_TEXTURE_MAIN]->textures[0])
builtinUniformInfo[BUILTIN_TEXTURE_MAIN]->textures[0]->release();
builtinUniformInfo[BUILTIN_TEXTURE_MAIN]->textures[0] = texture;
if (activeTextures[u->resourceIndex])
activeTextures[u->resourceIndex]->release();
activeTextures[u->resourceIndex] = texture;
}
}
+6 -17
View File
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2006-2023 LOVE Development Team
* Copyright (c) 2006-2024 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
@@ -76,7 +76,6 @@ public:
int getVertexAttributeIndex(const std::string &name) override;
const UniformInfo *getUniformInfo(const std::string &name) const override;
const UniformInfo *getUniformInfo(BuiltinUniform builtin) const override;
void updateUniform(const UniformInfo *info, int count) override;
@@ -84,40 +83,32 @@ public:
void sendTextures(const UniformInfo *info, graphics::Texture **textures, int count) override;
void sendBuffers(const UniformInfo *info, love::graphics::Buffer **buffers, int count) override;
bool hasUniform(const std::string &name) const override;
void setVideoTextures(graphics::Texture *ytexture, graphics::Texture *cbtexture, graphics::Texture *crtexture) override;
void setMainTex(graphics::Texture *texture);
private:
void calculateUniformBufferSizeAligned();
void compileShaders();
void createDescriptorSetLayout();
void createPipelineLayout();
void createDescriptorPoolSizes();
void createStreamBuffers();
void buildLocalUniforms(spirv_cross::Compiler &comp, const spirv_cross::SPIRType &type, size_t baseoff, const std::string &basename);
void createDescriptorPool();
VkDescriptorSet allocateDescriptorSet();
VkDeviceSize uniformBufferSizeAligned;
VkPipeline computePipeline;
uint32_t numTextures;
uint32_t numBuffers;
uint32_t numBufferViews;
VkDescriptorSetLayout descriptorSetLayout;
VkPipelineLayout pipelineLayout;
std::vector<VkDescriptorPoolSize> descriptorPoolSizes;
// we don't know how much memory we need per frame for the uniform buffer descriptors
// we keep a vector of stream buffers that gets dynamically increased if more memory is needed
std::vector<StreamBuffer*> streamBuffers;
std::vector<std::vector<VkDescriptorPool>> descriptorPools;
std::vector<VkDescriptorBufferInfo> descriptorBuffers;
std::vector<VkDescriptorImageInfo> descriptorImages;
std::vector<VkBufferView> descriptorBufferViews;
std::vector<VkWriteDescriptorSet> descriptorWrites;
std::vector<VkPipelineShaderStageCreateInfo> shaderStages;
std::vector<VkShaderModule> shaderModules;
@@ -126,7 +117,6 @@ private:
bool isCompute = false;
std::unordered_map<std::string, graphics::Shader::UniformInfo> uniformInfos;
UniformInfo *builtinUniformInfo[BUILTIN_MAX_ENUM];
std::unique_ptr<StreamBuffer> uniformBufferObjectBuffer;
@@ -138,7 +128,6 @@ private:
std::unordered_map<std::string, int> attributes;
uint32_t currentFrame;
uint32_t currentUsedUniformStreamBuffersCount;
uint32_t currentDescriptorPool;
};
+1 -1
View File
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2006-2023 LOVE Development Team
* Copyright (c) 2006-2024 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
+1 -1
View File
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2006-2023 LOVE Development Team
* Copyright (c) 2006-2024 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
+6 -1
View File
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2006-2023 LOVE Development Team
* Copyright (c) 2006-2024 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
@@ -96,6 +96,11 @@ ptrdiff_t StreamBuffer::getHandle() const
return (ptrdiff_t) buffer;
}
size_t StreamBuffer::getGPUReadOffset() const
{
return (frameIndex * bufferSize) + frameGPUReadOffset;
}
love::graphics::StreamBuffer::MapInfo StreamBuffer::map(size_t /*minsize*/)
{
// TODO: do we also need to wait until a fence is complete, here?
+2 -1
View File
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2006-2023 LOVE Development Team
* Copyright (c) 2006-2024 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
@@ -47,6 +47,7 @@ public:
virtual void unloadVolatile() override;
size_t getGPUReadOffset() const override;
MapInfo map(size_t minsize) override;
size_t unmap(size_t usedSize) override;
void markUsed(size_t usedSize) override;
+161 -106
View File
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2006-2023 LOVE Development Team
* Copyright (c) 2006-2024 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
@@ -47,11 +47,22 @@ Texture::Texture(love::graphics::Graphics *gfx, const Settings &settings, const
slices.clear();
}
Texture::Texture(love::graphics::Graphics *gfx, love::graphics::Texture *base, const Texture::ViewSettings &viewsettings)
: love::graphics::Texture(gfx, base, viewsettings)
, vgfx(dynamic_cast<Graphics*>(gfx))
, slices(viewsettings.type.get(base->getTextureType()))
, imageAspect(0)
{
loadVolatile();
}
bool Texture::loadVolatile()
{
allocator = vgfx->getVmaAllocator();
device = vgfx->getDevice();
bool root = rootView.texture == this;
if (isPixelFormatDepth(format))
imageAspect |= VK_IMAGE_ASPECT_DEPTH_BIT;
if (isPixelFormatStencil(format))
@@ -79,82 +90,115 @@ bool Texture::loadVolatile()
usageFlags |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
}
VkImageCreateFlags createFlags = 0;
layerCount = 1;
if (texType == TEXTURE_2D_ARRAY)
layerCount = getLayerCount();
else if (texType == TEXTURE_CUBE)
{
layerCount = 6;
createFlags |= VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT;
}
msaaSamples = vgfx->getMsaaCount(requestedMSAA);
VkImageCreateInfo imageInfo{};
imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
imageInfo.flags = createFlags;
imageInfo.imageType = Vulkan::getImageType(getTextureType());
imageInfo.extent.width = static_cast<uint32_t>(pixelWidth);
imageInfo.extent.height = static_cast<uint32_t>(pixelHeight);
imageInfo.extent.depth = static_cast<uint32_t>(depth);
imageInfo.arrayLayers = static_cast<uint32_t>(layerCount);
imageInfo.mipLevels = static_cast<uint32_t>(mipmapCount);
imageInfo.format = vulkanFormat.internalFormat;
imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
imageInfo.usage = usageFlags;
imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
imageInfo.samples = msaaSamples;
VmaAllocationCreateInfo imageAllocationCreateInfo{};
if (vmaCreateImage(allocator, &imageInfo, &imageAllocationCreateInfo, &textureImage, &textureImageAllocation, nullptr) != VK_SUCCESS)
throw love::Exception("failed to create image");
auto commandBuffer = vgfx->getCommandBufferForDataTransfer();
if (isPixelFormatDepthStencil(format))
imageLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
else if (computeWrite)
imageLayout = VK_IMAGE_LAYOUT_GENERAL;
else
imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
Vulkan::cmdTransitionImageLayout(commandBuffer, textureImage,
VK_IMAGE_LAYOUT_UNDEFINED, imageLayout,
0, VK_REMAINING_MIP_LEVELS,
0, VK_REMAINING_ARRAY_LAYERS);
bool hasdata = slices.get(0, 0) != nullptr;
if (hasdata)
if (root)
{
for (int mip = 0; mip < getMipmapCount(); mip++)
{
int sliceCount;
if (texType == TEXTURE_CUBE)
sliceCount = 6;
else
sliceCount = slices.getSliceCount();
VkImageCreateFlags createFlags = 0;
std::vector<VkFormat> vkviewformats;
for (int slice = 0; slice < sliceCount; slice++)
for (PixelFormat viewformat : viewFormats)
{
if (viewformat != format)
{
auto id = slices.get(slice, mip);
if (id != nullptr)
uploadImageData(id, mip, slice, 0, 0);
createFlags |= VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT;
TextureFormat f = Vulkan::getTextureFormat(viewformat);
vkviewformats.push_back(f.internalFormat);
}
}
if (texType == TEXTURE_CUBE || (texType == TEXTURE_2D_ARRAY && layerCount >= 6))
createFlags |= VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT;
msaaSamples = vgfx->getMsaaCount(requestedMSAA);
VkImageCreateInfo imageInfo{};
imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
imageInfo.flags = createFlags;
imageInfo.imageType = Vulkan::getImageType(getTextureType());
imageInfo.extent.width = static_cast<uint32_t>(pixelWidth);
imageInfo.extent.height = static_cast<uint32_t>(pixelHeight);
imageInfo.extent.depth = static_cast<uint32_t>(depth);
imageInfo.arrayLayers = static_cast<uint32_t>(layerCount);
imageInfo.mipLevels = static_cast<uint32_t>(mipmapCount);
imageInfo.format = vulkanFormat.internalFormat;
imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
imageInfo.usage = usageFlags;
imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
imageInfo.samples = msaaSamples;
VkImageFormatListCreateInfo viewFormatsInfo{};
viewFormatsInfo.sType = VK_STRUCTURE_TYPE_IMAGE_FORMAT_LIST_CREATE_INFO;
if (!vkviewformats.empty() && vgfx->getDeviceApiVersion() >= VK_API_VERSION_1_2)
{
viewFormatsInfo.viewFormatCount = (uint32)vkviewformats.size();
viewFormatsInfo.pViewFormats = vkviewformats.data();
imageInfo.pNext = &viewFormatsInfo;
}
VmaAllocationCreateInfo imageAllocationCreateInfo{};
if (vmaCreateImage(allocator, &imageInfo, &imageAllocationCreateInfo, &textureImage, &textureImageAllocation, nullptr) != VK_SUCCESS)
throw love::Exception("failed to create image");
auto commandBuffer = vgfx->getCommandBufferForDataTransfer();
if (isPixelFormatDepthStencil(format))
imageLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
else if (computeWrite)
imageLayout = VK_IMAGE_LAYOUT_GENERAL;
else
imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
Vulkan::cmdTransitionImageLayout(commandBuffer, textureImage, format,
VK_IMAGE_LAYOUT_UNDEFINED, imageLayout,
0, VK_REMAINING_MIP_LEVELS,
0, VK_REMAINING_ARRAY_LAYERS);
bool hasdata = slices.get(0, 0) != nullptr;
if (hasdata)
{
for (int mip = 0; mip < getMipmapCount(); mip++)
{
int sliceCount;
if (texType == TEXTURE_CUBE)
sliceCount = 6;
else
sliceCount = slices.getSliceCount();
for (int slice = 0; slice < sliceCount; slice++)
{
auto id = slices.get(slice, mip);
if (id != nullptr)
uploadImageData(id, mip, slice, 0, 0);
}
}
}
else
clear();
}
else
clear();
{
Texture *roottex = (Texture *) rootView.texture;
textureImage = roottex->textureImage;
textureImageAllocation = VK_NULL_HANDLE;
imageLayout = roottex->imageLayout;
msaaSamples = roottex->msaaSamples;
}
createTextureImageView();
textureSampler = vgfx->getCachedSampler(samplerState);
setSamplerState(samplerState);
if (!isPixelFormatDepthStencil(format) && slices.getMipmapCount() <= 1 && getMipmapsMode() != MIPMAPS_NONE)
if (root && !isPixelFormatDepthStencil(format) && slices.getMipmapCount() <= 1 && getMipmapsMode() != MIPMAPS_NONE)
generateMipmaps();
if (renderTarget)
@@ -172,9 +216,9 @@ bool Texture::loadVolatile()
viewInfo.viewType = Vulkan::getImageViewType(getTextureType());
viewInfo.format = vulkanFormat.internalFormat;
viewInfo.subresourceRange.aspectMask = imageAspect;
viewInfo.subresourceRange.baseMipLevel = mip;
viewInfo.subresourceRange.baseMipLevel = mip + rootView.startMipmap;
viewInfo.subresourceRange.levelCount = 1;
viewInfo.subresourceRange.baseArrayLayer = slice;
viewInfo.subresourceRange.baseArrayLayer = slice + rootView.startLayer;
viewInfo.subresourceRange.layerCount = 1;
viewInfo.components.r = vulkanFormat.swizzleR;
viewInfo.components.g = vulkanFormat.swizzleG;
@@ -189,15 +233,18 @@ bool Texture::loadVolatile()
int64 memsize = 0;
for (int mip = 0; mip < getMipmapCount(); mip++)
if (root)
{
int w = getPixelWidth(mip);
int h = getPixelHeight(mip);
int slices = getDepth(mip) * layerCount;
memsize += getPixelFormatSliceSize(format, w, h) * slices;
}
for (int mip = 0; mip < getMipmapCount(); mip++)
{
int w = getPixelWidth(mip);
int h = getPixelHeight(mip);
int slices = getDepth(mip) * layerCount;
memsize += getPixelFormatSliceSize(format, w, h) * slices;
}
memsize *= static_cast<int>(msaaSamples);
memsize *= static_cast<int>(msaaSamples);
}
setGraphicsMemorySize(memsize);
@@ -207,8 +254,16 @@ bool Texture::loadVolatile()
{
VkDebugUtilsObjectNameInfoEXT nameInfo{};
nameInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT;
nameInfo.objectType = VK_OBJECT_TYPE_IMAGE;
nameInfo.objectHandle = (uint64_t)textureImage;
if (root)
{
nameInfo.objectType = VK_OBJECT_TYPE_IMAGE;
nameInfo.objectHandle = (uint64_t)textureImage;
}
else
{
nameInfo.objectType = VK_OBJECT_TYPE_IMAGE_VIEW;
nameInfo.objectHandle = (uint64_t)textureImageView;
}
nameInfo.pObjectName = debugName.c_str();
vkSetDebugUtilsObjectNameEXT(device, &nameInfo);
}
@@ -230,13 +285,15 @@ void Texture::unloadVolatile()
textureImageAllocation = textureImageAllocation,
textureImageViews = std::move(renderTargetImageViews)] () {
vkDestroyImageView(device, textureImageView, nullptr);
vmaDestroyImage(allocator, textureImage, textureImageAllocation);
if (textureImageAllocation)
vmaDestroyImage(allocator, textureImage, textureImageAllocation);
for (const auto &views : textureImageViews)
for (const auto &view : views)
vkDestroyImageView(device, view, nullptr);
});
textureImage = VK_NULL_HANDLE;
textureImageAllocation = VK_NULL_HANDLE;
setGraphicsMemorySize(0);
}
@@ -278,8 +335,7 @@ ptrdiff_t Texture::getHandle() const
void Texture::setSamplerState(const SamplerState &s)
{
love::graphics::Texture::setSamplerState(s);
samplerState = validateSamplerState(s);
textureSampler = vgfx->getCachedSampler(samplerState);
}
@@ -291,16 +347,15 @@ VkImageLayout Texture::getImageLayout() const
void Texture::createTextureImageView()
{
auto vulkanFormat = Vulkan::getTextureFormat(format);
VkImageViewCreateInfo viewInfo{};
viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
viewInfo.image = textureImage;
viewInfo.viewType = Vulkan::getImageViewType(getTextureType());
viewInfo.format = vulkanFormat.internalFormat;
viewInfo.subresourceRange.aspectMask = imageAspect;
viewInfo.subresourceRange.baseMipLevel = 0;
viewInfo.subresourceRange.baseMipLevel = rootView.startMipmap;
viewInfo.subresourceRange.levelCount = getMipmapCount();
viewInfo.subresourceRange.baseArrayLayer = 0;
viewInfo.subresourceRange.baseArrayLayer = rootView.startLayer;
viewInfo.subresourceRange.layerCount = layerCount;
viewInfo.components.r = vulkanFormat.swizzleR;
viewInfo.components.g = vulkanFormat.swizzleG;
@@ -324,7 +379,7 @@ void Texture::clear()
if (imageLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)
{
Vulkan::cmdTransitionImageLayout(commandBuffer, textureImage,
Vulkan::cmdTransitionImageLayout(commandBuffer, textureImage, format,
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
0, VK_REMAINING_MIP_LEVELS, 0, VK_REMAINING_ARRAY_LAYERS);
@@ -332,7 +387,7 @@ void Texture::clear()
vkCmdClearColorImage(commandBuffer, textureImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, &clearColor, 1, &range);
Vulkan::cmdTransitionImageLayout(commandBuffer, textureImage,
Vulkan::cmdTransitionImageLayout(commandBuffer, textureImage, format,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
0, VK_REMAINING_MIP_LEVELS, 0, VK_REMAINING_ARRAY_LAYERS);
}
@@ -344,7 +399,7 @@ void Texture::clear()
}
else
{
Vulkan::cmdTransitionImageLayout(commandBuffer, textureImage,
Vulkan::cmdTransitionImageLayout(commandBuffer, textureImage, format,
imageLayout, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
0, VK_REMAINING_MIP_LEVELS, 0, VK_REMAINING_ARRAY_LAYERS);
@@ -354,7 +409,7 @@ void Texture::clear()
vkCmdClearDepthStencilImage(commandBuffer, textureImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, &depthStencilColor, 1, &range);
Vulkan::cmdTransitionImageLayout(commandBuffer, textureImage,
Vulkan::cmdTransitionImageLayout(commandBuffer, textureImage, format,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, imageLayout,
0, VK_REMAINING_MIP_LEVELS, 0, VK_REMAINING_ARRAY_LAYERS);
}
@@ -394,7 +449,7 @@ void Texture::generateMipmapsInternal()
auto commandBuffer = vgfx->getCommandBufferForDataTransfer();
if (imageLayout != VK_IMAGE_LAYOUT_GENERAL)
Vulkan::cmdTransitionImageLayout(commandBuffer, textureImage,
Vulkan::cmdTransitionImageLayout(commandBuffer, textureImage, format,
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
0, static_cast<uint32_t>(getMipmapCount()), 0, static_cast<uint32_t>(layerCount));
@@ -404,16 +459,16 @@ void Texture::generateMipmapsInternal()
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
barrier.subresourceRange.baseArrayLayer = 0;
barrier.subresourceRange.baseArrayLayer = rootView.startLayer;
barrier.subresourceRange.layerCount = static_cast<uint32_t>(layerCount);
barrier.subresourceRange.baseMipLevel = 0;
barrier.subresourceRange.baseMipLevel = rootView.startMipmap;
barrier.subresourceRange.levelCount = 1u;
uint32_t mipLevels = static_cast<uint32_t>(getMipmapCount());
for (uint32_t i = 1; i < mipLevels; i++)
{
barrier.subresourceRange.baseMipLevel = i - 1;
barrier.subresourceRange.baseMipLevel = rootView.startMipmap + i - 1;
barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
@@ -429,15 +484,15 @@ void Texture::generateMipmapsInternal()
blit.srcOffsets[0] = { 0, 0, 0 };
blit.srcOffsets[1] = { getPixelWidth(i - 1), getPixelHeight(i - 1), 1 };
blit.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
blit.srcSubresource.mipLevel = i - 1;
blit.srcSubresource.baseArrayLayer = 0;
blit.srcSubresource.mipLevel = rootView.startMipmap + i - 1;
blit.srcSubresource.baseArrayLayer = rootView.startLayer;
blit.srcSubresource.layerCount = static_cast<uint32_t>(layerCount);
blit.dstOffsets[0] = { 0, 0, 0 };
blit.dstOffsets[1] = { getPixelWidth(i), getPixelHeight(i), 1 };
blit.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
blit.dstSubresource.mipLevel = i;
blit.dstSubresource.baseArrayLayer = 0;
blit.dstSubresource.mipLevel = rootView.startMipmap + i;
blit.dstSubresource.baseArrayLayer = rootView.startLayer;
blit.dstSubresource.layerCount = static_cast<uint32_t>(layerCount);
vkCmdBlitImage(commandBuffer,
@@ -458,7 +513,7 @@ void Texture::generateMipmapsInternal()
1, &barrier);
}
barrier.subresourceRange.baseMipLevel = mipLevels - 1;
barrier.subresourceRange.baseMipLevel = rootView.startMipmap + mipLevels - 1;
barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
@@ -472,7 +527,7 @@ void Texture::generateMipmapsInternal()
1, &barrier);
}
void Texture::uploadByteData(PixelFormat pixelformat, const void *data, size_t size, int level, int slice, const Rect &r)
void Texture::uploadByteData(const void *data, size_t size, int level, int slice, const Rect &r)
{
VkBuffer stagingBuffer;
VmaAllocation vmaAllocation;
@@ -496,11 +551,11 @@ void Texture::uploadByteData(PixelFormat pixelformat, const void *data, size_t s
region.bufferRowLength = 0;
region.bufferImageHeight = 0;
uint32_t baseLayer;
if (getTextureType() == TEXTURE_VOLUME)
baseLayer = 0;
else
baseLayer = slice;
uint32_t baseLayer = rootView.startLayer;
if (getTextureType() != TEXTURE_VOLUME)
baseLayer += slice;
level += rootView.startMipmap;
region.imageSubresource.aspectMask = imageAspect;
region.imageSubresource.mipLevel = level;
@@ -520,7 +575,7 @@ void Texture::uploadByteData(PixelFormat pixelformat, const void *data, size_t s
if (imageLayout != VK_IMAGE_LAYOUT_GENERAL)
{
Vulkan::cmdTransitionImageLayout(commandBuffer, textureImage,
Vulkan::cmdTransitionImageLayout(commandBuffer, textureImage, format,
imageLayout, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
level, 1, baseLayer, 1);
@@ -533,7 +588,7 @@ void Texture::uploadByteData(PixelFormat pixelformat, const void *data, size_t s
&region
);
Vulkan::cmdTransitionImageLayout(commandBuffer, textureImage,
Vulkan::cmdTransitionImageLayout(commandBuffer, textureImage, format,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, imageLayout,
level, 1, baseLayer, 1);
}
@@ -558,8 +613,8 @@ void Texture::copyFromBuffer(graphics::Buffer *source, size_t sourceoffset, int
VkImageSubresourceLayers layers{};
layers.aspectMask = imageAspect;
layers.mipLevel = mipmap;
layers.baseArrayLayer = slice;
layers.mipLevel = mipmap + rootView.startMipmap;
layers.baseArrayLayer = slice + rootView.startLayer;
layers.layerCount = 1;
VkBufferImageCopy region{};
@@ -572,11 +627,11 @@ void Texture::copyFromBuffer(graphics::Buffer *source, size_t sourceoffset, int
if (imageLayout != VK_IMAGE_LAYOUT_GENERAL)
{
Vulkan::cmdTransitionImageLayout(commandBuffer, textureImage, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
Vulkan::cmdTransitionImageLayout(commandBuffer, textureImage, format, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
vkCmdCopyBufferToImage(commandBuffer, (VkBuffer)source->getHandle(), textureImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &region);
Vulkan::cmdTransitionImageLayout(commandBuffer, textureImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
Vulkan::cmdTransitionImageLayout(commandBuffer, textureImage, format, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
}
else
vkCmdCopyBufferToImage(commandBuffer, (VkBuffer)source->getHandle(), textureImage, VK_IMAGE_LAYOUT_GENERAL, 1, &region);
@@ -588,8 +643,8 @@ void Texture::copyToBuffer(graphics::Buffer *dest, int slice, int mipmap, const
VkImageSubresourceLayers layers{};
layers.aspectMask = imageAspect;
layers.mipLevel = mipmap;
layers.baseArrayLayer = slice;
layers.mipLevel = mipmap + rootView.startMipmap;
layers.baseArrayLayer = slice + rootView.startLayer;
layers.layerCount = 1;
VkBufferImageCopy region{};
@@ -603,11 +658,11 @@ void Texture::copyToBuffer(graphics::Buffer *dest, int slice, int mipmap, const
if (imageLayout != VK_IMAGE_LAYOUT_GENERAL)
{
Vulkan::cmdTransitionImageLayout(commandBuffer, textureImage, imageLayout, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);
Vulkan::cmdTransitionImageLayout(commandBuffer, textureImage, format, imageLayout, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);
vkCmdCopyImageToBuffer(commandBuffer, textureImage, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, (VkBuffer) dest->getHandle(), 1, &region);
Vulkan::cmdTransitionImageLayout(commandBuffer, textureImage, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, imageLayout);
Vulkan::cmdTransitionImageLayout(commandBuffer, textureImage, format, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, imageLayout);
}
else
vkCmdCopyImageToBuffer(commandBuffer, textureImage, VK_IMAGE_LAYOUT_GENERAL, (VkBuffer)dest->getHandle(), 1, &region);
+9 -7
View File
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2006-2023 LOVE Development Team
* Copyright (c) 2006-2024 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
@@ -40,11 +40,13 @@ class Texture final
, public Volatile
{
public:
Texture(love::graphics::Graphics *gfx, const Settings &settings, const Slices *data);
~Texture();
virtual bool loadVolatile() override;
virtual void unloadVolatile() override;
Texture(love::graphics::Graphics *gfx, const Settings &settings, const Slices *data);
Texture(love::graphics::Graphics *gfx, love::graphics::Texture *base, const Texture::ViewSettings &viewsettings);
virtual ~Texture();
bool loadVolatile() override;
void unloadVolatile() override;
void setSamplerState(const SamplerState &s) override;
@@ -59,9 +61,9 @@ public:
VkImageView getRenderTargetView(int mip, int layer);
VkSampleCountFlagBits getMsaaSamples() const;
void uploadByteData(PixelFormat pixelformat, const void *data, size_t size, int level, int slice, const Rect &r) override;
void uploadByteData(const void *data, size_t size, int level, int slice, const Rect &r) override;
void generateMipmapsInternal() override;
void generateMipmapsInternal() override;
int getMSAA() const override;
ptrdiff_t getHandle() const override;
+15 -11
View File
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2006-2023 LOVE Development Team
* Copyright (c) 2006-2024 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
@@ -291,6 +291,9 @@ TextureFormat Vulkan::getTextureFormat(PixelFormat format)
textureFormat.internalFormatRepresentation = FORMATREPRESENTATION_UINT;
break;
case PIXELFORMAT_DEPTH24_UNORM:
textureFormat.internalFormat = VK_FORMAT_X8_D24_UNORM_PACK32;
textureFormat.internalFormatRepresentation = FORMATREPRESENTATION_UINT;
break;
case PIXELFORMAT_DEPTH24_UNORM_STENCIL8:
textureFormat.internalFormat = VK_FORMAT_D24_UNORM_S8_UINT;
textureFormat.internalFormatRepresentation = FORMATREPRESENTATION_UINT;
@@ -816,7 +819,7 @@ VkIndexType Vulkan::getVulkanIndexBufferType(IndexDataType type)
}
}
static void setImageLayoutTransitionOptions(bool previous, VkImageLayout layout, VkAccessFlags &accessMask, VkPipelineStageFlags &stageFlags, bool &depthStencil)
static void setImageLayoutTransitionOptions(bool previous, VkImageLayout layout, VkAccessFlags &accessMask, VkPipelineStageFlags &stageFlags)
{
switch (layout)
{
@@ -838,7 +841,6 @@ static void setImageLayoutTransitionOptions(bool previous, VkImageLayout layout,
stageFlags = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
break;
case VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL:
depthStencil = true;
accessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT;
stageFlags = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
break;
@@ -863,7 +865,7 @@ static void setImageLayoutTransitionOptions(bool previous, VkImageLayout layout,
}
}
void Vulkan::cmdTransitionImageLayout(VkCommandBuffer commandBuffer, VkImage image, VkImageLayout oldLayout, VkImageLayout newLayout, uint32_t baseLevel, uint32_t levelCount, uint32_t baseLayer, uint32_t layerCount)
void Vulkan::cmdTransitionImageLayout(VkCommandBuffer commandBuffer, VkImage image, PixelFormat format, VkImageLayout oldLayout, VkImageLayout newLayout, uint32_t baseLevel, uint32_t levelCount, uint32_t baseLayer, uint32_t layerCount)
{
VkImageMemoryBarrier barrier{};
barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
@@ -877,17 +879,19 @@ void Vulkan::cmdTransitionImageLayout(VkCommandBuffer commandBuffer, VkImage ima
barrier.subresourceRange.baseArrayLayer = baseLayer;
barrier.subresourceRange.layerCount = layerCount;
bool depthStencil = false;
VkPipelineStageFlags sourceStage;
VkPipelineStageFlags destinationStage;
setImageLayoutTransitionOptions(true, oldLayout, barrier.srcAccessMask, sourceStage, depthStencil);
setImageLayoutTransitionOptions(false, newLayout, barrier.dstAccessMask, destinationStage, depthStencil);
setImageLayoutTransitionOptions(true, oldLayout, barrier.srcAccessMask, sourceStage);
setImageLayoutTransitionOptions(false, newLayout, barrier.dstAccessMask, destinationStage);
if (depthStencil)
barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT;
else
barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
const PixelFormatInfo &info = getPixelFormatInfo(format);
if (info.color)
barrier.subresourceRange.aspectMask |= VK_IMAGE_ASPECT_COLOR_BIT;
if (info.depth)
barrier.subresourceRange.aspectMask |= VK_IMAGE_ASPECT_DEPTH_BIT;
if (info.stencil)
barrier.subresourceRange.aspectMask |= VK_IMAGE_ASPECT_STENCIL_BIT;
vkCmdPipelineBarrier(
commandBuffer,
+2 -2
View File
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2006-2023 LOVE Development Team
* Copyright (c) 2006-2024 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
@@ -81,7 +81,7 @@ public:
static VkIndexType getVulkanIndexBufferType(IndexDataType type);
static void cmdTransitionImageLayout(
VkCommandBuffer, VkImage, VkImageLayout oldLayout, VkImageLayout newLayout,
VkCommandBuffer, VkImage, PixelFormat format, VkImageLayout oldLayout, VkImageLayout newLayout,
uint32_t baseLevel = 0, uint32_t levelCount = VK_REMAINING_MIP_LEVELS, uint32_t baseLayer = 0, uint32_t layerCount = VK_REMAINING_ARRAY_LAYERS);
};
+1 -1
View File
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2006-2023 LOVE Development Team
* Copyright (c) 2006-2024 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
+1 -1
View File
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2006-2023 LOVE Development Team
* Copyright (c) 2006-2024 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
+1 -1
View File
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2006-2023 LOVE Development Team
* Copyright (c) 2006-2024 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
+1 -1
View File
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2006-2023 LOVE Development Team
* Copyright (c) 2006-2024 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
+1 -1
View File
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2006-2023 LOVE Development Team
* Copyright (c) 2006-2024 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
+116 -25
View File
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2006-2023 LOVE Development Team
* Copyright (c) 2006-2024 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
@@ -799,6 +799,13 @@ static void luax_checktexturesettings(lua_State *L, int idx, bool opt, bool chec
if (s.type == TEXTURE_2D_ARRAY || s.type == TEXTURE_VOLUME)
s.layers = luax_checkintflag(L, idx, Texture::getConstant(Texture::SETTING_LAYERS));
}
else
{
s.width = luax_intflag(L, idx, Texture::getConstant(Texture::SETTING_WIDTH), s.width);
s.height = luax_intflag(L, idx, Texture::getConstant(Texture::SETTING_HEIGHT), s.height);
if (s.type == TEXTURE_2D_ARRAY || s.type == TEXTURE_VOLUME)
s.layers = luax_intflag(L, idx, Texture::getConstant(Texture::SETTING_LAYERS), s.layers);
}
lua_getfield(L, idx, Texture::getConstant(Texture::SETTING_MIPMAPS));
if (!lua_isnoneornil(L, -1))
@@ -824,6 +831,25 @@ static void luax_checktexturesettings(lua_State *L, int idx, bool opt, bool chec
s.computeWrite = luax_boolflag(L, idx, Texture::getConstant(Texture::SETTING_COMPUTE_WRITE), s.computeWrite);
lua_getfield(L, idx, Texture::getConstant(Texture::SETTING_VIEW_FORMATS));
if (!lua_isnoneornil(L, -1))
{
if (lua_type(L, -1) != LUA_TTABLE)
luaL_argerror(L, idx, "expected field 'viewformats' to be a table type");
for (int i = 1; i <= luax_objlen(L, -1); i++)
{
lua_rawgeti(L, -1, i);
const char *str = luaL_checkstring(L, -1);
PixelFormat viewformat = PIXELFORMAT_UNKNOWN;
if (!getConstant(str, viewformat))
luax_enumerror(L, "pixel format", str);
s.viewFormats.push_back(viewformat);
lua_pop(L, 1);
}
}
lua_pop(L, 1);
lua_getfield(L, idx, Texture::getConstant(Texture::SETTING_READABLE));
if (!lua_isnoneornil(L, -1))
s.readable.set(luax_checkboolean(L, -1));
@@ -1240,6 +1266,66 @@ int w_newVolumeImage(lua_State *L)
return w_newVolumeTexture(L);
}
int w_newTextureView(lua_State *L)
{
Texture *base = luax_checktexture(L, 1);
luaL_checktype(L, 2, LUA_TTABLE);
Texture::ViewSettings settings;
lua_getfield(L, 2, "format");
if (!lua_isnoneornil(L, -1))
{
const char *str = luaL_checkstring(L, -1);
if (!getConstant(str, settings.format.value))
luax_enumerror(L, "pixel format", str);
settings.format.hasValue = true;
}
lua_pop(L, 1);
lua_getfield(L, 2, "type");
if (!lua_isnoneornil(L, -1))
{
const char *str = luaL_checkstring(L, -1);
if (!Texture::getConstant(str, settings.type.value))
luax_enumerror(L, "texture type", Texture::getConstants(settings.type.value), str);
settings.type.hasValue = true;
}
lua_pop(L, 1);
lua_getfield(L, 2, "mipmapstart");
if (!lua_isnoneornil(L, -1))
settings.mipmapStart.set(luaL_checkint(L, -1) - 1);
lua_pop(L, 1);
lua_getfield(L, 2, "mipmapcount");
if (!lua_isnoneornil(L, -1))
settings.mipmapCount.set(luaL_checkint(L, -1));
lua_pop(L, 1);
lua_getfield(L, 2, "layerstart");
if (!lua_isnoneornil(L, -1))
settings.layerStart.set(luaL_checkint(L, -1) - 1);
lua_pop(L, 1);
lua_getfield(L, 2, "layers");
if (!lua_isnoneornil(L, -1))
settings.layerCount.set(luaL_checkint(L, -1));
lua_pop(L, 1);
lua_getfield(L, 2, "debugname");
if (!lua_isnoneornil(L, -1))
settings.debugName = luaL_checkstring(L, -1);
lua_pop(L, 1);
Texture *t = nullptr;
luax_catchexcept(L, [&]() { t = instance()->newTextureView(base, settings); });
luax_pushtype(L, t);
t->release();
return 1;
}
int w_newQuad(lua_State *L)
{
luax_checkgraphicscreated(L);
@@ -3052,13 +3138,12 @@ int w_draw(lua_State *L)
{
Drawable *drawable = nullptr;
Texture *texture = nullptr;
Quad *quad = nullptr;
Quad *quad = luax_totype<Quad>(L, 2);
int startidx = 2;
if (luax_istype(L, 2, Quad::type))
if (quad != nullptr)
{
texture = luax_checktexture(L, 1);
quad = luax_totype<Quad>(L, 2);
startidx = 3;
}
else if (lua_isnil(L, 2) && !lua_isnoneornil(L, 3))
@@ -3088,14 +3173,14 @@ int w_draw(lua_State *L)
int w_drawLayer(lua_State *L)
{
Texture *texture = luax_checktexture(L, 1);
Quad *quad = nullptr;
int layer = (int) luaL_checkinteger(L, 2) - 1;
int startidx = 3;
if (luax_istype(L, startidx, Quad::type))
int startidx = 3;
Quad *quad = luax_totype<Quad>(L, startidx);
if (quad != nullptr)
{
texture = luax_checktexture(L, 1);
quad = luax_totype<Quad>(L, startidx);
startidx++;
}
else if (lua_isnil(L, startidx) && !lua_isnoneornil(L, startidx + 1))
@@ -3815,25 +3900,31 @@ int w_inverseTransformPoint(lua_State *L)
return 2;
}
int w_setOrthoProjection(lua_State *L)
int w_setCustomProjection(lua_State *L)
{
float w = (float) luaL_checknumber(L, 1);
float h = (float) luaL_checknumber(L, 2);
float near = (float) luaL_optnumber(L, 3, -10.0);
float far = (float) luaL_optnumber(L, 4, 10.0);
math::Transform *transform = luax_totype<math::Transform>(L, 1);
if (transform != nullptr)
{
instance()->setCustomProjection(transform->getMatrix());
return 0;
}
luax_catchexcept(L, [&]() { instance()->setOrthoProjection(w, h, near, far); });
return 0;
}
math::Transform::MatrixLayout layout = math::Transform::MATRIX_ROW_MAJOR;
int w_setPerspectiveProjection(lua_State *L)
{
float verticalfov = (float) luaL_checknumber(L, 1);
float aspect = (float) luaL_checknumber(L, 2);
float near = (float) luaL_checknumber(L, 3);
float far = (float) luaL_checknumber(L, 4);
int idx = 1;
if (lua_type(L, idx) == LUA_TSTRING)
{
const char* layoutstr = lua_tostring(L, idx);
if (!math::Transform::getConstant(layoutstr, layout))
return luax_enumerror(L, "matrix layout", math::Transform::getConstants(layout), layoutstr);
luax_catchexcept(L, [&]() { instance()->setPerspectiveProjection(verticalfov, aspect, near, far); });
idx++;
}
float elements[16];
love::math::luax_checkmatrix(L, idx, layout, elements);
instance()->setCustomProjection(Matrix4(elements));
return 0;
}
@@ -3857,6 +3948,7 @@ static const luaL_Reg functions[] =
{ "newCubeTexture", w_newCubeTexture },
{ "newArrayTexture", w_newArrayTexture },
{ "newVolumeTexture", w_newVolumeTexture },
{ "newTextureView", w_newTextureView },
{ "newQuad", w_newQuad },
{ "newFont", w_newFont },
{ "newImageFont", w_newImageFont },
@@ -3988,8 +4080,7 @@ static const luaL_Reg functions[] =
{ "transformPoint", w_transformPoint },
{ "inverseTransformPoint", w_inverseTransformPoint },
{ "setOrthoProjection", w_setOrthoProjection },
{ "setPerspectiveProjection", w_setPerspectiveProjection },
{ "setCustomProjection", w_setCustomProjection },
{ "resetProjection", w_resetProjection },
// Deprecated
+1 -1
View File
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2006-2023 LOVE Development Team
* Copyright (c) 2006-2024 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
+1 -1
View File
@@ -3,7 +3,7 @@ R"luastring"--(
-- There is a matching delimiter at the bottom of the file.
--[[
Copyright (c) 2006-2023 LOVE Development Team
Copyright (c) 2006-2024 LOVE Development Team
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2006-2023 LOVE Development Team
* Copyright (c) 2006-2024 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages

Some files were not shown because too many files have changed in this diff Show More