mirror of
https://github.com/lscharen/iigs-sprite-compiler.git
synced 2024-08-31 18:29:06 +00:00
Add support for masks
This commit is contained in:
parent
243ff5175c
commit
06fa1bedac
@ -19,7 +19,7 @@ namespace SpriteCompiler.Test
|
||||
var search = SpriteGeneratorSearchProblem.Create();
|
||||
|
||||
// Act : solve the problem
|
||||
var solution = search.Search(problem, new SpriteGeneratorState());
|
||||
var solution = search.Search(problem, SpriteGeneratorState.Init(Enumerable.Empty<SpriteByte>()));
|
||||
|
||||
// Assert : The initial state IS the goal state
|
||||
Assert.AreEqual(1, solution.Count());
|
||||
@ -105,6 +105,60 @@ namespace SpriteCompiler.Test
|
||||
Assert.AreEqual(18, (int)solution.Last().PathCost);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestSingleByteWithMask()
|
||||
{
|
||||
// Arrange
|
||||
var problem = SpriteGeneratorSearchProblem.CreateSearchProblem();
|
||||
var search = SpriteGeneratorSearchProblem.Create();
|
||||
|
||||
// Act : solve the problem
|
||||
var data = new byte[] { 0x00, 0x11 };
|
||||
var mask = new byte[] { 0xFF, 0x00 };
|
||||
|
||||
var solution = search.Search(problem, SpriteGeneratorState.Init(data, mask));
|
||||
|
||||
// Assert
|
||||
//
|
||||
// Solution should be same as regular single-byte sprite, except
|
||||
// the store happens at offset 1, instead of 0.
|
||||
|
||||
// Write out the solution
|
||||
WriteOutSolution(solution);
|
||||
|
||||
Assert.AreEqual(5, solution.Count());
|
||||
Assert.AreEqual(14, (int)solution.Last().PathCost);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestSinglePixelMask()
|
||||
{
|
||||
// Arrange
|
||||
var problem = SpriteGeneratorSearchProblem.CreateSearchProblem();
|
||||
var search = SpriteGeneratorSearchProblem.Create();
|
||||
|
||||
// Act : solve the problem
|
||||
var data = new byte[] { 0x01, 0x11 };
|
||||
var mask = new byte[] { 0xF0, 0x00 };
|
||||
|
||||
var solution = search.Search(problem, SpriteGeneratorState.Init(data, mask));
|
||||
|
||||
// Assert
|
||||
//
|
||||
// Solution should be a single 16-bit RMW
|
||||
//
|
||||
// TCS
|
||||
// LDA 0,s
|
||||
// AND #$00F0
|
||||
// ORA #$1101
|
||||
// STA 0,s = 18 cycles
|
||||
|
||||
// Write out the solution
|
||||
WriteOutSolution(solution);
|
||||
|
||||
Assert.AreEqual(18, (int)solution.Last().PathCost);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestConsecutiveWordSprite()
|
||||
{
|
||||
|
@ -142,6 +142,39 @@ namespace SpriteCompiler.Problem
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class STACK_REL_8_BIT_READ_MODIFY_WRITE : CodeSequence
|
||||
{
|
||||
private readonly byte value;
|
||||
private readonly byte mask;
|
||||
private readonly byte offset;
|
||||
|
||||
public STACK_REL_8_BIT_READ_MODIFY_WRITE(byte value, byte mask, byte offset) : base(12) { this.value = value; this.mask = mask; this.offset = offset; }
|
||||
|
||||
public override SpriteGeneratorState Apply(SpriteGeneratorState state)
|
||||
{
|
||||
return state.Clone(_ =>
|
||||
{
|
||||
_.A = Register.UNINITIALIZED;
|
||||
_.RemoveByte((ushort)(offset + _.S.Value));
|
||||
});
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return "LDA " + offset.ToString("X2") + ",s / AND #$" + mask.ToString("X2") + " / ORA #$" + value.ToString("X2") + " / STA " + offset.ToString("X2") + ",s";
|
||||
}
|
||||
|
||||
public override string Emit()
|
||||
{
|
||||
return String.Join("\n",
|
||||
FormatLine("", "LDA", offset.ToString("X2") + ",s", "4 cycles"),
|
||||
FormatLine("", "AND", "#$" + mask.ToString("X2"), "2 cycles"),
|
||||
FormatLine("", "ORA", "#$" + value.ToString("X2"), "2 cycles"),
|
||||
FormatLine("", "STA", offset.ToString("X2") + ",s", "4 cycles")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class STACK_REL_16_BIT_STORE : CodeSequence
|
||||
{
|
||||
private readonly ushort value;
|
||||
@ -168,6 +201,39 @@ namespace SpriteCompiler.Problem
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class STACK_REL_16_BIT_READ_MODIFY_WRITE : CodeSequence
|
||||
{
|
||||
private readonly ushort value;
|
||||
private readonly ushort mask;
|
||||
private readonly byte offset;
|
||||
|
||||
public STACK_REL_16_BIT_READ_MODIFY_WRITE(ushort value, ushort mask, byte offset) : base(16) { this.value = value; this.mask = mask; this.offset = offset; }
|
||||
|
||||
public override SpriteGeneratorState Apply(SpriteGeneratorState state)
|
||||
{
|
||||
return state.Clone(_ =>
|
||||
{
|
||||
_.A = Register.UNINITIALIZED;
|
||||
_.RemoveWord((ushort)(offset + _.S.Value));
|
||||
});
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return "LDA " + offset.ToString("X2") + ",s / AND #$" + mask.ToString("X4") + " / ORA #$" + value.ToString("X4") + " / STA " + offset.ToString("X2") + ",s";
|
||||
}
|
||||
|
||||
public override string Emit()
|
||||
{
|
||||
return String.Join("\n",
|
||||
FormatLine("", "LDA", offset.ToString("X2") + ",s", "5 cycles"),
|
||||
FormatLine("", "AND", "#$" + mask.ToString("X4"), "3 cycles"),
|
||||
FormatLine("", "ORA", "#$" + value.ToString("X4"), "3 cycles"),
|
||||
FormatLine("", "STA", offset.ToString("X2") + ",s", "5 cycles")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class STACK_REL_16_BIT_IMMEDIATE_STORE : CodeSequence
|
||||
{
|
||||
private readonly ushort value;
|
||||
|
@ -30,4 +30,34 @@ namespace SpriteCompiler.Problem
|
||||
public byte Mask { get; private set; }
|
||||
public ushort Offset { get; private set; }
|
||||
}
|
||||
|
||||
public struct SpriteWord
|
||||
{
|
||||
private static ushort DataToMask(ushort data)
|
||||
{
|
||||
return (ushort)(
|
||||
(((data & 0xF000) == 0x00) ? 0xF000 : 0x0000) |
|
||||
(((data & 0x0F00) == 0x00) ? 0x0F00 : 0x0000) |
|
||||
(((data & 0x00F0) == 0x00) ? 0x00F0 : 0x0000) |
|
||||
(((data & 0x000F) == 0x00) ? 0x000F : 0x0000)
|
||||
);
|
||||
}
|
||||
|
||||
public SpriteWord(ushort data, ushort offset)
|
||||
: this(data, DataToMask(data), offset)
|
||||
{
|
||||
}
|
||||
|
||||
public SpriteWord(ushort data, ushort mask, ushort offset)
|
||||
: this()
|
||||
{
|
||||
Data = data;
|
||||
Mask = mask;
|
||||
Offset = offset;
|
||||
}
|
||||
|
||||
public ushort Data { get; private set; }
|
||||
public ushort Mask { get; private set; }
|
||||
public ushort Offset { get; private set; }
|
||||
}
|
||||
}
|
||||
|
@ -18,9 +18,16 @@
|
||||
return new SpriteGeneratorState();
|
||||
}
|
||||
|
||||
public static SpriteGeneratorState Init(IEnumerable<byte> bytes)
|
||||
public static SpriteGeneratorState Init(IEnumerable<byte> data)
|
||||
{
|
||||
return Init(bytes.Select((x, i) => new SpriteByte(x, (ushort)i)));
|
||||
return Init(data.Select((x, i) => new SpriteByte(x, (ushort)i)));
|
||||
}
|
||||
|
||||
public static SpriteGeneratorState Init(IEnumerable<byte> data, IEnumerable<byte> mask)
|
||||
{
|
||||
return Init(data.Zip(mask, (x, y) => new { Data = x, Mask = y })
|
||||
.Select((_, i) => new SpriteByte(_.Data, _.Mask, (ushort)i))
|
||||
.Where(_ => _.Mask != 0xFF));
|
||||
}
|
||||
|
||||
public SpriteGeneratorState()
|
||||
|
@ -7,29 +7,61 @@
|
||||
|
||||
public static class StateHelpers
|
||||
{
|
||||
public static byte? TryGetStackByte(this SpriteGeneratorState state, IDictionary<ushort, SpriteByte> data)
|
||||
public static SpriteByte? TryGetStackByte(this SpriteGeneratorState state, IDictionary<ushort, SpriteByte> data)
|
||||
{
|
||||
SpriteByte top;
|
||||
if (state.S.IsScreenOffset && data.TryGetValue((ushort)state.S.Value, out top))
|
||||
{
|
||||
return top.Data;
|
||||
return top;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static ushort? TryGetStackWord(this SpriteGeneratorState state, IDictionary<ushort, SpriteByte> data)
|
||||
public static SpriteWord? TryGetStackWord(this SpriteGeneratorState state, IDictionary<ushort, SpriteByte> data)
|
||||
{
|
||||
return TryGetStackWord(state, data, 0);
|
||||
}
|
||||
|
||||
public static ushort? TryGetStackWord(this SpriteGeneratorState state, IDictionary<ushort, SpriteByte> data, int offset)
|
||||
public static SpriteWord? TryGetStackWord(this SpriteGeneratorState state, IDictionary<ushort, SpriteByte> data, int offset)
|
||||
{
|
||||
// When we get a word, it's permissible for either the high or low byte to not exist, but not both
|
||||
SpriteByte high;
|
||||
SpriteByte low;
|
||||
if (state.S.IsScreenOffset && (state.S.Value + offset) > 0 && data.TryGetValue((ushort)(state.S.Value + offset), out high) && data.TryGetValue((ushort)(state.S.Value + offset - 1), out low))
|
||||
|
||||
// Make sure the range is within bounds
|
||||
if (state.S.IsScreenOffset && (state.S.Value + offset) > 0)
|
||||
{
|
||||
return (ushort)(low.Data + (high.Data << 8));
|
||||
// Try to get the high byte
|
||||
byte high_data = 0x00;
|
||||
byte high_mask = 0xFF;
|
||||
ushort high_offset = (ushort)(state.S.Value + offset);
|
||||
|
||||
if (data.TryGetValue(high_offset, out high))
|
||||
{
|
||||
high_data = high.Data;
|
||||
high_mask = high.Mask;
|
||||
}
|
||||
|
||||
// Try to get the low byte
|
||||
byte low_data = 0x00;
|
||||
byte low_mask = 0xFF;
|
||||
ushort low_offset = (ushort)(state.S.Value + offset - 1);
|
||||
|
||||
if (data.TryGetValue(low_offset, out low))
|
||||
{
|
||||
low_data = low.Data;
|
||||
low_mask = low.Mask;
|
||||
}
|
||||
|
||||
// At least some data need to be visible
|
||||
if (high_mask != 0xFF || low_mask != 0xFF)
|
||||
{
|
||||
var word_data = (ushort)(low_data + (high_data << 8));
|
||||
var word_mask = (ushort)(low_mask + (high_mask << 8));
|
||||
|
||||
return new SpriteWord(word_data, word_mask, low_offset);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
@ -74,45 +106,62 @@
|
||||
var topWord = state.TryGetStackWord(bytes);
|
||||
var nextWord = state.TryGetStackWord(bytes, -2); // Also get the next value below the current word
|
||||
|
||||
// We can always perform a PEA regardless of the register widths
|
||||
// If there is some data at the top of the stack, see what we can do
|
||||
if (topWord.HasValue)
|
||||
{
|
||||
yield return state.Apply(new PEA(topWord.Value));
|
||||
|
||||
// If any of the registers happen to match the value, we can do an optimized PHA/X/Y/D operations. Any one
|
||||
// PHx is as good as another and cannot affect the state, so just pick the first one.
|
||||
// First, the simple case -- the data has no mask
|
||||
if (topWord.Value.Mask == 0x000)
|
||||
{
|
||||
// If any of the registers has the exact value we need, then it is always fastest to just push the value
|
||||
if (state.LongA)
|
||||
{
|
||||
if (state.A.IsLiteral && state.A.Value == topWord.Value)
|
||||
if (state.A.IsLiteral && state.A.Value == topWord.Value.Data)
|
||||
{
|
||||
yield return state.Apply(new PHA());
|
||||
}
|
||||
else
|
||||
{
|
||||
yield return state.Apply(new LOAD_16_BIT_IMMEDIATE_AND_PUSH(topWord.Value));
|
||||
yield return state.Apply(new PEA(topWord.Value.Data));
|
||||
yield return state.Apply(new LOAD_16_BIT_IMMEDIATE_AND_PUSH(topWord.Value.Data));
|
||||
}
|
||||
}
|
||||
// Otherwise, the only alternative is a PEA instruction
|
||||
else
|
||||
{
|
||||
yield return state.Apply(new PEA(topWord.Value.Data));
|
||||
}
|
||||
|
||||
//else if (state.X.IsLiteral && state.X.Value == topWord.Value) { }
|
||||
//else if (state.Y.IsLiteral && state.Y.Value == topWord.Value) { }
|
||||
//else if (state.D.IsLiteral && state.D.Value == topWord.Value) { }
|
||||
|
||||
// If the top two workd match, it might be worthwhile to load the accumulator to start immediate PHAs
|
||||
}
|
||||
}
|
||||
|
||||
// If there is a valid byte, then we can look for an 8-bit push, or an immediate mode LDA #XX/STA 0,s
|
||||
if (topByte.HasValue)
|
||||
{
|
||||
if (topByte.Value.Mask == 0x00)
|
||||
{
|
||||
if (!state.LongA)
|
||||
{
|
||||
yield return state.Apply(new STACK_REL_8_BIT_IMMEDIATE_STORE(topByte.Value, 0));
|
||||
yield return state.Apply(new STACK_REL_8_BIT_IMMEDIATE_STORE(topByte.Value.Data, 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If the accumulator holds an offset then we could move to any byte position, but it is only beneficial to
|
||||
// either
|
||||
//
|
||||
// 1. Set the stack to the current accumulator value
|
||||
// 2. Set the stack to the start of a contiguous segment
|
||||
// 3. Set the stack to the end of a contiguous segment
|
||||
|
||||
// move to the first or last byte of each span. So , take the first byte and then look for any
|
||||
if (state.A.IsScreenOffset && !state.S.IsScreenOffset && state.LongA)
|
||||
{
|
||||
// If any of the open bytes are within 255 bytes of the accumulator, consider just
|
||||
// setting the stack to the accumulator value
|
||||
if (open.Any(x => (x.Offset - state.A.Value) >= 0 && (x.Offset - state.A.Value) < 256))
|
||||
{
|
||||
yield return state.Apply(new MOVE_STACK(0));
|
||||
}
|
||||
|
||||
for (var i = 0; i < open.Count; i++)
|
||||
{
|
||||
if (i == 0)
|
||||
@ -155,7 +204,11 @@
|
||||
{
|
||||
var offset = (byte)(word.Low.Offset - addr);
|
||||
var data = (ushort)(word.Low.Data + (word.High.Data << 8));
|
||||
var mask = (ushort)(word.Low.Mask + (word.High.Mask << 8));
|
||||
|
||||
// Easy case when mask is empty
|
||||
if (mask == 0x0000)
|
||||
{
|
||||
if (data == state.A.Value)
|
||||
{
|
||||
yield return state.Apply(new STACK_REL_16_BIT_STORE(data, offset));
|
||||
@ -165,6 +218,13 @@
|
||||
yield return state.Apply(new STACK_REL_16_BIT_IMMEDIATE_STORE(data, offset));
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise there is really only one choice LDA / AND / ORA / STA sequence
|
||||
else
|
||||
{
|
||||
yield return state.Apply(new STACK_REL_16_BIT_READ_MODIFY_WRITE(data, mask, offset));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
@ -180,8 +240,15 @@
|
||||
foreach (var datum in open.Where(WithinRangeOf(addr, 256)))
|
||||
{
|
||||
var offset = datum.Offset - addr;
|
||||
if (datum.Mask == 0x00)
|
||||
{
|
||||
yield return state.Apply(new STACK_REL_8_BIT_IMMEDIATE_STORE(datum.Data, (byte)offset));
|
||||
}
|
||||
else
|
||||
{
|
||||
yield return state.Apply(new STACK_REL_8_BIT_READ_MODIFY_WRITE(datum.Data, datum.Mask, (byte)offset));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -57,7 +57,7 @@
|
||||
.Callback(_ => data = _.Select(s => Convert.ToByte(s, 16)).ToList());
|
||||
|
||||
p.Setup<List<string>>('m', "mask")
|
||||
.Callback(_ => data = _.Select(s => Convert.ToByte(s, 16)).ToList());
|
||||
.Callback(_ => mask = _.Select(s => Convert.ToByte(s, 16)).ToList());
|
||||
|
||||
p.Setup<string>('i', "image")
|
||||
.Callback(_ => filename = _);
|
||||
@ -156,9 +156,16 @@
|
||||
var problem = SpriteGeneratorSearchProblem.CreateSearchProblem();
|
||||
var search = SpriteGeneratorSearchProblem.Create();
|
||||
|
||||
if (data.Count == mask.Count)
|
||||
{
|
||||
var solution = search.Search(problem, SpriteGeneratorState.Init(data, mask));
|
||||
WriteOutSolution(solution);
|
||||
}
|
||||
else
|
||||
{
|
||||
var solution = search.Search(problem, SpriteGeneratorState.Init(data));
|
||||
|
||||
WriteOutSolution(solution);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user