# HG changeset patch # User Paul Boddie # Date 1323031425 -3600 # Node ID 4460129be79e86a60bc3cc9f548694504aa95f86 # Parent d6b8cd0ac739c8821f72f108bf3cabc27516baa4 Changed the program architecture to involve an external screen update loop requesting pixel values from a ULA object which manages its own internal state, maintaining a pixel buffer for decoded memory content. diff -r d6b8cd0ac739 -r 4460129be79e ula.py --- a/ula.py Sun Dec 04 01:17:40 2011 +0100 +++ b/ula.py Sun Dec 04 21:43:45 2011 +0100 @@ -17,114 +17,171 @@ MAX_MEMORY = 0x10000 INTENSITY = 255 -palette = range(0, 8) * 2 - -def get_mode((width, depth, rows)): +def update(screen, ula): """ - Return the 'width', 'depth', 'rows', calculated character row size in - bytes, screen size in bytes, horizontal pixel scaling, vertical pixel - spacing, and line spacing between character rows, as elements of a tuple for - a particular screen mode. - """ - - return (width, depth, rows, - (width * depth * LINES_PER_ROW) / 8, # bits per row -> bytes per row - (width * depth * MAX_HEIGHT) / 8, # bits per screen -> bytes per screen - WIDTH / width, # pixel width in display pixels - HEIGHT / (rows * LINES_PER_ROW), # pixel height in display pixels - MAX_HEIGHT / rows - LINES_PER_ROW) # pixels between rows - -modes = map(get_mode, [ - (640, 1, 32), (320, 2, 32), (160, 4, 32), # (width, depth, rows) - (640, 1, 24), (320, 1, 32), (160, 2, 32), - (320, 1, 24) - ]) - -def update(screen, memory, start, mode): - - """ - Update the 'screen' array by reading from 'memory' at the given 'start' - address for the given 'mode'. + Update the 'screen' array by reading from the 'ula'. """ - # Get the width in pixels, colour depth in bits per pixel, number of - # character rows, character row size in bytes, screen size in bytes, - # horizontal pixel scaling factor, vertical pixel scaling factor, and line - # spacing in pixels. + ula.vsync() + y = 0 + while y < HEIGHT: + x = 0 + while x < WIDTH: + colour = ula.get_pixel_colour() + pixel = tuple(map(lambda x: x * INTENSITY, colour)) + screen[x][y] = pixel + x += 1 + ula.hsync() + y += 1 - width, depth, rows, row_size, screen_size, xscale, yscale, spacing = modes[mode] +class ULA: + + "The ULA functionality." - address = start - y = 0 - row = 0 + modes = [ + (640, 1, 32), (320, 2, 32), (160, 4, 32), # (width, depth, rows) + (640, 1, 24), (320, 1, 32), (160, 2, 32), + (320, 1, 24) + ] + + palette = range(0, 8) * 2 + + def __init__(self, memory): - while row < rows: - row_start = address - line = 0 + "Initialise the ULA with the given 'memory'." + + self.memory = memory + self.set_mode(6) - # Emit each character row. + # Internal state. + + self.buffer = [0] * 8 + + def set_mode(self, mode): - while line < LINES_PER_ROW: - line_start = address - ysub = 0 + """ + For the given 'mode', initialise the... - # Scale each row of pixels vertically. + * width in pixels + * colour depth in bits per pixel + * number of character rows + * character row size in bytes + * screen size in bytes + * default screen start address + * horizontal pixel scaling factor + * vertical pixel scaling factor + * line spacing in pixels + * number of entries in the pixel buffer + """ - while ysub < yscale: - x = 0 + self.width, self.depth, self.rows = self.modes[mode] + + row_size = (self.width * self.depth * LINES_PER_ROW) / 8 # bits per row -> bytes per row - # Emit each row of pixels. + self.screen_size = row_size * self.rows + self.screen_start = SCREEN_LIMIT - self.screen_size + self.xscale = WIDTH / self.width # pixel width in display pixels + self.yscale = HEIGHT / (self.rows * LINES_PER_ROW) # pixel height in display pixels + self.spacing = MAX_HEIGHT / self.rows - LINES_PER_ROW # pixels between rows + self.buffer_limit = 8 / self.depth - while x < WIDTH: - byte_value = memory[address] + def vsync(self): + + "Signal the start of a frame." - for colour in decode(byte_value, depth): - colour = get_physical_colour(palette[colour]) - pixel = tuple(map(lambda x: x * INTENSITY, colour)) + self.line_start = self.address = self.screen_start + self.line = 0 + self.ysub = 0 + self.reset_horizontal() - # Scale the pixels horizontally. + def reset_horizontal(self): + + "Reset horizontal state." + + self.xsub = 0 + self.buffer_index = self.buffer_limit # need refill - xsub = 0 - while xsub < xscale: - screen[x][y] = pixel - xsub += 1 - x += 1 + def hsync(self): + + "Signal the end of a line." + + self.reset_horizontal() + + # Scale pixels vertically. - # Advance to the next column. + self.ysub += 1 + + # Re-read the current line if appropriate. + + if self.ysub < self.yscale: + self.address = self.line_start + return + + # Otherwise, move on to the next line. - address += LINES_PER_ROW - address = wrap_address(address, screen_size) + self.ysub = 0 + self.line += 1 + + if self.line < LINES_PER_ROW: + self.address = self.line_start + 1 + self.wrap_address() + + # After the end of the last line in a row, the address should already + # have been positioned on the last line of the next column. - ysub += 1 - y += 1 + else: + self.address -= LINES_PER_ROW - 1 + self.wrap_address() - # Return to the address at the start of the line in order to be - # able to repeat the line. + self.line_start = self.address - address = line_start + # Move on to the next row if appropriate. + + if self.line == LINES_PER_ROW: + self.line = 0 - # Move on to the next line in the row. + def get_pixel_colour(self): - line += 1 - address += 1 + """ + Return a pixel colour by reading from the pixel buffer. + """ + + # Scale pixels horizontally. - # Skip spacing between rows. + if self.xsub == self.xscale: + self.xsub = 0 + self.buffer_index += 1 - if spacing: - y += spacing * yscale + if self.buffer_index == self.buffer_limit: + self.buffer_index = 0 + self.fill_pixel_buffer() + + self.xsub += 1 + return self.buffer[self.buffer_index] + + def fill_pixel_buffer(self): - # Move on to the next row. + """ + Fill the pixel buffer by translating memory content for the current + mode. + """ - address = row_start + row_size - address = wrap_address(address, screen_size) + byte_value = self.memory[self.address] - row += 1 + i = 0 + for colour in decode(byte_value, self.depth): + self.buffer[i] = get_physical_colour(self.palette[colour]) + i += 1 + + # Advance to the next column. -def wrap_address(address, screen_size): - if address >= SCREEN_LIMIT: - address -= screen_size - return address + self.address += LINES_PER_ROW + self.wrap_address() + + def wrap_address(self): + if self.address >= SCREEN_LIMIT: + self.address -= self.screen_size def get_physical_colour(value): @@ -196,10 +253,19 @@ memory = array.array("B", itertools.repeat(0, MAX_MEMORY)) a = pygame.surfarray.pixels3d(screen) + ula = ULA(memory) + # Test MODE 2. - fill(memory, 0x3000, 0x8000, encode((1, 6), 4)) - update(a, memory, 0x3000, 2) + ula.set_mode(2) + + fill(memory, 0x3000, 0x5800 - 320, encode((1, 6), 4)) + fill(memory, 0x5800 - 320, 0x8000, encode((2, 7), 4)) + update(a, ula) + mainloop() + + ula.screen_start = 0x5800 - 320 + update(a, ula) mainloop() # vim: tabstop=4 expandtab shiftwidth=4