/* Copyright (c) 2013-2016 Jeffrey Pfau * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "software.h" #include "gb/io.h" #include "util/memory.h" static void GBVideoSoftwareRendererInit(struct GBVideoRenderer* renderer, enum GBModel model); static void GBVideoSoftwareRendererDeinit(struct GBVideoRenderer* renderer); static uint8_t GBVideoSoftwareRendererWriteVideoRegister(struct GBVideoRenderer* renderer, uint16_t address, uint8_t value); static void GBVideoSoftwareRendererWritePalette(struct GBVideoRenderer* renderer, int index, uint16_t value); static void GBVideoSoftwareRendererDrawRange(struct GBVideoRenderer* renderer, int startX, int endX, int y, struct GBObj** obj, size_t oamMax); static void GBVideoSoftwareRendererFinishScanline(struct GBVideoRenderer* renderer, int y); static void GBVideoSoftwareRendererFinishFrame(struct GBVideoRenderer* renderer); static void GBVideoSoftwareRendererGetPixels(struct GBVideoRenderer* renderer, unsigned* stride, const void** pixels); static void GBVideoSoftwareRendererPutPixels(struct GBVideoRenderer* renderer, unsigned stride, void* pixels); static void GBVideoSoftwareRendererDrawBackground(struct GBVideoSoftwareRenderer* renderer, uint8_t* maps, int startX, int endX, int y, int sx, int sy); static void GBVideoSoftwareRendererDrawObj(struct GBVideoSoftwareRenderer* renderer, struct GBObj* obj, int startX, int endX, int y); void GBVideoSoftwareRendererCreate(struct GBVideoSoftwareRenderer* renderer) { renderer->d.init = GBVideoSoftwareRendererInit; renderer->d.deinit = GBVideoSoftwareRendererDeinit; renderer->d.writeVideoRegister = GBVideoSoftwareRendererWriteVideoRegister; renderer->d.writePalette = GBVideoSoftwareRendererWritePalette, renderer->d.drawRange = GBVideoSoftwareRendererDrawRange; renderer->d.finishScanline = GBVideoSoftwareRendererFinishScanline; renderer->d.finishFrame = GBVideoSoftwareRendererFinishFrame; renderer->d.getPixels = GBVideoSoftwareRendererGetPixels; renderer->d.putPixels = 0; renderer->temporaryBuffer = 0; } static void GBVideoSoftwareRendererInit(struct GBVideoRenderer* renderer, enum GBModel model) { struct GBVideoSoftwareRenderer* softwareRenderer = (struct GBVideoSoftwareRenderer*) renderer; softwareRenderer->scy = 0; softwareRenderer->scx = 0; softwareRenderer->wy = 0; softwareRenderer->currentWy = 0; softwareRenderer->wx = 0; softwareRenderer->model = model; int y; for (y = 0; y < GB_VIDEO_VERTICAL_PIXELS; ++y) { color_t* row = &softwareRenderer->outputBuffer[softwareRenderer->outputBufferStride * y]; int x; for (x = 0; x < GB_VIDEO_HORIZONTAL_PIXELS; ++x) { row[x] = softwareRenderer->palette[0]; } } } static void GBVideoSoftwareRendererDeinit(struct GBVideoRenderer* renderer) { struct GBVideoSoftwareRenderer* softwareRenderer = (struct GBVideoSoftwareRenderer*) renderer; UNUSED(softwareRenderer); } static uint8_t GBVideoSoftwareRendererWriteVideoRegister(struct GBVideoRenderer* renderer, uint16_t address, uint8_t value) { struct GBVideoSoftwareRenderer* softwareRenderer = (struct GBVideoSoftwareRenderer*) renderer; switch (address) { case REG_LCDC: softwareRenderer->lcdc = value; break; case REG_SCY: softwareRenderer->scy = value; break; case REG_SCX: softwareRenderer->scx = value; break; case REG_WY: softwareRenderer->wy = value; softwareRenderer->currentWy = value; break; case REG_WX: softwareRenderer->wx = value; break; } return value; } static void GBVideoSoftwareRendererWritePalette(struct GBVideoRenderer* renderer, int index, uint16_t value) { struct GBVideoSoftwareRenderer* softwareRenderer = (struct GBVideoSoftwareRenderer*) renderer; #ifdef COLOR_16_BIT #ifdef COLOR_5_6_5 color_t color = 0; color |= (value & 0x001F) << 11; color |= (value & 0x03E0) << 1; color |= (value & 0x7C00) >> 10; #else color_t color = value; #endif #else color_t color = 0; color |= (value << 3) & 0xF8; color |= (value << 6) & 0xF800; color |= (value << 9) & 0xF80000; #endif softwareRenderer->palette[index] = color; } static void GBVideoSoftwareRendererDrawRange(struct GBVideoRenderer* renderer, int startX, int endX, int y, struct GBObj** obj, size_t oamMax) { struct GBVideoSoftwareRenderer* softwareRenderer = (struct GBVideoSoftwareRenderer*) renderer; uint8_t* maps = &softwareRenderer->d.vram[GB_BASE_MAP]; if (GBRegisterLCDCIsTileMap(softwareRenderer->lcdc)) { maps += GB_SIZE_MAP; } if (GBRegisterLCDCIsBgEnable(softwareRenderer->lcdc)) { GBVideoSoftwareRendererDrawBackground(softwareRenderer, maps, startX, endX, y, softwareRenderer->scx, softwareRenderer->scy); if (GBRegisterLCDCIsWindow(softwareRenderer->lcdc) && softwareRenderer->wy <= y && endX >= softwareRenderer->wx - 7) { maps = &softwareRenderer->d.vram[GB_BASE_MAP]; if (GBRegisterLCDCIsWindowTileMap(softwareRenderer->lcdc)) { maps += GB_SIZE_MAP; } GBVideoSoftwareRendererDrawBackground(softwareRenderer, maps, softwareRenderer->wx - 7, endX, y, 7 - softwareRenderer->wx, (softwareRenderer->currentWy - y) - softwareRenderer->wy); } } else { memset(&softwareRenderer->row[startX], 0, endX - startX); } if (GBRegisterLCDCIsObjEnable(softwareRenderer->lcdc)) { size_t i; for (i = 0; i < oamMax; ++i) { GBVideoSoftwareRendererDrawObj(softwareRenderer, obj[i], startX, endX, y); } } color_t* row = &softwareRenderer->outputBuffer[softwareRenderer->outputBufferStride * y]; int x; for (x = startX; x < endX; x += 8) { row[x] = softwareRenderer->palette[softwareRenderer->row[x]]; row[x + 1] = softwareRenderer->palette[softwareRenderer->row[x + 1]]; row[x + 2] = softwareRenderer->palette[softwareRenderer->row[x + 2]]; row[x + 3] = softwareRenderer->palette[softwareRenderer->row[x + 3]]; row[x + 4] = softwareRenderer->palette[softwareRenderer->row[x + 4]]; row[x + 5] = softwareRenderer->palette[softwareRenderer->row[x + 5]]; row[x + 6] = softwareRenderer->palette[softwareRenderer->row[x + 6]]; row[x + 7] = softwareRenderer->palette[softwareRenderer->row[x + 7]]; } } static void GBVideoSoftwareRendererFinishScanline(struct GBVideoRenderer* renderer, int y) { struct GBVideoSoftwareRenderer* softwareRenderer = (struct GBVideoSoftwareRenderer*) renderer; if (GBRegisterLCDCIsBgEnable(softwareRenderer->lcdc) && GBRegisterLCDCIsWindow(softwareRenderer->lcdc) && softwareRenderer->wy <= y && softwareRenderer->wx - 7 < GB_VIDEO_HORIZONTAL_PIXELS) { ++softwareRenderer->currentWy; } } static void GBVideoSoftwareRendererFinishFrame(struct GBVideoRenderer* renderer) { struct GBVideoSoftwareRenderer* softwareRenderer = (struct GBVideoSoftwareRenderer*) renderer; if (softwareRenderer->temporaryBuffer) { mappedMemoryFree(softwareRenderer->temporaryBuffer, GB_VIDEO_HORIZONTAL_PIXELS * GB_VIDEO_VERTICAL_PIXELS * 4); softwareRenderer->temporaryBuffer = 0; } softwareRenderer->currentWy = softwareRenderer->wy; } static void GBVideoSoftwareRendererDrawBackground(struct GBVideoSoftwareRenderer* renderer, uint8_t* maps, int startX, int endX, int y, int sx, int sy) { uint8_t* data = renderer->d.vram; uint8_t* attr = &maps[GB_SIZE_VRAM_BANK0]; if (!GBRegisterLCDCIsTileData(renderer->lcdc)) { data += 0x1000; } int topY = (((y + sy) >> 3) & 0x1F) * 0x20; int bottomY = (y + sy) & 7; if (startX < 0) { startX = 0; } int x; if ((startX + sx) & 7) { int startX2 = startX + 8 - ((startX + sx) & 7); for (x = startX; x < startX2; ++x) { uint8_t* localData = data; int localY = bottomY; int topX = ((x + sx) >> 3) & 0x1F; int bottomX = 7 - ((x + sx) & 7); int bgTile; if (GBRegisterLCDCIsTileData(renderer->lcdc)) { bgTile = maps[topX + topY]; } else { bgTile = ((int8_t*) maps)[topX + topY]; } int p = 0; if (renderer->model >= GB_MODEL_CGB) { GBObjAttributes attrs = attr[topX + topY]; p = GBObjAttributesGetCGBPalette(attrs) * 4; if (GBObjAttributesIsBank(attrs)) { localData += GB_SIZE_VRAM_BANK0; } if (GBObjAttributesIsYFlip(attrs)) { localY = 7 - bottomY; } if (GBObjAttributesIsXFlip(attrs)) { bottomX = 7 - bottomX; bottomX = 7 - bottomX; } } uint8_t tileDataLower = data[(bgTile * 8 + localY) * 2]; uint8_t tileDataUpper = data[(bgTile * 8 + localY) * 2 + 1]; tileDataUpper >>= bottomX; tileDataLower >>= bottomX; renderer->row[x] = p | ((tileDataUpper & 1) << 1) | (tileDataLower & 1); } startX = startX2; } for (x = startX; x < endX; x += 8) { uint8_t* localData = data; int localY = bottomY; int topX = ((x + sx) >> 3) & 0x1F; int bgTile; if (GBRegisterLCDCIsTileData(renderer->lcdc)) { bgTile = maps[topX + topY]; } else { bgTile = ((int8_t*) maps)[topX + topY]; } int p = 0; if (renderer->model >= GB_MODEL_CGB) { GBObjAttributes attrs = attr[topX + topY]; p = GBObjAttributesGetCGBPalette(attrs) * 4; if (GBObjAttributesIsBank(attrs)) { localData += GB_SIZE_VRAM_BANK0; } if (GBObjAttributesIsYFlip(attrs)) { localY = 7 - bottomY; } if (GBObjAttributesIsXFlip(attrs)) { uint8_t tileDataLower = localData[(bgTile * 8 + localY) * 2]; uint8_t tileDataUpper = localData[(bgTile * 8 + localY) * 2 + 1]; renderer->row[x + 0] = p | ((tileDataUpper & 1) << 1) | (tileDataLower & 1); renderer->row[x + 1] = p | (tileDataUpper & 2) | ((tileDataLower & 2) >> 1); renderer->row[x + 2] = p | ((tileDataUpper & 4) >> 1) | ((tileDataLower & 4) >> 2); renderer->row[x + 3] = p | ((tileDataUpper & 8) >> 2) | ((tileDataLower & 8) >> 3); renderer->row[x + 4] = p | ((tileDataUpper & 16) >> 3) | ((tileDataLower & 16) >> 4); renderer->row[x + 5] = p | ((tileDataUpper & 32) >> 4) | ((tileDataLower & 32) >> 5); renderer->row[x + 6] = p | ((tileDataUpper & 64) >> 5) | ((tileDataLower & 64) >> 6); renderer->row[x + 7] = p | ((tileDataUpper & 128) >> 6) | ((tileDataLower & 128) >> 7); continue; } } uint8_t tileDataLower = localData[(bgTile * 8 + localY) * 2]; uint8_t tileDataUpper = localData[(bgTile * 8 + localY) * 2 + 1]; renderer->row[x + 7] = p | ((tileDataUpper & 1) << 1) | (tileDataLower & 1); renderer->row[x + 6] = p | (tileDataUpper & 2) | ((tileDataLower & 2) >> 1); renderer->row[x + 5] = p | ((tileDataUpper & 4) >> 1) | ((tileDataLower & 4) >> 2); renderer->row[x + 4] = p | ((tileDataUpper & 8) >> 2) | ((tileDataLower & 8) >> 3); renderer->row[x + 3] = p | ((tileDataUpper & 16) >> 3) | ((tileDataLower & 16) >> 4); renderer->row[x + 2] = p | ((tileDataUpper & 32) >> 4) | ((tileDataLower & 32) >> 5); renderer->row[x + 1] = p | ((tileDataUpper & 64) >> 5) | ((tileDataLower & 64) >> 6); renderer->row[x + 0] = p | ((tileDataUpper & 128) >> 6) | ((tileDataLower & 128) >> 7); } } static void GBVideoSoftwareRendererDrawObj(struct GBVideoSoftwareRenderer* renderer, struct GBObj* obj, int startX, int endX, int y) { int ix = obj->x - 8; if (endX < ix || startX >= ix + 8) { return; } if (obj->x < endX) { endX = obj->x; } if (obj->x - 8 > startX) { startX = obj->x - 8; } if (startX < 0) { startX = 0; } uint8_t* data = renderer->d.vram; int tileOffset = 0; int bottomY; if (GBObjAttributesIsYFlip(obj->attr)) { bottomY = 7 - ((y - obj->y - 16) & 7); if (GBRegisterLCDCIsObjSize(renderer->lcdc) && y - obj->y < -8) { ++tileOffset; } } else { bottomY = (y - obj->y - 16) & 7; if (GBRegisterLCDCIsObjSize(renderer->lcdc) && y - obj->y >= -8) { ++tileOffset; } } uint8_t mask = GBObjAttributesIsPriority(obj->attr) ? ~0x1C : ~0x3F; int p; if (renderer->model >= GB_MODEL_CGB) { p = (GBObjAttributesGetCGBPalette(obj->attr) + 8) * 4; if (GBObjAttributesIsBank(obj->attr)) { data += GB_SIZE_VRAM_BANK0; } } else { p = (GBObjAttributesGetPalette(obj->attr) + 8) * 4; } int bottomX; int x; for (x = startX; x < endX; ++x) { if (GBObjAttributesIsXFlip(obj->attr)) { bottomX = (x - obj->x) & 7; } else { bottomX = 7 - ((x - obj->x) & 7); } int objTile = obj->tile + tileOffset; uint8_t tileDataLower = data[(objTile * 8 + bottomY) * 2]; uint8_t tileDataUpper = data[(objTile * 8 + bottomY) * 2 + 1]; tileDataUpper >>= bottomX; tileDataLower >>= bottomX; color_t current = renderer->row[x]; if (((tileDataUpper | tileDataLower) & 1) && !(current & mask)) { renderer->row[x] = p | ((tileDataUpper & 1) << 1) | (tileDataLower & 1); } } } static void GBVideoSoftwareRendererGetPixels(struct GBVideoRenderer* renderer, unsigned* stride, const void** pixels) { struct GBVideoSoftwareRenderer* softwareRenderer = (struct GBVideoSoftwareRenderer*) renderer; // TODO: Share with GBAVideoSoftwareRendererGetPixels #ifdef COLOR_16_BIT *stride = GB_VIDEO_HORIZONTAL_PIXELS; if (!softwareRenderer->temporaryBuffer) { softwareRenderer->temporaryBuffer = anonymousMemoryMap(GB_VIDEO_HORIZONTAL_PIXELS * GB_VIDEO_VERTICAL_PIXELS * 4); } *pixels = softwareRenderer->temporaryBuffer; unsigned y, x; for (y = 0; y < GB_VIDEO_VERTICAL_PIXELS; ++y) { for (x = 0; x < GB_VIDEO_HORIZONTAL_PIXELS; ++x) { color_t inColor = softwareRenderer->outputBuffer[softwareRenderer->outputBufferStride * y + x]; uint32_t outColor; #ifdef COLOR_5_6_5 outColor = (inColor & 0x1F) << 19; outColor |= (inColor & 0x7C0) << 5; outColor |= (inColor & 0xF800) >> 8; #else outColor = (inColor & 0x1F) << 3; outColor |= (inColor & 0x3E0) << 6; outColor |= (inColor & 0x7C00) << 9; #endif softwareRenderer->temporaryBuffer[GB_VIDEO_HORIZONTAL_PIXELS * y + x] = outColor; } } #else *stride = softwareRenderer->outputBufferStride; *pixels = softwareRenderer->outputBuffer; #endif }