247 lines
11 KiB
Markdown
247 lines
11 KiB
Markdown
В 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)).
|
||
|
||
|
||
|
||
----------
|
||
|
||
|
||
# Функции `PutDispEnv` и `PutDrawEnv` в библиотеке PsyQ для PlayStation 1
|
||
|
||
Эти функции управляют графическим окружением (display и drawing environment) и являются ключевыми для работы с двойной буферизацией на PS1.
|
||
|
||
## `PutDispEnv` - Установка параметров отображения
|
||
|
||
```c
|
||
void PutDispEnv(DISPENV *env);
|
||
```
|
||
|
||
### Назначение:
|
||
Устанавливает параметры **области вывода изображения** на экран:
|
||
- Позиция и размер выводимой области
|
||
- Режимы интерлейсинга
|
||
- Цвет фона за пределами игровой области
|
||
|
||
### Структура DISPENV:
|
||
```c
|
||
typedef struct {
|
||
u_short disp; // Настройки дисплея
|
||
u_short screen; // Размер экрана
|
||
u_short isrgb24; // RGB24 режим (0/1)
|
||
u_short pad; // Выравнивание
|
||
} DISPENV;
|
||
```
|
||
|
||
### Пример использования:
|
||
```c
|
||
DISPENV dispEnv;
|
||
SetDefDispEnv(&dispEnv, 0, 0, 320, 240); // Установка параметров по умолчанию
|
||
dispEnv.screen.w = 320; // Ширина выводимой области
|
||
dispEnv.screen.h = 240; // Высота выводимой области
|
||
PutDispEnv(&dispEnv); // Применение настроек
|
||
```
|
||
|
||
## `PutDrawEnv` - Установка параметров рендеринга
|
||
|
||
```c
|
||
void PutDrawEnv(DRAWENV *env);
|
||
```
|
||
|
||
### Назначение:
|
||
Конфигурирует параметры **области рисования**:
|
||
- Область отсечения (clipping)
|
||
- Смещение рисования
|
||
- Цвет фона
|
||
- Флаги отрисовки
|
||
|
||
### Структура DRAWENV:
|
||
```c
|
||
typedef struct {
|
||
RECT clip; // Область отсечения
|
||
short ofs[2]; // Смещение рисования
|
||
RECT tw; // Область текстуры (если используется)
|
||
u_short tpage; // Страница текстуры
|
||
u_short dtd; // Флаги (dither, draw background)
|
||
} DRAWENV;
|
||
```
|
||
|
||
### Пример использования:
|
||
```c
|
||
DRAWENV drawEnv;
|
||
SetDefDrawEnv(&drawEnv, 0, 0, 320, 240);
|
||
drawEnv.clip.x = 0; // Область отсечения
|
||
drawEnv.clip.y = 0;
|
||
drawEnv.clip.w = 320;
|
||
drawEnv.clip.h = 240;
|
||
drawEnv.isbg = 1; // Включить очистку фона
|
||
setRGB0(&drawEnv, 0, 0, 0); // Черный фон
|
||
PutDrawEnv(&drawEnv);
|
||
```
|
||
|
||
## Совместное использование в двойной буферизации
|
||
|
||
Типичный цикл рендеринга с двойной буферизацией:
|
||
|
||
```c
|
||
DB db[2]; // Double buffer структура
|
||
int active = 0;
|
||
|
||
while(1) {
|
||
// Очистка и подготовка OT
|
||
ClearOTagR(db[active].ot, OT_LENGTH);
|
||
|
||
// Добавление примитивов
|
||
// ... (код рендеринга)
|
||
|
||
// Отправка команд на отрисовку
|
||
DrawOTag(db[active].ot);
|
||
|
||
// Переключение буферов
|
||
PutDispEnv(&db[active].disp);
|
||
PutDrawEnv(&db[active].draw);
|
||
|
||
active ^= 1; // Смена активного буфера
|
||
VSync(0); // Ожидание VBlank
|
||
}
|
||
```
|
||
|
||
## Ключевые особенности:
|
||
|
||
1. **Двойная буферизация** - обычно используют два набора DISPENV/DRAWENV
|
||
2. **VSync синхронизация** - переключение лучше делать во время VBlank
|
||
3. **Размеры областей** - должны соответствовать выбранному разрешению
|
||
4. **Производительность** - правильная настройка уменьшает артефакты
|
||
|
||
Эти функции являются фундаментальными для управления графическим конвейером PS1 и требуют точной настройки для оптимальной работы. |