В 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 и требуют точной настройки для оптимальной работы.