Skip to content

Hello Triangle MSAA

This example showcases the minimal changes needed to enable multisample anti-aliasing. Read the Hello Triangle example to see the differences.

The first place to look is createRenderTarget, where the MSAA texture and view are initialized. This function is called on initialization and on window resize.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
void HelloTriangleMSAA::createRenderTarget()
{
    // Reset depthTextureView as depthStencilAttachment view as it might
    // have been recreated following a resize
    m_commandRecorderOptions.depthStencilAttachment.view = m_depthTextureView;

    const TextureOptions options = {
        .type = TextureType::TextureType2D,
        .format = m_swapchainFormat,
        .extent = { .width = m_swapchainExtent.width, .height = m_swapchainExtent.height, .depth = 1 },
        .mipLevels = 1,
        .samples = m_samples.get(),
        .usage = TextureUsageFlagBits::ColorAttachmentBit,
        .memoryUsage = MemoryUsage::GpuOnly,
        .initialLayout = TextureLayout::Undefined
    };
    m_msaaTexture = m_device.createTexture(options);
    m_msaaTextureView = m_msaaTexture.createView();

    if (isMsaaEnabled())
        m_commandRecorderOptions.colorAttachments[0].view = m_msaaTextureView;
}

bool HelloTriangleMSAA::isMsaaEnabled() const
{
    return m_samples.get() != SampleCountFlagBits::Samples1Bit;
}

void HelloTriangleMSAA::setMsaaSampleCount(SampleCountFlagBits samples)
{
    if (samples == m_samples.get())
        return;

    // get new pipeline
    for (size_t i = 0; i < m_supportedSampleCounts.size(); ++i) {
        if (m_supportedSampleCounts[i] == samples) {
            m_currentPipelineIndex = i;
            break;
        }
    }

    // the ExampleEngineLayer will recreate the depth view when we do this
    m_samples = samples;

    // we must also refresh the view(s) we handle, and reattach them
    createRenderTarget();

    // update the samples option that will configure the render pass
    m_commandRecorderOptions.samples = samples;
}

void HelloTriangleMSAA::drawMsaaSettings(ImGuiContext *ctx)
{
    constexpr ImVec2 winOffset(200, 150);
    constexpr ImVec2 buttonSize(120, 40);
    constexpr size_t maxMessageLen = 40;

    ImGui::SetCurrentContext(ctx);
    ImGui::SetNextWindowPos(ImVec2((float)m_window->width() - winOffset.x, winOffset.y));
    ImGui::SetNextWindowSize(ImVec2(0, 0), ImGuiCond_FirstUseEver);
    ImGui::Begin(
            "Controls",
            nullptr,
            ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize);

    auto getButtonLabel = [](SampleCountFlagBits samples) -> const char * {
        switch (samples) {
        case SampleCountFlagBits::Samples1Bit:
            return "No MSAA";
        case SampleCountFlagBits::Samples2Bit:
            return "2x MSAA";
        case SampleCountFlagBits::Samples4Bit:
            return "4x MSAA";
        case SampleCountFlagBits::Samples8Bit:
            return "8x MSAA";
        case SampleCountFlagBits::Samples16Bit:
            return "16x MSAA";
        case SampleCountFlagBits::Samples32Bit:
            return "32x MSAA";
        case SampleCountFlagBits::Samples64Bit:
            return "64x MSAA";
        default:
            return "Unknown";
        }
    };

    int selectedIndex = m_requestedSampleCountIndex;
    for (int i = 0; i < m_supportedSampleCounts.size(); ++i) {
        ImGui::RadioButton(getButtonLabel(m_supportedSampleCounts[i]), &selectedIndex, i);
    }

    // so we can deal with it in updateScene
    m_requestedSampleCountIndex = selectedIndex;

    ImGui::End();
}

Filename: hello_triangle_msaa/hello_triangle_msaa.cpp

This creates a texture and a view with the correct multisampling configuration and new dimensions, which we will attach to the render pass option struct (KDGpu::RenderPassCommandRecorderOptions). This texture is able to hold more information (samples) per texel. Additionally, we need to configure the render pass option struct to multisample, passing in the same number of samples we did for the texture above.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
    m_commandRecorderOptions = {
        .colorAttachments = {
            {
                .view = m_msaaTextureView, // The multisampled view which will change on resize.
                .resolveView = {}, // Not setting the swapchain texture view just yet. That's handled at render.
                .clearValue = { 0.3f, 0.3f, 0.3f, 1.0f },
                .finalLayout = TextureLayout::PresentSrc
            }
        },
        .depthStencilAttachment = {
            .view = m_depthTextureView,
        },
        // configure for multisampling
        .samples = m_samples.get()
    };

Filename: hello_triangle_msaa/hello_triangle_msaa.cpp

Notice that the render pass options contains two separate entries: one for view and one for resolveView. The view is set to the multisample texture, which has extra sampling information. During an MSAA pass, though, the multisampled texture needs to have its extra information "resolved" into a final, regular texture: the resolveView.

Also configure the graphics pipeline (KDGpu::GraphicsPipelineOptions) to multisample:

1
2
3
        .multisample = {
            .samples = samples
        }

Filename: hello_triangle_msaa/hello_triangle_msaa.cpp

Lastly, on each frame, update the resolveView with the swapchain view that we want to render to.

1
2
        // When using MSAA, we update the resolveView instead of the view
        m_commandRecorderOptions.colorAttachments[0].resolveView = m_swapchainViews.at(m_currentSwapchainImageIndex);

Filename: hello_triangle_msaa/hello_triangle_msaa.cpp


Updated on 2024-08-28 at 00:05:10 +0000