HD Packs: Added ability to have multiple backgrounds on different layers (up to 40)

This commit is contained in:
Sour 2020-05-08 20:08:41 -04:00
parent 193235b11f
commit c1f841b6dd
6 changed files with 132 additions and 107 deletions

View file

@ -313,7 +313,8 @@ struct HdBackgroundInfo
vector<HdPackCondition*> Conditions;
float HorizontalScrollRatio;
float VerticalScrollRatio;
bool BehindBgPrioritySprites;
uint8_t Priority;
uint32_t Left;
uint32_t Top;
@ -377,7 +378,6 @@ enum class HdPackOptions
None = 0,
NoSpriteLimit = 1,
AlternateRegisterRange = 2,
NoContours = 4,
DisableCache = 8,
DontRenderOriginalTiles = 16
};

View file

@ -50,13 +50,13 @@ void HdNesPack::DrawColor(uint32_t color, uint32_t *outputBuffer, uint32_t scale
}
}
void HdNesPack::DrawCustomBackground(uint32_t *outputBuffer, uint32_t x, uint32_t y, uint32_t scale, uint32_t screenWidth)
void HdNesPack::DrawCustomBackground(HdBackgroundInfo& bgInfo, uint32_t *outputBuffer, uint32_t x, uint32_t y, uint32_t scale, uint32_t screenWidth)
{
int brightness = _hdData->Backgrounds[_backgroundIndex].Brightness;
uint32_t left = _hdData->Backgrounds[_backgroundIndex].Left;
uint32_t top = _hdData->Backgrounds[_backgroundIndex].Top;
uint32_t width = _hdData->Backgrounds[_backgroundIndex].Data->Width;
uint32_t *pngData = _hdData->Backgrounds[_backgroundIndex].data() + ((top + y) * _hdData->Scale * width) + ((left + x) * _hdData->Scale);
int brightness = bgInfo.Brightness;
uint32_t left = bgInfo.Left;
uint32_t top = bgInfo.Top;
uint32_t width = bgInfo.Data->Width;
uint32_t *pngData = bgInfo.data() + ((top + y) * _hdData->Scale * width) + ((left + x) * _hdData->Scale);
uint32_t pixelColor;
for(uint32_t i = 0; i < scale; i++) {
@ -145,32 +145,37 @@ uint32_t HdNesPack::GetScale()
return _hdData->Scale;
}
void HdNesPack::OnLineStart(HdPpuPixelInfo &lineFirstPixel)
void HdNesPack::OnLineStart(HdPpuPixelInfo &lineFirstPixel, uint8_t y)
{
_scrollX = ((lineFirstPixel.TmpVideoRamAddr & 0x1F) << 3) | lineFirstPixel.XScroll | ((lineFirstPixel.TmpVideoRamAddr & 0x400) ? 0x100 : 0);
_useCachedTile = false;
if(_backgroundIndex >= 0) {
int32_t scrollY = (((lineFirstPixel.TmpVideoRamAddr & 0x3E0) >> 2) | ((lineFirstPixel.TmpVideoRamAddr & 0x7000) >> 12)) + ((lineFirstPixel.TmpVideoRamAddr & 0x800) ? 240 : 0);
HdBackgroundInfo &bgInfo = _hdData->Backgrounds[_backgroundIndex];
_bgScrollX = (int32_t)(_scrollX * bgInfo.HorizontalScrollRatio);
_bgScrollY = (int32_t)(scrollY * bgInfo.VerticalScrollRatio);
int32_t scrollY = (((lineFirstPixel.TmpVideoRamAddr & 0x3E0) >> 2) | ((lineFirstPixel.TmpVideoRamAddr & 0x7000) >> 12)) + ((lineFirstPixel.TmpVideoRamAddr & 0x800) ? 240 : 0);
for(int layer = 0; layer < 4; layer++) {
for(int i = 0; i < _activeBgCount[layer]; i++) {
HdBgConfig& cfg = _bgConfig[layer * HdNesPack::PriorityLevelsPerLayer + i];
HdBackgroundInfo& bgInfo = _hdData->Backgrounds[cfg.BackgroundIndex];
cfg.BgScrollX = (int32_t)(_scrollX * bgInfo.HorizontalScrollRatio);
cfg.BgScrollY = (int32_t)(scrollY * bgInfo.VerticalScrollRatio);
if(y >= -cfg.BgScrollY && (y + bgInfo.Top + cfg.BgScrollY + 1) * _hdData->Scale <= bgInfo.Data->Height) {
cfg.BgMinX = -cfg.BgScrollX;
cfg.BgMaxX = bgInfo.Data->Width / _hdData->Scale - bgInfo.Left - cfg.BgScrollX - 1;
} else {
cfg.BgMinX = -1;
cfg.BgMaxX = -1;
}
}
}
}
void HdNesPack::OnBeforeApplyFilter()
int32_t HdNesPack::GetLayerIndex(uint8_t priority)
{
_palette = _hdData->Palette.size() == 0x40 ? _hdData->Palette.data() : _settings->GetRgbPalette();
_contoursEnabled = (_hdData->OptionFlags & (int)HdPackOptions::NoContours) == 0;
_cacheEnabled = (_hdData->OptionFlags & (int)HdPackOptions::DisableCache) == 0;
if(_hdData->OptionFlags & (int)HdPackOptions::NoSpriteLimit) {
_settings->SetFlags(EmulationFlags::RemoveSpriteLimit | EmulationFlags::AdaptiveSpriteLimit);
}
_backgroundIndex = -1;
for(size_t i = 0; i < _hdData->Backgrounds.size(); i++) {
if(_hdData->Backgrounds[i].Priority != priority) {
continue;
}
bool isMatch = true;
for(HdPackCondition* condition : _hdData->Backgrounds[i].Conditions) {
if(!condition->CheckCondition(_hdScreenInfo, 0, 0, nullptr)) {
@ -180,10 +185,32 @@ void HdNesPack::OnBeforeApplyFilter()
}
if(isMatch) {
_backgroundIndex = (int32_t)i;
break;
return (int32_t)i;
}
}
return -1;
}
void HdNesPack::OnBeforeApplyFilter()
{
_palette = _hdData->Palette.size() == 0x40 ? _hdData->Palette.data() : _settings->GetRgbPalette();
_cacheEnabled = (_hdData->OptionFlags & (int)HdPackOptions::DisableCache) == 0;
if(_hdData->OptionFlags & (int)HdPackOptions::NoSpriteLimit) {
_settings->SetFlags(EmulationFlags::RemoveSpriteLimit | EmulationFlags::AdaptiveSpriteLimit);
}
for(int layer = 0; layer < 4; layer++) {
uint32_t activeCount = 0;
for(int i = 0; i < HdNesPack::PriorityLevelsPerLayer; i++) {
int32_t index = GetLayerIndex(layer * HdNesPack::PriorityLevelsPerLayer + i);
if(index >= 0) {
_bgConfig[layer*10+activeCount].BackgroundIndex = index;
activeCount++;
}
}
_activeBgCount[layer] = activeCount;
}
for(unique_ptr<HdPackCondition> &condition : _hdData->Conditions) {
condition->ClearCache();
@ -235,39 +262,15 @@ HdPackTileInfo* HdNesPack::GetMatchingTile(uint32_t x, uint32_t y, HdPpuTileInfo
return nullptr;
}
bool HdNesPack::IsNextToSprite(uint32_t x, uint32_t y)
bool HdNesPack::DrawBackgroundLayer(uint8_t priority, uint32_t x, uint32_t y, uint32_t* outputBuffer, uint32_t screenWidth)
{
bool hasNonBackgroundSurrounding = false;
auto processAdjacentTile = [&hasNonBackgroundSurrounding](HdPpuPixelInfo& pixelInfo) {
if(pixelInfo.Tile.BgColorIndex != 0) {
hasNonBackgroundSurrounding = true;
} else {
for(int i = 0; i < pixelInfo.SpriteCount; i++) {
if(pixelInfo.Sprite[i].SpriteColorIndex == 0 || pixelInfo.Sprite[i].SpriteColor != pixelInfo.Sprite[i].BgColor) {
hasNonBackgroundSurrounding |= pixelInfo.Sprite[i].TileIndex != HdPpuTileInfo::NoTile && pixelInfo.Sprite[i].SpriteColorIndex != 0;
}
if(hasNonBackgroundSurrounding) {
break;
}
}
}
};
for(int i = -1; i <= 1; i++) {
if((int)y + i < 0 || y + i >= PPU::ScreenHeight) {
continue;
}
for(int j = -1; j <= 1; j++) {
if((int)x + j < 0 || x + j >= PPU::ScreenWidth) {
continue;
}
if(!hasNonBackgroundSurrounding) {
processAdjacentTile(_hdScreenInfo->ScreenTiles[(i + y) * 256 + j + x]);
}
}
HdBgConfig bgConfig = _bgConfig[(int)priority];
if((int32_t)x >= bgConfig.BgMinX && (int32_t)x <= bgConfig.BgMaxX) {
HdBackgroundInfo& bgInfo = _hdData->Backgrounds[bgConfig.BackgroundIndex];
DrawCustomBackground(bgInfo, outputBuffer, x + bgConfig.BgScrollX, y + bgConfig.BgScrollY, _hdData->Scale, screenWidth);
return true;
}
return hasNonBackgroundSurrounding;
return false;
}
void HdNesPack::GetPixels(uint32_t x, uint32_t y, HdPpuPixelInfo &pixelInfo, uint32_t *outputBuffer, uint32_t screenWidth)
@ -285,26 +288,9 @@ void HdNesPack::GetPixels(uint32_t x, uint32_t y, HdPpuPixelInfo &pixelInfo, uin
DrawColor(_palette[pixelInfo.Tile.PpuBackgroundColor], outputBuffer, _hdData->Scale, screenWidth);
bool hasCustomBackground = false;
bool hasNonBackgroundSurrounding = false;
bool backgroundBehindBgSprites = false;
if(_backgroundIndex >= 0) {
HdBackgroundInfo &bgInfo = _hdData->Backgrounds[_backgroundIndex];
//Enable custom background if the current pixel fits within the background's boundaries
hasCustomBackground =
(int32_t)x >= -_bgScrollX &&
(int32_t)y >= -_bgScrollY &&
(y + bgInfo.Top + _bgScrollY + 1) * _hdData->Scale <= bgInfo.Data->Height &&
(x + bgInfo.Left + _bgScrollX + 1) * _hdData->Scale <= bgInfo.Data->Width;
if(hasCustomBackground) {
hasNonBackgroundSurrounding = _contoursEnabled && IsNextToSprite(x, y);
if(bgInfo.BehindBgPrioritySprites) {
DrawCustomBackground(outputBuffer, x + _bgScrollX, y + _bgScrollY, _hdData->Scale, screenWidth);
backgroundBehindBgSprites = true;
}
}
bool hasBackground = false;
for(int i = 0; i < _activeBgCount[0]; i++) {
hasBackground |= DrawBackgroundLayer(HdNesPack::BehindBgSpritesPriority+i, x, y, outputBuffer, screenWidth);
}
if(hasSprite) {
@ -324,20 +310,23 @@ void HdNesPack::GetPixels(uint32_t x, uint32_t y, HdPpuPixelInfo &pixelInfo, uin
}
}
if (hasCustomBackground && !backgroundBehindBgSprites) {
DrawCustomBackground(outputBuffer, x + _bgScrollX, y + _bgScrollY, _hdData->Scale, screenWidth);
for(int i = 0; i < _activeBgCount[1]; i++) {
hasBackground |= DrawBackgroundLayer(HdNesPack::BehindBgPriority+i, x, y, outputBuffer, screenWidth);
}
if(hdPackTileInfo) {
DrawTile(pixelInfo.Tile, *hdPackTileInfo, outputBuffer, screenWidth);
} else if(renderOriginalTiles) {
//Draw regular SD background tile
bool useCustomBackground = !hasNonBackgroundSurrounding && hasCustomBackground && pixelInfo.Tile.BgColorIndex == 0;
if(!useCustomBackground && (pixelInfo.Tile.BgColorIndex != 0 || hasNonBackgroundSurrounding)) {
if(!hasBackground || pixelInfo.Tile.BgColorIndex != 0) {
DrawColor(_palette[pixelInfo.Tile.BgColor], outputBuffer, _hdData->Scale, screenWidth);
}
}
for(int i = 0; i < _activeBgCount[2]; i++) {
DrawBackgroundLayer(HdNesPack::BehindFgSpritesPriority+i, x, y, outputBuffer, screenWidth);
}
if(hasSprite) {
for(int k = pixelInfo.SpriteCount - 1; k >= 0; k--) {
if(!pixelInfo.Sprite[k].BackgroundPriority && lowestBgSprite > k) {
@ -350,6 +339,10 @@ void HdNesPack::GetPixels(uint32_t x, uint32_t y, HdPpuPixelInfo &pixelInfo, uin
}
}
}
for(int i = 0; i < _activeBgCount[3]; i++) {
DrawBackgroundLayer(HdNesPack::ForegroundPriority+i, x, y, outputBuffer, screenWidth);
}
}
void HdNesPack::Process(HdScreenInfo *hdScreenInfo, uint32_t* outputBuffer, OverscanDimensions &overscan)
@ -360,7 +353,7 @@ void HdNesPack::Process(HdScreenInfo *hdScreenInfo, uint32_t* outputBuffer, Over
OnBeforeApplyFilter();
for(uint32_t i = overscan.Top, iMax = 240 - overscan.Bottom; i < iMax; i++) {
OnLineStart(hdScreenInfo->ScreenTiles[i << 8]);
OnLineStart(hdScreenInfo->ScreenTiles[i << 8], i);
uint32_t bufferIndex = (i - overscan.Top) * screenWidth * hdScale;
uint32_t lineStartIndex = bufferIndex;
for(uint32_t j = overscan.Left, jMax = 256 - overscan.Right; j < jMax; j++) {

View file

@ -7,19 +7,33 @@ class EmulationSettings;
class HdNesPack
{
private:
struct HdBgConfig
{
int32_t BackgroundIndex = -1;
int32_t BgScrollX = 0;
int32_t BgScrollY = 0;
int16_t BgMinX = -1;
int16_t BgMaxX = -1;
};
shared_ptr<HdPackData> _hdData;
EmulationSettings *_settings;
int32_t _backgroundIndex = -1;
static constexpr uint8_t PriorityLevelsPerLayer = 10;
static constexpr uint8_t BehindBgSpritesPriority = 0 * PriorityLevelsPerLayer;
static constexpr uint8_t BehindBgPriority = 1 * PriorityLevelsPerLayer;
static constexpr uint8_t BehindFgSpritesPriority = 2 * PriorityLevelsPerLayer;
static constexpr uint8_t ForegroundPriority = 3 * PriorityLevelsPerLayer;
uint8_t _activeBgCount[4] = {};
HdBgConfig _bgConfig[40] = {};
HdScreenInfo *_hdScreenInfo = nullptr;
uint32_t* _palette = nullptr;
bool _contoursEnabled = false;
HdPackTileInfo* _cachedTile = nullptr;
bool _cacheEnabled = false;
bool _useCachedTile = false;
int32_t _scrollX = 0;
int32_t _bgScrollX = 0;
int32_t _bgScrollY = 0;
__forceinline void BlendColors(uint8_t output[4], uint8_t input[4]);
__forceinline uint32_t AdjustBrightness(uint8_t input[4], int brightness);
@ -29,16 +43,17 @@ private:
__forceinline HdPackTileInfo* GetCachedMatchingTile(uint32_t x, uint32_t y, HdPpuTileInfo* tile);
__forceinline HdPackTileInfo* GetMatchingTile(uint32_t x, uint32_t y, HdPpuTileInfo* tile, bool* disableCache = nullptr);
__forceinline bool IsNextToSprite(uint32_t x, uint32_t y);
__forceinline void DrawCustomBackground(uint32_t *outputBuffer, uint32_t x, uint32_t y, uint32_t scale, uint32_t screenWidth);
__forceinline bool DrawBackgroundLayer(uint8_t priority, uint32_t x, uint32_t y, uint32_t* outputBuffer, uint32_t screenWidth);
__forceinline void DrawCustomBackground(HdBackgroundInfo& bgInfo, uint32_t *outputBuffer, uint32_t x, uint32_t y, uint32_t scale, uint32_t screenWidth);
void OnLineStart(HdPpuPixelInfo &lineFirstPixel);
void OnLineStart(HdPpuPixelInfo &lineFirstPixel, uint8_t y);
int32_t GetLayerIndex(uint8_t priority);
void OnBeforeApplyFilter();
__forceinline void GetPixels(uint32_t x, uint32_t y, HdPpuPixelInfo &pixelInfo, uint32_t *outputBuffer, uint32_t screenWidth);
__forceinline void ProcessGrayscaleAndEmphasis(HdPpuPixelInfo &pixelInfo, uint32_t* outputBuffer, uint32_t hdScreenWidth);
public:
static constexpr uint32_t CurrentVersion = 105;
static constexpr uint32_t CurrentVersion = 106;
HdNesPack(shared_ptr<HdPackData> hdData, EmulationSettings* settings);
~HdNesPack();

View file

@ -371,9 +371,6 @@ void HdPackBuilder::SaveHdPack()
if(_hdData.OptionFlags & (int)HdPackOptions::AlternateRegisterRange) {
ss << "alternateRegisterRange,";
}
if(_hdData.OptionFlags & (int)HdPackOptions::NoContours) {
ss << "disableContours,";
}
if(_hdData.OptionFlags & (int)HdPackOptions::DisableCache) {
ss << "disableCache,";
}

View file

@ -392,8 +392,6 @@ void HdPackLoader::ProcessOptionTag(vector<string> &tokens)
_data->OptionFlags |= (int)HdPackOptions::NoSpriteLimit;
} else if(token == "alternateRegisterRange") {
_data->OptionFlags |= (int)HdPackOptions::AlternateRegisterRange;
} else if(token == "disableContours") {
_data->OptionFlags |= (int)HdPackOptions::NoContours;
} else if(token == "disableCache") {
_data->OptionFlags |= (int)HdPackOptions::DisableCache;
} else if(token == "disableOriginalTiles") {
@ -577,7 +575,7 @@ void HdPackLoader::ProcessBackgroundTag(vector<string> &tokens, vector<HdPackCon
}
backgroundInfo.HorizontalScrollRatio = 0;
backgroundInfo.VerticalScrollRatio = 0;
backgroundInfo.BehindBgPrioritySprites = false;
backgroundInfo.Priority = 10;
backgroundInfo.Left = 0;
backgroundInfo.Top = 0;
@ -605,7 +603,12 @@ void HdPackLoader::ProcessBackgroundTag(vector<string> &tokens, vector<HdPackCon
}
if(tokens.size() > 4) {
checkConstraint(_data->Version >= 102, "[HDPack] This feature requires version 102+ of HD Packs");
backgroundInfo.BehindBgPrioritySprites = tokens[4] == "Y";
if(_data->Version >= 106) {
backgroundInfo.Priority = std::stoi(tokens[4]);
checkConstraint(backgroundInfo.Priority >= 0 && backgroundInfo.Priority < 40, "[HDPack] Invalid background priority value");
} else {
backgroundInfo.Priority = tokens[4] == "Y" ? 0 : 10;
}
}
if(tokens.size() > 6) {
checkConstraint(_data->Version >= 105, "[HDPack] This feature requires version 105+ of HD Packs");

View file

@ -55,14 +55,14 @@ Before you start recording, select the options you want to use and the location
## File Format (hires.txt) ##
The following are the specifications for the hires.txt file, as of version "105".
The following are the specifications for the hires.txt file, as of version "106".
### &lt;ver&gt; tag ###
**Syntax**: `<ver>[integer]`
**Example**: `<ver>105`
**Example**: `<ver>106`
The format's version number (currently 105).
The format's version number (currently 106).
### &lt;scale&gt; tag ###
@ -191,10 +191,10 @@ When `default tile` is enabled (with `Y`), the tile is marked as the `default ti
### &lt;background&gt; tag ###
**Syntax**: `<background>[name - text], [brightness level - float (default: 1.0)], [horizontal scroll ratio (optional) - float], [vertical scroll ratio (optional) - float], [showBehindBackgroundPrioritySprites (optional) - Y or N], [image left offset (optional) - int], [image top offset (optional) - int]`
**Example**: `<background>myBackground.png,1.0,0,0,N,0,0`
**Syntax**: `<background>[name - text], [brightness level - float (default: 1.0)], [horizontal scroll ratio (optional) - float], [vertical scroll ratio (optional) - float], [priority level (optional) - int, 0 to 39 (default 10)], [image left offset (optional) - int], [image top offset (optional) - int]`
**Example**: `<background>myBackground.png,1.0,0,0,10,0,0`
`<background>` tags meant to be used alongside conditions to add a background image under certain conditions (e.g on a specific screen, for example).
`<background>` tags are meant to be used alongside conditions to add a background image under certain conditions (e.g on a specific screen, for example).
The `Horizontal Scroll Ratio` and `Vertical Scroll Ratio` parameters are optional (their default value is `0.0`) and can be used to specify at what speed the background picture should scroll compared to the NES' scrolling.
This can be used to create simple parallax scrolling effects.
@ -202,7 +202,20 @@ This can be used to create simple parallax scrolling effects.
The `Image Left Offset` and `Image Top Offset` parameters are optional (their default value is `0`) and can be used to offset the position of the background image within the PNG file. e.g: Specifying 100 for the left offset will cause the background to be scrolled 100 pixels to the left by default.
With conditions, this can be used to create scrolling effects that are based on something other than the PPU's current scroll offset.
When the `Show Behind Background Priority Sprites` parameter is enabled (`Y`), the background priority sprites will be shown in front of the background image.
The `Priority Level` parameter determines where the background is inserted compared to the NES' 3 "layers" (background sprites -> background tiles -> foreground sprites).
The screen is drawn in this order:
* Priority 0 to 9 HD backgrounds (priority 0 is below priority 1, etc.)
* Background-priority sprites
* Priority 10 to 19 HD backgrounds
* Background tiles
* Priority 20 to 29 HD backgrounds
* Foreground-priority sprites
* Priority 30 to 39 HD backgrounds
Note: Only a single background can be active per priority level.
Backgrounds can use a PNG's alpha channel to allow the graphics below to show through them (this can be used to add a fog effect to the screen, etc.)
### &lt;options&gt; tag ###
@ -211,7 +224,6 @@ When the `Show Behind Background Priority Sprites` parameter is enabled (`Y`), t
**Available options**:
`disableSpriteLimit`: Forces the emulator to disable the sprite limit when the HD pack is loaded.
`disableContours`: Disables the outline effect that appears around sprites/tiles when using the `<background>` feature.
`disableOriginalTiles`: Normally, when a replacement for a tile is not found, the original tile is drawn. When this option is enabled, the original tiles are never drawn, only their HD replacements.
### &lt;bgm&gt; tag ###
@ -309,6 +321,11 @@ These registers return the ASCII string `NEA` (NES Enhanced Audio) - this can be
## File Format Changelog ##
### Version 106 ###
* Replaced `Show Behind Background Priority Sprites` flag for `<background>` tags by a 0 to 39 priority level value.
* Removed contour feature for backgrounds, and removed `disableContours` option.
### Version 105 ###
* Brightness values above 1.0 are now allowed.