Java for Playstation 2-可以吗?

图片

引言


在这个项目中,我想回答一个问题:是否可以为Playstation 2编写Java API并在其上创建图形演示。 我不想透露破坏者,但答案是肯定的。

几年前,我启动了一个Java Grinder项目,该项目接收已编译的Java .class文件并实际上充当反汇编程序。 但是,它没有反汇编为Java汇编程序代码,而是反汇编为实际处理器的汇编源代码。 如果类文件需要其他类文件,则还将读取和处理它们。 所有API方法调用都将作为内置汇编代码或对执行其预期任务的预写函数的调用写入输出。

由于Java Grinder是用C ++编写的,它是面向对象的,抽象的,多态的以及更多引人关注的HR单词,为了对其进行扩展,主要需要创建Playstation2类,扩展新的R5900类,扩展Generator的主要类。

结果,该项目的规模超出了我的预期。 该系统本身很简单,但是我仍然有很多东西要学习,而且找到高质量的信息并不是那么简单。 实际上,在这个项目中,我第一次参加了真正的3D编程。 在另一篇文章中,我已经谈到了在Playstation 2编程页面中学到的知识。

以下是视频和开发过程的详细说明。

录影带



我通过将音频和视频电缆连接到DVD刻录机在PS2 slim上录制了一个演示。 我有点担心PS2具有某种Macrovision保护会破坏视频信号,但是它要么被关闭,要么DVD刻录机忽略了它。 该视频从真实的Playstation 2演示开始,该演示运行了一个演示。 控制台连接到复合信号到VGA转换器,连接到LCD显示器,证明该演示程序在真实计算机上运行。 然后,我将直接从PS2中录制的真实视频粘贴到DVD刻录机中。

YouTube: https//youtu.be/AjM069oKUGs

mikekohn.net上的类似项目


Java Grinder:Playstation 2 Java
Sega Genesis Java
苹果IIgs Java
TI99 / 4A Java
C64 Java
dsPIC Mandelbrots
Atari 2600 Java
chipKIT Java
Java磨床
naken_asm

演示版


记得Sega Genesis Java演示,我后悔没让它变得更有趣。 然后,让我更有趣地演示Java API的功能。 当我开始这个项目时,我决定做一些更严肃的事情。 不幸的是,在研究系统和创建API的过程中,我再次感到筋疲力尽,以至于我没有足够的力量进行大型演示。

  • 30亿个设备徽标:这是低分辨率的30亿个设备,它们运行着乔·戴维森Commodore 64 Java演示创建的Java徽标。
  • 徽标:我用标记画了它们并对其进行了扫描(Java徽标除外)。
  • 星星:我实际上是从Sega Genesis Java演示中复制了代码,并对其进行了修改以与Playstation 2 API一起使用。 此处的文本也用标记书写并进行了扫描。
  • Mandelbrot分形:使用矢量单元0进行演示,该单元可计算分形,矢量单元1进行3D计算。 MIPS控制两个矢量设备的功能。
  • 多维数据集:我在Wings3d中绘制了这些多维数据集,并编写了C代码以将STL文件转换为Java Grinder可以使用的数组。 我手动添加了颜色。
  • 正方形环:仅是尝试在屏幕上绘制许多移动对象。 在系统开始变慢之前,可能值得添加更多对象。

乐曲


在演示中,我创作并录制了三首歌曲,但结果只使用了两首。 第一个作品实际上是我为大约一年前在我的网站上发布的另一个项目( 硬币接收器项目 )写的旋律……最初只有一部分。 项目完成后,我认为在吉他上独奏会很有趣,录制后,我想像音乐会在演示中飞舞的同时播放。 几个月后,我设法使用了它。 作品中的吉他是Fender Strat I扇贝

在文章发表的前一天,我记录了第二篇作品。 吉他的独奏声音有点...醉了,因为它是用我变成无品格的吉他弹奏的。 我不太擅长播放,高音很快消失,但是幻灯片听起来很酷。 我的Yngwie想要套件上演奏有节奏的部分(便宜的扇贝形Squier Strat,DOD YJM308超速档和由9伏电池供电的Mini-Marshall)。

我使用写得很久的Drums ++程序为两种乐曲编写了 。 它接收以我自己的语言录制的输入文本文件,并将它们转换为.mid文件,我将其导入到Apple Garage Band中,之后您可以录制基础音轨和吉他音轨。 源文件fretless.dppshoebox.dpp在我的演示资源库的Assets文件夹中。

音乐由Playstation 2 SPU2播放,并且借助R5900,它可以完成其他工作。 由于缺乏好的文档,我几乎没有任何音乐就完成了演示。 在下面的更多内容。

这是MP3格式的两条音轨:


发展历程


该项目已经开发了很长时间。 我开始在naken_asm中将R5900 Emotion Engine指令添加到MIPS汇编器,然后使用浮点指令和宏/微向量单元指令。 为了在其他项目上工作,我作了很大的休息,我研究了此演示所需的所有其他方面,并继续为Java Grinder添加他们的支持。 如果有人对底层细节感兴趣,那么我创建了一个页面,试图在该页面上记录所有收集到的信息: Playstation 2编程

我主要使用PCXS2仿真器进行编程。 这非常方便,因为我可以在其中检查屏幕上的寄存器等。 但是在开发Sega Genesis时,它绝对不像MAME那样灵活和简单。 例如,在MAME中,更容易检查内存,RAM和视频/音频寄存器,以确保Java Grinder正常工作。

使用Sega的代码时,我犯了一个错误-在编写演示之前,我没有在计算机上对其进行测试。 模拟器忽略了Sega代码中的至少三个怪异之处,但它们并不喜欢真实的机器。 这次,在编写了代码的各个部分之后,我在真实的机器上对其进行了测试,以便演示完成后,它可以在真实的设备和仿真器上运行。 我再次遇到了在仿真器中起作用的东西,但是没有在真正的PS2上开始。 我还发现它可以在真实的Playstation 2上运行,但在模拟器中的运行不正确。

API功能


  • 向量单元0具有用于加载/运行代​​码以及加载/卸载数据的方法。
  • 向量单元1执行3D旋转和投影。
  • 使用16位或24位格式的纹理(透明度以黑色表示)。
  • 可以对16位格式的纹理进行RLE编码。
  • 用于绘制带有或不带有纹理的点,线,三角形的代码。
  • 古罗的雾和阴影。
  • 用于访问随机数生成器的方法。
  • 使用两个上下文(替换页面)
  • 将大的二进制数据插入已编译的汇编代码中。
  • 播放音乐。

API


API的主要部分在Playstation2类中设置。 最初,我打算给他很大的自由度-可以设置视频模式等,但是后来我认为隐藏所有这些困难可能会更好。 本质上,它只是设置了隔行扫描的640x448显示器。 与其他Java Grinder项目一样,我基本上根据需要添加了方法/功能。

我给无聊的名称Draw3D提供了另一组类。 本质上,它们定义了图形合成器可以支持16位,24位和32位纹理的所有类型的图元。 我曾考虑添加8位纹理,但决定不这样做。 Draw3D提供3D旋转,投影,DMA硬件传输,纹理等。 您可能会问为什么我没有基于OpenGL创建它,但是我以前从未使用过OpenGL。 曾几何时,我从事简单的ps2dev编程,但是那里没有什么认真的,我几乎不记得那个项目,所以我重复一遍-我们可以假设这是我第一次在3D中做严肃的事情。

这里有一些使用所有这些东西的示例,不仅在演示中,而且在samples文件夹中。
API中几乎隐藏了所有困难。 开发人员无需担心清除缓存,3D计算等问题。 但是,这样做的回报是多功能性下降。 因此,例如,如果处理器更改了纹理,但仅更改了前64个像素,那么您只需要清除一条64字节的缓存行,而Java Grinder可以清除整个图像。 它标记对象,因此仅在必要时清除它们,但清除整个内存片段。 更改64个字节时,整个图像的更改几率也很高。

向量单位0(VU0)


Java Grinder用户可以自由使用VU0。 我使用了名为“两个向量单位,一个MIPS”的演示部件来渲染Mandelbrot分形。 可以更好地优化代码,例如,大多数向量单元浮点指令的运行时间为1,延迟为4。据我了解,这意味着,如果寄存器是指令的目标,则可以在1个周期内执行,但对于为了使结果可用,需要4个周期。 如果使用了该寄存器,则向量单元将保持空闲状态直到准备就绪。 因此,您的想法是需要安排指令,以便您可以在1个周期内执行每个指令而不会造成停机。 去年,当我在Playstation 3上创建Mandelbrot分形时 ,我在优化此代码的同时计算了8个像素(2个SIMD寄存器),并注意到速度有了很大提高。 在当前情况下,我试图使代码更易于阅读,因此我没有理会其优化。

VU0仅包含4 KB的数据存储器,因此您不会在其中写入整个分形图像,因此MIPS一次仅发送图像的1/8的坐标。

使用VU0时遇到的奇怪之处:我最初使用指令运行VU0代码,并使用VIF0_STAT验证其执行是否完成。 如果您不使用VIF软件包启动VU0,则似乎VIF0_STAT不起作用。 该问题已在仿真器中修复,但该错误仍然存​​在于实际硬件中。 结果,我发现在两种情况下,vcallms和在寄存器29中使用cfc2均有效。

在我看来,向量单元指令集缺少在Intel X86_64,Playstation 3甚至MIPS R5900 Emotion Engine指令集中找到的并行比较指令。 Mandelbrot分形必须迭代计算公式,直到结果更好为止,因此使用一组不同的指令,我将执行并行比较,从而为所有二进制1或0创建一个掩码。该掩码可用于停止颜色计数器的增量。 对于Playstation 2,我必须得出一个非常尴尬的公式,该公式与分形公式非常接近。 我在mandelbrot_vu0.asm的源代码中对此进行了文档记录,并注释掉了Python。

我在Playstation 2控制台的矢量单位设备中发现它很棒,我看不到任何其他SIMD指令集,在这些指令集中,所有FPU指令都可以具有.xyzw属性,告诉该指令它具有四个浮点数中的哪个影响。 也就是说,如果我需要指令的结果仅影响向量的x分量,则只需在指令末尾添加.x即可。 另一个有趣的事情是它是一组VLIW指令,也就是说,在每个周期中,两个指令同时执行。 实际上,该模块更像是DSP,而不是通用处理器。

向量单元1(VU1)


VG1由Java Grinder保留,用于执行3D旋转,移动和投影。 使用draw()方法将Draw3D对象传递给VU1,该方法使用矢量单位汇编器转换点并将其传输到Graphics Synthesizer。 VU1中的汇编程序代码可以更好地进行速度优化,但它适合我的用途,因此我决定使代码易于阅读(未优化)。

为了研究推算和转弯的公式,我使用了Wikipedia: 推算转弯

3D转换代码也以简单的.asm: rotation_vu1.asm文件形式存在于naken_asm存储库中。

MIPS R5900


在开始从事这个项目之前,我真的不喜欢MIPS指令集。 实际上,使用此CPU非常容易。 此CPU的Emotion Engine版本具有非常方便的功能。 最值得注意的是,寄存器的长度为128位,但实际上它们仅用于加载/存储和SIMD。 也就是说,实际上,它们是128位寄存器,64位ALU和32位指针。

也可以在MIPS主要代码中引入优化,但是我这样做并不是为了保持代码的可读性或时间不足。 例如,如果设置后立即使用了目标指令寄存器,则MIPS CPU空闲一个周期。 此行为可以改善。

Java黑客


Java Grinder本身也有自己的独特之处,但是却缺少一些东西,主要是因为我最初针对的是MSP430,而且大部分时间都是实验。 缺少的要素之一是无法为对象分配内存。 我在Playstation 2上添加了此功能,主要是使用Draw3D API实例化多个对象。 我没有编写任何内存分配器或垃圾收集器,因此所有新调用都在堆栈上进行。 我当时在考虑实现类似动态内存分配器的功能,但最终我决定不使其复杂化。 我还为Playstation 2添加了对浮点数(float)的扩展支持(部分支持仍在Epiphany / Parallella代码中)。 仍然不支持其他一些东西,例如long和double类型。

我做的最烦人的事可能与Java类文件的严格限制有关。 如果我没记错的话,Java方法不能大于64K。 也许这很正常,但是当类文件中有一个静态数组并且它没有作为二进制数据转储到类文件中时,就会出现问题。 它将它作为Java汇编器指令放在静态初始化器中的类文件中,以创建数组。 我试图将图像作为静态字节[]数组保存到类文件中,但是其中一些不适合,因此我向Memory Java Grinder类文件添加了一个方法:

byte[] Memory.preloadByteArray(String filename); 

它不会在运行时下载此文件,而是使用.binfile naken_asm指令在构建时下载它。 图像在组装过程中被复制到输出二进制文件中。

考虑到所有这些,我真的希望James Gosling永远不会迷失我的项目。

图片


Draw3D API可以使用16位,24位和32位纹理。 纹理像素可以逐像素设置,也可以使用byte []数组加载。 我还添加了以{length,lo16,hi16}格式对图像进行RLE压缩的功能,其中lo16和hi16是little endian格式的16位颜色,将其复制到纹理“ length”时间。

工具


在Sega上创建用于创建图像,音乐等的工具时,我使用Google Go语言只是为了学习一种新语言。 这次我尝试了Rust。 第一个工具将二进制文件转换为Java源代码,第二个工具将BMP转换为二进制格式,可以将其加载到纹理中,包括RLE格式。 结果,我用Python编写了它们,以防万一有人想和我一起创建演示。

音效


确定了图形和矢量单位设备的工作原理后,最后一步就是声音。 我认为这将是最简单的部分,尤其是在使用Sony SPU2描述研究PDF之后。 我错了 系统的这一部分记录很少。

我发现的第一件事是SPU2(声音处理单元)连接到IOP(I / O处理器,又称Playstation 1处理器)。 Playstation 2 CPU通过称为SIF的方式连接到此IOP。 索尼的PDF仅提及SIF DMA,但未提及其用法。

结果,我拒绝使用SIF,但决定将链接器添加到naken_asm,以便可以使用PS2DEV SDK中的kernel.a。 链接器已获得,但失败了。

在这个阶段,我已经决定无法使声音正常工作,我只想在没有它的情况下完成演示。 但这让我很痛苦,所以我决定看一下各种Playstation 2仿真器的源代码,以了解SIF的工作原理。 最后,我弄清楚了如何直接从IOP中的MIPS R3000代码访问内存并运行它(naken_asm存储库的samples文件夹中有一个示例)。 我设法使声音可以在模拟器中工作。

最后,我发现IOP内存​​(包括SPU2)位于Emotion Engine空间中,因此我付出了很多努力(文档非常小,没有一个仿真器可以完全正确地实现它,但是对于他们来说,工作并不重要),我学会了使用声音。

仿真器与铁器的比较


我发现在真实计算机和仿真器中执行之间存在一些差异。

  • 如果GIF软件包将PRIM寄存器设置为两个IIP值(着色方法),并且FIX位均为1,则仿真器将考虑IIP位并执行Gouro着色,而实际设备将执行平坦着色。
  • 如果GIF数据包是通过PATH3(EE直达GS)传输的,并且未设置EOP(数据包结束)标志,则如果VU1尝试通过PATH 1(VU1到GS)发送GIF数据包,这将导致实际硬件死机,但是将在模拟器中工作。
  • 无需在DMA传输之前跳过清除CPU缓存的操作,但是在实际的计算机上会导致奇怪的行为。
  • 将SPU2放入EE空间时,仿真器可以简单地将音频数据记录在FIFO SPU2中。 在实际的Playstation 2上,在记录32个字之后,有必要写入寄存器以发出清除FIFO的命令。 同样,在实际硬件上,当设置SPU2的发送/开始地址时,发送模式应设置为0。仿真器不在乎该模式的值是否为0。
  • 即使在内核模式下,将EE中的IOP从EE写入分配的内存寄存器也会在实际计算机上崩溃。 无论当前的CPU模式如何,仿真器都可以使此类操作正常工作。
  • 在仿真器中可以使用SIF DMA通道,但是我仍然无法使它们在实际设备上运行。 即使在内核模式下,我也收到SIF DMA寄存器的内存访问错误。
  • 使用VU0计算分形时,仿真器太慢,无法运行演示,因此声音不同步。

总结一下


我想几乎在购买Playstation 2的那一刻就编写一些程序。 实际上,我已经有很长时间的PS2 Linux套件了(我认为这就是我购买Playstation 2的原因),甚至我尝试使用C编写PS2DEV库,但是与直接使用汇编语言进行编程相比,这是完全不同的体验。铁。

我必须感谢Lukash保存了旧的汇编程序源代码和PS2文档。 不知道如果没有Duke 3 Star演示,我是否可以入门,它可以帮助我初始化设备。 我也感谢PCSX2仿真器的开发人员,他们极大地加快了调试过程。 另外,如果我没有看过模拟器的源代码并且不了解出了什么问题,我将无法弄清楚声音。

还要感谢索尼提供的这款精美的小型计算机。 如果Sony的某人读了这篇文章,这里有个提示:为什么不将其缩小到Rapsberry Pi的大小并作为爱好项目的板出售呢? :)。

制作演示


git clone https://github.com/mikeakohn/playstation2_demo.git
git clone https://github.com/mikeakohn/java_grinder.git
git clone https://github.com/mikeakohn/naken_asm.git
cd naken_asm
./configure
make
cd ..
cd java_grinder
make java
make
cd ..
cd playstation2_demo
make

Source: https://habr.com/ru/post/zh-CN423751/


All Articles