Skip to content

Big memory leak problem #129

@st0rmbtw

Description

@st0rmbtw

On macOS with the Metal renderer, memory leaks every frame.
In 3 minutes memory usage of a simple app reached 6 GB.

image

Minimal repro:

quad.metal:

#include <metal_stdlib>
#include <simd/simd.h>

using namespace metal;

struct VertexIn
{
    float2 position [[attribute(0)]];
    float2 uv       [[attribute(1)]];
};

struct VertexOut
{
    float4 position [[position]];
    float2 uv;
};

vertex VertexOut VS(VertexIn inp [[stage_in]])
{
    VertexOut outp;

    outp.position = float4(inp.position, 0.0, 1.0);
    outp.uv    = inp.uv;

    return outp;
}

fragment float4 PS(VertexOut inp [[stage_in]])
{
    return float4(inp.uv, 1.0, 1.0);
}

main.mm:

#include <stdio.h>
#include <LLGL/LLGL.h>
#include <LLGL/Platform/NativeHandle.h>
#include <LLGL/Utils/VertexFormat.h>
#include <LLGL/Utils/TypeNames.h>
#include <memory>

#define GLFW_EXPOSE_NATIVE_COCOA

#include <GLFW/glfw3.h>
#include <GLFW/glfw3native.h>
#include <LLGL/LLGL.h>

class CustomSurface : public LLGL::Surface {
public:
    CustomSurface(GLFWwindow *window, const LLGL::Extent2D& size) : m_size(size), m_wnd(window) {}
    ~CustomSurface();

    bool GetNativeHandle(void* nativeHandle, std::size_t nativeHandleSize) override;
    LLGL::Extent2D GetContentSize() const override { return m_size; };
    bool AdaptForVideoMode(LLGL::Extent2D* resolution, bool* fullscreen) override;
    void ResetPixelFormat() override {};
    LLGL::Display* FindResidentDisplay() const override { return LLGL::Display::GetPrimary(); };

    bool ProcessEvents();
private:
    LLGL::Extent2D m_size;
    GLFWwindow* m_wnd = nullptr;
};

CustomSurface::~CustomSurface() {
    glfwDestroyWindow(m_wnd);
}

bool CustomSurface::GetNativeHandle(void* nativeHandle, std::size_t nativeHandleSize) {
    auto handle = reinterpret_cast<LLGL::NativeHandle*>(nativeHandle);
    handle->responder = glfwGetCocoaWindow(m_wnd);
    return true;
}

bool CustomSurface::AdaptForVideoMode(LLGL::Extent2D *resolution, bool *fullscreen) {
    m_size = *resolution;
    glfwSetWindowSize(m_wnd, m_size.width, m_size.height);
    return true;
}

bool CustomSurface::ProcessEvents() {
    glfwPollEvents();
    return !glfwWindowShouldClose(m_wnd);
}

int main(void) {
    LLGL::Log::RegisterCallbackStd();

    if (!glfwInit()) return -1;

    GLFWwindow *window = glfwCreateWindow(1280, 720, "AAA", nullptr, nullptr);

    LLGL::Report report;
    auto context = LLGL::RenderSystem::Load("Metal", &report);

    const LLGL::Display* display = LLGL::Display::GetPrimary();
    const std::uint32_t resScale = (display != nullptr ? static_cast<std::uint32_t>(display->GetScale()) : 1u);

    const auto resolution = LLGL::Extent2D(1280 * resScale, 720 * resScale);

    LLGL::SwapChainDescriptor swapChainDesc;
    swapChainDesc.resolution = resolution;

    auto surface = std::make_shared<CustomSurface>(window, resolution);

    auto swapChain = context->CreateSwapChain(swapChainDesc, surface);
    swapChain->SetVsyncInterval(0);

    auto commands = context->CreateCommandBuffer(LLGL::CommandBufferFlags::ImmediateSubmit);

    const auto& info = context->GetRendererInfo();

    float vertices[] = {
        -0.5f, -0.5f, 0.0f, 0.0f,
        0.5f,  -0.5f, 1.0f, 0.0f,
        -0.5f, 0.5f,  0.0f, 1.0f,
        0.5f,  0.5f,  1.0f, 1.0f,
    };

    LLGL::VertexFormat vertexFormat;
    vertexFormat.AppendAttribute({"a_position", LLGL::Format::RG32Float});
    vertexFormat.AppendAttribute({"a_uv", LLGL::Format::RG32Float});
    vertexFormat.SetStride(sizeof(float) * 4);

    LLGL::BufferDescriptor vertexBufferDesc;
    vertexBufferDesc.size = sizeof(vertices);
    vertexBufferDesc.bindFlags = LLGL::BindFlags::VertexBuffer;
    vertexBufferDesc.vertexAttribs = vertexFormat.attributes;

    LLGL::Buffer* vertexBuffer = context->CreateBuffer(vertexBufferDesc, vertices);

    LLGL::ShaderDescriptor vertexShaderDesc, fragmentShaderDesc;
    vertexShaderDesc = { LLGL::ShaderType::Vertex,   "assets/shaders/quad.metal", "VS", "1.1" };
    fragmentShaderDesc = { LLGL::ShaderType::Fragment, "assets/shaders/quad.metal", "PS", "1.1" };

    vertexShaderDesc.vertex.inputAttribs = vertexFormat.attributes;

    LLGL::Shader* vertexShader = context->CreateShader(vertexShaderDesc);
    LLGL::Shader* fragmentShader = context->CreateShader(fragmentShaderDesc);

    LLGL::PipelineLayoutDescriptor pipelineLayoutDesc;

    LLGL::PipelineLayout* pipelineLayout = context->CreatePipelineLayout(pipelineLayoutDesc);

    LLGL::GraphicsPipelineDescriptor pipelineDesc;
    pipelineDesc.vertexShader = vertexShader;
    pipelineDesc.fragmentShader = fragmentShader;
    pipelineDesc.pipelineLayout = pipelineLayout;
    pipelineDesc.indexFormat = LLGL::Format::R32UInt;
    pipelineDesc.primitiveTopology = LLGL::PrimitiveTopology::TriangleStrip;
    pipelineDesc.renderPass = swapChain->GetRenderPass();

    LLGL::PipelineState* pipelineState = context->CreatePipelineState(pipelineDesc);
    if (const LLGL::Report* report = pipelineState->GetReport()) {
        if (report->HasErrors()) LLGL::Log::Errorf("%s", report->GetText());
        return -1;
    }

    double prevTick = glfwGetTime();
    
    while (surface->ProcessEvents()) {
        double currentTick = glfwGetTime();
        const double deltaTime = (currentTick - prevTick);
        prevTick = currentTick;

        commands->Begin();
        {
            commands->SetVertexBuffer(*vertexBuffer);

            commands->BeginRenderPass(*swapChain);
            {
                commands->Clear(LLGL::ClearFlags::Color, LLGL::ClearValue(0.0f, 0.0f, 0.0f, 1.0f));
                commands->SetViewport(swapChain->GetResolution());
                commands->SetPipelineState(*pipelineState);

                commands->Draw(4, 0);
            }
            commands->EndRenderPass();
        }
        commands->End();

        swapChain->Present();
    }

    return 0;
}

Metadata

Metadata

Assignees

Labels

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions