[nostalgia/core] Get TileSheetSet working
This commit is contained in:
		@@ -23,24 +23,26 @@ install(
 | 
			
		||||
		include/nostalgia/modules
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
# Studio
 | 
			
		||||
if(TURBINE_BUILD_TYPE STREQUAL "Native")
 | 
			
		||||
	add_library(
 | 
			
		||||
		NostalgiaStudioModules STATIC
 | 
			
		||||
			studiomodules.cpp
 | 
			
		||||
	)
 | 
			
		||||
	target_link_libraries(
 | 
			
		||||
		NostalgiaStudioModules PUBLIC
 | 
			
		||||
			StudioAppLib
 | 
			
		||||
			NostalgiaCore-Studio-ImGui
 | 
			
		||||
			NostalgiaScene-Studio
 | 
			
		||||
	)
 | 
			
		||||
	install(
 | 
			
		||||
		FILES
 | 
			
		||||
			studiomodules.hpp
 | 
			
		||||
		DESTINATION
 | 
			
		||||
			include/nostalgia/modules
 | 
			
		||||
	)
 | 
			
		||||
if(${OLYMPIC_BUILD_STUDIO})
 | 
			
		||||
	# Studio
 | 
			
		||||
	if(TURBINE_BUILD_TYPE STREQUAL "Native")
 | 
			
		||||
		add_library(
 | 
			
		||||
			NostalgiaStudioModules STATIC
 | 
			
		||||
				studiomodules.cpp
 | 
			
		||||
		)
 | 
			
		||||
		target_link_libraries(
 | 
			
		||||
			NostalgiaStudioModules PUBLIC
 | 
			
		||||
				StudioAppLib
 | 
			
		||||
				NostalgiaCore-Studio-ImGui
 | 
			
		||||
				NostalgiaScene-Studio
 | 
			
		||||
		)
 | 
			
		||||
		install(
 | 
			
		||||
			FILES
 | 
			
		||||
				studiomodules.hpp
 | 
			
		||||
			DESTINATION
 | 
			
		||||
				include/nostalgia/modules
 | 
			
		||||
		)
 | 
			
		||||
	endif()
 | 
			
		||||
endif()
 | 
			
		||||
 | 
			
		||||
add_library(NostalgiaProfile INTERFACE)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,7 @@
 | 
			
		||||
add_subdirectory(src)
 | 
			
		||||
if(NOT TURBINE_BUILD_TYPE STREQUAL "GBA")
 | 
			
		||||
	add_subdirectory(test)
 | 
			
		||||
endif()
 | 
			
		||||
 | 
			
		||||
install(
 | 
			
		||||
	DIRECTORY
 | 
			
		||||
 
 | 
			
		||||
@@ -45,7 +45,11 @@ struct TileSheetSetEntrySection {
 | 
			
		||||
	static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.TileSheetSetEntrySection";
 | 
			
		||||
	static constexpr auto TypeVersion = 1;
 | 
			
		||||
	int32_t begin = 0;
 | 
			
		||||
	int32_t size = 0;
 | 
			
		||||
	int32_t tiles = 0;
 | 
			
		||||
	[[nodiscard]]
 | 
			
		||||
	constexpr auto end() const noexcept {
 | 
			
		||||
		return begin + tiles - 1;
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
oxModelBegin(TileSheetSetEntrySection)
 | 
			
		||||
@@ -68,6 +72,7 @@ oxModelEnd()
 | 
			
		||||
struct TileSheetSet {
 | 
			
		||||
	static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.TileSheetSet";
 | 
			
		||||
	static constexpr auto TypeVersion = 1;
 | 
			
		||||
	static constexpr auto Preloadable = true;
 | 
			
		||||
	int bpp = 0;
 | 
			
		||||
	ox::Vector<TileSheetSetEntry> entries;
 | 
			
		||||
};
 | 
			
		||||
@@ -101,6 +106,10 @@ ox::Error loadSpriteTileSheet(
 | 
			
		||||
		ox::FileAddress const&tilesheetAddr,
 | 
			
		||||
		bool loadDefaultPalette = false) noexcept;
 | 
			
		||||
 | 
			
		||||
ox::Error loadSpriteTileSheet(
 | 
			
		||||
		Context &ctx,
 | 
			
		||||
		TileSheetSet const&set) noexcept;
 | 
			
		||||
 | 
			
		||||
void setBgTile(Context &ctx, uint_t bgIdx, int column, int row, uint8_t tile) noexcept;
 | 
			
		||||
 | 
			
		||||
void clearBg(Context &ctx, uint_t bgIdx) noexcept;
 | 
			
		||||
 
 | 
			
		||||
@@ -41,6 +41,7 @@ struct GbaTileMapTarget {
 | 
			
		||||
	// and only exist to communicate with the loading process
 | 
			
		||||
	size_t tileWriteIdx = 0;
 | 
			
		||||
	unsigned targetBpp = 0;
 | 
			
		||||
	TileSheetSetEntry const*setEntry = nullptr;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
constexpr ox::Error model(auto *io, ox::CommonPtrWith<GbaPaletteTarget> auto *t) noexcept {
 | 
			
		||||
@@ -63,7 +64,6 @@ constexpr ox::Error model(auto *io, ox::CommonPtrWith<GbaTileMapTarget> auto *t)
 | 
			
		||||
	oxReturnError(io->template setTypeInfo<CompactTileSheet>());
 | 
			
		||||
	oxReturnError(io->field("bpp", &t->bpp));
 | 
			
		||||
	oxReturnError(io->field("defaultPalette", &t->defaultPalette));
 | 
			
		||||
	uint16_t intermediate = 0;
 | 
			
		||||
	if (t->targetBpp == 0) {
 | 
			
		||||
		t->targetBpp = t->bpp;
 | 
			
		||||
	}
 | 
			
		||||
@@ -71,32 +71,75 @@ constexpr ox::Error model(auto *io, ox::CommonPtrWith<GbaTileMapTarget> auto *t)
 | 
			
		||||
		return OxError(1, "Cannot load an 8 BPP tilesheet into a 4 BPP CBB");
 | 
			
		||||
	}
 | 
			
		||||
	ox::Error out{};
 | 
			
		||||
	if (t->targetBpp == t->bpp) {
 | 
			
		||||
		const auto handleTileMap = [t, &intermediate](std::size_t i, uint8_t const*tile) {
 | 
			
		||||
	uint16_t intermediate = 0;
 | 
			
		||||
	if (t->setEntry) {
 | 
			
		||||
		// The following code is atrocious, but it works.
 | 
			
		||||
		// It might be possible to clean it up a little, but it probably
 | 
			
		||||
		// cannot be seriously optimized without preloading TileSheets.
 | 
			
		||||
		// Consider building an array of bitmaps (Array<uint64_t, 4>)
 | 
			
		||||
		// so this can be done with a single bitmap look up.
 | 
			
		||||
		if (t->targetBpp == t->bpp) {
 | 
			
		||||
			TileSheetSetEntrySection const*section = &t->setEntry->sections[0];
 | 
			
		||||
			size_t sectionIdx = 1;
 | 
			
		||||
			size_t writeIt = 0;
 | 
			
		||||
			size_t const fourBpp = t->bpp == 4;
 | 
			
		||||
			const auto handleTileMap = [t, &intermediate, §ion, §ionIdx, &writeIt, fourBpp]
 | 
			
		||||
					(std::size_t i, uint8_t const *tile) {
 | 
			
		||||
				if (!section) {
 | 
			
		||||
					return ox::Error{};
 | 
			
		||||
				}
 | 
			
		||||
				auto const tileIdx = static_cast<int>((i * (2 * fourBpp)) / PixelsPerTile);
 | 
			
		||||
				if (tileIdx < section->begin) {
 | 
			
		||||
					return ox::Error{};
 | 
			
		||||
				}
 | 
			
		||||
				if (tileIdx > section->end()) {
 | 
			
		||||
					if (sectionIdx >= t->setEntry->sections.size()) {
 | 
			
		||||
						section = nullptr;
 | 
			
		||||
						return ox::Error{};
 | 
			
		||||
					}
 | 
			
		||||
					section = &t->setEntry->sections[sectionIdx];
 | 
			
		||||
					++sectionIdx;
 | 
			
		||||
					if (tileIdx < section->begin || tileIdx > section->end()) {
 | 
			
		||||
						return ox::Error{};
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
				if (writeIt & 1) { // writeIt is odd
 | 
			
		||||
					intermediate |= static_cast<uint16_t>(*tile) << 8;
 | 
			
		||||
					t->tileMap[writeIt / 2] = intermediate;
 | 
			
		||||
					++t->tileWriteIdx;
 | 
			
		||||
				} else { // writeIt is even
 | 
			
		||||
					intermediate = *tile & 0x00ff;
 | 
			
		||||
				}
 | 
			
		||||
				++writeIt;
 | 
			
		||||
				return ox::Error{};
 | 
			
		||||
			};
 | 
			
		||||
			out = io->template field<uint8_t, decltype(handleTileMap)>("tileMap", handleTileMap);
 | 
			
		||||
		} else if (t->targetBpp > t->bpp) { // 4 -> 8 bits
 | 
			
		||||
			const auto handleTileMap = [t, &intermediate](std::size_t i, uint8_t const *tile) {
 | 
			
		||||
				if (i & 1) { // i is odd
 | 
			
		||||
					intermediate |= static_cast<uint16_t>(*tile) << 8;
 | 
			
		||||
					// write 4 pixels from intermediate
 | 
			
		||||
					t->tileMap[i - 3] = (intermediate & 0b0000'0000'0000'1111) >> 0;
 | 
			
		||||
					t->tileMap[i - 2] = (intermediate & 0b0000'0000'1111'0000) >> 4;
 | 
			
		||||
					t->tileMap[i - 1] = (intermediate & 0b0000'1111'0000'0000) >> 8;
 | 
			
		||||
					t->tileMap[i - 0] = (intermediate & 0b1111'0000'0000'0000) >> 14;
 | 
			
		||||
					++t->tileWriteIdx;
 | 
			
		||||
				} else { // i is even
 | 
			
		||||
					intermediate = *tile & 0x00ff;
 | 
			
		||||
				}
 | 
			
		||||
				return ox::Error{};
 | 
			
		||||
			};
 | 
			
		||||
			out = io->template field<uint8_t, decltype(handleTileMap)>("tileMap", handleTileMap);
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		const auto handleTileMap = [t, &intermediate](std::size_t i, const uint8_t *tile) {
 | 
			
		||||
			if (i & 1) { // i is odd
 | 
			
		||||
				intermediate |= static_cast<uint16_t>(*tile) << 8;
 | 
			
		||||
				t->tileMap[i / 2] = intermediate;
 | 
			
		||||
				++t->tileWriteIdx;
 | 
			
		||||
			} else { // i is even
 | 
			
		||||
				intermediate = *tile & 0x00ff;
 | 
			
		||||
			}
 | 
			
		||||
			return ox::Error{};
 | 
			
		||||
		};
 | 
			
		||||
		out = io->template field<uint8_t, decltype(handleTileMap)>("tileMap", handleTileMap);
 | 
			
		||||
	} else if (t->targetBpp > t->bpp) { // 4 -> 8 bits
 | 
			
		||||
		const auto handleTileMap = [t, &intermediate](std::size_t i, uint8_t const*tile) {
 | 
			
		||||
			if (i & 1) { // i is odd
 | 
			
		||||
				intermediate |= static_cast<uint16_t>(*tile) << 8;
 | 
			
		||||
				// write 4 pixels from intermediate
 | 
			
		||||
				t->tileMap[i - 3] = (intermediate & 0b0000'0000'0000'1111) >> 0;
 | 
			
		||||
				t->tileMap[i - 2] = (intermediate & 0b0000'0000'1111'0000) >> 4;
 | 
			
		||||
				t->tileMap[i - 1] = (intermediate & 0b0000'1111'0000'0000) >> 8;
 | 
			
		||||
				t->tileMap[i - 0] = (intermediate & 0b1111'0000'0000'0000) >> 14;
 | 
			
		||||
				++t->tileWriteIdx;
 | 
			
		||||
			} else { // i is even
 | 
			
		||||
				intermediate = *tile & 0x00ff;
 | 
			
		||||
			}
 | 
			
		||||
			return ox::Error{};
 | 
			
		||||
			return OxError(0);
 | 
			
		||||
		};
 | 
			
		||||
		out = io->template field<uint8_t, decltype(handleTileMap)>("tileMap", handleTileMap);
 | 
			
		||||
	}
 | 
			
		||||
@@ -150,6 +193,7 @@ static ox::Error loadTileSheetSet(
 | 
			
		||||
				.bpp = bpp,
 | 
			
		||||
				.defaultPalette = {},
 | 
			
		||||
				.tileMap = tileMapTargetMem + tileWriteIdx,
 | 
			
		||||
				.setEntry = &entry,
 | 
			
		||||
		};
 | 
			
		||||
		oxReturnError(ox::readMC(ts, static_cast<std::size_t>(tsStat.size), &target));
 | 
			
		||||
		tileWriteIdx += target.tileWriteIdx;
 | 
			
		||||
 
 | 
			
		||||
@@ -513,10 +513,14 @@ static ox::Result<TileSheetData> buildSetTsd(
 | 
			
		||||
	for (auto const&entry : set.entries) {
 | 
			
		||||
		oxRequire(tilesheet, readObj<CompactTileSheet>(kctx, entry.tilesheet));
 | 
			
		||||
		oxRequire(tsd, normalizeTileSheet(*tilesheet));
 | 
			
		||||
		for (auto const p : tsd.pixels) {
 | 
			
		||||
			setTsd.pixels.push_back(p);
 | 
			
		||||
		for (auto const&s : entry.sections) {
 | 
			
		||||
			auto const size = s.tiles * PixelsPerTile;
 | 
			
		||||
			for (auto i = 0; i < size; ++i) {
 | 
			
		||||
				auto const srcIdx = static_cast<size_t>(i) + static_cast<size_t>(s.begin * PixelsPerTile);
 | 
			
		||||
				setTsd.pixels.push_back(tsd.pixels[srcIdx]);
 | 
			
		||||
			}
 | 
			
		||||
			setTsd.height += TileHeight * s.tiles;
 | 
			
		||||
		}
 | 
			
		||||
		setTsd.height += tsd.height;
 | 
			
		||||
	}
 | 
			
		||||
	return setTsd;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										11
									
								
								src/nostalgia/modules/core/test/CMakeLists.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								src/nostalgia/modules/core/test/CMakeLists.txt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
			
		||||
add_executable(
 | 
			
		||||
	NostalgiaCoreTest
 | 
			
		||||
		tests.cpp
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
target_link_libraries(
 | 
			
		||||
	NostalgiaCoreTest
 | 
			
		||||
		NostalgiaCore
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
add_test("[NostalgiaCore] readWriteTileSheet" ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/NostalgiaCoreTest readWriteTileSheet)
 | 
			
		||||
							
								
								
									
										38
									
								
								src/nostalgia/modules/core/test/tests.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								src/nostalgia/modules/core/test/tests.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,38 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#undef NDEBUG
 | 
			
		||||
 | 
			
		||||
#include <map>
 | 
			
		||||
#include <ox/std/error.hpp>
 | 
			
		||||
#include <ox/mc/mc.hpp>
 | 
			
		||||
#include <nostalgia/core/core.hpp>
 | 
			
		||||
 | 
			
		||||
using namespace nostalgia;
 | 
			
		||||
 | 
			
		||||
static std::map<ox::StringView, ox::Error(*)()> tests = {
 | 
			
		||||
	{
 | 
			
		||||
		"readWriteTileSheet",
 | 
			
		||||
		[]() -> ox::Error {
 | 
			
		||||
			core::TileSheet in;
 | 
			
		||||
			oxRequire(buff, ox::writeMC(in));
 | 
			
		||||
			oxRequire(out, ox::readMC<core::TileSheet>(buff));
 | 
			
		||||
			oxAssert(in.subsheet.name == out.subsheet.name, "subsheet.name serialization broken");
 | 
			
		||||
			return {};
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
int main(int argc, const char **args) {
 | 
			
		||||
	int retval = -1;
 | 
			
		||||
	if (argc > 0) {
 | 
			
		||||
		auto const testName = ox::StringView(args[1]);
 | 
			
		||||
		if (tests.find(testName) != tests.end()) {
 | 
			
		||||
			retval = static_cast<int>(tests[testName]());
 | 
			
		||||
		} else {
 | 
			
		||||
			retval = 1;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return retval;
 | 
			
		||||
}
 | 
			
		||||
@@ -76,9 +76,47 @@ static ox::Error runTest(turbine::Context &tctx) {
 | 
			
		||||
	core::puts(*cctx, 10, 9, "DOPENESS!!!");
 | 
			
		||||
	turbine::setUpdateHandler(tctx, testUpdateHandler);
 | 
			
		||||
	turbine::setKeyEventHandler(tctx, testKeyEventHandler);
 | 
			
		||||
	//core::setBgStatus(*cctx, 1, true);
 | 
			
		||||
	//core::setBgStatus(*cctx, 2, true);
 | 
			
		||||
	//core::setBgStatus(*cctx, 3, true);
 | 
			
		||||
	return turbine::run(tctx);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
[[maybe_unused]]
 | 
			
		||||
static ox::Error runTileSheetSetTest(turbine::Context &tctx) {
 | 
			
		||||
	// this should make the screen display 'ABCDB'
 | 
			
		||||
	constexpr ox::FileAddress PaletteAddr = ox::StringLiteral("/Palettes/Charset.npal");
 | 
			
		||||
	oxRequireM(cctx, core::init(tctx));
 | 
			
		||||
	turbine::setApplicationData(tctx, cctx.get());
 | 
			
		||||
	oxRequire(tsStat, turbine::rom(tctx)->stat(PaletteAddr));
 | 
			
		||||
	core::TileSheetSet const set{
 | 
			
		||||
		.bpp = 4,
 | 
			
		||||
		.entries = {
 | 
			
		||||
			{ .tilesheet = ox::StringLiteral("/TileSheets/Chester.ng"), .sections{{.begin = 0, .tiles = 1}} },
 | 
			
		||||
			{ .tilesheet = ox::StringLiteral("/TileSheets/AB.ng"),      .sections{{.begin = 0, .tiles = 2}} },
 | 
			
		||||
			{ .tilesheet = ox::StringLiteral("/TileSheets/CD.ng"),      .sections{{.begin = 0, .tiles = 2}} },
 | 
			
		||||
			{ .tilesheet = ox::StringLiteral("/TileSheets/AB.ng"),      .sections{{.begin = 1, .tiles = 1}} },
 | 
			
		||||
		},
 | 
			
		||||
	};
 | 
			
		||||
	oxReturnError(core::loadBgTileSheet(*cctx, 0, set));
 | 
			
		||||
	oxReturnError(core::loadSpriteTileSheet(*cctx, set));
 | 
			
		||||
	oxReturnError(core::loadBgPalette(*cctx, PaletteAddr));
 | 
			
		||||
	oxReturnError(core::loadSpritePalette(*cctx, PaletteAddr));
 | 
			
		||||
	core::setBgStatus(*cctx, 0, true);
 | 
			
		||||
	core::setBgTile(*cctx, 0, 10, 9, 1);
 | 
			
		||||
	core::setBgTile(*cctx, 0, 11, 9, 2);
 | 
			
		||||
	core::setBgTile(*cctx, 0, 13, 9, 4);
 | 
			
		||||
	core::setSprite(*cctx, 16, {
 | 
			
		||||
			.enabled = true,
 | 
			
		||||
			.x = 12 * 8,
 | 
			
		||||
			.y = 9 * 8,
 | 
			
		||||
			.tileIdx = 3,
 | 
			
		||||
	});
 | 
			
		||||
	core::setSprite(*cctx, 17, {
 | 
			
		||||
		.enabled = true,
 | 
			
		||||
		.x = 14 * 8,
 | 
			
		||||
		.y = 9 * 8,
 | 
			
		||||
		.tileIdx = 5,
 | 
			
		||||
	});
 | 
			
		||||
	turbine::setKeyEventHandler(tctx, testKeyEventHandler);
 | 
			
		||||
	return turbine::run(tctx);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -116,5 +154,5 @@ static ox::Error runScene(turbine::Context &tctx) {
 | 
			
		||||
 | 
			
		||||
ox::Error run(ox::UniquePtr<ox::FileSystem> &&fs) noexcept {
 | 
			
		||||
	oxRequireM(tctx, turbine::init(std::move(fs), "Nostalgia"));
 | 
			
		||||
	return runTest(*tctx);
 | 
			
		||||
	return runTileSheetSetTest(*tctx);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user