Source code for oberon.display

# -*- coding: utf-8 -*-
#
#    Copyright © 2019 Simon Forman
#
#    This file is part of PythonOberon
#
#    PythonOberon is free software: you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation, either version 3 of the License, or
#    (at your option) any later version.
#
#    PythonOberon is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with PythonOberon.  If not see <http://www.gnu.org/licenses/>.
#
'''

Display
========================

This module encapsulates the PyGame library and provides a mixin class
:py:class:`ScreenRAMMixin` for the :py:class:`oberon.risc.ByteAddressed32BitRAM`
class that visualizes the memory-mapped raster display on a PyGame surface.

'''
from sys import stderr

try:
    import pygame
    from pygame.locals import *
except ImportError:
    print('Unable to import pygame.', file=stderr)
    PYGAME = False
else:
    PYGAME = True
    display_flip = pygame.display.flip

    WHITE = pygame.Color(0xFF, 0xFF, 0xFF)
    CURSOR = None



SIZE = WIDTH, HEIGHT = 1024, 768
'Size of the screen in pixels.'

DISPLAY_START = 0xE7F00
'RAM address of the start of the memory-mapped raster display.'

DISPLAY_SIZE_IN_BYTES = WIDTH * HEIGHT // 8
'As the name implies, the number of bytes in the display portion of the RAM.'

WORDS_IN_SCANLINE = WIDTH // 32
'The number of 32-bit words in one horizontal scan line of the display.'


WHITE = 23


[docs]def initialize_screen(): ''' Fire up PyGame and return a screen surface of :py:obj:`SIZE`. ''' pygame.init() screen = pygame.display.set_mode(SIZE) global CURSOR CURSOR = pygame.Surface((32, 1), depth=screen.get_bitsize()) return screen
[docs]class ScreenRAMMixin(object): ''' A mixin class for the :py:class:`oberon.risc.ByteAddressed32BitRAM` that updates the PyGame screen pixels when data are written to RAM addresses within the memory-mapped raster display. '''
[docs] def set_screen(self, screen): ''' Connect a PyGame surface to the RAM. ''' self.screen = screen screen_size_hack(self)
[docs] def put(self, address, word): ''' Extends :py:meth:`oberon.risc.ByteAddressed32BitRAM.put` to check for writes to the memory-mapped raster display RAM and update the PyGame screen accordingly. ''' super(ScreenRAMMixin, self).put(address, word) if ( DISPLAY_START <= address < DISPLAY_START + DISPLAY_SIZE_IN_BYTES ): # Convert byte RAM address to word screen address. address = (address - DISPLAY_START) >> 2 update_screen(self.screen, address, word)
def __setitem__(self, key, value): self.put(key, value)
[docs]def screen_size_hack(ram, width=WIDTH, height=HEIGHT): ''' Tuck a "magic number" and the screen dimensions into a location in RAM at the start of the display. (I forget how they are used by the system.) If you have PyGame installed you can see the data in the pixels in the lower-left (origin) corner of the screen. ''' ram[DISPLAY_START] = 0x53697A66 # magic value 'SIZE'+1 ram[DISPLAY_START + 4] = width ram[DISPLAY_START + 8] = height
[docs]def coords_of_word(address): ''' Given the address in words already adjusted for :py:obj:`DISPLAY_START` return a generator that yields the (x, y) coords of the pixels in that word. ''' y, word_x = divmod(address, WORDS_IN_SCANLINE) y = HEIGHT - y - 1 pixel_x = word_x * 32 for x in range(pixel_x, pixel_x + 32): yield x, y
[docs]def bits_of_int(i): '''Yield thirty-two bits of an integer as 0 or 1, LSB to MSB.''' for _ in range(32): yield i & 1 i >>= 1
[docs]def update_screen(screen, address, value): ''' Update the contents of the PyGame ``screen`` at ``address`` with the bit values from the integer ``value``. ''' COLORS = 0, (255, 255, 255) for x, bit in enumerate(bits_of_int(value)): CURSOR.set_at((x, 0), COLORS[bit]) y, x = divmod(address, WORDS_IN_SCANLINE) screen.blit(CURSOR, (x << 5, HEIGHT - y - 1)) display_flip()