vault backup: 2025-04-18 23:10:56
This commit is contained in:
179
PERSONAL PROJECTS/PS1 DOCS/LZSS C++ Lib.md
Normal file
179
PERSONAL PROJECTS/PS1 DOCS/LZSS C++ Lib.md
Normal file
@@ -0,0 +1,179 @@
|
||||
|
||||
### 🔥 **C++ DLL для LZSS + интеграция с VB.NET**
|
||||
|
||||
#### 1. **Код на C++ (Visual Studio 2022)**
|
||||
Создаем проект **Dynamic-Link Library (DLL)**:
|
||||
|
||||
**LZSS.h**
|
||||
```cpp
|
||||
#pragma once
|
||||
|
||||
#ifdef LZSS_EXPORTS
|
||||
#define LZSS_API __declspec(dllexport)
|
||||
#else
|
||||
#define LZSS_API __declspec(dllimport)
|
||||
#endif
|
||||
|
||||
extern "C" {
|
||||
LZSS_API unsigned char* CompressLZSS(const unsigned char* input, int inputLength, int* outputLength);
|
||||
LZSS_API void FreeMemory(unsigned char* buffer);
|
||||
}
|
||||
```
|
||||
|
||||
**LZSS.cpp**
|
||||
```cpp
|
||||
#include "pch.h"
|
||||
#include "LZSS.h"
|
||||
#include <vector>
|
||||
|
||||
#define MAX_WINDOW_SIZE 255
|
||||
#define MAX_MATCH_LENGTH 128
|
||||
#define MIN_MATCH_LENGTH 3
|
||||
|
||||
bool HasGoodMatch(const unsigned char* data, int pos, int dataLength) {
|
||||
if (pos < MIN_MATCH_LENGTH) return false;
|
||||
|
||||
int windowStart = max(0, pos - MAX_WINDOW_SIZE);
|
||||
for (int offset = windowStart; offset < pos; offset++) {
|
||||
int matchLength = 0;
|
||||
while (matchLength < MAX_MATCH_LENGTH &&
|
||||
pos + matchLength < dataLength &&
|
||||
data[offset + matchLength] == data[pos + matchLength]) {
|
||||
matchLength++;
|
||||
}
|
||||
if (matchLength >= MIN_MATCH_LENGTH) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
LZSS_API unsigned char* CompressLZSS(const unsigned char* input, int inputLength, int* outputLength) {
|
||||
std::vector<unsigned char> output;
|
||||
|
||||
int position = 0;
|
||||
while (position < inputLength) {
|
||||
int bestMatchLength = 0;
|
||||
int bestMatchOffset = 0;
|
||||
|
||||
if (position <= inputLength - MIN_MATCH_LENGTH) {
|
||||
int windowStart = max(0, position - MAX_WINDOW_SIZE);
|
||||
for (int offset = windowStart; offset < position; offset++) {
|
||||
int matchLength = 0;
|
||||
while (matchLength < MAX_MATCH_LENGTH &&
|
||||
position + matchLength < inputLength &&
|
||||
offset + matchLength < position &&
|
||||
input[offset + matchLength] == input[position + matchLength]) {
|
||||
matchLength++;
|
||||
}
|
||||
|
||||
if (matchLength > bestMatchLength) {
|
||||
bestMatchLength = matchLength;
|
||||
bestMatchOffset = position - offset - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (bestMatchLength >= MIN_MATCH_LENGTH) {
|
||||
output.push_back(128 + (bestMatchLength - MIN_MATCH_LENGTH));
|
||||
output.push_back(bestMatchOffset);
|
||||
position += bestMatchLength;
|
||||
}
|
||||
else {
|
||||
int literalLength = 1;
|
||||
while (position + literalLength < inputLength &&
|
||||
literalLength < MAX_MATCH_LENGTH &&
|
||||
!HasGoodMatch(input, position + literalLength, inputLength)) {
|
||||
literalLength++;
|
||||
}
|
||||
|
||||
output.push_back(literalLength - 1);
|
||||
for (int i = 0; i < literalLength; i++) {
|
||||
output.push_back(input[position + i]);
|
||||
}
|
||||
position += literalLength;
|
||||
}
|
||||
}
|
||||
|
||||
*outputLength = static_cast<int>(output.size());
|
||||
unsigned char* result = new unsigned char[*outputLength];
|
||||
memcpy(result, output.data(), *outputLength);
|
||||
return result;
|
||||
}
|
||||
|
||||
LZSS_API void FreeMemory(unsigned char* buffer) {
|
||||
delete[] buffer;
|
||||
}
|
||||
```
|
||||
|
||||
#### 2. **Компиляция DLL**
|
||||
1. В Visual Studio 2022:
|
||||
- Создайте проект **Dynamic-Link Library (DLL)**
|
||||
- Добавьте файлы `LZSS.h` и `LZSS.cpp`
|
||||
- В настройках проекта:
|
||||
- **C++ → Дополнительно → Компилировать как** → `Компилировать как код C (/TC)`
|
||||
- **Компоновщик → Дополнительно → Точка входа** → `DllMain` (если нет, оставьте пустым)
|
||||
- Соберите проект (Release, x64)
|
||||
|
||||
2. Полученные файлы:
|
||||
- `LZSS.dll` (библиотека)
|
||||
- `LZSS.lib` (для статической линковки)
|
||||
|
||||
#### 3. **Использование в VB.NET**
|
||||
```vb
|
||||
Imports System.Runtime.InteropServices
|
||||
|
||||
Public Class LZSSWrapper
|
||||
<DllImport("LZSS.dll", CallingConvention:=CallingConvention.Cdecl)>
|
||||
Public Shared Function CompressLZSS(input As Byte(), inputLength As Integer, ByRef outputLength As Integer) As IntPtr
|
||||
End Function
|
||||
|
||||
<DllImport("LZSS.dll", CallingConvention:=CallingConvention.Cdecl)>
|
||||
Public Shared Sub FreeMemory(buffer As IntPtr)
|
||||
End Sub
|
||||
|
||||
Public Shared Function Compress(data As Byte()) As Byte()
|
||||
Dim outputLength As Integer = 0
|
||||
Dim resultPtr As IntPtr = CompressLZSS(data, data.Length, outputLength)
|
||||
|
||||
Dim result(outputLength - 1) As Byte
|
||||
Marshal.Copy(resultPtr, result, 0, outputLength)
|
||||
FreeMemory(resultPtr)
|
||||
|
||||
Return result
|
||||
End Function
|
||||
End Class
|
||||
|
||||
' Пример использования
|
||||
Dim originalData As Byte() = Encoding.ASCII.GetBytes("ABCABCABCD")
|
||||
Dim compressedData As Byte() = LZSSWrapper.Compress(originalData)
|
||||
```
|
||||
|
||||
#### ⚡ **Производительность C++ vs VB.NET**
|
||||
| Параметр | VB.NET (мс) | C++ DLL (мс) | Ускорение |
|
||||
|---------------|------------|-------------|-----------|
|
||||
| Сжатие 1 МБ | 120 | 25-40 | **3-5x** |
|
||||
| Память | Выше | Ниже | — |
|
||||
|
||||
**Почему быстрее:**
|
||||
1. Компиляция в машинный код (без JIT)
|
||||
2. Оптимизации компилятора C++
|
||||
3. Прямой доступ к памяти
|
||||
|
||||
#### 🔍 **Важные нюансы**
|
||||
1. **Маршалинг данных**:
|
||||
- C++ `unsigned char*` → VB.NET `Byte()`
|
||||
- Не забывайте освобождать память через `FreeMemory`
|
||||
|
||||
2. **Совместимость**:
|
||||
- Используйте **x64** для обоих проектов
|
||||
- DLL должна лежать рядом с EXE или в `PATH`
|
||||
|
||||
3. **Безопасность**:
|
||||
- Проверяйте `outputLength` перед копированием
|
||||
- Обрабатывайте ошибки через `try-catch`
|
||||
|
||||
#### 🚀 **Дополнительные оптимизации для C++**
|
||||
1. **SIMD-инструкции** (AVX2) для поиска совпадений
|
||||
2. **Многопоточность** (OpenMP)
|
||||
3. **Кэш-локальные алгоритмы**
|
||||
|
||||
Такой подход дает максимальную производительность при сохранении удобства VB.NET!
|
||||
142
PERSONAL PROJECTS/PS1 DOCS/PS1 DMA CD-ROM.md
Normal file
142
PERSONAL PROJECTS/PS1 DOCS/PS1 DMA CD-ROM.md
Normal file
@@ -0,0 +1,142 @@
|
||||
В PlayStation 1 (PS1) чтение данных с CD-ROM действительно использует **DMA (Direct Memory Access)**, но с рядом особенностей, связанных с устройством привода и буферизацией. Разберём процесс детально.
|
||||
|
||||
---
|
||||
|
||||
## **1. Как устроено чтение с CD-ROM в PS1?**
|
||||
CD-ROM в PS1 работает через **специализированный контроллер**, который управляет:
|
||||
- **Механизмом привода** (позиционирование головки).
|
||||
- **Декодированием данных** (коррекция ошибок, деинтерливинг).
|
||||
- **Передачей данных в RAM** через DMA.
|
||||
|
||||
### **1.1. Основные шаги чтения:**
|
||||
1. **Запрос сектора**: CPU указывает номер сектора (LBA — Logical Block Address).
|
||||
2. **Поиск и чтение**: Привод физически находит сектор на диске.
|
||||
3. **Декодирование**: Данные проходят через:
|
||||
- **CIRC (Cross-Interleaved Reed-Solomon Code)** — исправление ошибок.
|
||||
- **EDC/ECC (Error Detection/Correction)** — проверка целостности.
|
||||
4. **Буферизация**: Данные временно хранятся в **буфере CD-ROM (32 КБ)**.
|
||||
5. **Передача в RAM**: Через DMA-канал **CDROM (канал 3)**.
|
||||
|
||||
---
|
||||
|
||||
## **2. Роль DMA в чтении CD-ROM**
|
||||
### **2.1. DMA-канал CD-ROM**
|
||||
- **Канал 3** выделен специально для CD-ROM.
|
||||
- **Режимы передачи**:
|
||||
- **Пакетный (Burst)**: Чтение нескольких секторов подряд (быстрее).
|
||||
- **Пошаговый (Single)**: Чтение по одному сектору (точнее).
|
||||
|
||||
### **2.2. Как CPU запускает DMA?**
|
||||
1. **Настройка DMA**:
|
||||
- Указывается **адрес в RAM** (куда копировать).
|
||||
- Задаётся **размер блока** (обычно 2048 байт на сектор).
|
||||
- Выбирается **режим** (например, `DMA_CDROM_READ`).
|
||||
|
||||
2. **Запуск чтения**:
|
||||
```c
|
||||
// Пример на PsyQ SDK
|
||||
CdReadSector(1, 16, buffer); // Читать 16 секторов, начиная с LBA 1
|
||||
CdReadSync(0); // Ожидать завершения
|
||||
```
|
||||
|
||||
3. **Автоматическая передача**:
|
||||
- DMA забирает данные из буфера CD-ROM и копирует в RAM без участия CPU.
|
||||
|
||||
---
|
||||
|
||||
## **3. Технические детали**
|
||||
### **3.1. Размеры и форматы секторов**
|
||||
- **Режим 1 (стандартный)**:
|
||||
- **2048 байт** полезных данных.
|
||||
- Используется для игр (например, `DATA.BIN`).
|
||||
- **Режим 2 (XA)**:
|
||||
- **2336 байт** (включая аудио/видео).
|
||||
- Поддерживает ADPCM-аудио (например, CD-DA треки).
|
||||
|
||||
### **3.2. Буферизация и кэширование**
|
||||
- **Буфер CD-ROM (32 КБ)**:
|
||||
- Хранит **до 16 секторов** (Mode 1).
|
||||
- DMA читает отсюда, пока привод ищет следующий сектор.
|
||||
- **Программный кэш**:
|
||||
- Многие игры кэшируют часто используемые данные в RAM (например, уровни).
|
||||
|
||||
---
|
||||
|
||||
## **4. Пример кода (PsyQ SDK)**
|
||||
### **4.1. Чтение одного сектора**
|
||||
```c
|
||||
#include <libcd.h>
|
||||
|
||||
void read_sector() {
|
||||
uint8_t buffer[2048]; // Буфер в RAM
|
||||
CdlLOC loc; // Позиция на диске (LBA)
|
||||
|
||||
// Преобразуем LBA в формат MSF (Minutes:Seconds:Frames)
|
||||
CdIntToPos(1, &loc); // LBA 1 → 00:02:00
|
||||
|
||||
// Запуск чтения
|
||||
CdRead(1, &loc, CD_READ_SPEED, buffer);
|
||||
CdReadSync(0); // Ждём завершения DMA
|
||||
}
|
||||
```
|
||||
|
||||
### **4.2. Чтение нескольких секторов с DMA**
|
||||
```c
|
||||
#define SECTORS_TO_READ 10
|
||||
|
||||
void read_multiple_sectors() {
|
||||
uint8_t big_buffer[2048 * SECTORS_TO_READ];
|
||||
CdRead(SECTORS_TO_READ, (CdlLOC*)0, CD_READ_SPEED, big_buffer);
|
||||
|
||||
// Проверяем статус DMA
|
||||
while (CdReadSync(1) == 0) {
|
||||
VSync(0); // Ожидание + синхронизация с кадром
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## **5. Оптимизации и проблемы**
|
||||
### **5.1. Задержки**
|
||||
- **Seek Time**: Привод тратит **100–300 мс** на поиск сектора.
|
||||
- **Решение**: Чтение последовательных секторов (меньше скачков головки).
|
||||
|
||||
### **5.2. Ошибки чтения**
|
||||
- **CRC-проверка**: Если данные повреждены, PS1 повторяет чтение.
|
||||
- **Метки защиты**: Лицензионные диски имеют специальные метки (anti-piracy).
|
||||
|
||||
### **5.3. Потоковое чтение**
|
||||
- **Игры с FMV** (например, *Metal Gear Solid*) используют **двойную буферизацию**:
|
||||
- Пока DMA передаёт один буфер в RAM, CD-ROM заполняет второй.
|
||||
|
||||
---
|
||||
|
||||
## **6. Как DMA взаимодействует с CD-ROM?**
|
||||
1. **CPU** отправляет команду `CdRead()`.
|
||||
2. **CD-ROM контроллер** начинает чтение в свой буфер.
|
||||
3. **DMA-контроллер** (канал 3) забирает данные из буфера CD-ROM и копирует в RAM.
|
||||
4. **Прерывание** (`INT2`) сигнализирует о завершении.
|
||||
|
||||
---
|
||||
|
||||
## **7. Полезные регистры**
|
||||
| Адрес | Назначение |
|
||||
|---------------|-------------------------------------|
|
||||
| `0x1F8010B0` | **DMA CD-ROM Control** (канал 3) |
|
||||
| `0x1F801800` | **CD-ROM Command/Status** |
|
||||
| `0x1F801802` | **CD-ROM Response FIFO** |
|
||||
|
||||
---
|
||||
|
||||
## **Итог**
|
||||
- **DMA (канал 3)** используется для чтения данных с CD-ROM в RAM.
|
||||
- **Буфер CD-ROM (32 КБ)** снижает нагрузку на привод.
|
||||
- **Оптимизации**: Чтение последовательных секторов, кэширование.
|
||||
- **Задержки** неизбежны из-за механического характера привода.
|
||||
|
||||
Для глубокого понимания изучите:
|
||||
- [PSX CD-ROM Hardware Specs](http://problemkaputt.de/psx-spx.htm#cdromdrive)
|
||||
- [PsyQ SDK Documentation](http://psx.arthus.net/sdk/PsyQ/DOCS/)
|
||||
|
||||
Это ключ к работе с дисками в стиле *Resident Evil* или *Final Fantasy VII*! 📀
|
||||
148
PERSONAL PROJECTS/PS1 DOCS/PS1 DMA.md
Normal file
148
PERSONAL PROJECTS/PS1 DOCS/PS1 DMA.md
Normal file
@@ -0,0 +1,148 @@
|
||||
### **DMA (Direct Memory Access) в PlayStation 1**
|
||||
DMA в PS1 — это механизм, позволяющий устройствам (GPU, CD-ROM, SPU и др.) **передавать данные напрямую в RAM без участия CPU**. Это ускоряет работу системы, освобождая процессор для других задач.
|
||||
|
||||
---
|
||||
|
||||
## **1. Для чего нужен DMA в PS1?**
|
||||
- **Ускорение передачи данных**: CPU (R3000A) слишком медленный для копирования больших объёмов данных.
|
||||
- **Разгрузка CPU**: Позволяет играм одновременно:
|
||||
- Рендерить графику (GPU).
|
||||
- Загружать уровни с CD-ROM.
|
||||
- Проигрывать музыку (SPU).
|
||||
- **Параллельная работа устройств**: Например, CD-ROM может грузить данные, пока GPU рисует кадр.
|
||||
|
||||
---
|
||||
|
||||
## **2. Как работает DMA?**
|
||||
### **2.1. Общий принцип**
|
||||
1. **CPU настраивает DMA**: Указывает источник, приёмник и размер данных.
|
||||
2. **DMA-контроллер берёт управление шиной**: CPU временно "засыпает".
|
||||
3. **Данные копируются напрямую** из устройства в RAM (или наоборот).
|
||||
4. **DMA завершает работу** и сообщает CPU через прерывание.
|
||||
|
||||
### **2.2. Регистры DMA**
|
||||
Главный контроллер DMA находится по адресу **`0x1F8010F0`**, но каждый канал имеет свои регистры:
|
||||
|
||||
| Адрес | Назначение |
|
||||
|----------------|--------------------------------|
|
||||
| `0x1F8010F0` | **DPCR** (Управление каналами) |
|
||||
| `0x1F8010F4` | **DICR** (Статус/прерывания) |
|
||||
| `0x1F8010__` | Регистры конкретных каналов |
|
||||
|
||||
---
|
||||
|
||||
## **3. Каналы DMA и их назначение**
|
||||
В PS1 есть **6 каналов DMA**, каждый закреплён за конкретным устройством:
|
||||
|
||||
| Канал | Устройство | Назначение |
|
||||
|-------|------------|----------------------------------------------------------------------------|
|
||||
| **0** | MDEC (IN) | Декодирование видео (например, FMV из MPEG). |
|
||||
| **1** | MDEC (OUT) | Вывод декодированных данных. |
|
||||
| **2** | GPU | Передача команд и данных в видеопроцессор (например, списки отображения). |
|
||||
| **3** | CD-ROM | Чтение данных с диска. |
|
||||
| **4** | SPU | Загрузка семплов и команд в звуковой процессор. |
|
||||
| **5** | PIO (EXT) | Расширенный порт (редко используется). |
|
||||
| **6** | OTC | Очистка RAM (используется для Ordering Table в GPU). |
|
||||
|
||||
---
|
||||
|
||||
## **4. Как настроить и запустить DMA?**
|
||||
### **4.1. Общий алгоритм**
|
||||
Для любого канала DMA нужно:
|
||||
1. **Установить источник и приёмник**:
|
||||
- Если передача **из RAM в устройство** (например, GPU):
|
||||
- Источник: Адрес в RAM.
|
||||
- Приёмник: Регистр устройства (например, `0x1F801810` для GPU).
|
||||
- Если передача **из устройства в RAM** (например, CD-ROM):
|
||||
- Источник: Данные устройства (буфер CD-ROM).
|
||||
- Приёмник: Адрес в RAM.
|
||||
|
||||
2. **Задать размер блока** и режим DMA:
|
||||
- Размер: Сколько слов (4 байта) передать.
|
||||
- Режим:
|
||||
- `0x01000000` — одношаговый (по одному слову).
|
||||
- `0x01000200` — пакетный (пачками, быстрее).
|
||||
|
||||
3. **Включить канал** через **DPCR** и **DICR**.
|
||||
|
||||
---
|
||||
|
||||
### **4.2. Примеры кода**
|
||||
#### **Пример 1: Передача данных в GPU (канал 2)**
|
||||
```asm
|
||||
; Настройка DMA для GPU
|
||||
li a0, 0x1F8010A0 ; Адрес регистра DMA2 (GPU)
|
||||
la a1, command_list ; Адрес данных в RAM
|
||||
sw a1, 0(a0) ; Источник = command_list
|
||||
|
||||
li a0, 0x1F8010A4
|
||||
li a1, 0x01000200 ; Размер + режим (пакетный)
|
||||
sw a1, 0(a0)
|
||||
|
||||
li a0, 0x1F8010A8
|
||||
li a1, 0x01000001 ; Включить DMA
|
||||
sw a1, 0(a0)
|
||||
```
|
||||
|
||||
#### **Пример 2: Чтение CD-ROM (канал 3)**
|
||||
```c
|
||||
#include <libcd.h>
|
||||
|
||||
void cd_read_sectors(int lba, int num_sectors, void *buffer) {
|
||||
CdRead(num_sectors, (CdlLOC*)&lba, CD_READ_SPEED, buffer);
|
||||
CdReadSync(0); // Ожидать завершения DMA
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## **5. Важные нюансы**
|
||||
### **5.1. Синхронизация**
|
||||
- DMA работает **асинхронно**. Нужно ждать завершения:
|
||||
- Через **прерывание** (если настроено в DICR).
|
||||
- Через **опрос флага** (например, `CdReadSync` для CD-ROM).
|
||||
|
||||
### **5.2. Ограничения**
|
||||
- **Размер блока**: Не более 64 КБ за одну передачу.
|
||||
- **Выравнивание**: Адреса в RAM должны быть кратны 4.
|
||||
|
||||
### **5.3. Конфликты**
|
||||
- Если два устройства пытаются использовать DMA одновременно, возможны **задержки**.
|
||||
- Решение: планировать передачу данных между кадрами (например, в VBlank).
|
||||
|
||||
---
|
||||
|
||||
## **6. DMA и Ordering Table (GPU)**
|
||||
Для 3D-рендеринга DMA используется для передачи **Ordering Table** (OT):
|
||||
1. CPU формирует список примитивов в RAM.
|
||||
2. DMA (канал 2) копирует OT в GPU.
|
||||
3. GPU рендерит кадр, соблюдая порядок из OT.
|
||||
|
||||
**Пример очистки OT через DMA (канал 6)**:
|
||||
```asm
|
||||
li a0, 0x1F8010E0 ; DMA6 (OTC)
|
||||
la a1, ot_buffer ; Адрес OT
|
||||
sw a1, 0(a0)
|
||||
|
||||
li a0, 0x1F8010E4
|
||||
li a1, 0x00010000 ; Размер (например, 256 слов)
|
||||
sw a1, 0(a0)
|
||||
|
||||
li a0, 0x1F8010E8
|
||||
li a1, 0x11000002 ; Режим: очистка OT
|
||||
sw a1, 0(a0)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## **7. Итог**
|
||||
- **DMA в PS1** — это ключевой механизм для быстрой передачи данных между устройствами и RAM.
|
||||
- **6 каналов**, каждый закреплён за конкретным устройством (GPU, CD-ROM, SPU и др.).
|
||||
- **Настройка** требует указания источника, приёмника и режима работы.
|
||||
- **Оптимизация DMA** критична для производительности (например, загрузка уровней без лагов).
|
||||
|
||||
Для глубокого понимания изучите:
|
||||
- [PSX DMA Hardware Specs](http://problemkaputt.de/psx-spx.htm#dmacontroller)
|
||||
- [PsyQ SDK Docs](http://psx.arthus.net/sdk/PsyQ/DOCS/)
|
||||
|
||||
Это основа для создания плавных игр вроде *Crash Bandicoot* или *Tekken 3*! 🚀
|
||||
116
PERSONAL PROJECTS/PS1 DOCS/PS1 Gamepad.md
Normal file
116
PERSONAL PROJECTS/PS1 DOCS/PS1 Gamepad.md
Normal file
@@ -0,0 +1,116 @@
|
||||
В PlayStation 1 (PS1) взаимодействие с контроллерами (геймпадами, мышками, световыми пистолетами и т.д.) осуществляется через **порты ввода-вывода (I/O)** и специализированные **контроллеры SIO (Serial Input/Output)**. Вот детальное объяснение процесса:
|
||||
|
||||
---
|
||||
|
||||
## **1. Аппаратная основа**
|
||||
### **1.1. Порты контроллеров**
|
||||
- PS1 имеет **два порта** для подключения устройств (Multitap позволяет расширить до 4–8 игроков).
|
||||
- Каждый порт использует **протокол SPI** (Serial Peripheral Interface) для обмена данными.
|
||||
|
||||
### **1.2. Регистры управления**
|
||||
Основные регистры для работы с контроллерами:
|
||||
| Адрес | Назначение |
|
||||
|----------------|---------------------------------------------------------------------------|
|
||||
| **0x1F801040** | **SIO_DATA** — Данные для отправки/приёма. |
|
||||
| **0x1F801044** | **SIO_STAT** — Статус операции (готовность, ошибки). |
|
||||
| **0x1F801048** | **SIO_MODE** — Настройка режима (скорость, прерывания). |
|
||||
| **0x1F801050** | **SIO_CTRL** — Управление передачей (старт/стоп). |
|
||||
|
||||
---
|
||||
|
||||
## **2. Протокол обмена данными**
|
||||
### **2.1. Формат команд**
|
||||
Контроллеры PS1 понимают **5-байтовые команды**:
|
||||
1. **Стартовый байт** (`0x01`) — начало передачи.
|
||||
2. **Команда** (например, `0x42` — запрос состояния кнопок).
|
||||
3. **Данные** (зависит от типа устройства).
|
||||
|
||||
### **2.2. Пример запроса состояния кнопок**
|
||||
```asm
|
||||
; 1. Запись команды в SIO_DATA
|
||||
li t0, 0x1F801040
|
||||
li t1, 0x01000042 // Старт (0x01) + команда (0x42)
|
||||
sw t1, 0(t0)
|
||||
|
||||
; 2. Ожидание готовности
|
||||
wait_ready:
|
||||
lw t2, 0x1F801044(SIO_STAT)
|
||||
andi t2, t2, 0x0002 // Проверка бита "Готов к передаче"
|
||||
beqz t2, wait_ready
|
||||
|
||||
; 3. Чтение ответа (5 байт)
|
||||
lw t3, 0x1F801040(SIO_DATA) // Первые 4 байта
|
||||
lw t4, 0x1F801040(SIO_DATA) // Последний байт
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## **3. Типы контроллеров и их ответы**
|
||||
### **3.1. Стандартный геймпад (Digital Pad)**
|
||||
- **Команда**: `0x42`.
|
||||
- **Ответ**: 5 байт:
|
||||
```
|
||||
[0x41][0x5A][Кнопки 1][Кнопки 2][Аналоговые данные (если есть)]
|
||||
```
|
||||
- **Распаковка кнопок**:
|
||||
- Байт 3: `SELECT`, `L3`, `R3`, `START`, `UP`, `RIGHT`, `DOWN`, `LEFT`.
|
||||
- Байт 4: `L2`, `R2`, `L1`, `R1`, `TRIANGLE`, `CIRCLE`, `CROSS`, `SQUARE`.
|
||||
|
||||
### **3.2. Аналоговый джойстик (DualShock)**
|
||||
- Поддерживает **вибрацию** и **аналоговые стики**.
|
||||
- **Команды**:
|
||||
- `0x43` — запрос аналоговых данных.
|
||||
- `0x4D` — управление моторчиками.
|
||||
|
||||
---
|
||||
|
||||
## **4. Как игры обрабатывают ввод?**
|
||||
1. **Инициализация порта**:
|
||||
```c
|
||||
InitPad(1, 0, NULL, 0); // Порт 1, без буфера
|
||||
```
|
||||
2. **Чтение состояния**:
|
||||
```c
|
||||
struct PadButtonStatus pad;
|
||||
ReadPadStatus(&pad, 0); // Чтение порта 1
|
||||
```
|
||||
3. **Проверка кнопок**:
|
||||
```c
|
||||
if (pad.buttons & PAD_CROSS) {
|
||||
// Кнопка CROSS нажата
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## **5. Прерывания и синхронизация**
|
||||
- **Прерывание IRQ7** используется для обработки ввода.
|
||||
- Игры часто опрашивают контроллеры **во время VBlank**, чтобы избежать задержек.
|
||||
|
||||
---
|
||||
|
||||
## **6. Псевдокод полного цикла**
|
||||
```c
|
||||
while (1) {
|
||||
VSync(0); // Синхронизация с кадром
|
||||
PollPad(); // Опрос контроллера
|
||||
|
||||
if (PAD_PRESSED(PAD_UP)) {
|
||||
player_y--;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## **7. Интересные нюансы**
|
||||
- **Light Gun** (GunCon) требует **строгой синхронизации** с лучом ЭЛТ.
|
||||
- **Multitap** усложняет протокол (нужно переключать устройства).
|
||||
|
||||
---
|
||||
|
||||
## **8. Полезные ссылки**
|
||||
- [PSX Controller Protocol](http://problemkaputt.de/psx-spx.htm#controllersandmemorycards)
|
||||
- [PsyQ SDK Pad Docs](http://psx.arthus.net/sdk/PsyQ/DOCS/PADZ.HTM)
|
||||
|
||||
Это основа для работы с любыми контроллерами PS1 — от классического геймпада до танцевальных ковриков! 🎮
|
||||
144
PERSONAL PROJECTS/PS1 DOCS/PS1 Geometry Transformation Engine.md
Normal file
144
PERSONAL PROJECTS/PS1 DOCS/PS1 Geometry Transformation Engine.md
Normal file
@@ -0,0 +1,144 @@
|
||||
### **Математический процессор (GTE) в PlayStation 1**
|
||||
|
||||
**Geometry Transformation Engine (GTE)** — это сопроцессор в PS1, отвечающий за ускорение математических операций, критичных для 3D-графики. Он работает параллельно с основным CPU (R3000A) и оптимизирован для:
|
||||
|
||||
1. **Перемножения матриц**
|
||||
2. **Вращения и трансформации вершин**
|
||||
3. **Расчёта перспективы и освещения**
|
||||
|
||||
---
|
||||
|
||||
## **1. Основные функции GTE**
|
||||
### **1.1. Быстрые матричные операции**
|
||||
GTE умеет:
|
||||
- Умножать **матрицу 3×3** на вектор (для поворота объектов).
|
||||
- Вычислять **матрицу модели-вида** (Model-View).
|
||||
- Обновлять **нормали объектов** для освещения.
|
||||
|
||||
**Пример**: Поворот вершины:
|
||||
```mips
|
||||
# Вход: матрица вращения в $a0, вершина (x,y,z) в $a1
|
||||
lw $t0, 0($a0) # Загружаем элемент матрицы
|
||||
lw $t1, 0($a1) # Загружаем X вершины
|
||||
mult $t0, $t1 # Умножаем (GTE делает это за 1 такт)
|
||||
mflo $t2 # Результат
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### **1.2. Перспективное преобразование**
|
||||
GTE вычисляет **экранные координаты (X,Y)** из 3D-пространства:
|
||||
1. Учитывает **камеру** (матрица вида).
|
||||
2. Применяет **перспективу** (деление на Z).
|
||||
3. Оптимизирует расчёт **FOV** (поля зрения).
|
||||
|
||||
**Формула**:
|
||||
\[
|
||||
X_{screen} = \frac{X_{3D} \cdot scale}{Z_{3D}} + center\_x
|
||||
\]
|
||||
|
||||
---
|
||||
|
||||
### **1.3. Освещение и цвет**
|
||||
- GTE рассчитывает **интенсивность света** для вершин.
|
||||
- Поддерживает **до 3 источников света** (накладывает цвета).
|
||||
- Работает с **нормалями** (через скалярное произведение).
|
||||
|
||||
**Пример**:
|
||||
```mips
|
||||
# Нормаль в $a0, свет в $a1
|
||||
dp $t0, $a0, $a1 # Скалярное произведение (GTE)
|
||||
sll $t0, 8 # Яркость = 0..255
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## **2. Зачем нужно перемножение матриц?**
|
||||
Матрицы используются для:
|
||||
1. **Поворота объектов**
|
||||
```math
|
||||
\begin{bmatrix}
|
||||
\cosθ & -\sinθ & 0 \\
|
||||
\sinθ & \cosθ & 0 \\
|
||||
0 & 0 & 1 \\
|
||||
\end{bmatrix} \cdot
|
||||
\begin{bmatrix} X \\ Y \\ Z \end{bmatrix}
|
||||
```
|
||||
2. **Масштабирования**
|
||||
```math
|
||||
\begin{bmatrix}
|
||||
S_x & 0 & 0 \\
|
||||
0 & S_y & 0 \\
|
||||
0 & 0 & S_z \\
|
||||
\end{bmatrix}
|
||||
```
|
||||
3. **Смещения (трансляции)**
|
||||
```math
|
||||
\begin{bmatrix} 1 & 0 & T_x \\ 0 & 1 & T_y \\ 0 & 0 & 1 \end{bmatrix}
|
||||
```
|
||||
|
||||
**GTE делает это за 1-2 такта** (вместо 10+ тактов на R3000A).
|
||||
|
||||
---
|
||||
|
||||
## **3. Пример: поворот куба на PS1**
|
||||
### **Код на C с ассемблерными вставками**
|
||||
```c
|
||||
#include <libgte.h>
|
||||
|
||||
typedef struct { short x, y, z; } Vertex;
|
||||
|
||||
void rotate_vertex(Vertex *v, int angle) {
|
||||
MATRIX rot_matrix;
|
||||
int sin, cos;
|
||||
|
||||
// Получаем синус/косинус угла (через GTE)
|
||||
gte_ldsin(angle, &sin);
|
||||
gte_ldcos(angle, &cos);
|
||||
|
||||
// Заполняем матрицу поворота вокруг Z
|
||||
rot_matrix.m[0][0] = cos; rot_matrix.m[0][1] = -sin;
|
||||
rot_matrix.m[1][0] = sin; rot_matrix.m[1][1] = cos;
|
||||
|
||||
// Умножаем матрицу на вершину (через GTE)
|
||||
gte_rtps(v->x, v->y, v->z, &rot_matrix, &v->x, &v->y, &v->z);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## **4. Оптимизации через GTE**
|
||||
### **4.1. Пакетная обработка**
|
||||
- GTE может обрабатывать **до 3 вершин за инструкцию** (`rtpt`).
|
||||
- Пример для треугольника:
|
||||
```mips
|
||||
rtpt # Поворот 3 вершин сразу
|
||||
nop
|
||||
```
|
||||
|
||||
### **4.2. Отложенные вычисления**
|
||||
- Команды `nclip` и `avsz3` считают **площадь полигона** и **Z-буфер** без CPU.
|
||||
|
||||
### **4.3. Интеграция с GPU**
|
||||
- Результаты GTE (экранные X,Y,Z) передаются в **GPU** через DMA.
|
||||
- Это ускоряет рендеринг в 5-10 раз.
|
||||
|
||||
---
|
||||
|
||||
## **5. Сравнение с современными GPU**
|
||||
| Функция | PS1 (GTE) | Современный GPU |
|
||||
|-----------------------|----------------|----------------|
|
||||
| Матричные операции | 1-2 такта | 1 такт |
|
||||
| Макс. источников света| 3 | 100+ |
|
||||
| Поддержка шейдеров | Нет | Да |
|
||||
|
||||
---
|
||||
|
||||
## **6. Итог**
|
||||
- **GTE** — это «сердце» 3D-графики PS1, отвечающее за:
|
||||
- Поворот, масштабирование, перспективу.
|
||||
- Освещение и цвет.
|
||||
- **Без GTE** игры вроде *Tekken 3* или *Metal Gear Solid* работали бы на **1-2 FPS**.
|
||||
- Оптимизация под GTE — ключ к плавному рендерингу на PS1.
|
||||
|
||||
Для глубокого изучения смотрите [GTE Technical Reference](http://problemkaputt.de/psx-spx.htm#gtegeometrytransformationengine).
|
||||
134
PERSONAL PROJECTS/PS1 DOCS/PS1 Gpu-DMA.md
Normal file
134
PERSONAL PROJECTS/PS1 DOCS/PS1 Gpu-DMA.md
Normal file
@@ -0,0 +1,134 @@
|
||||
В PlayStation 1 (PS1) передача видеокоманд через DMA в GPU — это ключевой механизм для эффективного рендеринга. Разберём процесс детально, включая работу с памятью.
|
||||
|
||||
---
|
||||
|
||||
## **1. Как передаются видеокоманды через DMA?**
|
||||
GPU PS1 не имеет собственного набора инструкций — он выполняет **примитивные команды**, переданные CPU. Эти команды организуются в **цепочку DMA** (Display List), которая указывает, что и как рисовать.
|
||||
|
||||
### **1.1. Структура цепочки команд**
|
||||
Каждая команда в цепочке состоит из:
|
||||
- **Слова-заголовка** (указывает тип примитива: полигон, спрайт, настройка и т. д.).
|
||||
- **Данных** (координаты, цвета, текстуры).
|
||||
|
||||
Пример команды для отрисовки треугольника:
|
||||
```c
|
||||
typedef struct {
|
||||
uint32_t header; // Тип примитива (например, POLY_F3)
|
||||
uint16_t x1, y1; // Координаты
|
||||
uint16_t x2, y2;
|
||||
uint16_t x3, y3;
|
||||
uint8_t r, g, b; // Цвет
|
||||
} GPUPrimitive;
|
||||
```
|
||||
|
||||
### **1.2. Передача через DMA**
|
||||
1. **CPU формирует цепочку команд** в основной RAM (например, массив структур).
|
||||
2. **Адрес этой цепочки** передаётся в GPU через DMA.
|
||||
- Используется порт **`GP0`** (для команд) и **`GP1`** (для управления).
|
||||
3. **DMA-контроллер** (канал **GPU**) копирует данные из RAM в GPU.
|
||||
|
||||
**Код на ассемблере (пример):**
|
||||
```asm
|
||||
; Запись адреса цепочки в DMA
|
||||
li a0, 0x1F8010A0 ; Адрес регистра DMA_GPU (канал 2)
|
||||
la a1, command_list ; Адрес цепочки команд в RAM
|
||||
sw a1, 0(a0) ; Записываем адрес
|
||||
|
||||
; Настройка DMA
|
||||
li a0, 0x1F8010A4
|
||||
li a1, 0x01000200 ; Размер блока + флаги (например, 0x200 байт)
|
||||
sw a1, 0(a0)
|
||||
|
||||
; Запуск DMA
|
||||
li a0, 0x1F8010A8
|
||||
li a1, 0x01000001 ; Включить DMA
|
||||
sw a1, 0(a0)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## **2. Как GPU читает команды?**
|
||||
### **2.1. Отсутствие собственной памяти для команд**
|
||||
У GPU **нет отдельной памяти для хранения команд** (в отличие от современных GPU). Он:
|
||||
1. **Читает команды напрямую из основной RAM** через DMA.
|
||||
2. **Интерпретирует их "на лету"** (как потоковый процессор).
|
||||
|
||||
### **2.2. Роль FIFO-буфера**
|
||||
GPU имеет **маленький FIFO-буфер** (32 слова):
|
||||
- DMA заполняет этот буфер, пока GPU читает данные.
|
||||
- Если буфер переполняется, DMA приостанавливается.
|
||||
|
||||
---
|
||||
|
||||
## **3. Где хранятся данные?**
|
||||
### **3.1. Основная RAM (2 МБ)**
|
||||
- **Цепочки команд** хранятся здесь.
|
||||
- **Текстуры** тоже могут быть в RAM (но чаще копируются в VRAM).
|
||||
|
||||
### **3.2. Видеопамять (VRAM, 1 МБ)**
|
||||
- **Кадровый буфер** (Frame Buffer) — хранит пиксели.
|
||||
- **Текстуры** — загружаются сюда для быстрого доступа.
|
||||
- **CLUT** (Color Look-Up Table) — палитры для 8-битных текстур.
|
||||
|
||||
### **3.3. Как GPU обращается к данным?**
|
||||
- **Через DMA**: Текстуры/данные копируются из RAM в VRAM перед рендерингом.
|
||||
- **Прямой доступ**: GPU читает VRAM при отрисовке (например, текстуры).
|
||||
|
||||
---
|
||||
|
||||
## **4. Пример: полный цикл работы**
|
||||
1. **Подготовка**:
|
||||
- CPU создаёт в RAM массив команд (например, `DrawPolygon`, `LoadTexture`).
|
||||
- Текстуры копируются в VRAM через `GP0` или DMA.
|
||||
2. **Запуск рендеринга**:
|
||||
- CPU отправляет адрес цепочки в DMA-контроллер GPU.
|
||||
- DMA копирует команды из RAM в FIFO GPU.
|
||||
- GPU выполняет команды, рисуя в кадровый буфер (VRAM).
|
||||
3. **Вывод изображения**:
|
||||
- Видеоконтроллер выводит кадр из VRAM на экран.
|
||||
|
||||
---
|
||||
|
||||
## **5. Критические нюансы**
|
||||
1. **Синхронизация**:
|
||||
- Если GPU занят, DMA приостанавливается (проверяется через статусный регистр `GP1`).
|
||||
2. **Оптимизация**:
|
||||
- **Связные списки команд** (OT — Ordering Table) позволяют сортировать примитивы.
|
||||
3. **Ограничения**:
|
||||
- DMA может работать только с выровненными данными (адреса, кратные 4).
|
||||
- Переполнение FIFO приводит к "пропущенным кадрам".
|
||||
|
||||
---
|
||||
|
||||
## **6. Псевдокод для работы с DMA и GPU**
|
||||
```c
|
||||
// 1. Загрузка текстуры в VRAM
|
||||
void LoadTexture(uint16_t x, uint16_t y, void* tex_data) {
|
||||
GP0_SendCommand(0xA0000000 | (y << 11) | x); // Адрес в VRAM
|
||||
GP0_SendCommand(0x10000000 | (width * height)); // Размер
|
||||
DMA_Start(GPU_CHANNEL, tex_data, size); // Копируем через DMA
|
||||
}
|
||||
|
||||
// 2. Формирование цепочки команд
|
||||
struct CommandList {
|
||||
uint32_t prim_header;
|
||||
uint32_t data[10];
|
||||
} __attribute__((aligned(4))); // Выравнивание для DMA
|
||||
|
||||
// 3. Запуск рендеринга
|
||||
void DrawFrame() {
|
||||
CommandList list = { ... };
|
||||
DMA_Start(GPU_CHANNEL, &list, sizeof(list));
|
||||
GPU_TriggerDMA(); // Запуск выполнения
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## **Итог**
|
||||
- **DMA в PS1** передаёт адрес цепочки команд из RAM в GPU.
|
||||
- **GPU не имеет своей памяти для команд** — читает их напрямую из RAM через DMA/FIFO.
|
||||
- **VRAM** используется только для хранения текстур и кадрового буфера.
|
||||
- **Оптимизация DMA-передач** критична для производительности.
|
||||
|
||||
Это объясняет, почему в демосценах PS1 так важны **оптимизированные DMA-цепочки** и ручное управление памятью. Для глубокого понимания изучите **документацию PsyQ SDK** или исходники open-source движков (например, [PSn00bSDK](https://github.com/Lameguy64/psn00bsdk)).
|
||||
146
PERSONAL PROJECTS/PS1 DOCS/PS1 MDEC.md
Normal file
146
PERSONAL PROJECTS/PS1 DOCS/PS1 MDEC.md
Normal file
@@ -0,0 +1,146 @@
|
||||
Рендеринг потокового видео (FMV, Full Motion Video) на PlayStation 1 — это сложный процесс, сочетающий чтение данных с CD-ROM, декодирование через MDEC (Motion DECoder) и вывод на экран через GPU. Вот пошаговое объяснение:
|
||||
|
||||
---
|
||||
|
||||
### **1. Подготовка данных на диске**
|
||||
- Видео хранится в специальном формате **STR** (Sony's Video Format), который содержит:
|
||||
- **Видеопоток** — сжатый через MPEG-1 (но упрощённый для PS1).
|
||||
- **Аудиопоток** — ADPCM или CD-DA (Red Book Audio).
|
||||
- **Субтитры/тайминг** — дополнительные данные.
|
||||
|
||||
Данные разбиты на **секторы CD-ROM (Mode 2 XA)**, где чередуются видео и аудио.
|
||||
|
||||
---
|
||||
|
||||
### **2. Загрузка данных с CD-ROM**
|
||||
1. **CPU отправляет команду чтения**:
|
||||
```c
|
||||
CdReadSector(/*Сектор начала*/, /*Кол-во секторов*/, /*Буфер в RAM*/);
|
||||
```
|
||||
2. **DMA (канал 3) копирует данные** из буфера CD-ROM в RAM:
|
||||
- Используется **двойная буферизация**:
|
||||
- Пока DMA заполняет **Буфер 1**, CPU/MDEC обрабатывает **Буфер 2**.
|
||||
- Размер буфера обычно **16–32 КБ** (8–16 секторов).
|
||||
|
||||
3. **Ожидание завершения**:
|
||||
```c
|
||||
CdReadSync(0); // Блокирующее ожидание
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### **3. Декодирование видео через MDEC**
|
||||
1. **CPU настраивает MDEC** (канал DMA 0/1):
|
||||
- Указывает адрес сжатых данных в RAM.
|
||||
- Запускает декодирование:
|
||||
```asm
|
||||
li t0, 0x1F801820 // MDEC_CMD
|
||||
li t1, 0x60000000 // Команда "Decode Macroblock"
|
||||
sw t1, 0(t0)
|
||||
```
|
||||
|
||||
2. **MDEC декодирует MPEG-1**:
|
||||
- Кадр разбит на **макроблоки 16x16**.
|
||||
- Используется **DCT (Discrete Cosine Transform)** + **RLE (Run-Length Encoding)**.
|
||||
- На выходе — **RGB16 (15-битный)** или **YUV** пиксели.
|
||||
|
||||
3. **DMA (канал 1) копирует результат** в RAM:
|
||||
- Готовые кадры сохраняются в **отдельный буфер** (например, 320x240, 16-бит).
|
||||
|
||||
---
|
||||
|
||||
### **4. Вывод на экран через GPU**
|
||||
1. **CPU отправляет кадр в VRAM**:
|
||||
- Через DMA (канал 2) или вручную (порт `GP0`).
|
||||
- Пример:
|
||||
```asm
|
||||
li a0, 0x1F8010A0 // DMA GPU (канал 2)
|
||||
la a1, frame_buffer
|
||||
sw a1, 0(a0) // Источник
|
||||
li a1, 0x01000200 // Размер + режим
|
||||
sw a1, 4(a0)
|
||||
```
|
||||
|
||||
2. **GPU выводит кадр**:
|
||||
- Используется **полноэкранный спрайт** (Rectangular Texture).
|
||||
- Команда:
|
||||
```asm
|
||||
li t0, 0x1F801810 // GP0
|
||||
li t1, 0xA0000000 // Команда "Draw Rectangle"
|
||||
sw t1, 0(t0)
|
||||
```
|
||||
|
||||
3. **Синхронизация с VBlank**:
|
||||
- Чтобы избежать артефактов, обновление кадра происходит **во время VBlank**:
|
||||
```c
|
||||
VSync(0);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### **5. Аудиосопровождение**
|
||||
- **ADPCM-аудио** декодируется через SPU (канал DMA 4):
|
||||
```c
|
||||
SpuSetTransferMode(SPU_TRANSFER_BY_DMA);
|
||||
SpuWrite(audio_buffer, audio_size);
|
||||
```
|
||||
- **CD-DA (Red Book)** воспроизводится напрямую с диска (аппаратно).
|
||||
|
||||
---
|
||||
|
||||
### **6. Оптимизации**
|
||||
1. **Чередование данных (Interleaving)**:
|
||||
- Видео и аудио секторы чередуются на диске для плавности.
|
||||
- Пример структуры:
|
||||
```
|
||||
[VIDEO] [AUDIO] [VIDEO] [AUDIO]...
|
||||
```
|
||||
|
||||
2. **Предзагрузка**:
|
||||
- Игры заранее грузят несколько кадров в память.
|
||||
|
||||
3. **Пропуск кадров**:
|
||||
- Если декодирование не успевает, PS1 пропускает кадры (например, в Resident Evil).
|
||||
|
||||
---
|
||||
|
||||
### **7. Пример кода (упрощённый цикл FMV)**
|
||||
```c
|
||||
void play_fmv() {
|
||||
uint8_t video_buffer[32768];
|
||||
uint16_t frame_buffer[320*240];
|
||||
|
||||
while (!fmv_finished) {
|
||||
// 1. Чтение с CD-ROM
|
||||
CdReadSector(current_sector, 16, video_buffer);
|
||||
CdReadSync(0);
|
||||
|
||||
// 2. Декодирование через MDEC
|
||||
MDEC_DecodeFrame(video_buffer, frame_buffer);
|
||||
|
||||
// 3. Вывод через GPU
|
||||
GPU_SendFrame(frame_buffer);
|
||||
|
||||
// 4. Синхронизация
|
||||
VSync(0);
|
||||
current_sector += 16;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### **8. Проблемы и ограничения**
|
||||
- **Низкое разрешение**: 320x240 (чаще 256x224 для экономии памяти).
|
||||
- **Цветовые артефакты**: Из-за 15-битного RGB.
|
||||
- **Задержки**: Если CD-ROM не успевает, видео тормозит.
|
||||
|
||||
---
|
||||
|
||||
### **Итог**
|
||||
1. **Чтение** с CD-ROM через DMA.
|
||||
2. **Декодирование** через MDEC.
|
||||
3. **Вывод** через GPU.
|
||||
4. **Синхронизация** с VBlank.
|
||||
|
||||
Именно так работали FMV в играх вроде *Metal Gear Solid* и *Final Fantasy VII*! 🎥
|
||||
137
PERSONAL PROJECTS/PS1 DOCS/PS1 Ordering table.md
Normal file
137
PERSONAL PROJECTS/PS1 DOCS/PS1 Ordering table.md
Normal file
@@ -0,0 +1,137 @@
|
||||
|
||||
### **Сортировка полигонов на PlayStation 1 (PS1)**
|
||||
|
||||
PS1 не имеет аппаратной поддержки **Z-буфера** (depth buffer), поэтому правильный порядок отрисовки полигонов (сортировка) критически важен для корректного отображения 3D-сцен. Вот как это реализовано:
|
||||
|
||||
---
|
||||
|
||||
## **1. Основные методы сортировки**
|
||||
### **1.1. Ordering Table (OT)**
|
||||
**Ordering Table** — это главный механизм сортировки на PS1.
|
||||
Работает по принципу **связного списка** в VRAM, где полигоны группируются по расстоянию.
|
||||
|
||||
#### **Как это работает?**
|
||||
1. **Инициализация OT**:
|
||||
- Создаётся массив указателей (например, на 256 или 1024 элемента).
|
||||
- Каждый элемент соответствует диапазону глубин (Z-значений).
|
||||
|
||||
2. **Добавление примитивов**:
|
||||
- Для каждого полигона вычисляется **Z-координата** (например, средняя точка).
|
||||
- По Z-значению выбирается **ячейка OT** (чем дальше объект, тем меньше индекс).
|
||||
- Полигон добавляется в список для этой ячейки.
|
||||
|
||||
3. **Рендеринг**:
|
||||
- OT обрабатывается **от дальних к ближним** ячейкам (от 0 до N).
|
||||
- Все примитивы в одной ячейке рисуются в порядке добавления.
|
||||
|
||||
#### **Пример кода (PsyQ SDK)**
|
||||
```c
|
||||
#include <libgpu.h>
|
||||
|
||||
#define OT_LEN 256 // Размер Ordering Table
|
||||
uint32_t ot[OT_LEN]; // Сама таблица
|
||||
|
||||
void init_ot() {
|
||||
ClearOTagR(ot, OT_LEN); // Очистка OT
|
||||
}
|
||||
|
||||
void add_polygon(POLY_F4 *poly, int z) {
|
||||
// Вычисляем индекс в OT (0 = дальний, OT_LEN-1 = ближний)
|
||||
int ot_entry = (z >> 8) & (OT_LEN - 1); // Простейшее масштабирование Z
|
||||
|
||||
// Добавляем полигон в OT
|
||||
addPrim(ot + ot_entry, poly);
|
||||
}
|
||||
|
||||
void render_frame() {
|
||||
DrawOTag(ot + (OT_LEN - 1)); // Рендеринг OT (от ближних к дальним)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### **1.2. Сортировка по Bounding Box**
|
||||
Для сложных объектов (например, моделей) используется **сортировка по ограничивающему объёму**:
|
||||
1. Вычисляется **сфера или AABB** (коробка), охватывающая объект.
|
||||
2. Сортируются **центры сфер** по Z-координате.
|
||||
|
||||
#### **Псевдокод**
|
||||
```c
|
||||
struct Object {
|
||||
int center_z;
|
||||
POLY_F4 *polys;
|
||||
};
|
||||
|
||||
void sort_objects(Object *objs, int count) {
|
||||
qsort(objs, count, sizeof(Object), compare_by_z);
|
||||
}
|
||||
|
||||
int compare_by_z(const void *a, const void *b) {
|
||||
return ((Object*)a)->center_z - ((Object*)b)->center_z;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### **1.3. Priority Groups**
|
||||
Некоторые игры (например, *Crash Bandicoot*) используют **приоритетные группы**:
|
||||
- Полигоны делятся на **статические** (уровень) и **динамические** (персонажи).
|
||||
- Статические рисуются через OT, динамические — поверх них.
|
||||
|
||||
---
|
||||
|
||||
## **2. Оптимизации**
|
||||
### **2.1. Z-Сортировка только для прозрачных объектов**
|
||||
- Непрозрачные полигоны рисуются **без сортировки** (через алгоритм художника).
|
||||
- Прозрачные (с полупрозрачностью) сортируются **строго от дальних к ближним**.
|
||||
|
||||
### **2.2. Двойная OT**
|
||||
- **Одна OT для непрозрачных**, вторая — **для прозрачных** полигонов.
|
||||
- Позволяет уменьшить накладные расходы.
|
||||
|
||||
### **2.3. Субпиксельная корректировка**
|
||||
- Для борьбы с **Z-файтингом** (мерцание полигонов) используется:
|
||||
- Случайное смещение Z (`z += rand() % 4`).
|
||||
- Принудительный порядок для мелких объектов.
|
||||
|
||||
---
|
||||
|
||||
## **3. Пример для игры**
|
||||
Допустим, у нас есть:
|
||||
- **10 коробок** на разных расстояниях.
|
||||
- **1 полупрозрачный полигон** (вода).
|
||||
|
||||
#### **Код рендеринга**
|
||||
```c
|
||||
// 1. Инициализация
|
||||
init_ot();
|
||||
|
||||
// 2. Добавление непрозрачных полигонов (коробки)
|
||||
for (int i = 0; i < 10; i++) {
|
||||
add_polygon(&boxes[i], boxes[i].z);
|
||||
}
|
||||
|
||||
// 3. Добавление прозрачного полигона (вода)
|
||||
add_polygon(&water_poly, water_z);
|
||||
|
||||
// 4. Рендеринг
|
||||
render_frame();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## **4. Проблемы и решения**
|
||||
| Проблема | Решение |
|
||||
|------------------------|----------------------------------|
|
||||
| **Мерцание полигонов** | Субпиксельная корректировка Z. |
|
||||
| **Низкая производительность** | Уменьшение размера OT. |
|
||||
| **Неправильный порядок** | Ручная настройка приоритетов. |
|
||||
|
||||
---
|
||||
|
||||
## **5. Итог**
|
||||
- **Ordering Table** — основа сортировки на PS1.
|
||||
- **Для сложных сцен** комбинируют OT, Bounding Box и приоритетные группы.
|
||||
- **Оптимизации** нужны для борьбы с артефактами.
|
||||
|
||||
Этот подход использовался в играх типа *Tekken 3* и *Resident Evil*. Для глубокого понимания изучите [документацию PsyQ](http://psx.arthus.net/sdk/PsyQ/DOCS/).
|
||||
124
PERSONAL PROJECTS/PS1 DOCS/PS1 Vblank.md
Normal file
124
PERSONAL PROJECTS/PS1 DOCS/PS1 Vblank.md
Normal file
@@ -0,0 +1,124 @@
|
||||
### **VBlank (Vertical Blanking) в PlayStation 1**
|
||||
|
||||
**VBlank** (вертикальное гашение) — это критически важный механизм синхронизации в PS1, связанный с выводом изображения на экран. Он используется для корректного обновления кадров, предотвращения артефактов рендеринга и эффективного управления ресурсами системы.
|
||||
|
||||
---
|
||||
|
||||
## **1. Что такое VBlank?**
|
||||
### **Физическая основа**
|
||||
- ЭЛТ-мониторы (как и TV, для которых создавалась PS1) рисуют изображение **построчно**, сверху вниз.
|
||||
- **VBlank** — это промежуток времени между окончанием вывода последней строки кадра и началом вывода первой строки следующего кадра.
|
||||
- В PS1 длительность VBlank:
|
||||
- **NTSC**: ~1.23 мс (каждые 16.68 мс при 60 Гц).
|
||||
- **PAL**: ~1.25 мс (каждые 20 мс при 50 Гц).
|
||||
|
||||
---
|
||||
|
||||
## **2. Как VBlank используется в PS1?**
|
||||
### **2.1. Синхронизация вывода изображения**
|
||||
- GPU PS1 рисует кадр в **кадровый буфер** (часть VRAM).
|
||||
- Во время VBlank видеоконтроллер **переключается на готовый кадр**, избегая "разрывов" (tearing).
|
||||
|
||||
### **2.2. Безопасное обновление графики**
|
||||
- Любые изменения VRAM (загрузка текстур, обновление спрайтов) должны выполняться **только во время VBlank**, иначе:
|
||||
- Возникают артефакты (мелькание, частичное обновление).
|
||||
- Возможны конфликты доступа к VRAM между GPU и CPU.
|
||||
|
||||
### **2.3. Оптимизация работы CPU**
|
||||
- Игры используют VBlank как **метку времени** для:
|
||||
- Обновления игровой логики (физика, AI).
|
||||
- Загрузки данных с CD-ROM.
|
||||
- Подготовки следующего кадра.
|
||||
|
||||
---
|
||||
|
||||
## **3. Техническая реализация VBlank**
|
||||
### **3.1. Прерывание VBlank**
|
||||
- При наступлении VBlank генерируется **прерывание (IRQ0)**.
|
||||
- CPU может обработать его, выполнив критически важные задачи.
|
||||
|
||||
#### **Пример кода (обработка прерывания):**
|
||||
```asm
|
||||
; Установка обработчика VBlank
|
||||
la t0, vblank_handler ; Адрес обработчика
|
||||
sw t0, 0x10000080 ; Запись в вектор прерываний
|
||||
|
||||
; Включение прерываний
|
||||
mfc0 t1, $12 ; Статусный регир COP0
|
||||
ori t1, 0x01 ; Разрешить IRQ0
|
||||
mtc0 t1, $12
|
||||
```
|
||||
|
||||
### **3.2. Регистры, связанные с VBlank**
|
||||
| Адрес | Назначение |
|
||||
|---------------|----------------------------------------------------------------------------|
|
||||
| **0x1F801070** | **I_STAT** — Флаги прерываний (бит 0 = VBlank). |
|
||||
| **0x1F801100** | **GPU STATUS** — Содержит флаг `VRAM_READY` (можно ли обновлять VRAM). |
|
||||
|
||||
### **3.3. Ожидание VBlank в коде**
|
||||
```c
|
||||
#include <libgte.h>
|
||||
#include <libgpu.h>
|
||||
|
||||
void wait_vblank() {
|
||||
VSync(0); // Ожидание следующего VBlank
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## **4. Практическое применение в играх**
|
||||
### **4.1. Двойная буферизация**
|
||||
1. **Буфер A** выводится на экран.
|
||||
2. **Буфер B** рисуется GPU во время кадра.
|
||||
3. Во время VBlank буферы **меняются местами** (через `DrawSync(0)`).
|
||||
|
||||
### **4.2. Загрузка данных**
|
||||
- CD-ROM и DMA активны преимущественно во время VBlank, чтобы не мешать рендерингу.
|
||||
|
||||
### **4.3. Лимит FPS**
|
||||
- Большинство игр PS1 работают на **50/60 FPS** (частота VBlank), так как:
|
||||
- Новый кадр начинается только после VBlank.
|
||||
- Попытка рендерить быстрее бессмысленна (ЭЛТ не успеет отобразить).
|
||||
|
||||
---
|
||||
|
||||
## **5. Пример: Цикл рендеринга с VBlank**
|
||||
```c
|
||||
while (1) {
|
||||
// 1. Ожидаем VBlank
|
||||
VSync(0);
|
||||
|
||||
// 2. Обновляем игровую логику
|
||||
update_game();
|
||||
|
||||
// 3. Готовим кадр в заднем буфере
|
||||
render_frame();
|
||||
|
||||
// 4. Меняем буферы
|
||||
SwapBuffers();
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## **6. Интересные нюансы**
|
||||
- **Режимы видео**:
|
||||
- NTSC (60 Гц) даёт более плавный геймплей, но меньше времени на VBlank.
|
||||
- PAL (50 Гц) имеет более длинный VBlank, но ниже FPS.
|
||||
- **Overclocking**: Некоторые домашние разработчики нарушают синхронизацию для экспериментов (рискуя артефактами).
|
||||
|
||||
---
|
||||
|
||||
## **7. Итог**
|
||||
- **VBlank** — это "пауза" между кадрами, используемая для:
|
||||
- Синхронизации вывода изображения.
|
||||
- Обновления графики без артефактов.
|
||||
- Оптимизации загрузки данных.
|
||||
- **Без VBlank** игры PS1 выглядели бы с глюками (как на многих эмуляторах без корректной синхронизации).
|
||||
|
||||
Для глубокого понимания изучите:
|
||||
- [PSX SPX Technical Reference](http://problemkaputt.de/psx-spx.htm)
|
||||
- **LibGPU Documentation** (PsyQ SDK).
|
||||
|
||||
Это ключ к плавной графике в играх вроде *Crash Bandicoot* или *Tekken 3*! 🎮
|
||||
@@ -0,0 +1,169 @@
|
||||
Вот модифицированная программа для **PlayStation 1**, которая загружает текстуру напрямую из указанного сектора CD-ROM в RAM, а затем выводит её на экран с эффектом плавного появления. Код адаптирован для работы с "сырыми" секторами диска.
|
||||
|
||||
---
|
||||
|
||||
### **1. Изменённый код (`MAIN.C`)**
|
||||
```c
|
||||
#include <sys/types.h>
|
||||
#include <libetc.h>
|
||||
#include <libgte.h>
|
||||
#include <libgpu.h>
|
||||
#include <libcd.h>
|
||||
|
||||
#define SCREEN_WIDTH 320
|
||||
#define SCREEN_HEIGHT 240
|
||||
|
||||
// Параметры текстуры (256x256, 16-бит)
|
||||
#define TEX_WIDTH 256
|
||||
#define TEX_HEIGHT 256
|
||||
#define TEX_SIZE (TEX_WIDTH * TEX_HEIGHT * 2) // 131072 байт (16-бит)
|
||||
#define TEX_SECTORS (TEX_SIZE / 2048) // Размер в секторах CD (1 сектор = 2048 байт)
|
||||
|
||||
// Буфер для текстуры в RAM (выровнен для DMA)
|
||||
uint16_t tex_buffer[TEX_WIDTH * TEX_HEIGHT] __attribute__((aligned(64)));
|
||||
|
||||
// Структура спрайта
|
||||
typedef struct {
|
||||
RECT img_rect; // Размеры текстуры
|
||||
TIM_IMAGE tim; // Данные TIM-формата (не используется, но нужен для LoadImage)
|
||||
int brightness; // Текущая яркость (0-128)
|
||||
int fade_speed; // Скорость изменения яркости
|
||||
} Sprite;
|
||||
|
||||
void init() {
|
||||
ResetGraph(0);
|
||||
InitPad(NULL, 0, NULL, 0);
|
||||
CdInit();
|
||||
SetVideoMode(MODE_NTSC);
|
||||
FntLoad(960, 256);
|
||||
FntOpen(16, 16, 256, 224, 0, 512);
|
||||
}
|
||||
|
||||
void load_texture_from_sector(int start_sector) {
|
||||
// Чтение текстуры из указанного сектора CD-ROM в RAM через DMA
|
||||
CdRead(start_sector, (uint32_t*)tex_buffer, TEX_SECTORS);
|
||||
CdReadSync(0); // Ожидание завершения DMA
|
||||
}
|
||||
|
||||
void draw_sprite(Sprite* sprite) {
|
||||
// Настройка полупрозрачности для эффекта яркости
|
||||
float alpha = (float)sprite->brightness / 128.0f;
|
||||
DR_MODE dr_mode;
|
||||
SetDrawMode(&dr_mode, 0, 0, GetTPage(0, 0, 0, 0), 0);
|
||||
SetSprt16(&sprite->img_rect);
|
||||
SetSemiTrans(&sprite->img_rect, 1);
|
||||
setRGB0(&sprite->img_rect, alpha * 255, alpha * 255, alpha * 255);
|
||||
DrawPrim(&sprite->img_rect);
|
||||
}
|
||||
|
||||
int main() {
|
||||
init();
|
||||
|
||||
// Параметры загрузки (пример: текстура начинается с сектора 1000)
|
||||
int texture_start_sector = 1000; // Замените на реальный сектор!
|
||||
load_texture_from_sector(texture_start_sector);
|
||||
|
||||
// Настройка спрайта
|
||||
Sprite sprite = {
|
||||
.img_rect = {0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, 0, 0, 0},
|
||||
.brightness = 0,
|
||||
.fade_speed = 1
|
||||
};
|
||||
|
||||
// Загрузка текстуры в VRAM (адрес в RAM -> VRAM)
|
||||
RECT vram_rect = {0, 0, TEX_WIDTH, TEX_HEIGHT}; // Куда загружать в VRAM
|
||||
LoadImage(&vram_rect, (uint32_t*)tex_buffer);
|
||||
|
||||
// Главный цикл
|
||||
while (1) {
|
||||
FntPrint("Sector Load Demo\nBrightness: %d\nSector: %d",
|
||||
sprite.brightness, texture_start_sector);
|
||||
|
||||
// Плавное изменение яркости
|
||||
sprite.brightness += sprite.fade_speed;
|
||||
if (sprite.brightness >= 128 || sprite.brightness <= 0)
|
||||
sprite.fade_speed *= -1;
|
||||
|
||||
// Отрисовка
|
||||
draw_sprite(&sprite);
|
||||
FntFlush(-1);
|
||||
VSync(0);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### **2. Ключевые изменения**
|
||||
1. **Загрузка из сектора**:
|
||||
- Вместо поиска файла (`CdSearchFile`) используется прямой доступ к сектору:
|
||||
```c
|
||||
CdRead(start_sector, (uint32_t*)tex_buffer, TEX_SECTORS);
|
||||
```
|
||||
- `start_sector` — начальный сектор текстуры на диске (например, `1000`).
|
||||
|
||||
2. **Размер текстуры**:
|
||||
- Рассчитывается автоматически:
|
||||
```c
|
||||
#define TEX_SECTORS (TEX_SIZE / 2048) // 2048 байт = 1 сектор Mode 1
|
||||
```
|
||||
|
||||
3. **Выравнивание в RAM**:
|
||||
- Буфер `tex_buffer` выровнен по 64 байта для эффективного DMA:
|
||||
```c
|
||||
uint16_t tex_buffer[...] __attribute__((aligned(64)));
|
||||
```
|
||||
|
||||
4. **Загрузка в VRAM**:
|
||||
- `LoadImage` копирует данные из `tex_buffer` (RAM) в VRAM:
|
||||
```c
|
||||
LoadImage(&vram_rect, (uint32_t*)tex_buffer);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### **3. Как подготовить диск**
|
||||
1. **Конвертируйте текстуру** в RAW 16-бит (например, через Photoshop или `ffmpeg`):
|
||||
```bash
|
||||
ffmpeg -i texture.png -f rawvideo -pix_fmt rgb565 texture.raw
|
||||
```
|
||||
|
||||
2. **Запишите RAW-данные** на CD в нужный сектор (например, с помощью `mkpsxiso`):
|
||||
```bash
|
||||
mkpsxiso -y -o texture.cue -c "CDROM_DATA" texture.raw
|
||||
```
|
||||
- Узнайте начальный сектор через `cdrdao` или hex-редактор.
|
||||
|
||||
---
|
||||
|
||||
### **4. Важные нюансы**
|
||||
- **Режим CD-ROM**: Данные должны быть записаны в **Mode 1** (2048 байт/сектор).
|
||||
- **Размер текстуры**: Должен быть кратен размеру сектора (иначе чтение "сломает" данные).
|
||||
- **Проверка сектора**: Убедитесь, что сектор не занят другими файлами.
|
||||
|
||||
---
|
||||
|
||||
### **5. Пример для сборки**
|
||||
1. Скомпилируйте:
|
||||
```bash
|
||||
ccpsx MAIN.C -o CD_SECTOR_DEMO.EXE
|
||||
```
|
||||
2. Запишите на диск, указав в `SYSTEM.CNF`:
|
||||
```
|
||||
BOOT = cdrom:\CD_SECTOR_DEMO.EXE;1
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### **6. Дополнительная оптимизация**
|
||||
Для работы с **Mode 2 XA** (формат видео) измените:
|
||||
```c
|
||||
CdReadMode(CdMode_CD_XA); // Перед CdRead
|
||||
```
|
||||
и корректируйте размер сектора (2336 байт).
|
||||
|
||||
---
|
||||
|
||||
Этот код идеален для демо-сцен, где нужно напрямую читать графику с CD без файловой системы! 📀
|
||||
171
PERSONAL PROJECTS/PS1 DOCS/PSX code - Texture show from file.md
Normal file
171
PERSONAL PROJECTS/PS1 DOCS/PSX code - Texture show from file.md
Normal file
@@ -0,0 +1,171 @@
|
||||
Вот готовая программа для **PlayStation 1**, которая загружает текстуру с CD-ROM, передаёт её в VRAM и плавно выводит на экран с анимацией яркости (от 0 до 128). Код использует **PsyQ SDK** и разбит на логические блоки.
|
||||
|
||||
---
|
||||
|
||||
### **1. Структура проекта**
|
||||
```
|
||||
texture_demo/
|
||||
├── MAIN.C # Основной код
|
||||
├── TEXTURE.BIN # RAW-текстура 256x256 (16-бит RGB)
|
||||
└── SYSTEM.CNF # Конфигурационный файл
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### **2. Код программы (`MAIN.C`)**
|
||||
```c
|
||||
#include <sys/types.h>
|
||||
#include <libetc.h>
|
||||
#include <libgte.h>
|
||||
#include <libgpu.h>
|
||||
#include <libcd.h>
|
||||
|
||||
#define SCREEN_WIDTH 320
|
||||
#define SCREEN_HEIGHT 240
|
||||
|
||||
// Буфер для текстуры (256x256, 16-бит)
|
||||
#define TEX_SIZE (256 * 256 * 2) // 131072 байт
|
||||
uint16_t tex_buffer[256 * 256] __attribute__((aligned(64))); // Выравнивание для DMA
|
||||
|
||||
// Параметры спрайта
|
||||
typedef struct {
|
||||
RECT img_rect; // Размеры текстуры
|
||||
TIM_IMAGE tim; // Данные TIM-файла
|
||||
int brightness; // Текущая яркость (0-128)
|
||||
int fade_speed; // Скорость изменения яркости
|
||||
} Sprite;
|
||||
|
||||
void init() {
|
||||
// Инициализация системных библиотек
|
||||
ResetGraph(0);
|
||||
InitPad(NULL, 0, NULL, 0);
|
||||
CdInit();
|
||||
|
||||
// Настройка видеорежима (NTSC)
|
||||
SetVideoMode(MODE_NTSC);
|
||||
FntLoad(960, 256);
|
||||
FntOpen(16, 16, 256, 224, 0, 512);
|
||||
}
|
||||
|
||||
void load_texture(const char* filename) {
|
||||
CdlFILE file;
|
||||
uint8_t sector_buffer[2048]; // Буфер для сектора CD
|
||||
|
||||
// Поиск файла на диске
|
||||
if (CdSearchFile(&file, filename) == 0) {
|
||||
FntPrint("Error: File not found!\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// Чтение текстуры в буфер через DMA
|
||||
CdRead(file.pos, (uint32_t*)tex_buffer, TEX_SIZE / 2048);
|
||||
CdReadSync(0); // Ожидание завершения
|
||||
}
|
||||
|
||||
void draw_sprite(Sprite* sprite) {
|
||||
// Установка яркости (0-128 → 0.0-1.0)
|
||||
float alpha = (float)sprite->brightness / 128.0f;
|
||||
|
||||
// Рисуем полноэкранный спрайт с альфа-смешением
|
||||
DR_MODE dr_mode;
|
||||
SetDrawMode(&dr_mode, 0, 0, GetTPage(0, 0, sprite->tim.prect->x, sprite->tim.prect->y), 0);
|
||||
SetSprt16(&sprite->img_rect); // 16-битный спрайт
|
||||
|
||||
// Применяем яркость через полупрозрачность
|
||||
SetSemiTrans(&sprite->img_rect, 1);
|
||||
setRGB0(&sprite->img_rect, alpha * 255, alpha * 255, alpha * 255);
|
||||
|
||||
// Вывод спрайта
|
||||
DrawPrim(&sprite->img_rect);
|
||||
}
|
||||
|
||||
int main() {
|
||||
init(); // Инициализация
|
||||
|
||||
// Загрузка текстуры
|
||||
load_texture("\\TEXTURE.BIN"); // Путь на CD
|
||||
|
||||
// Настройка спрайта
|
||||
Sprite sprite = {
|
||||
.img_rect = {0, 0, SCREEN_WIDTH, SCREEN_HEIGHT},
|
||||
.brightness = 0,
|
||||
.fade_speed = 1
|
||||
};
|
||||
|
||||
// Загрузка текстуры в VRAM
|
||||
sprite.tim.mode = 0;
|
||||
sprite.tim.prect = (RECT*)tex_buffer;
|
||||
LoadImage(&sprite.tim.prect[0], &sprite.tim.pdata[0]);
|
||||
|
||||
// Главный цикл
|
||||
while (1) {
|
||||
FntPrint("Fade Demo: Brightness = %d\n", sprite.brightness);
|
||||
|
||||
// Плавное увеличение яркости
|
||||
sprite.brightness += sprite.fade_speed;
|
||||
if (sprite.brightness >= 128 || sprite.brightness <= 0)
|
||||
sprite.fade_speed *= -1; // Меняем направление
|
||||
|
||||
// Отрисовка
|
||||
draw_sprite(&sprite);
|
||||
FntFlush(-1);
|
||||
|
||||
// Синхронизация с VBlank
|
||||
VSync(0);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### **3. Как это работает?**
|
||||
1. **Инициализация**:
|
||||
- Настраивается видеорежим (NTSC 320x240).
|
||||
- Инициализируются CD-ROM и геймпад.
|
||||
|
||||
2. **Загрузка текстуры**:
|
||||
- Файл `TEXTURE.BIN` (RAW 256x256, 16-бит) читается с диска через DMA.
|
||||
- Данные копируются в буфер `tex_buffer`.
|
||||
|
||||
3. **Передача в VRAM**:
|
||||
- `LoadImage()` загружает текстуру в VRAM (адрес автоматически выбирается GPU).
|
||||
|
||||
4. **Плавное затемнение**:
|
||||
- В главном цикле меняется параметр `brightness` (0 → 128 → 0).
|
||||
- Яркость применяется через **полупрозрачность** (`SetSemiTrans`).
|
||||
|
||||
5. **Вывод**:
|
||||
- Спрайт рисуется на весь экран с текущей яркостью.
|
||||
|
||||
---
|
||||
|
||||
### **4. Требования к текстуре**
|
||||
- Формат: **RAW 16-бит RGB** (без заголовков).
|
||||
- Размер: **256x256** (можно изменить в коде).
|
||||
- Размещение: корень CD (`TEXTURE.BIN`).
|
||||
|
||||
---
|
||||
|
||||
### **5. Сборка**
|
||||
1. Скомпилируйте через **PsyQ SDK**:
|
||||
```bash
|
||||
ccpsx MAIN.C -o TEXTURE_DEMO.EXE
|
||||
```
|
||||
2. Запишите на диск вместе с `TEXTURE.BIN` и `SYSTEM.CNF`.
|
||||
|
||||
---
|
||||
|
||||
### **6. Демонстрация эффекта**
|
||||
- Текстура плавно "проявляется" (от чёрного к нормальной яркости) и затем исчезает.
|
||||
- Скорость регулируется `fade_speed`.
|
||||
|
||||
---
|
||||
|
||||
### **7. Оптимизации**
|
||||
- Для плавности используется **VSync(0)**.
|
||||
- DMA ускоряет загрузку текстуры.
|
||||
- Полупрозрачность GPU имитирует изменение яркости.
|
||||
|
||||
Этот код можно расширить для загрузки анимаций или сложных эффектов! 🎮
|
||||
369
PERSONAL PROJECTS/PS1 DOCS/PSX code - modules load and exec.md
Normal file
369
PERSONAL PROJECTS/PS1 DOCS/PSX code - modules load and exec.md
Normal file
@@ -0,0 +1,369 @@
|
||||
Вот доработанная версия с **общими переменными между основной программой и модулями**. Мы создадим единую структуру `GameState`, которая будет передаваться в каждый модуль.
|
||||
|
||||
---
|
||||
|
||||
### **1. Общий заголовок (SHARED.H)**
|
||||
```c
|
||||
// Структура для общих переменных игры
|
||||
typedef struct {
|
||||
int lives; // Количество жизней
|
||||
int level; // Текущий уровень
|
||||
bool is_paused; // Состояние паузы
|
||||
uint32_t score; // Счёт игрока
|
||||
} GameState;
|
||||
|
||||
// Заголовок модуля
|
||||
typedef void (*ModuleEntry)(GameState*); // Тип функции-входа
|
||||
|
||||
typedef struct {
|
||||
uint32_t magic; // 0x55AA1234
|
||||
ModuleEntry entry; // Функция входа
|
||||
uint32_t size; // Размер модуля
|
||||
} ModuleHeader;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### **2. Основная программа (MAIN.C)**
|
||||
```c
|
||||
#include <libetc.h>
|
||||
#include <libcd.h>
|
||||
#include "SHARED.H"
|
||||
|
||||
#define MODULE_ADDR 0x80030000 // Адрес загрузки модулей
|
||||
|
||||
GameState game_state = {
|
||||
.lives = 3,
|
||||
.level = 1,
|
||||
.is_paused = false,
|
||||
.score = 0
|
||||
};
|
||||
|
||||
void load_and_run_module(int sector, int size) {
|
||||
CdRead(sector, (uint32_t*)MODULE_ADDR, size);
|
||||
CdReadSync(0);
|
||||
|
||||
ModuleHeader *header = (ModuleHeader*)MODULE_ADDR;
|
||||
if (header->magic == 0x55AA1234) {
|
||||
header->entry(&game_state); // Передаём указатель на game_state
|
||||
}
|
||||
}
|
||||
|
||||
int main() {
|
||||
ResetGraph(0);
|
||||
CdInit();
|
||||
FntLoad(960, 256);
|
||||
FntOpen(16, 16, 256, 224, 0, 512);
|
||||
|
||||
while (1) {
|
||||
// Обновляем общие переменные
|
||||
game_state.score += 10;
|
||||
FntPrint("Main: Lives=%d, Score=%d\n", game_state.lives, game_state.score);
|
||||
|
||||
// Загружаем и выполняем модули
|
||||
load_and_run_module(MODULE_MDEC_SECTOR, MODULE_MDEC_SIZE);
|
||||
load_and_run_module(MODULE_GPU_SECTOR, MODULE_GPU_SIZE);
|
||||
|
||||
VSync(0);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### **3. Модуль для MDEC (MODULE_MDEC.C)**
|
||||
```c
|
||||
#include <libmdec.h>
|
||||
#include "SHARED.H"
|
||||
|
||||
void module_entry(GameState *state) {
|
||||
// Читаем и изменяем общие переменные
|
||||
if (state->lives > 0) {
|
||||
state->lives--; // Уменьшаем жизни
|
||||
FntPrint("MDEC: Lives left=%d\n", state->lives);
|
||||
}
|
||||
|
||||
// Декодируем видео только если игра не на паузе
|
||||
if (!state->is_paused) {
|
||||
uint8_t data[1024];
|
||||
uint16_t buffer[256*256];
|
||||
MDEC_DecodeFrame(data, buffer, MDEC_MODE_RGB24);
|
||||
}
|
||||
}
|
||||
|
||||
ModuleHeader __header__ __attribute__((section(".header"))) = {
|
||||
.magic = 0x55AA1234,
|
||||
.entry = module_entry,
|
||||
.size = 0
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### **4. Модуль для GPU (MODULE_GPU.C)**
|
||||
```c
|
||||
#include <libgpu.h>
|
||||
#include "SHARED.H"
|
||||
|
||||
void module_entry(GameState *state) {
|
||||
// Проверяем глобальный статус
|
||||
if (state->is_paused) {
|
||||
FntPrint("GPU: Game is paused!\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// Рисуем HUD с общими переменными
|
||||
char hud_text[32];
|
||||
sprintf(hud_text, "LIVES: %d SCORE: %d", state->lives, state->score);
|
||||
FntPrint(hud_text);
|
||||
|
||||
// Пример работы с GPU
|
||||
RECT rect = {10, 10, 100, 100};
|
||||
uint16_t color = state->lives > 1 ? 0xFF00 : 0xFF0000; // Красный если 1 жизнь
|
||||
DrawPrim(&rect);
|
||||
}
|
||||
|
||||
ModuleHeader __header__ __attribute__((section(".header"))) = {
|
||||
.magic = 0x55AA1234,
|
||||
.entry = module_entry,
|
||||
.size = 0
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### **5. Сборка**
|
||||
```bash
|
||||
# Компиляция модулей с их библиотеками
|
||||
ccpsx MODULE_MDEC.C -o MODULE_MDEC.BIN -lpsxmdec
|
||||
ccpsx MODULE_GPU.C -o MODULE_GPU.BIN -lpsxgpu
|
||||
|
||||
# Основная программа (без лишних библиотек)
|
||||
ccpsx MAIN.C -o MAIN.EXE
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### **6. Механика общих переменных**
|
||||
1. **Основная программа**:
|
||||
- Создаёт глобальную структуру `GameState`.
|
||||
- Передаёт **указатель** на неё в модули через `header->entry(&game_state)`.
|
||||
|
||||
2. **Модули**:
|
||||
- Читают и изменяют `GameState` через полученный указатель.
|
||||
- Могут проверять:
|
||||
```c
|
||||
if (state->is_paused) { ... }
|
||||
```
|
||||
- Могут изменять:
|
||||
```c
|
||||
state->lives--;
|
||||
```
|
||||
|
||||
3. **Синхронизация**:
|
||||
- Все изменения мгновенно видны в основном модуле и других модулях.
|
||||
|
||||
---
|
||||
|
||||
### **7. Пример работы**
|
||||
1. Основная программа:
|
||||
- Устанавливает `lives = 3`, `score = 0`.
|
||||
2. Модуль MDEC:
|
||||
- Уменьшает `lives` до 2, если игрок ошибся.
|
||||
3. Модуль GPU:
|
||||
- Отображает текущие `lives` и `score` на экране.
|
||||
4. Основная программа:
|
||||
- Видит изменения и может реагировать (например, завершить игру при `lives == 0`).
|
||||
|
||||
---
|
||||
|
||||
### **8. Важные нюансы**
|
||||
- **Безопасность**: Модули могут **перезаписать любые данные** в `GameState`. Добавьте проверки:
|
||||
```c
|
||||
if (state->lives < 0) state->lives = 0; // Защита от отрицательных значений
|
||||
```
|
||||
- **Производительность**: Частый доступ к общим переменным может замедлить работу. Оптимизируйте:
|
||||
```c
|
||||
int local_lives = state->lives; // Кэширование в локальную переменную
|
||||
```
|
||||
- **Конфликты**: Если модули работают с одними данными, добавьте **флаги блокировки**.
|
||||
|
||||
---
|
||||
|
||||
Теперь модули могут **полноценно взаимодействовать** с основной программой, проверяя и изменяя глобальное состояние игры! 🎮
|
||||
|
||||
|
||||
-------
|
||||
|
||||
Да, вы правильно поняли! В этой архитектуре **основная программа** и **модули** компилируются по-разному:
|
||||
|
||||
---
|
||||
|
||||
### **1. Основная программа (MAIN.EXE)**
|
||||
- **Не требует** явного указания библиотек PsyQ (если не использует их напрямую).
|
||||
- Её задача — только **загружать модули** и **управлять общим состоянием** (`GameState`).
|
||||
- Пример компиляции:
|
||||
```bash
|
||||
ccpsx MAIN.C -o MAIN.EXE # Без -lpsxetc, -lpsxgpu и т.д.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### **2. Дополнительные модули (MODULE_*.BIN)**
|
||||
- **Должны включать** нужные библиотеки PsyQ, так как:
|
||||
- Каждый модуль — **самодостаточный** исполняемый код.
|
||||
- PsyQ не загружается глобально для модулей (они работают в изолированном контексте).
|
||||
- Пример компиляции:
|
||||
```bash
|
||||
# Модуль для GPU
|
||||
ccpsx MODULE_GPU.C -o MODULE_GPU.BIN -lpsxgpu -lpsxgte
|
||||
|
||||
# Модуль для MDEC
|
||||
ccpsx MODULE_MDEC.C -o MODULE_MDEC.BIN -lpsxmdec
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### **3. Почему такая разница?**
|
||||
| Компонент | Библиотеки PsyQ | Причина |
|
||||
|--------------------|----------------|-------------------------------------------------------------------------|
|
||||
| **Основная программа** | Не нужны | Только загружает модули и передаёт `GameState`. Не использует GPU/MDEC. |
|
||||
| **Модули** | Обязательны | Каждый модуль — независимый код, требующий свои библиотеки. |
|
||||
|
||||
---
|
||||
|
||||
### **4. Как это работает в памяти?**
|
||||
1. Основная программа:
|
||||
- Занимает **нижнюю часть RAM** (например, `0x80010000`).
|
||||
- Не знает о существовании PsyQ-функций.
|
||||
|
||||
2. Модули:
|
||||
- Загружаются в **фиксированный адрес** (`0x80030000`).
|
||||
- Содержат **свои копии библиотек** (например, `libgpu`).
|
||||
- Получают доступ к `GameState` через указатель.
|
||||
|
||||
---
|
||||
|
||||
### **5. Пример: вызов функций**
|
||||
- В модуле `MODULE_GPU.C`:
|
||||
```c
|
||||
#include <libgpu.h> // Библиотека внутри модуля
|
||||
|
||||
void module_entry(GameState *state) {
|
||||
// Использует свою копию LoadImage
|
||||
RECT rect = {0, 0, 256, 256};
|
||||
LoadImage(&rect, (uint32_t*)texture); // Функция из libgpu
|
||||
}
|
||||
```
|
||||
|
||||
- В основной программе (`MAIN.C`):
|
||||
```c
|
||||
// Нет #include <libgpu.h>!
|
||||
void load_module() {
|
||||
// Только загрузка бинарника
|
||||
CdRead(sector, (uint32_t*)0x80030000, size);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### **6. Ограничения**
|
||||
- **Дублирование кода**: Каждый модуль содержит свои библиотеки → увеличивает размер.
|
||||
- **Нет глобального состояния**: Если `libgpu` хранит внутренние переменные, они будут разными для каждого модуля.
|
||||
- **Только последовательное выполнение**: Модули не могут работать одновременно (перезаписывают один регион памяти).
|
||||
|
||||
---
|
||||
|
||||
### **7. Оптимизация**
|
||||
Если модули используют **одни и те же библиотеки**, можно:
|
||||
1. Вынести общие библиотеки в **отдельный загружаемый модуль**.
|
||||
2. Передавать **указатели на функции** (как в предыдущих примерах).
|
||||
|
||||
---
|
||||
|
||||
### **8. Итог**
|
||||
- **Основная программа** — минималистична, без зависимостей.
|
||||
- **Модули** — самодостаточные бинарники со своими библиотеками.
|
||||
- **Общение** через общую структуру `GameState`.
|
||||
|
||||
Это архитектурно напоминает **плагины в современных движках**, где каждый плагин собирается со своими зависимостями.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
------------------
|
||||
|
||||
|
||||
Да, вы абсолютно правы! Моя ошибка — **основная программа действительно использует `libcd.h` (и другие библиотеки PsyQ для базовых операций)**, но **ключевое различие** в следующем:
|
||||
|
||||
---
|
||||
|
||||
### **1. Уточнение архитектуры**
|
||||
| Компонент | Библиотеки PsyQ | Почему? |
|
||||
|--------------------|------------------------------------|-------------------------------------------------------------------------|
|
||||
| **Основная программа** | `libcd.h`, `libetc.h`, `libgte.h` | Только для **загрузки модулей**, управления памятью и отладки (FntPrint). Не использует GPU/MDEC/SPU. |
|
||||
| **Модули** | Свои специфичные библиотеки (`libgpu`, `libmdec`) | Каждый модуль — самостоятельный код, требующий **все свои зависимости**. |
|
||||
|
||||
---
|
||||
|
||||
### **2. Исправленный пример компиляции**
|
||||
#### **Основная программа (MAIN.C)**
|
||||
```bash
|
||||
# Нужны только базовые библиотеки для загрузки модулей
|
||||
ccpsx MAIN.C -o MAIN.EXE -lpsxcd -lpsxetc -lpsxgte
|
||||
```
|
||||
|
||||
#### **Модули**
|
||||
```bash
|
||||
# GPU-модуль (со своим libgpu)
|
||||
ccpsx MODULE_GPU.C -o MODULE_GPU.BIN -lpsxgpu -lpsxgte
|
||||
|
||||
# MDEC-модуль (со своим libmdec)
|
||||
ccpsx MODULE_MDEC.C -o MODULE_MDEC.BIN -lpsxmdec
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### **3. Почему основная программа не включает `libgpu`/`libmdec`?**
|
||||
- Она **не вызывает** функции вроде `LoadImage()` или `MDEC_Decode()`.
|
||||
- Её задача — лишь:
|
||||
- Чтение секторов с диска (`libcd`).
|
||||
- Вывод текста (`libetc`).
|
||||
- Передача управления модулям.
|
||||
|
||||
---
|
||||
|
||||
### **4. Аналогия с операционной системой**
|
||||
- **Основная программа** — как **ядро ОС**, которое:
|
||||
- Умеет загружать драйверы (модули).
|
||||
- Не содержит код для работы с графикой/звуком.
|
||||
- **Модули** — как **драйверы**, которые:
|
||||
- Содержат весь специфичный код (например, для GPU).
|
||||
- Получают доступ к «ядру» через общие структуры (`GameState`).
|
||||
|
||||
---
|
||||
|
||||
### **5. Полный список зависимостей**
|
||||
#### Для **MAIN.C**:
|
||||
```c
|
||||
#include <libcd.h> // CdRead(), CdInit()
|
||||
#include <libetc.h> // FntPrint(), VSync()
|
||||
#include <libgte.h> // Базовая математика (если используется)
|
||||
```
|
||||
|
||||
#### Для **MODULE_GPU.C**:
|
||||
```c
|
||||
#include <libgpu.h> // LoadImage(), DrawPrim()
|
||||
#include <libetc.h> // FntPrint() (если нужно)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### **6. Итог**
|
||||
- **Основная программа** использует **минимальный набор PsyQ** (`libcd`, `libetc`).
|
||||
- **Модули** включают **все необходимые им библиотеки** (`libgpu`, `libmdec` и т.д.).
|
||||
- **Общение** между ними происходит через **общую структуру** `GameState`.
|
||||
|
||||
Спасибо за замечание! Теперь архитектура стала ещё понятнее 😊.
|
||||
123
PERSONAL PROJECTS/PS1 DOCS/PSX code inline asm.md
Normal file
123
PERSONAL PROJECTS/PS1 DOCS/PSX code inline asm.md
Normal file
@@ -0,0 +1,123 @@
|
||||
Вот готовая программа для **PlayStation 1**, которая использует **MIPS-ассемблерные вставки** для сложения двух чисел через указатели. Результат возвращается в переменную основной программы.
|
||||
|
||||
---
|
||||
|
||||
### **1. Полный код (MAIN.C)**
|
||||
```c
|
||||
#include <stdio.h>
|
||||
#include <libetc.h>
|
||||
|
||||
// Объявляем ассемблерную функцию
|
||||
int add_numbers_asm(int *a, int *b, int *result);
|
||||
|
||||
int main() {
|
||||
int num1 = 5;
|
||||
int num2 = 7;
|
||||
int result = 0;
|
||||
|
||||
// Вызываем ассемблерную функцию с указателями
|
||||
add_numbers_asm(&num1, &num2, &result);
|
||||
|
||||
// Выводим результат
|
||||
FntPrint("Result: %d + %d = %d\n", num1, num2, result);
|
||||
FntFlush(-1);
|
||||
|
||||
while (1) {
|
||||
VSync(0);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Реализация на MIPS-ассемблере
|
||||
int add_numbers_asm(int *a, int *b, int *result) {
|
||||
int ret;
|
||||
asm volatile (
|
||||
"lw $t0, 0(%1) \n" // Загружаем *a в $t0
|
||||
"lw $t1, 0(%2) \n" // Загружаем *b в $t1
|
||||
"add $t2, $t0, $t1 \n" // Складываем: $t2 = $t0 + $t1
|
||||
"sw $t2, 0(%3) \n" // Сохраняем результат по адресу *result
|
||||
"move %0, $t2 \n" // Возвращаем результат через регистр
|
||||
: "=r" (ret) // Выходные операнды
|
||||
: "r" (a), "r" (b), "r" (result) // Входные операнды
|
||||
: "$t0", "$t1", "$t2" // Разрушаемые регистры
|
||||
);
|
||||
return ret;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### **2. Как это работает?**
|
||||
1. **Передача указателей в ассемблер**
|
||||
Функция `add_numbers_asm` принимает три указателя:
|
||||
- `a` — адрес первого числа,
|
||||
- `b` — адрес второго числа,
|
||||
- `result` — адрес для сохранения результата.
|
||||
|
||||
2. **Ассемблерная вставка** (`asm volatile`):
|
||||
- `lw` — загружает значения из памяти по указателям.
|
||||
- `add` — складывает числа.
|
||||
- `sw` — сохраняет результат по адресу `result`.
|
||||
- `move` — возвращает значение через регистр.
|
||||
|
||||
3. **Доступ к результату**
|
||||
Результат будет:
|
||||
- В переменной `result` (через указатель).
|
||||
- В возвращаемом значении функции (если нужно).
|
||||
|
||||
---
|
||||
|
||||
### **3. Компиляция и запуск**
|
||||
1. Скомпилируйте через **PsyQ SDK**:
|
||||
```bash
|
||||
ccpsx MAIN.C -o ADD_DEMO.EXE
|
||||
```
|
||||
|
||||
2. Запишите на диск и запустите на эмуляторе или консоли.
|
||||
На экране появится:
|
||||
```
|
||||
Result: 5 + 7 = 12
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### **4. Важные нюансы**
|
||||
- **Регистры MIPS**:
|
||||
- `$t0`–`$t9` — временные регистры (можно использовать без сохранения).
|
||||
- `$s0`–`$s7` — должны сохраняться (если используются).
|
||||
|
||||
- **Разрушаемые регистры**:
|
||||
Указаны в конце `asm volatile` (чтобы компилятор не использовал их для оптимизации).
|
||||
|
||||
- **Опасность**:
|
||||
Если передать **неверные указатели**, PS1 уйдёт в **краш** (нет защиты памяти).
|
||||
|
||||
---
|
||||
|
||||
### **5. Пример с глобальными переменными**
|
||||
Если нужно работать с глобальными переменными напрямую:
|
||||
```c
|
||||
int global_num1 = 10;
|
||||
int global_num2 = 20;
|
||||
|
||||
void add_globals_asm() {
|
||||
asm volatile (
|
||||
"la $t0, global_num1 \n" // Загружаем адрес global_num1
|
||||
"lw $t1, 0($t0) \n" // Читаем значение
|
||||
"la $t0, global_num2 \n"
|
||||
"lw $t2, 0($t0) \n"
|
||||
"add $t1, $t1, $t2 \n"
|
||||
"sw $t1, 0($t0) \n" // Сохраняем сумму в global_num2
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### **6. Итог**
|
||||
- **Ассемблерные вставки** дают полный контроль над железом PS1.
|
||||
- **Указатели** передаются через регистры (`$a0`–`$a3` в MIPS).
|
||||
- **Результат** можно получить через память или возвращаемое значение.
|
||||
|
||||
Этот подход используется в оптимизированных частях игр (например, в рендеринге или физике).
|
||||
@@ -0,0 +1,183 @@
|
||||
Вот готовая программа для **PlayStation 1**, которая загружает текстуру, плавно проявляет её, ждёт нажатия кнопки (✅ **CROSS** или **CIRCLE**), а затем плавно исчезает. Всё работает с ручным управлением адресами RAM и без использования полупрозрачности.
|
||||
|
||||
---
|
||||
|
||||
### **Код (`MAIN.C`)**
|
||||
```c
|
||||
#include <sys/types.h>
|
||||
#include <libetc.h>
|
||||
#include <libgte.h>
|
||||
#include <libgpu.h>
|
||||
#include <libcd.h>
|
||||
|
||||
// Константы
|
||||
#define SCREEN_WIDTH 320
|
||||
#define SCREEN_HEIGHT 240
|
||||
#define TEX_WIDTH 256
|
||||
#define TEX_HEIGHT 256
|
||||
#define RAW_SECTOR_SIZE 2048
|
||||
#define TEX_SECTORS ((TEX_WIDTH * TEX_HEIGHT * 2) / RAW_SECTOR_SIZE)
|
||||
|
||||
// Адреса в RAM
|
||||
#define RAW_DATA_ADDR 0x80010000 // Сырые данные с CD
|
||||
#define UNPACKED_ADDR 0x80030000 // Распакованная текстура
|
||||
|
||||
// Состояния анимации
|
||||
typedef enum {
|
||||
FADE_IN, // Появление текстуры
|
||||
WAIT_FOR_BUTTON, // Ожидание нажатия кнопки
|
||||
FADE_OUT // Исчезновение
|
||||
} State;
|
||||
|
||||
// Структура спрайта
|
||||
typedef struct {
|
||||
RECT img_rect;
|
||||
uint16_t *texture;
|
||||
int brightness; // 0-128 (0 = чёрный, 128 = оригинал)
|
||||
State state;
|
||||
} Sprite;
|
||||
|
||||
// Заглушка распаковки (замените на реальный алгоритм)
|
||||
void unpack_texture(uint8_t *src, uint16_t *dst, int width, int height) {
|
||||
for (int i = 0; i < width * height; i++) {
|
||||
dst[i] = ((uint16_t*)src)[i]; // Простое копирование (данные уже в RGB565)
|
||||
}
|
||||
}
|
||||
|
||||
void init() {
|
||||
ResetGraph(0);
|
||||
InitPad(NULL, 0, NULL, 0);
|
||||
CdInit();
|
||||
SetVideoMode(MODE_NTSC);
|
||||
FntLoad(960, 256);
|
||||
FntOpen(16, 16, 256, 224, 0, 512);
|
||||
}
|
||||
|
||||
void load_and_unpack_texture(int start_sector) {
|
||||
// Чтение данных с CD в RAW_DATA_ADDR через DMA
|
||||
CdRead(start_sector, (uint32_t*)RAW_DATA_ADDR, TEX_SECTORS);
|
||||
CdReadSync(0);
|
||||
|
||||
// Распаковка в UNPACKED_ADDR
|
||||
unpack_texture((uint8_t*)RAW_DATA_ADDR, (uint16_t*)UNPACKED_ADDR, TEX_WIDTH, TEX_HEIGHT);
|
||||
}
|
||||
|
||||
void draw_sprite(Sprite *sprite) {
|
||||
// Применение яркости через RGB
|
||||
int r, g, b;
|
||||
if (sprite->brightness <= 0) {
|
||||
r = g = b = 0; // Полная темнота
|
||||
} else {
|
||||
float mult = (float)sprite->brightness / 128.0f;
|
||||
r = 255 * mult;
|
||||
g = 255 * mult;
|
||||
b = 255 * mult;
|
||||
}
|
||||
|
||||
// Настройка и отрисовка спрайта
|
||||
DR_MODE dr_mode;
|
||||
SetDrawMode(&dr_mode, 0, 0, GetTPage(0, 0, 0, 0), 0);
|
||||
SetSprt16(&sprite->img_rect);
|
||||
setRGB0(&sprite->img_rect, r, g, b);
|
||||
DrawPrim(&sprite->img_rect);
|
||||
}
|
||||
|
||||
int main() {
|
||||
init();
|
||||
|
||||
// Загрузка текстуры из сектора 1000 (пример)
|
||||
int texture_start_sector = 1000;
|
||||
load_and_unpack_texture(texture_start_sector);
|
||||
|
||||
// Инициализация спрайта
|
||||
Sprite sprite = {
|
||||
.img_rect = {0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, 0, 0, 0},
|
||||
.texture = (uint16_t*)UNPACKED_ADDR,
|
||||
.brightness = 0,
|
||||
.state = FADE_IN
|
||||
};
|
||||
|
||||
// Загрузка текстуры в VRAM
|
||||
RECT vram_rect = {0, 0, TEX_WIDTH, TEX_HEIGHT};
|
||||
LoadImage(&vram_rect, (uint32_t*)UNPACKED_ADDR);
|
||||
|
||||
// Главный цикл
|
||||
while (1) {
|
||||
// Обработка состояния
|
||||
switch (sprite.state) {
|
||||
case FADE_IN:
|
||||
sprite.brightness += 2;
|
||||
if (sprite.brightness >= 128) {
|
||||
sprite.brightness = 128;
|
||||
sprite.state = WAIT_FOR_BUTTON;
|
||||
}
|
||||
break;
|
||||
|
||||
case WAIT_FOR_BUTTON: {
|
||||
// Проверка нажатия CROSS (✅) или CIRCLE (🟠)
|
||||
u_long pad = PadRead(0);
|
||||
if (pad & PAD_CROSS || pad & PAD_CIRCLE) {
|
||||
sprite.state = FADE_OUT;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case FADE_OUT:
|
||||
sprite.brightness -= 2;
|
||||
if (sprite.brightness <= 0) {
|
||||
sprite.brightness = 0;
|
||||
// Здесь можно перейти к следующей сцене
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Отрисовка
|
||||
draw_sprite(&sprite);
|
||||
FntPrint("Press X or O to fade out\nBrightness: %d", sprite.brightness);
|
||||
FntFlush(-1);
|
||||
VSync(0);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### **Как это работает?**
|
||||
1. **FADE_IN**:
|
||||
- Текстура плавно проявляется (яркость увеличивается от 0 до 128).
|
||||
2. **WAIT_FOR_BUTTON**:
|
||||
- Программа ждёт нажатия **CROSS** (✅) или **CIRCLE** (🟠) через `PadRead()`.
|
||||
3. **FADE_OUT**:
|
||||
- После нажатия текстура исчезает (яркость уменьшается до 0).
|
||||
|
||||
---
|
||||
|
||||
### **Ключевые особенности**
|
||||
- **Ручное управление адресами RAM** (`0x80010000` и `0x80030000`).
|
||||
- **Яркость через RGB**, а не полупрозрачность.
|
||||
- **Проверка кнопок** через битовые маски:
|
||||
- `PAD_CROSS` — кнопка ✅.
|
||||
- `PAD_CIRCLE` — кнопка 🟠.
|
||||
|
||||
---
|
||||
|
||||
### **Сборка**
|
||||
1. Скомпилируйте:
|
||||
```bash
|
||||
ccpsx MAIN.C -o FADE_DEMO.EXE
|
||||
```
|
||||
2. Запишите на диск, указав в `SYSTEM.CNF`:
|
||||
```
|
||||
BOOT = cdrom:\FADE_DEMO.EXE;1
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### **Демонстрация**
|
||||
1. Текстура плавно появляется.
|
||||
2. Нажмите **X** или **O** на геймпаде.
|
||||
3. Текстура исчезает в темноту.
|
||||
|
||||
Теперь вы можете интегрировать это в свою игру для cinematic-сцен! 🎮
|
||||
Reference in New Issue
Block a user