Русский
Русский
English
Статистика
Реклама

Перевод Эмуляция компьютера интерпретатор CHIP-8 и формирование изображений

Недавно мы опубликовали перевод первого материала из серии статей, посвящённой эмуляции компьютера. Автор этих статей подробно рассказывает о написании интерпретатора CHIP-8 на C++. В той публикации мы устроили опрос о целесообразности перевода продолжения цикла. Почти 94% тех, кто принял участие в опросе, продолжение перевода поддержали. Поэтому сегодня мы представляем вашему вниманию второй материал о CHIP-8.



Подготовка к выводу изображений


В прошлый раз мы написали интерпретатор CHIP-8, который способен выполнять все операции за исключением одной Dxyn (DRW Vx, Vy, nibble). Ради упрощения реализации этой инструкции мы инкапсулируем графическую память и код в классе Image. Кадр размером 64x32 пикселя будет представлен в виде единого фрагмента данных в памяти. Каждому пикселю будет соответствовать один байт:

0x000:|--------------------------------------------------------------|0x040:|                               |0x080:|                               |0x0C0:|                               |...0x7C0:|--------------------------------------------------------------|

Для описания этой памяти нам понадобится три значения: количество строк, количество столбцов и начальный адрес (нам его даёт malloc). Если у нас есть этот адрес, указывающий на элемент графической памяти, находящийся в верхнем левом углу вышеприведённой схемы, обращение к отдельным пикселям будет выполняться очень просто. Вот несколько примеров:

img[col=0, row=0] = img[0]img[col=0, row=1] = img[width]img[col=1, row=3] = img[3*width+1]

Теперь, когда у нас есть эти сведения, мы готовы к тому, чтобы создать соответствующий заголовочный файл:

// image.hclass Image {public:// Выделение и освобождение памяти в ctor и dtor.Image(int cols, int rows);~Image();uint8_t* Row(int r);// Возвращает пиксель, который может быть изменён.uint8_t& At(int c, int r);void SetAll(uint8_t value);private:int cols_;int rows_;uint8_t* data_;};

Тут надо обратить внимание на то, что мы динамически выделяем память, владельцем которой будет этот класс. В более крупной системе мы могли бы решить воспользоваться std::unique_ptr вместе с особой функцией для выделения памяти. Но тут мы просто используем malloc в конструкторе и free в деструкторе класса.

// image.cppImage::Image(int cols, int rows) {data_ = static_cast<uint8_t*>(malloc(cols * rows * sizeof(uint8_t)));cols_ = cols;rows_ = rows;}Image::~Image() {free(data_);}uint8_t* Image::Row(int r) {return &data_[r * cols_];}uint8_t& Image::At(int c, int r) {return Row(r)[c];}void Image::SetAll(uint8_t value) {std::memset(data_, value, rows_ * cols_);}void Image::DrawToStdout() {for (int r = 0; r < rows_; r++) {for (int c = 0; c < cols_; c++) {if (At(c,r) > 0) {std::cout << "X";} else {std::cout << " ";}}std::cout << std::endl;}std::cout << std::endl;}

Здесь мне удобнее пользоваться такими именами переменных, как rows_ (строки) и cols_ (столбцы), а не x и y. Дело в том, что имя переменной rows чётко ассоциируется у меня со строками, а вот о том, что такое x, я вполне могу забыть. Функция At возвращает uint8_t&, что даёт нам возможность и получать значения отдельных пикселей, и устанавливать эти значения. Это плохо с точки зрения инкапсуляции, но такой приём часто используется в графических API. Мы, кроме того, предусмотрели тут удобную функцию DrawToStdout, которая позволяет выводить в консоль то, что должно быть отображено на экране, делая это даже тогда, когда подсистема графического вывода эмулятора ещё не реализована. Сейчас мы можем добавить в класс CpuChip8 поле frame_ типа Image и поработать над реализацией соответствующих механизмов.

// cpu_chip8.hclass CpuChip8 {public:constexpr innt kFrameWidth = 64;constexpr innt kFrameHeight = 32;CpuChip8() : frame_(kFrameWidth, kFrameHeight) {}...private:...Image frame_;};

Теперь давайте поговорим о том, как CHIP-8 выполняет вывод графических данных. А именно, рисование спрайта в текущем (и единственном) кадровом буфере выполняется по принципам, используемым в операции XOR. Все спрайты описываются в виде изображений с глубиной цвета в 1 бит (каждый пиксель может быть либо включен, либо выключен). Ширина спрайта равняется 8 битам, высота может меняться. Ограничение на ширину спрайта применяется из-за того, что каждый пиксель спрайта представлен единственным битом. Посмотрим на описание набора шрифтов, присутствующее в предыдущем материале, и попробуем расшифровать одну из цифр.

0xF0, 0x90, 0x90, 0x90, 0xF0, // 00xF0 это 1111 0000 -> XXXX0x90 это 1001 0000 -> X X0x90 это 1001 0000 -> X X0x90 это 1001 0000 -> X X0xF0 это 1111 0000 -> XXXX

Замечательно! Помните, я говорил о том, что вывод графики основан на операции XOR? Так вот, это значит, что единственный способ убрать спрайт с экрана заключается в том, чтобы вывести ещё один спрайт поверх него (фактически тот же самый спрайт), так как 1 1 даёт 0. Именно поэтому при работе с CHIP-8-программами часто заметно мерцание, так как спрайты постоянно выводятся на экран и стираются с него для вывода движущихся объектов.

Итак, мы готовы к тому, чтобы создать функцию для вывода спрайтов. Нам понадобится начальная точка и сам спрайт (область памяти). Так как спрайты могут иметь переменную высоту, мы получаем и соответствующий параметр, описывающий её. Отмечу, что одна особенность интерпретатора CHIP-8 потребовала некоторого времени на её отладку. Она заключается в том, что интерпретатор поддерживает вывод графики за пределами экрана. Когда спрайт выходит за границы экрана, его рисование продолжается на другой стороне экрана. Это поведение проявляется и при указании стартовых координат спрайта (то есть вывод 15 строк в координате 255,255 это совершенно нормально). Кроме того, интерпретатору нужно сообщать о том, был ли при выводе спрайта стёрт какой-нибудь пиксель (это часто используется для обнаружения столкновений объектов, выводимых на экран).

// image.cpp// Возвращает true в том случае, если новое значение стирает пиксель.bool Image::XOR(int c, int r, uint8_t val) {uint8_t& current_val = At(c, r);uint8_t prev_val = current_val;current_val ^= val;return current_val == 0 && prev_val > 0;}bool Image::XORSprite(int c, int r, int height, uint8_t* sprite) {// Переход на другую сторону экрана при выводе спрайта.bool pixel_was_disabled = false;for (int y = 0; y < height; y++) {int current_r = r + y;while (current_r >= rows_) { current_r -= rows_; }uint8_t sprite_byte = sprite[y];for (int x = 0; x < 8; x++) {int current_c = c + x;while (current_c >= cols_) { current_c -= cols_; }// Обратите внимание: Сканирование выполняется от MSbit до LSbituint8_t sprite_val = (sprite_byte & (0x80 >> x)) >> (7-x);pixel_was_disabled |= XOR(current_c, current_r, sprite_val);}}return pixel_was_disabled;}

Нам нужно позаботиться о том, чтобы извлекать биты, представляя их значениями 1 или 0. Так как класс Image поддерживает [0..255], операции XOR, без этого ограничения, могут наделать много беспорядка. Когда же применяется это ограничение, соответствующая инструкция нашего CPU получается очень простой нужно всего лишь извлечь параметры, необходимые для вызова XORSprite.

CpuChip8::Instruction CpuChip8::GenDRAW(uint8_t reg_x, uint8_t reg_y, uint8_t n_rows) {return [this, reg_x, reg_y, n_rows]() {uint8_t x_coord = v_registers_[reg_x];uint8_t y_coord = v_registers_[reg_y];bool pixels_unset = frame_.XORSprite(x_coord, y_coord, n_rows,memory_ + index_register_);v_registers_[0xF] = pixels_unset;NEXT;};}

Итоги


Если вы дошли до этого момента у вас уже должна появиться возможность запускать некоторые ROMы! Поставьте вызов DrawToStdout после цикла выполнения кода и понаблюдайте за тем, что попадает в консоль. Правда, пока на нашем интерпретаторе можно запускать только программы, не ожидающие пользовательского ввода.

В следующем материале из этой серии мы подключим к проекту библиотеку SDL, что позволит выводить графику на экран.

Если бы вы писали собственный интерпретатор CHIP-8 каким языком программирования вы бы пользовались?

Источник: habr.com
К списку статей
Опубликовано: 01.01.2021 18:11:05
0

Сейчас читают

Комментариев (0)
Имя
Электронная почта

Блог компании ruvds.com

Программирование

C++

Chip-8

Разработка

Ruvds_перевод

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru