Class: PSX::Display

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

Constant Summary collapse

WIDTH =
320
HEIGHT =
240
SCALE =

640x480 window

2

Instance Method Summary collapse

Constructor Details

#initialize(title: "PSX-Ruby") ⇒ Display

Returns a new instance of Display.



11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# File 'lib/psx/display.rb', line 11

def initialize(title: "PSX-Ruby")
  SDL2.init(SDL2::INIT_VIDEO)

  @window = SDL2::Window.create(
    title,
    SDL2::Window::POS_CENTERED,
    SDL2::Window::POS_CENTERED,
    WIDTH * SCALE,
    HEIGHT * SCALE,
    0
  )

  @renderer = @window.create_renderer(-1, SDL2::Renderer::Flags::ACCELERATED)

  # Input state
  @quit_requested = false
  @controller_state = 0xFFFF  # All buttons released (active low)

  # Performance tracking
  @frame_count = 0
  @last_fps_time = Time.now
  @fps = 0

  # Cache texture reference
  @texture = nil

  # Pre-allocated RGBA buffer (320x240x4 = 307,200 bytes)
  @rgba_buffer = "\x00".b * (WIDTH * HEIGHT * 4)
  @rgba_buffer_size = WIDTH * HEIGHT * 4

  # Track if we've seen real content
  @has_rendered_content = false
end

Instance Method Details

#closeObject



139
140
141
142
143
# File 'lib/psx/display.rb', line 139

def close
  @texture&.destroy
  @renderer.destroy
  @window.destroy
end

#controller_stateObject



135
136
137
# File 'lib/psx/display.rb', line 135

def controller_state
  @controller_state
end

#poll_eventsObject



118
119
120
121
122
123
124
125
126
127
128
129
# File 'lib/psx/display.rb', line 118

def poll_events
  while (event = SDL2::Event.poll)
    case event
    when SDL2::Event::Quit
      @quit_requested = true
    when SDL2::Event::KeyDown
      handle_key(event.scancode, true)
    when SDL2::Event::KeyUp
      handle_key(event.scancode, false)
    end
  end
end

#quit_requested?Boolean

Returns:

  • (Boolean)


131
132
133
# File 'lib/psx/display.rb', line 131

def quit_requested?
  @quit_requested
end

#update(framebuffer) ⇒ Object



45
46
47
48
49
50
51
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
# File 'lib/psx/display.rb', line 45

def update(framebuffer)
  width = framebuffer[:width]
  height = framebuffer[:height]

  # GPU now provides pre-packed RGBA string
  rgba = framebuffer[:rgba]

  # Check if framebuffer has any non-zero content (only until first content seen)
  if rgba && !@has_rendered_content
    # Quick check: sample a few bytes instead of iterating all
    has_content = rgba.getbyte(0) != 0 || rgba.getbyte(rgba.bytesize / 2) != 0
    @has_rendered_content = true if has_content
  end

  if rgba && @has_rendered_content
    @rgba_buffer = rgba
  else
    # Use cached loading screen if size matches
    buffer_size = width * height * 4
    if @loading_screen_size != buffer_size
      # Generate loading screen pattern once
      rgba_arr = []
      height.times do |y|
        width.times do |x|
          if (x % 32 < 2) || (y % 32 < 2)
            rgba_arr.push(40, 40, 80, 255)  # Grid lines
          else
            rgba_arr.push(0, 0, 40, 255)    # Dark blue
          end
        end
      end
      @loading_screen = rgba_arr.pack("C*")
      @loading_screen_size = buffer_size
    end
    @rgba_buffer = @loading_screen
  end

  # Create SDL surface from RGBA data
  surface = SDL2::Surface.from_string(
    @rgba_buffer,
    width,
    height,
    32,           # depth (bits per pixel)
    width * 4,    # pitch (bytes per row)
    0x000000FF,   # R mask
    0x0000FF00,   # G mask
    0x00FF0000,   # B mask
    0xFF000000    # A mask
  )

  # Create texture from surface
  @texture&.destroy
  @texture = @renderer.create_texture_from(surface)
  surface.destroy

  # Render (stretch to window size)
  @renderer.copy(@texture, nil, nil)
  @renderer.present

  # Update FPS counter
  @frame_count += 1
  @total_frames = (@total_frames || 0) + 1
  now = Time.now
  elapsed = now - @last_fps_time
  if elapsed >= 1.0
    @fps = (@frame_count / elapsed).round(1)
    status = has_content ? "RENDERING" : "Loading..."
    @window.title = "PSX-Ruby - #{@fps} FPS - Frame #{@total_frames} - #{status}"
    @frame_count = 0
    @last_fps_time = now
  end
end