330. Композиция горизонтальной области просмотра фреймбуфера
Составить область просмотра 256x240 из двух соседних фреймбуферов nametable по горизонтали.
Урок 330 из 356 · tests/chapter_13_scrolling/test_330_compose_horizontal_framebuffer_viewport.py
Файл для обновления
emulator/rendering/background_viewport.pyСправочная документация
https://www.nesdev.org/wiki/PPU_scrolling
https://www.nesdev.org/wiki/PPU_nametablesЗачем нужен этот шаг
Каждый отрисованный nametable имеет ширину 256 пикселей, но горизонтальная прокрутка может привести к тому, что один экран будет содержать пиксели из двух соседних nametable. Этот шаг рассматривает их как один логический фон 512x240 и выбирает из него область просмотра 256x240.
Модель координат
left framebuffer: logical X 0-255
right framebuffer: logical X 256-511Для каждого целевого X экрана
logical X = (viewport X + screen X) modulo 512Операция по модулю оборачивает правый край логической пары обратно к её левому краю.
Пример стыка
viewport X = 200
screen X 0-55 <- left X 200-255
screen X 56-255 <- right X 0-199Инварианты
- оба исходных фреймбуфера имеют размер ровно 256x240
- возвращаемый фреймбуфер имеет размер ровно 256x240
- ни один из исходных фреймбуферов не изменяется
- каждый выходной пиксель берётся из одной и той же строки Y одного исходного фреймбуфера
Распространённое заблуждение
Эта функция не реализует зеркалирование nametable картриджа. PpuBus владеет зеркалированием адресов; эта функция лишь составляет уже отрисованных логических соседей.
Вне рамок
- вертикальная прокрутка
- композиция маски непрозрачности фона
- чтения PPU или PpuBus
- рендеринг тайлов nametable
- интеграция с Console
- pygame
- точные переносы v/t по тактам
Полный пример реализации
# emulator/rendering/background_viewport.py
from emulator.rendering.framebuffer import Framebuffer
def compose_horizontal_framebuffer_viewport(
left: Framebuffer,
right: Framebuffer,
viewport_x: int,
) -> Framebuffer:
expected_size = (
NAMETABLE_PIXEL_WIDTH,
NAMETABLE_PIXEL_HEIGHT,
)
if (left.width, left.height) != expected_size:
raise ValueError("Left nametable framebuffer must be 256x240")
if (right.width, right.height) != expected_size:
raise ValueError("Right nametable framebuffer must be 256x240")
logical_width = NAMETABLE_PIXEL_WIDTH * 2
result = Framebuffer(
width=NAMETABLE_PIXEL_WIDTH,
height=NAMETABLE_PIXEL_HEIGHT,
)
for screen_y in range(NAMETABLE_PIXEL_HEIGHT):
for screen_x in range(NAMETABLE_PIXEL_WIDTH):
logical_x = (viewport_x + screen_x) % logical_width
if logical_x < NAMETABLE_PIXEL_WIDTH:
source = left
source_x = logical_x
else:
source = right
source_x = logical_x - NAMETABLE_PIXEL_WIDTH
color = source.get_pixel(source_x, screen_y)
result.set_pixel(screen_x, screen_y, color)
return resultЗапустить этот урок
uv run pytest tests/chapter_13_scrolling/test_330_compose_horizontal_framebuffer_viewport.py -v