Skip to content

Commit

Permalink
* Add Mode4Flip demo
Browse files Browse the repository at this point in the history
* Move LCD/Video functions to its own namespace: LCD
* Input support
* Convert mode 4 assets to GBA native format in the build script using zigimg to read bitmap file
  • Loading branch information
wendigojaeger committed Jan 3, 2020
1 parent 0fab3de commit fa0a513
Show file tree
Hide file tree
Showing 21 changed files with 428 additions and 70 deletions.
6 changes: 4 additions & 2 deletions .gitignore
@@ -1,2 +1,4 @@
zig-cache
/refs
zig-cache
/refs
*.agi
*.agp
4 changes: 4 additions & 0 deletions .gitmodules
@@ -0,0 +1,4 @@
[submodule "GBA/assetconverter/zigimg"]
path = GBA/assetconverter/zigimg
url = git@github.com:mlarouche/zigimg.git
branch = master
24 changes: 24 additions & 0 deletions .vscode/launch.json
Expand Up @@ -27,6 +27,30 @@
"text": "file ${workspaceFolder}/zig-cache/raw/${fileBasenameNoExtension} -enable-pretty-printing"
}]
},
},
{
"name": "Release",
"type": "cppdbg",
"request": "launch",
"stopAtEntry": true,
"MIMode": "gdb",
"externalConsole": true,
"program": "${workspaceFolder}/zig-cache/raw/${fileBasenameNoExtension}",
"serverLaunchTimeout": 10000,
"cwd": "${workspaceRoot}",
"preLaunchTask": "run",
"postDebugTask": "kill-emulator",
"targetArchitecture": "arm",
"miDebuggerServerAddress": "localhost:2345",
"windows": {
"miDebuggerPath": "C:/devkitPro/devkitARM/bin/arm-none-eabi-gdb.exe",
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
"ignoreFailures": true,
"text": "file ${workspaceFolder}/zig-cache/raw/${fileBasenameNoExtension} -enable-pretty-printing"
}]
},
}
]
}
2 changes: 1 addition & 1 deletion .vscode/tasks.json
Expand Up @@ -45,7 +45,7 @@
"type": "shell",
"dependsOn": ["Build"],
"windows": {
"command": "C:\\mGBA\\mGBA.exe zig-cache/bin/${fileBasenameNoExtension}.gba"
"command": "C:\\mGBA\\mGBA.exe zig-cache/bin/${fileBasenameNoExtension}.gba;sleep 0;echo debuggerReady"
},
"presentation": {
"clear": true,
Expand Down
109 changes: 109 additions & 0 deletions GBA/assetconverter/image_converter.zig
@@ -0,0 +1,109 @@
const bmp = @import("zigimg/zigimg.zig").bmp;
const Allocator = @import("std").mem.Allocator;
const ArrayList = @import("std").ArrayList;
const OctTreeQuantizer = @import("zigimg/zigimg.zig").octree_quantizer.OctTreeQuantizer;
const Color = @import("zigimg/zigimg.zig").color.Color;
const fs = @import("std").fs;
const mem = @import("std").mem;

pub const ImageConverterError = error{InvalidPixelData};

const GBAColor = packed struct {
r: u5,
g: u5,
b: u5,
};

pub const ImageSourceTarget = struct {
source: []const u8,
target: []const u8,
};

pub const ImageConverter = struct {
pub fn convertMode4Image(allocator: *Allocator, images: []ImageSourceTarget, targetPaletteFilePath: []const u8) !void {
var quantizer = OctTreeQuantizer.init(allocator);
defer quantizer.deinit();

const ImageConvertInfo = struct {
imageInfo: *const ImageSourceTarget,
image: bmp.Bitmap,
};

var imageConvertList = ArrayList(ImageConvertInfo).init(allocator);
defer imageConvertList.deinit();

for (images) |imageInfo| {
var convertInfo = try imageConvertList.addOne();
convertInfo.imageInfo = &imageInfo;
convertInfo.image = try bmp.Bitmap.fromFile(allocator, imageInfo.source);

if (convertInfo.image.pixels) |pixelData| {
for (pixelData) |pixel| {
try quantizer.addColor(pixel.premultipliedAlpha());
}
} else {
return ImageConverterError.InvalidPixelData;
}
}
var paletteStorage: [256]Color = undefined;
var palette = try quantizer.makePalette(255, paletteStorage[0..]);

var paletteFile = try openWriteFile(targetPaletteFilePath);
defer paletteFile.close();

var paletteOut = paletteFile.outStream();
var paletteOutStream = &paletteOut.stream;

// Write palette file
var paletteCount: usize = 0;
for (palette) |entry| {
const gbaColor = colorToGBAColor(entry);
try paletteOutStream.writeIntLittle(u15, @bitCast(u15, gbaColor));
paletteCount += 2;
}

// Align palette file to a power of 4
var diff = mem.alignForward(paletteCount, 4) - paletteCount;
var index: usize = 0;
while (index < diff) : (index += 1) {
try paletteOutStream.writeIntLittle(u8, 0);
}

for (imageConvertList.toSlice()) |convertInfo| {
if (convertInfo.image.pixels) |pixelData| {
var imageFile = try openWriteFile(convertInfo.imageInfo.target);
defer imageFile.close();

var imageOut = imageFile.outStream();
var imageOutStream = &imageOut.stream;

// Write image file
var pixelCount: usize = 0;
for (pixelData) |pixel| {
var rawPaletteIndex: usize = try quantizer.getPaletteIndex(pixel.premultipliedAlpha());
var paletteIndex: u8 = @intCast(u8, rawPaletteIndex);
try imageOutStream.writeIntLittle(u8, paletteIndex);
pixelCount += 1;
}

diff = mem.alignForward(pixelCount, 4) - pixelCount;
index = 0;
while (index < diff) : (index += 1) {
try imageOutStream.writeIntLittle(u8, 0);
}
}
}
}

fn openWriteFile(path: []const u8) !fs.File {
return try fs.cwd().createFile(path, fs.File.CreateFlags{});
}

fn colorToGBAColor(color: Color) GBAColor {
return GBAColor{
.r = @intCast(u5, (color.R >> 3) & 0x1f),
.g = @intCast(u5, (color.G >> 3) & 0x1f),
.b = @intCast(u5, (color.B >> 3) & 0x1f),
};
}
};
1 change: 1 addition & 0 deletions GBA/assetconverter/zigimg
Submodule zigimg added at e15233
45 changes: 45 additions & 0 deletions GBA/builder.zig
@@ -1,10 +1,15 @@
const Builder = @import("std").build.Builder;
const LibExeObjStep = @import("std").build.LibExeObjStep;
const Step = @import("std").build.Step;
const builtin = @import("std").builtin;
const fmt = @import("std").fmt;
const FixedBufferAllocator = @import("std").heap.FixedBufferAllocator;
const std = @import("std");
const fs = @import("std").fs;
const ArrayList = @import("std").ArrayList;
const ImageConverter = @import("assetconverter/image_converter.zig").ImageConverter;

pub const ImageSourceTarget = @import("assetconverter/image_converter.zig").ImageSourceTarget;

const GBALinkerScript = "GBA/gba.ld";

Expand Down Expand Up @@ -80,3 +85,43 @@ pub fn addGBAExecutable(b: *Builder, romName: []const u8, sourceFile: []const u8

return exe;
}

const Mode4ConvertStep = struct {
step: Step,
builder: *Builder,
images: [] ImageSourceTarget,
targetPalettePath: [] const u8,

pub fn init(b: *Builder, images: [] ImageSourceTarget, targetPalettePath: [] const u8) Mode4ConvertStep {
return Mode4ConvertStep {
.builder = b,
.step = Step.init(b.fmt("ConvertMode4Image {}", .{targetPalettePath}), b.allocator, make),
.images = images,
.targetPalettePath = targetPalettePath,
};
}

fn make(step: *Step) !void {
const self = @fieldParentPtr(Mode4ConvertStep, "step", step);
const ImageSourceTargetList = ArrayList(ImageSourceTarget);

var fullImages = ImageSourceTargetList.init(self.builder.allocator);
defer fullImages.deinit();

for (self.images) |imageSourceTarget| {
try fullImages.append(ImageSourceTarget {
.source = self.builder.pathFromRoot(imageSourceTarget.source),
.target = self.builder.pathFromRoot(imageSourceTarget.target),
});
}
const fullTargetPalettePath = self.builder.pathFromRoot(self.targetPalettePath);

try ImageConverter.convertMode4Image(self.builder.allocator, fullImages.toSlice(), fullTargetPalettePath);
}
};

pub fn convertMode4Images(libExe: *LibExeObjStep, images: [] ImageSourceTarget, targetPalettePath: [] const u8) void {
const convertImageStep = libExe.builder.allocator.create(Mode4ConvertStep) catch unreachable;
convertImageStep.* = Mode4ConvertStep.init(libExe.builder, images, targetPalettePath);
libExe.step.dependOn(&convertImageStep.step);
}
51 changes: 28 additions & 23 deletions GBA/core.zig
@@ -1,13 +1,19 @@
const root = @import("root");
const isUpper = @import("std").ascii.isUpper;
const isDigit = @import("std").ascii.isDigit;

pub const GBA = struct {
pub const MEM_IO = @intToPtr(*volatile u32, 0x04000000);
pub const VRAM = @intToPtr([*]volatile u16, 0x06000000);
pub const REG_DISPCNT = @intToPtr(*volatile u32, @ptrToInt(MEM_IO) + 0x0000);
pub const REG_DISPSTAT = @intToPtr(*volatile u16, @ptrToInt(MEM_IO) + 0x0004);
pub const REG_VCOUNT = @intToPtr(*volatile u16, @ptrToInt(MEM_IO) + 0x0006);
pub const BG_PALETTE_RAM = @intToPtr([*]volatile u16, 0x05000000);
pub const OBJ_PALETTE_RAM = @intToPtr([*]volatile u16, 0x05000200);
pub const KEYINPUT = @intToPtr(*volatile u16, 0x4000130);
pub const EWRAM = @intToPtr([*]volatile u8, 0x02000000);
pub const IWRAM = @intToPtr([*]volatile u8, 0x03000000);

pub const MODE4_FRONT_VRAM = VRAM;
pub const MODE4_BACK_VRAM = @intToPtr([*]volatile u16, 0x0600A000);

pub const SCREEN_WIDTH = 240;
pub const SCREEN_HEIGHT = 160;
Expand Down Expand Up @@ -59,6 +65,9 @@ pub const GBA = struct {
};

comptime {
const isUpper = @import("std").ascii.isUpper;
const isDigit = @import("std").ascii.isDigit;

for (gameName) |value, index| {
var validChar = isUpper(value) or isDigit(value);

Expand Down Expand Up @@ -120,31 +129,10 @@ pub const GBA = struct {
}
};

pub const DisplayMode = enum {
Mode0,
Mode1,
Mode2,
Mode3,
Mode4,
Mode5,
};

pub const DisplayLayers = struct {
pub const Background0 = 0x0100;
pub const Background1 = 0x0200;
pub const Background2 = 0x0400;
pub const Background3 = 0x0800;
pub const Object = 0x1000;
};

pub inline fn toNativeColor(red: u8, green: u8, blue: u8) u16 {
return @as(u16, red & 0x1f) | (@as(u16, green & 0x1f) << 5) | (@as(u16, blue & 0x1f) << 10);
}

pub inline fn setupDisplay(mode: DisplayMode, layers: u32) void {
REG_DISPCNT.* = @enumToInt(mode) | layers;
}

pub const RamResetFlags = struct {
pub const clearEwRam = 1 << 0;
pub const clearIwram = 1 << 1;
Expand Down Expand Up @@ -185,9 +173,26 @@ export nakedcc fn GBAMain() linksection(".gbamain") noreturn {
\\bx r0
);

GBAZigStartup();
}

extern var __bss_lma: u8;
extern var __bss_start__: u8;
extern var __bss_end__: u8;
extern var __data_lma: u8;
extern var __data_start__: u8;
extern var __data_end__: u8;

fn GBAZigStartup() noreturn {
// Use BIOS function to clear all data
GBA.BIOSRegisterRamReset();

// Clear .bss
@memset(@as(*volatile [1]u8, &__bss_start__), 0, @ptrToInt(&__bss_end__) - @ptrToInt(&__bss_start__));

// Copy .data section to EWRAM
@memcpy(@ptrCast([*]u8, &__data_start__), @ptrCast([*]const u8, &__data_lma), @ptrToInt(&__data_end__) - @ptrToInt(&__data_start__));

// call user's main
if (@hasDecl(root, "main")) {
root.main();
Expand Down
10 changes: 8 additions & 2 deletions GBA/gba.ld
Expand Up @@ -46,14 +46,20 @@ SECTIONS
.ARM.exidx : { *(.ARM.exidx* .gnu.linkonce.armexidx.*) } >rom
__exidx_end = .;

.data : {
__data_lma = .;
.data : AT(__data_lma) {
__data_start__ = ABSOLUTE(.);
*(.data)
. = ALIGN(4);
__data_end__ = ABSOLUTE(.);
} >ewram = 0xff

.bss : {
__bss_lma = .;
.bss : AT(__bss_lma) {
__bss_start__ = ABSOLUTE(.);
*(COMMON)
*(.bss)
. = ALIGN(4);
__bss_end__ = ABSOLUTE(.);
} >ewram
}
4 changes: 3 additions & 1 deletion GBA/gba.zig
@@ -1,4 +1,6 @@
pub const Color = @import("color.zig").Color;
pub const Debug = @import("debug.zig").Debug;
pub const GBA = @import("core.zig").GBA;
pub const Mode3 = @import("mode3.zig").Mode3;
pub const Input = @import("input.zig").Input;
pub const LCD = @import("lcd.zig").LCD;
pub const Mode3 = @import("mode3.zig").Mode3;
40 changes: 40 additions & 0 deletions GBA/input.zig
@@ -0,0 +1,40 @@
const GBA = @import("core.zig").GBA;

pub const Input = struct {
var previousInput: u16 = 0;
var currentInput: u16 = 0;

pub const Keys = struct {
pub const A = 1 << 0;
pub const B = 1 << 1;
pub const Select = 1 << 2;
pub const Start = 1 << 3;
pub const Right = 1 << 4;
pub const Left = 1 << 5;
pub const Up = 1 << 6;
pub const Down = 1 << 7;
pub const R = 1 << 8;
pub const L = 1 << 9;
};

pub fn readInput() void {
previousInput = currentInput;
currentInput = ~GBA.KEYINPUT.*;
}

pub inline fn isKeyDown(keys: u16) bool {
return (currentInput & keys) == keys;
}

pub inline fn isKeyJustPressed(keys: u16) bool {
return (previousInput & keys) == 0 and (currentInput & keys) == keys;
}

pub inline fn isKeyJustReleased(keys: u16) bool {
return (previousInput & keys) == keys and (currentInput & keys) == 0;
}

pub inline fn isKeyUp(keys: u16) bool {
return (currentInput & keys) == 0;
}
};

0 comments on commit fa0a513

Please sign in to comment.