blob: 6b64af12d2e9db9e984c4d3ababe72d50657f863 [file] [edit]
/*
* Copyright 2026 Google LLC
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "include/gpu/graphite/Context.h"
#include "include/gpu/graphite/Recorder.h"
#include "src/gpu/graphite/sparse_strips/AlphaAtlasManager.h"
#include "src/gpu/graphite/sparse_strips/SparseStripsConfig.h"
#include "tests/CtsEnforcement.h"
#include "tests/Test.h"
namespace skgpu::graphite {
DEF_GRAPHITE_TEST_FOR_RENDERING_CONTEXTS(SparseStrips_AtlasDimensionsAndStride,
reporter,
context,
CtsEnforcement::kToBeDetermined) {
auto recorder = context->makeRecorder();
AlphaAtlasManager atlasManager(recorder.get());
// Initial allocation (e.g. 512 bytes = 8 tiles of 8x8)
REPORTER_ASSERT(reporter, atlasManager.requestAlphaSpace(512) != nullptr);
{
auto alloc = atlasManager.finalizeRun();
REPORTER_ASSERT(reporter, alloc.has_value());
auto [alphaIndex, texPage] = *alloc;
REPORTER_ASSERT(reporter, alphaIndex == 0);
REPORTER_ASSERT(reporter, texPage == 0);
}
REPORTER_ASSERT(reporter, atlasManager.numPages() == 1);
// Initial page dimensions: 8192 x 1
sk_sp<TextureProxy> proxy = atlasManager.getPageProxy(0);
REPORTER_ASSERT(reporter, proxy != nullptr);
REPORTER_ASSERT(reporter, proxy->dimensions().width() == SparseStripConfig::kAtlasWidth);
REPORTER_ASSERT(reporter, proxy->dimensions().height() == 1);
// Verify row byte stride alignment (32,768 bytes/row = 8192 pixels * 4 bytes/pixel)
int32_t strideBytes = SparseStripConfig::kAtlasWidthBytes;
REPORTER_ASSERT(reporter, strideBytes == 32768);
// Subsequent allocation on same row/page
REPORTER_ASSERT(reporter, atlasManager.requestAlphaSpace(1024) != nullptr);
{
auto alloc = atlasManager.finalizeRun();
REPORTER_ASSERT(reporter, alloc.has_value());
auto [alphaIndex, texPage] = *alloc;
REPORTER_ASSERT(reporter, alphaIndex == 512);
REPORTER_ASSERT(reporter, texPage == 0);
}
REPORTER_ASSERT(reporter, atlasManager.numPages() == 1);
}
DEF_GRAPHITE_TEST_FOR_RENDERING_CONTEXTS(SparseStrips_AtlasDynamicGrowthAndDoubling,
reporter,
context,
CtsEnforcement::kToBeDetermined) {
auto recorder = context->makeRecorder();
AlphaAtlasManager atlasManager(recorder.get());
// Page 0: allocate exactly 1 row (32,768 bytes)
REPORTER_ASSERT(reporter,
atlasManager.requestAlphaSpace(SparseStripConfig::kAtlasWidthBytes) != nullptr);
{
auto alloc = atlasManager.finalizeRun();
REPORTER_ASSERT(reporter, alloc.has_value());
auto [alphaIndex, texPage] = *alloc;
REPORTER_ASSERT(reporter, texPage == 0);
}
REPORTER_ASSERT(reporter, atlasManager.getPageProxy(0)->dimensions().height() == 1);
// Page 1: allocating 1 more tile exceeds Page 0, triggers doubling -> 2 rows (65,536 bytes)
REPORTER_ASSERT(reporter, atlasManager.requestAlphaSpace(64) != nullptr);
{
auto alloc = atlasManager.finalizeRun();
REPORTER_ASSERT(reporter, alloc.has_value());
auto [alphaIndex, texPage] = *alloc;
REPORTER_ASSERT(reporter, alphaIndex == 0); // Atomic on new page, starts at 0
REPORTER_ASSERT(reporter, texPage == 1);
}
REPORTER_ASSERT(reporter, atlasManager.numPages() == 2);
REPORTER_ASSERT(reporter, atlasManager.getPageProxy(1)->dimensions().height() == 2);
}
DEF_GRAPHITE_TEST_FOR_RENDERING_CONTEXTS(SparseStrips_AtlasNullBufferAndNullCaps,
reporter,
context,
CtsEnforcement::kToBeDetermined) {
auto recorder = context->makeRecorder();
AlphaAtlasManager atlasManager(recorder.get());
// Page 0: 1 row
REPORTER_ASSERT(reporter,
atlasManager.requestAlphaSpace(SparseStripConfig::kAtlasWidthBytes) != nullptr);
{
auto alloc = atlasManager.finalizeRun();
REPORTER_ASSERT(reporter, alloc.has_value());
auto [alphaIndex, texPage] = *alloc;
REPORTER_ASSERT(reporter, texPage == 0);
}
REPORTER_ASSERT(reporter, atlasManager.numPages() == 1);
// Page 1: 2 rows
REPORTER_ASSERT(
reporter,
atlasManager.requestAlphaSpace(SparseStripConfig::kAtlasWidthBytes * 2) != nullptr);
{
auto alloc = atlasManager.finalizeRun();
REPORTER_ASSERT(reporter, alloc.has_value());
auto [alphaIndex, texPage] = *alloc;
REPORTER_ASSERT(reporter, texPage == 1);
}
REPORTER_ASSERT(reporter, atlasManager.numPages() == 2);
// Verify EndCaps recording of NullCaps
EndCaps ends;
ends.addCap(0, 0, 16, 0, 0);
REPORTER_ASSERT(reporter, !ends.hasNullCaps());
REPORTER_ASSERT(reporter, ends.firstNullCapIndex() == -1);
ends.addCap(16, 0, 16, 0, 1);
REPORTER_ASSERT(reporter, !ends.hasNullCaps());
REPORTER_ASSERT(reporter, ends.firstNullCapIndex() == -1);
// Exceeding Page 1: NullCaps handling allows MakeStrips to run to completion!
// Transitions to the 3rd nullbuffer (kNullSlot).
uint8_t* nullPtr = atlasManager.requestAlphaSpace(64);
REPORTER_ASSERT(reporter, nullPtr != nullptr);
nullPtr[0] = 0xAB;
auto alloc3 = atlasManager.finalizeRun();
REPORTER_ASSERT(reporter, alloc3.has_value());
REPORTER_ASSERT(reporter, alloc3->second == AlphaAtlasManager::kNullSlot);
REPORTER_ASSERT(reporter, alloc3->first == 0);
REPORTER_ASSERT(reporter, atlasManager.hasNullBuffer());
REPORTER_ASSERT(reporter, atlasManager.getPageProxy(alloc3->second) == nullptr);
REPORTER_ASSERT(reporter, atlasManager.getPageData(alloc3->second)[0] == 0xAB);
REPORTER_ASSERT(reporter, atlasManager.numPages() == 2);
if (alloc3->second == AlphaAtlasManager::kNullSlot) {
ends.markFirstNullCap();
}
ends.addCap(32, 0, 16, alloc3->first, alloc3->second);
REPORTER_ASSERT(reporter, ends.hasNullCaps());
REPORTER_ASSERT(reporter, ends.firstNullCapIndex() == 2);
// Subsequent nullcap does not overwrite firstNullCapIndex
uint8_t* nullPtr2 = atlasManager.requestAlphaSpace(32);
REPORTER_ASSERT(reporter, nullPtr2 != nullptr);
auto alloc4 = atlasManager.finalizeRun();
REPORTER_ASSERT(reporter, alloc4.has_value());
if (alloc4->second == AlphaAtlasManager::kNullSlot) {
ends.markFirstNullCap();
}
ends.addCap(48, 0, 16, alloc4->first, alloc4->second);
REPORTER_ASSERT(reporter, ends.hasNullCaps());
REPORTER_ASSERT(reporter, ends.firstNullCapIndex() == 2);
// Flush: recordUploads retires older slot 0
atlasManager.recordUploads(nullptr);
REPORTER_ASSERT(reporter, atlasManager.numPages() == 1);
REPORTER_ASSERT(reporter, atlasManager.getPageProxy(0) == nullptr);
REPORTER_ASSERT(reporter, atlasManager.getPageProxy(1) != nullptr);
REPORTER_ASSERT(reporter, atlasManager.hasNullBuffer());
// Resolve nullcaps: migrates nullbuffer data to freed slot 0 and updates endcaps
bool resolved = atlasManager.resolveNullCaps(&ends);
REPORTER_ASSERT(reporter, resolved);
REPORTER_ASSERT(reporter, !ends.hasNullCaps());
REPORTER_ASSERT(reporter, ends.firstNullCapIndex() == -1);
REPORTER_ASSERT(reporter, !atlasManager.hasNullBuffer());
REPORTER_ASSERT(reporter, atlasManager.numPages() == 2);
REPORTER_ASSERT(reporter, atlasManager.getPageProxy(0) != nullptr);
// Endcaps 2 and 3 now point to slot 0 and have valid proxies
REPORTER_ASSERT(reporter, ends.caps()[2].fTexPage == 0);
REPORTER_ASSERT(reporter, ends.caps()[3].fTexPage == 0);
REPORTER_ASSERT(reporter, ends.proxies()[0] == atlasManager.getPageProxy(0));
REPORTER_ASSERT(reporter, ends.proxies()[1] == atlasManager.getPageProxy(1));
REPORTER_ASSERT(reporter, ends.drawStartIndex() == 2);
REPORTER_ASSERT(reporter, ends.drawEndIndex() == 4);
REPORTER_ASSERT(reporter, atlasManager.getPageData(0)[0] == 0xAB);
}
DEF_GRAPHITE_TEST_FOR_RENDERING_CONTEXTS(SparseStrips_AtlasStraddleToNullBuffer,
reporter,
context,
CtsEnforcement::kNever) {
auto recorder = context->makeRecorder();
AlphaAtlasManager atlasManager(recorder.get());
// Fill Page 0
REPORTER_ASSERT(reporter,
atlasManager.requestAlphaSpace(SparseStripConfig::kAtlasWidthBytes) != nullptr);
auto alloc0 = atlasManager.finalizeRun();
REPORTER_ASSERT(reporter, alloc0.has_value());
// Fill Page 1 almost full, leaving 64 bytes
int32_t page1Capacity = SparseStripConfig::kAtlasWidthBytes * 2;
REPORTER_ASSERT(reporter, atlasManager.requestAlphaSpace(page1Capacity - 64) != nullptr);
auto alloc1 = atlasManager.finalizeRun();
REPORTER_ASSERT(reporter, alloc1.has_value());
REPORTER_ASSERT(reporter, alloc1->second == 1);
// Now start a run that straddles past Page 1's capacity:
// Chunk 1: 64 bytes (fills Page 1)
uint8_t* p1 = atlasManager.requestAlphaSpace(64);
REPORTER_ASSERT(reporter, p1 != nullptr);
for (int i = 0; i < 64; ++i) {
p1[i] = static_cast<uint8_t>(i + 1);
}
// Chunk 2: 64 bytes (overruns Page 1)
uint8_t* p2 = atlasManager.requestAlphaSpace(64);
REPORTER_ASSERT(reporter, p2 != nullptr);
for (int i = 0; i < 64; ++i) {
p2[i] = static_cast<uint8_t>(i + 65);
}
// Finalize run: should memmove the entire 128 bytes to kNullSlot!
auto straddleAlloc = atlasManager.finalizeRun();
REPORTER_ASSERT(reporter, straddleAlloc.has_value());
REPORTER_ASSERT(reporter, straddleAlloc->second == AlphaAtlasManager::kNullSlot);
REPORTER_ASSERT(reporter, straddleAlloc->first == 0);
REPORTER_ASSERT(reporter, atlasManager.activeSlot() == AlphaAtlasManager::kNullSlot);
REPORTER_ASSERT(reporter, atlasManager.hasNullBuffer());
const uint8_t* nullData = atlasManager.getPageData(AlphaAtlasManager::kNullSlot);
REPORTER_ASSERT(reporter, nullData != nullptr);
for (int i = 0; i < 128; ++i) {
REPORTER_ASSERT(reporter, nullData[i] == static_cast<uint8_t>(i + 1));
}
}
DEF_GRAPHITE_TEST_FOR_RENDERING_CONTEXTS(SparseStrips_AtlasEndCapAtomicity,
reporter,
context,
CtsEnforcement::kToBeDetermined) {
auto recorder = context->makeRecorder();
AlphaAtlasManager atlasManager(recorder.get());
// Allocate almost all of Page 0, leaving 64 bytes
REPORTER_ASSERT(
reporter,
atlasManager.requestAlphaSpace(SparseStripConfig::kAtlasWidthBytes - 64) != nullptr);
{
auto alloc = atlasManager.finalizeRun();
REPORTER_ASSERT(reporter, alloc.has_value());
auto [alphaIndex, texPage] = *alloc;
REPORTER_ASSERT(reporter, texPage == 0);
REPORTER_ASSERT(reporter, alphaIndex == 0);
}
// Request 128 bytes (which cannot fit in the remaining 64 bytes of Page 0).
// The allocation must be atomic to Page 1: entire 128 bytes at offset 0 of Page 1.
REPORTER_ASSERT(reporter, atlasManager.requestAlphaSpace(128) != nullptr);
{
auto alloc = atlasManager.finalizeRun();
REPORTER_ASSERT(reporter, alloc.has_value());
auto [alphaIndex, texPage] = *alloc;
REPORTER_ASSERT(reporter, texPage == 1);
REPORTER_ASSERT(reporter, alphaIndex == 0);
}
REPORTER_ASSERT(reporter, atlasManager.numPages() == 2);
}
DEF_GRAPHITE_TEST_FOR_RENDERING_CONTEXTS(SparseStrips_AtlasFlipFlopAtFlush,
reporter,
context,
CtsEnforcement::kToBeDetermined) {
auto recorder = context->makeRecorder();
AlphaAtlasManager atlasManager(recorder.get());
// Slot 0: 1 row (32,768 bytes)
REPORTER_ASSERT(reporter,
atlasManager.requestAlphaSpace(SparseStripConfig::kAtlasWidthBytes) != nullptr);
{
auto alloc = atlasManager.finalizeRun();
REPORTER_ASSERT(reporter, alloc.has_value());
auto [alphaIndex, texPage] = *alloc;
REPORTER_ASSERT(reporter, texPage == 0);
}
REPORTER_ASSERT(reporter, atlasManager.activeSlot() == 0);
sk_sp<TextureProxy> initialSlot0 = atlasManager.getPageProxy(0);
REPORTER_ASSERT(reporter, atlasManager.numPages() == 1);
// Slot 1: 2 rows (65,536 bytes) via XOR flip-flop (0 ^ 1 = 1)
REPORTER_ASSERT(reporter, atlasManager.requestAlphaSpace(64) != nullptr);
{
auto alloc = atlasManager.finalizeRun();
REPORTER_ASSERT(reporter, alloc.has_value());
auto [alphaIndex, texPage] = *alloc;
REPORTER_ASSERT(reporter, texPage == 1);
}
REPORTER_ASSERT(reporter, atlasManager.activeSlot() == 1);
sk_sp<TextureProxy> initialSlot1 = atlasManager.getPageProxy(1);
REPORTER_ASSERT(reporter, atlasManager.numPages() == 2);
// Flush: recordUploads(nullptr) retires older slot 0 (1 ^ 1 = 0).
// Slot 1 stays right in place in slot 1 (no moving needed)!
atlasManager.recordUploads(nullptr);
REPORTER_ASSERT(reporter, atlasManager.numPages() == 1);
REPORTER_ASSERT(reporter, atlasManager.getPageProxy(0) == nullptr);
REPORTER_ASSERT(reporter, atlasManager.getPageProxy(1) == initialSlot1);
REPORTER_ASSERT(reporter, atlasManager.activeSlot() == 1);
// Continue allocating in current active slot (Slot 1):
REPORTER_ASSERT(reporter, atlasManager.requestAlphaSpace(64) != nullptr);
{
auto alloc = atlasManager.finalizeRun();
REPORTER_ASSERT(reporter, alloc.has_value());
auto [alphaIndex, texPage] = *alloc;
REPORTER_ASSERT(reporter, texPage == 1);
}
REPORTER_ASSERT(reporter, atlasManager.numPages() == 1);
// Fill Slot 1 and trigger flip-flop to Slot 0 (1 ^ 1 = 0) -> doubled to 4 rows!
REPORTER_ASSERT(
reporter,
atlasManager.requestAlphaSpace(SparseStripConfig::kAtlasWidthBytes * 2) != nullptr);
{
auto alloc = atlasManager.finalizeRun();
REPORTER_ASSERT(reporter, alloc.has_value());
auto [alphaIndex, texPage] = *alloc;
REPORTER_ASSERT(reporter, texPage == 0);
}
REPORTER_ASSERT(reporter, atlasManager.activeSlot() == 0);
REPORTER_ASSERT(reporter, atlasManager.numPages() == 2);
REPORTER_ASSERT(reporter, atlasManager.getPageProxy(0)->dimensions().height() == 4);
// Second flush: retires older slot 1 (0 ^ 1 = 1). Slot 0 remains active!
atlasManager.recordUploads(nullptr);
REPORTER_ASSERT(reporter, atlasManager.numPages() == 1);
REPORTER_ASSERT(reporter, atlasManager.getPageProxy(1) == nullptr);
REPORTER_ASSERT(reporter, atlasManager.getPageProxy(0)->dimensions().height() == 4);
REPORTER_ASSERT(reporter, atlasManager.activeSlot() == 0);
}
DEF_GRAPHITE_TEST_FOR_RENDERING_CONTEXTS(SparseStrips_AtlasStraddleAndMemmove,
reporter,
context,
CtsEnforcement::kToBeDetermined) {
auto recorder = context->makeRecorder();
AlphaAtlasManager atlasManager(recorder.get());
// Page 0 has 1 row (32,768 bytes).
// Fill Page 0 up to 64 bytes before the end.
int32_t almostFull = SparseStripConfig::kAtlasWidthBytes - 64;
REPORTER_ASSERT(reporter, atlasManager.requestAlphaSpace(almostFull) != nullptr);
{
auto alloc = atlasManager.finalizeRun();
REPORTER_ASSERT(reporter, alloc.has_value());
auto [alphaIndex, texPage] = *alloc;
REPORTER_ASSERT(reporter, texPage == 0);
REPORTER_ASSERT(reporter, alphaIndex == 0);
}
// Now start a run of 128 bytes (two 64-byte chunks).
// Chunk 1 (64 bytes): fills the rest of Page 0.
uint8_t* ptr1 = atlasManager.requestAlphaSpace(64);
REPORTER_ASSERT(reporter, ptr1 != nullptr);
for (int i = 0; i < 64; ++i) {
ptr1[i] = static_cast<uint8_t>(i + 1);
}
// Chunk 2 (64 bytes): straddles past Page 0's capacity into overrun!
uint8_t* ptr2 = atlasManager.requestAlphaSpace(64);
REPORTER_ASSERT(reporter, ptr2 != nullptr);
for (int i = 0; i < 64; ++i) {
ptr2[i] = static_cast<uint8_t>(i + 65);
}
// Finalize the run: should detect straddle, create Page 1, and copy 128 bytes to offset 0 of
// Page 1!
{
auto alloc = atlasManager.finalizeRun();
REPORTER_ASSERT(reporter, alloc.has_value());
auto [alphaIndex, texPage] = *alloc;
REPORTER_ASSERT(reporter, texPage == 1);
REPORTER_ASSERT(reporter, alphaIndex == 0);
}
REPORTER_ASSERT(reporter, atlasManager.numPages() == 2);
REPORTER_ASSERT(reporter, atlasManager.activeSlot() == 1);
// Verify that the written bytes were accurately moved into Page 1 at offset 0
sk_sp<TextureProxy> proxy1 = atlasManager.getPageProxy(1);
REPORTER_ASSERT(reporter, proxy1 != nullptr);
REPORTER_ASSERT(reporter, proxy1->dimensions().height() == 2); // doubled rows
const uint8_t* page1Data = atlasManager.getPageData(1);
REPORTER_ASSERT(reporter, page1Data != nullptr);
for (int i = 0; i < 128; ++i) {
REPORTER_ASSERT(reporter, page1Data[i] == static_cast<uint8_t>(i + 1));
}
}
} // namespace skgpu::graphite