如何在 C64 上显示边框中的精灵?

发布于 2024-08-06 16:19:14 字数 75 浏览 1 评论 0原文

我见过很酷的 C64 演示,在屏幕的边框区域显示精灵。这不应该是不可能的;我认为他们设法以某种方式欺骗了图形芯片。他们到底是怎么做到的?

I've seen cool C64 demos showing sprites in the border area of the screen. It shouldn't be possible; I think they managed to fool the graphics chip somehow. How exactly did they do it?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

生寂 2024-08-13 16:19:14

是的,你需要汇编器。这是一个中断计时技巧。 VIC 能够在边框中显示精灵,但框架只是隐藏它们,因此精灵会在其后面滑动。它连接到 VIC 显示的扫描线。

对于下/上边框,非常简单:

  • 编写一个中断,同步以在下边框之前的某个扫描线、7 像素或类似的位置处开始。
  • 设置VIC中的寄存器,使边框变小。 (有一个寄存器可以做到这一点。)
  • VIC 现在认为边界已经开始并且不会开始绘制它。
  • ->底部没有边框。
  • 在实际边界之后编程另一个中断以将其设置回原始状态。

对于左/右边框中的精灵,情况更为复杂,因为必须对每个扫描线重复该过程:

  • 编程一个中断,同步以在某个扫描线开始。
  • 然后执行一些 NOP,直到距离右边框 7 个像素。
  • 设置VIC中的寄存器,使边框变小。
  • ->右侧没有边框。
  • 执行一些 NOP,直到到达真实边界并将寄存器设置回原始值。
  • 再次执行一些 NOP,直到步骤 2。

问题是所有这些 NOP 都忙于等待并窃取您的资源的周期。

我可以从下边框的精灵滚动条中为您找到一些代码。
这是代码。 (它是从一些演示中盗取的。)

C198  78        SEI
C199  20 2E C1  JSR C12E     # clear sprite area
C19C  20 48 C1  JSR C148     # init VIC
C19F  A9 BF     LDA #BF      # set up IRQ in C1BF
C1A1  A2 C1     LDX #C1
C1A3  8D 14 03  STA 0314
C1A6  8E 15 03  STX 0315
C1A9  A9 1B     LDA #1B
C1AB  8D 11 D0  STA D011
C1AE  A9 F7     LDA #F7
C1B0  8D 12 D0  STA D012
C1B3  A9 01     LDA #01
C1B5  8D 1A D0  STA D01A
C1B8  A9 7F     LDA #7F
C1BA  8D 0D DC  STA DC0D
C1BD  58        CLI
C1BE  60        RTS

----------------------------------
# init VIC
C148  A2 00     LDX #00
C14A  BD 88 C1  LDA C188,X
C14D  9D 00 D0  STA D000,X   # set first 16 values from table
C150  E8        INX
C151  E0 10     CPX #10
C153  D0 F5     BNE C14A
C155  A9 FF     LDA #FF
C157  8D 15 D0  STA D015
C15A  A9 00     LDA #00
C15C  8D 1C D0  STA D01C
C15F  A9 FF     LDA #FF
C161  8D 17 D0  STA D017
C164  8D 1D D0  STA D01D
C167  A9 C0     LDA #C0
C169  8D 10 D0  STA D010
C16C  A9 F8     LDA #F8
C16E  A2 00     LDX #00
C170  9D F8 07  STA 07F8,X
C173  18        CLC
C174  69 01     ADC #01
C176  E8        INX
C177  E0 08     CPX #08
C179  D0 F5     BNE C170
C17B  A9 0E     LDA #0E
C17D  A2 00     LDX #00
C17F  9D 27 D0  STA D027,X
C182  E8        INX
C183  E0 08     CPX #08
C185  D0 F8     BNE C17F
C187  60        RTS

----------------------------------
# data set into VIC registers
C188  00 F7 30 F7 60 F7 90 F7
C190  C0 F7 F0 F7 20 F7 50 F7

----------------------------------
# main IRQ routine
C1BF  A2 08     LDX #08
C1C1  CA        DEX
C1C2  D0 FD     BNE C1C1
C1C4  A2 28     LDX #28      # 40 or so lines
C1C6  EA        NOP          # "timing"
C1C7  EA        NOP
C1C8  EA        NOP
C1C9  EA        NOP
C1CA  CE 16 D0  DEC D016     # fiddle register
C1CD  EE 16 D0  INC D016
C1D0  AC 12 D0  LDY D012
C1D3  88        DEY
C1D4  EA        NOP
C1D5  98        TYA
C1D6  29 07     AND #07
C1D8  09 18     ORA #18
C1DA  8D 11 D0  STA D011
C1DD  24 EA     BIT   EA
C1DF  EA        NOP
C1E0  EA        NOP
C1E1  CA        DEX
C1E2  10 E4     BPL C1C8     # repeat next line
C1E4  A9 1B     LDA #1B
C1E6  8D 11 D0  STA D011
C1E9  A9 01     LDA #01
C1EB  8D 19 D0  STA D019
C1EE  20 00 C0  JSR C000     # call main code
C1F1  4C 31 EA  JMP EA31     # finish IRQ

Yes, you need assembler. It's an interrupt timing trick. The VIC is able to show sprites in the border, but the frame is just hiding them, so the sprites slides behind it. It's connected to scan lines displayed by the VIC.

For lower/upper borders it's quite simple:

  • Program an interrupt, synced to start at a certain scan line, 7 pixel or something like that before the lower border.
  • Set the register in VIC to make the border smaller. (There is a register that can do that.)
  • VIC now believes that the border already started and does not start to paint it.
  • -> No border at the bottom.
  • Program another interrupt after the real border to set it back to original.

For sprites in the left/right borders, it's more complicated because the process has to be repeated for every scan line:

  • Program an interrupt, synced to start at a certain scan line.
  • Then do some NOPs until you are 7 pixels before the right border.
  • Set the register in VIC to make the border smaller.
  • -> No border at the right side.
  • Do some NOPs until you are after the real border and set the register back to original value.
  • Again do some NOPs until step 2.

The problem is that all these NOPs are busy waits and steal the cycles you have for your stuff.

I was able to find some code for you, from an sprite scroller in the lower border.
Here is the code. (It was ripped from some demo.)

C198  78        SEI
C199  20 2E C1  JSR C12E     # clear sprite area
C19C  20 48 C1  JSR C148     # init VIC
C19F  A9 BF     LDA #BF      # set up IRQ in C1BF
C1A1  A2 C1     LDX #C1
C1A3  8D 14 03  STA 0314
C1A6  8E 15 03  STX 0315
C1A9  A9 1B     LDA #1B
C1AB  8D 11 D0  STA D011
C1AE  A9 F7     LDA #F7
C1B0  8D 12 D0  STA D012
C1B3  A9 01     LDA #01
C1B5  8D 1A D0  STA D01A
C1B8  A9 7F     LDA #7F
C1BA  8D 0D DC  STA DC0D
C1BD  58        CLI
C1BE  60        RTS

----------------------------------
# init VIC
C148  A2 00     LDX #00
C14A  BD 88 C1  LDA C188,X
C14D  9D 00 D0  STA D000,X   # set first 16 values from table
C150  E8        INX
C151  E0 10     CPX #10
C153  D0 F5     BNE C14A
C155  A9 FF     LDA #FF
C157  8D 15 D0  STA D015
C15A  A9 00     LDA #00
C15C  8D 1C D0  STA D01C
C15F  A9 FF     LDA #FF
C161  8D 17 D0  STA D017
C164  8D 1D D0  STA D01D
C167  A9 C0     LDA #C0
C169  8D 10 D0  STA D010
C16C  A9 F8     LDA #F8
C16E  A2 00     LDX #00
C170  9D F8 07  STA 07F8,X
C173  18        CLC
C174  69 01     ADC #01
C176  E8        INX
C177  E0 08     CPX #08
C179  D0 F5     BNE C170
C17B  A9 0E     LDA #0E
C17D  A2 00     LDX #00
C17F  9D 27 D0  STA D027,X
C182  E8        INX
C183  E0 08     CPX #08
C185  D0 F8     BNE C17F
C187  60        RTS

----------------------------------
# data set into VIC registers
C188  00 F7 30 F7 60 F7 90 F7
C190  C0 F7 F0 F7 20 F7 50 F7

----------------------------------
# main IRQ routine
C1BF  A2 08     LDX #08
C1C1  CA        DEX
C1C2  D0 FD     BNE C1C1
C1C4  A2 28     LDX #28      # 40 or so lines
C1C6  EA        NOP          # "timing"
C1C7  EA        NOP
C1C8  EA        NOP
C1C9  EA        NOP
C1CA  CE 16 D0  DEC D016     # fiddle register
C1CD  EE 16 D0  INC D016
C1D0  AC 12 D0  LDY D012
C1D3  88        DEY
C1D4  EA        NOP
C1D5  98        TYA
C1D6  29 07     AND #07
C1D8  09 18     ORA #18
C1DA  8D 11 D0  STA D011
C1DD  24 EA     BIT   EA
C1DF  EA        NOP
C1E0  EA        NOP
C1E1  CA        DEX
C1E2  10 E4     BPL C1C8     # repeat next line
C1E4  A9 1B     LDA #1B
C1E6  8D 11 D0  STA D011
C1E9  A9 01     LDA #01
C1EB  8D 19 D0  STA D019
C1EE  20 00 C0  JSR C000     # call main code
C1F1  4C 31 EA  JMP EA31     # finish IRQ
南烟 2024-08-13 16:19:14

这一切都取决于时机。 C64 有一种方法可以在绘制屏幕时查询电子束的准确垂直位置。
当新的一行开始时,您必须等待几个周期(您可以使用 NOP 指令对此进行计时),然后您必须设置视频芯片的硬件寄存器,该寄存器负责设置屏幕模式(和边框宽度)。
通过精确地计时,并在每条扫描线上再次执行此操作,整个侧边消失了。

底部边框也用类似的技巧消失了。在垂直边框开始的确切扫描线上,您也必须设置禁用该帧的底部边框的视频模式。

事实上,这整件事必须在集会中完成。否则你永远无法准确把握时机。

顺便说一句,我认为侧边技巧归功于 1001 Crew(荷兰组合)。我不确定是谁完成了第一个底部边框技巧。

It all relied on timing. The C64 had a method to query the exact vertical location of the electron beam while it was drawing the screen.
When a new line started, you had to wait a few cycles (you could time this using the NOP instruction) and then you had to set a hardware register of the videochip which was responsible for setting the screenmode (and the border width).
By timing it exactly right, and doing it every scanline again, the whole sideborder disappeared.

The bottom border went away with a similar trick. On the exact scanline where the vertical border started you too had to set the videomode which disabled the bottom border for that frame.

Indeed this whole thing had to be done in assembly. Otherwise you could never get the timing exactly right.

As a side note, I think the sideborder trick was credited to the 1001 Crew (a Dutch group). I'm not sure who pulled off the first bottom border trick.

冷夜 2024-08-13 16:19:14

有关在 C64 上打开边框主题的优秀教程,
请查看 Pasi Ojala 在 C=Hacking Issue 6 中的精彩文章。

无需太技术性,该技巧使用了 VIC 的一个功能
芯片可让您在 25/24 行和 40/38 列文本/图形之间切换,以及
包括在正确的时刻进行此切换以欺骗 VIC
认为它已经打开了边界,但事实上它已经打开了
没有。查看上面的文章以获得更全面的解释
带有代码示例。

For a good tutorial on the topic of opening the borders on the C64,
check out Pasi Ojala's excellent article in C=Hacking Issue 6.

Without getting too technical, the trick uses a feature of the VIC
chip to let you switch between 25/24 rows and 40/38 columns of text/graphics, and
involves making this switch at the exact right moment to fool the VIC
into thinking it has already switched the borders on when in fact it
hasn't. Check out the above article for a more thorough explanation
with code examples.

初雪 2024-08-13 16:19:14

那是很久以前的事了。

我知道有一个依赖于显示器频率的解决方案。

对于 CRT,即使当前像素位于正常屏幕之外,它也是已知的。
所以你可以操纵射线。

我的垃圾堆里一定有一些 C64 书籍。

题外话,但 VIC20(C64 的前身)的图形很有趣。无法操纵每个像素,但您可以更改现有的字符。因此,您用从 0 到 ... 的所有字符填充了屏幕,并更改了字符以将像素设置到屏幕上。 ;-)。

That is a long time ago.

I know there was a solution that relied on the frequency of the monitor.

With a CRT, the current pixel is known even if it was outside of the normal screen.
So you could manipulate the ray.

Somewhere in my junkpile there must be some C64 books.

Offtopic, but graphics with the VIC20 (the predecessor of the C64) was fun. There was no way to manipulate each pixel, but you could change the existing characters. So you filled the screen with all characters from 0 to ... and changed the characters to set pixels to the screen. ;-).

暗恋未遂 2024-08-13 16:19:14

正如已经说过的,你必须欺骗 VIC,让其认为边界已经开始,但我写这篇文章的原因是因为最上面的答案有点不精确:我完全找不到寄存器来创建边界较小,所以这就是您执行此操作的方式(至少对于顶部和底部):您等到 VIC 到达第 25 个字符行,然后启用 24 行($D011,位 3)。您可以对左边框和右边框执行相同的操作,只需使用 38 列($D016,位 3),但要做到这一点,您需要非常精确的计时,并且还需要通过设置垂直滚动寄存器来消除坏线,因此扫描线 mod 8 永远不等于滚动值。当然,您不能再使用正常显示,因为坏行实际上不仅仅是坏行,它们还用于加载字符数据,我认为这些数据在非边界区域中每第 8 行重复一次。我个人在阅读最佳答案时有点困惑,希望对您有所帮助。
(另外,最上面的答案有一个错误:你没有让边框变小,而是让它变大)

As it was already said, you have to fool the VIC to think that the border already started, but the reason why I write this is because the top answer is a little bit inprecise: I was totally unable to find a register to make the border smaller, so this is the way you do it (at least for top and bottom): You wait until the VIC reached the 25th character row and then you enable 24 rows ($D011, bit 3). You can do the same thing for the left and the right border, just with 38 cols ($D016, bit 3), but to do that you need very precise timing and you also need to eliminate the bad lines by setting the vertical scroll register, so the scanline mod 8 is never equal to the scroll value. Of course, you can't use the normal display anymore because the bad lines actually aren't just bad, they are used to load character data I think, stuff that repeats for every 8th line in the non-border area. I personally was a little bit confused when I read the top answer, I hope that can help.
(Also, the top answer has a mistake: You don't make the border smaller, you make it bigger)

花之痕靓丽 2024-08-13 16:19:14

时机是关键。当 CRT 光束从左向右移动时,通过更改过扫描(边框)颜色,在边框中创建图像。产生图像需要两个定时信号——垂直刷新和水平刷新。通过检测何时发生水平和垂直刷新,您可以启动一系列汇编指令来更改边框颜色以生成图像。您需要计算出每个边框像素的 CPU 时钟周期数,并使用它来创建在正确位置更改边框颜色的代码。

在编写游戏时它效果不佳,因为 CPU 开销太大,无法腾出时间来处理用户输入和游戏状态。

Timing was the key. The image was created in the border by changing the overscan (border) colour as the CRT's beam moved from left to right. There are two timing signals required to produce an image - vertical refresh and horizontal refresh. By detecting when the horizontal and vertical refresh occurs you can start a sequence of assembler instructions to change the border colour to produce an image. You need to work out the number of CPU clock ticks per border pixel and use that to create code that changes the border colour at the right point.

It doesn't work very well when it comes to writing games as the CPU overhead is too great for there to be any time lift to process user input and game state.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文