316. Быстрая отрисовка framebuffer в pygame
Улучшаем отрисовку framebuffer в pygame, заменяя попиксельные прямоугольники на массовый blit.
Урок 316 из 356 · tests/chapter_10_performance/test_316_fast_pygame_framebuffer_draw.py
Файл для обновления
tools/show_framebuffer.pyЗачем нужен этот шаг
Исходная draw_framebuffer() была намеренно простой и учебной:
for each framebuffer pixel:
create/fill one scaled pygame rectangleДля кадра NES это означает
256 * 240 = 61,440 pygame drawing operations per frameЭто медленно, потому что Python многократно пересекает границу с pygame/SDL для тысяч крошечных прямоугольников.
Этот шаг сохраняет старую реализацию для сравнения, переименовав её
old_draw_framebuffer(...)Затем он создаёт новую, более быструю draw_framebuffer(...), которая:
1. packs the framebuffer pixels into one RGB byte buffer
2. creates one pygame Surface from that buffer
3. scales that Surface when needed
4. blits the complete image to the windowМентальная модель
Old path:
Python -> pygame draw tiny rect
Python -> pygame draw tiny rect
Python -> pygame draw tiny rect
... 61,440 times per frame
New path:
Python builds one RGB buffer
pygame uploads/scales/blits one imageМассовые операции обычно намного быстрее, чем множество мелких вызовов из Python в pygame.
Пример реализации
def old_draw_framebuffer(
surface: pygame.Surface,
framebuffer: Framebuffer,
scale: int,
) -> None:
# Keep the old rectangle-based implementation for comparison.
...
def draw_framebuffer(
surface: pygame.Surface,
framebuffer: Framebuffer,
scale: int,
) -> None:
# Write the framebuffer to pygame surface using one image upload.
rgb_bytes = bytearray(framebuffer.width * framebuffer.height * 3)
write_index = 0
for color in framebuffer.pixels:
red, green, blue = color
rgb_bytes[write_index] = red
rgb_bytes[write_index + 1] = green
rgb_bytes[write_index + 2] = blue
write_index += 3
frame_surface = pygame.image.frombuffer(
bytes(rgb_bytes),
(framebuffer.width, framebuffer.height),
"RGB",
)
if scale == 1:
surface.blit(frame_surface, (0, 0))
return
scaled_surface = pygame.transform.scale(
frame_surface,
(framebuffer.width * scale, framebuffer.height * scale),
)
surface.blit(scaled_surface, (0, 0))Важная граница
Эта оптимизация относится к tools/show_framebuffer.py. Ядро эмулятора по-прежнему производит чистый Framebuffer и не должно импортировать pygame.
Вне рамок
- кэширование поверхностей/буферов
- NumPy/surfarray
- Numba
- изменение main.py
- выравнивание кадров / ограничение скорости
Запустить этот урок
uv run pytest tests/chapter_10_performance/test_316_fast_pygame_framebuffer_draw.py -v