Class: PSX::GPU

Inherits:
Object
  • Object
show all
Defined in:
lib/psx/gpu.rb

Overview

Graphics Processing Unit Handles 2D/3D rendering to VRAM

Constant Summary collapse

VRAM_WIDTH =

VRAM dimensions

1024
VRAM_HEIGHT =
512
STAT_TEXTURE_PAGE_X =

Status register bits

0x0000_000F
STAT_TEXTURE_PAGE_Y =

Texture page X base (N*64)

0x0000_0010
STAT_SEMI_TRANSPARENCY =

Texture page Y base (N*256)

0x0000_0060
STAT_TEXTURE_DEPTH =

Semi-transparency mode

0x0000_0180
STAT_DITHER =

Texture color depth

0x0000_0200
STAT_DRAW_TO_DISPLAY =

Dither enabled

0x0000_0400
STAT_SET_MASK_BIT =

Drawing to display area allowed

0x0000_0800
STAT_DRAW_PIXELS =

Set mask bit when drawing

0x0000_1000
STAT_INTERLACE_FIELD =

Draw pixels (0=always, 1=not to masked)

0x0000_2000
STAT_REVERSE_FLAG =

Interlace field

0x0000_4000
STAT_TEXTURE_DISABLE =

Reverse flag

0x0000_8000
STAT_HORIZONTAL_RES2 =

Texture disable

0x0001_0000
STAT_HORIZONTAL_RES1 =

Horizontal resolution 2

0x0006_0000
STAT_VERTICAL_RES =

Horizontal resolution 1

0x0008_0000
STAT_VIDEO_MODE =

Vertical resolution (0=240, 1=480)

0x0010_0000
STAT_COLOR_DEPTH =

Video mode (0=NTSC, 1=PAL)

0x0020_0000
STAT_VERTICAL_INTERLACE =

Display color depth (0=15bit, 1=24bit)

0x0040_0000
STAT_DISPLAY_ENABLE =

Vertical interlace

0x0080_0000
STAT_IRQ =

Display enable (0=enabled, 1=disabled)

0x0100_0000
STAT_DMA_REQUEST =

IRQ flag

0x0200_0000
STAT_CMD_READY =

DMA request

0x0400_0000
STAT_VRAM_TO_CPU_READY =

Ready for command

0x0800_0000
STAT_DMA_READY =

Ready for VRAM to CPU transfer

0x1000_0000
STAT_DMA_DIRECTION =

Ready for DMA

0x6000_0000
STAT_DRAWING_ODD =

DMA direction

0x8000_0000
CMD_NOP =

GP0 command types

0x00
CMD_CLEAR_CACHE =
0x01
CMD_FILL_RECT =
0x02
CMD_POLY_BASE =

0x20-0x3F polygons

0x20
CMD_LINE_BASE =

0x40-0x5F lines

0x40
CMD_RECT_BASE =

0x60-0x7F rectangles

0x60
CMD_COPY_VRAM_VRAM =
0x80
CMD_COPY_CPU_VRAM =
0xA0
CMD_COPY_VRAM_CPU =
0xC0
CMD_ENV_BASE =

0xE0-0xEF environment commands

0xE0

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(interrupts: nil) ⇒ GPU

Returns a new instance of GPU.



52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
# File 'lib/psx/gpu.rb', line 52

def initialize(interrupts: nil)
  @interrupts = interrupts

  # VRAM: 1024x512 16-bit pixels
  @vram = Array.new(VRAM_WIDTH * VRAM_HEIGHT, 0)

  # Status register
  @status = STAT_CMD_READY | STAT_DMA_READY | STAT_VRAM_TO_CPU_READY

  # Display settings
  @display_enabled = false
  @display_start_x = 0
  @display_start_y = 0
  @display_h_start = 0x200
  @display_h_end = 0xC00
  @display_v_start = 0x10
  @display_v_end = 0x100
  @video_mode = :ntsc
  @horizontal_res = 320
  @vertical_res = 240
  @color_depth_24 = false
  @interlaced = false

  # Drawing area
  @draw_area_left = 0
  @draw_area_top = 0
  @draw_area_right = 0
  @draw_area_bottom = 0
  @draw_offset_x = 0
  @draw_offset_y = 0

  # Texture settings
  @texture_page_x = 0
  @texture_page_y = 0
  @texture_depth = 0  # 0=4bit, 1=8bit, 2=15bit
  @semi_transparency = 0
  @texture_window_mask_x = 0
  @texture_window_mask_y = 0
  @texture_window_offset_x = 0
  @texture_window_offset_y = 0
  @texture_disable_allow = false

  # Mask settings
  @set_mask_bit = false
  @check_mask_bit = false

  # Command buffer for multi-word commands
  @cmd_buffer = []
  @cmd_remaining = 0
  @current_cmd = 0

  # VRAM transfer state
  @vram_transfer_x = 0
  @vram_transfer_y = 0
  @vram_transfer_start_x = 0  # Starting X for line wrap detection
  @vram_transfer_width = 0
  @vram_transfer_height = 0
  @vram_transfer_count = 0
  @vram_transfer_mode = nil  # :cpu_to_vram or :vram_to_cpu
  @vram_read_buffer = []

  # Framebuffer caching
  @framebuffer_dirty = true
  @framebuffer_cache = nil

  # DMA direction
  @dma_direction = 0

  # Interlace field (toggled by VBlank)
  @odd_field = false
end

Instance Attribute Details

#vramObject (readonly)

Returns the value of attribute vram.



50
51
52
# File 'lib/psx/gpu.rb', line 50

def vram
  @vram
end

Instance Method Details

#framebufferObject

Get current framebuffer as RGBA packed binary string for display Returns hash with :width, :height, :rgba (binary string) Uses caching to avoid regenerating unchanged frames



253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
# File 'lib/psx/gpu.rb', line 253

def framebuffer
  # Return cached framebuffer if VRAM hasn't changed
  if !@framebuffer_dirty && @framebuffer_cache &&
     @framebuffer_cache[:width] == @horizontal_res &&
     @framebuffer_cache[:height] == @vertical_res
    return @framebuffer_cache
  end

  width = @horizontal_res
  height = @vertical_res
  num_pixels = width * height

  # Build RGBA array and pack once (faster than building string)
  rgba_arr = Array.new(num_pixels * 4)
  dst_i = 0

  height.times do |y|
    vram_row = (@display_start_y + y) * VRAM_WIDTH + @display_start_x
    width.times do |x|
      color16 = @vram[vram_row + x] || 0

      # Convert 15-bit to 24-bit RGB
      rgba_arr[dst_i] = (color16 & 0x001F) << 3      # R
      rgba_arr[dst_i + 1] = (color16 & 0x03E0) >> 2  # G
      rgba_arr[dst_i + 2] = (color16 & 0x7C00) >> 7  # B
      rgba_arr[dst_i + 3] = 255                       # A
      dst_i += 4
    end
  end

  @framebuffer_dirty = false
  @framebuffer_cache = { width: width, height: height, rgba: rgba_arr.pack("C*") }
end

#gp0(value) ⇒ Object

GP0 - Rendering commands and VRAM access



155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
# File 'lib/psx/gpu.rb', line 155

def gp0(value)
  if @vram_transfer_mode == :cpu_to_vram
    vram_write_data(value)
    return
  end

  if @cmd_remaining > 0
    @cmd_buffer << value
    @cmd_remaining -= 1

    if @cmd_remaining == 0
      execute_gp0_command
    end
    return
  end

  cmd = (value >> 24) & 0xFF
  @current_cmd = cmd
  @cmd_buffer = [value]

  case cmd
  when CMD_NOP
    # Do nothing
  when CMD_CLEAR_CACHE
    # Clear texture cache - ignore for now
  when CMD_FILL_RECT
    @cmd_remaining = 2
  when 0x20..0x3F
    # Polygons
    @cmd_remaining = polygon_word_count(cmd) - 1
  when 0x40..0x5F
    # Lines
    @cmd_remaining = line_word_count(cmd) - 1
  when 0x60..0x7F
    # Rectangles
    @cmd_remaining = rectangle_word_count(cmd) - 1
  when CMD_COPY_VRAM_VRAM
    @cmd_remaining = 3
  when CMD_COPY_CPU_VRAM
    @cmd_remaining = 2
  when CMD_COPY_VRAM_CPU
    @cmd_remaining = 2
  when 0xE1
    gp0_draw_mode(value)
  when 0xE2
    gp0_texture_window(value)
  when 0xE3
    gp0_draw_area_top_left(value)
  when 0xE4
    gp0_draw_area_bottom_right(value)
  when 0xE5
    gp0_draw_offset(value)
  when 0xE6
    gp0_mask_settings(value)
  else
    # Unknown command - ignore
  end

  execute_gp0_command if @cmd_remaining == 0 && @cmd_buffer.length > 0
end

#gp1(value) ⇒ Object

GP1 - Display control



217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
# File 'lib/psx/gpu.rb', line 217

def gp1(value)
  cmd = (value >> 24) & 0xFF

  case cmd
  when 0x00
    gp1_reset
  when 0x01
    gp1_reset_command_buffer
  when 0x02
    gp1_acknowledge_irq
  when 0x03
    gp1_display_enable(value)
  when 0x04
    gp1_dma_direction(value)
  when 0x05
    gp1_display_start(value)
  when 0x06
    gp1_horizontal_range(value)
  when 0x07
    gp1_vertical_range(value)
  when 0x08
    gp1_display_mode(value)
  when 0x09
    gp1_texture_disable(value)
  when 0x10..0x1F
    gp1_gpu_info(value)
  end
end

#gp1_texture_disable(value) ⇒ Object



246
247
248
# File 'lib/psx/gpu.rb', line 246

def gp1_texture_disable(value)
  @texture_disable_allow = (value & 1) != 0
end

#mark_dirtyObject

Mark framebuffer as needing regeneration (call when VRAM is modified)



288
289
290
# File 'lib/psx/gpu.rb', line 288

def mark_dirty
  @framebuffer_dirty = true
end

#read_dataObject



146
147
148
149
150
151
152
# File 'lib/psx/gpu.rb', line 146

def read_data
  if @vram_transfer_mode == :vram_to_cpu && !@vram_read_buffer.empty?
    @vram_read_buffer.shift
  else
    0
  end
end

#statusObject



129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
# File 'lib/psx/gpu.rb', line 129

def status
  stat = @status

  # Update dynamic bits
  stat &= ~STAT_DISPLAY_ENABLE
  stat |= STAT_DISPLAY_ENABLE unless @display_enabled

  stat &= ~STAT_DMA_DIRECTION
  stat |= (@dma_direction << 29) & STAT_DMA_DIRECTION

  # Bit 31: Drawing odd lines in interlace mode
  stat &= ~STAT_DRAWING_ODD
  stat |= STAT_DRAWING_ODD if @odd_field

  stat
end

#vblankObject

Called by emulator on VBlank to toggle interlace field



125
126
127
# File 'lib/psx/gpu.rb', line 125

def vblank
  @odd_field = !@odd_field
end