-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtutorial.c
More file actions
189 lines (156 loc) · 6.31 KB
/
Copy pathtutorial.c
File metadata and controls
189 lines (156 loc) · 6.31 KB
1
2
3
4
5
6
7
8
9
10
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
44
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
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
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
#define _GNU_SOURCE
#include <errno.h>
#include <fcntl.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <unistd.h>
#include <xf86drm.h>
#include <xf86drmMode.h>
// Playdate Target Specs
#define GAME_WIDTH 400
#define GAME_HEIGHT 240
// uConsole Native Display Specs
#define SCREEN_WIDTH 1280
#define SCREEN_HEIGHT 720
typedef struct {
uint32_t fb_id;
uint32_t handle;
uint32_t pitch;
uint64_t size;
uint32_t *map;
} Buffer;
int main() {
// 1. Open the DRM device node for the R01 display panel
int drm_fd = open("/dev/dri/card0", O_RDWR | O_CLOEXEC);
if (drm_fd < 0) {
perror("Failed to open DRM device /dev/dri/card0");
return 1;
}
// Set DRM master permissions
drmSetMaster(drm_fd);
// 2. Locate active connectors and displays
drmModeRes *resources = drmModeGetResources(drm_fd);
if (!resources) {
perror("Failed to get DRM resources");
close(drm_fd);
return 1;
}
drmModeConnector *connector = NULL;
for (int i = 0; i < resources->count_connectors; i++) {
connector = drmModeGetConnector(drm_fd, resources->connectors[i]);
if (connector->connection == DRM_MODE_CONNECTED) break;
drmModeFreeConnector(connector);
connector = NULL;
}
if (!connector) {
fprintf(stderr, "No active display connector found!\n");
drmModeFreeResources(resources);
close(drm_fd);
return 1;
}
// Select native video mode and active CRTC (Display Controller)
drmModeModeInfo mode = connector->modes[0];
uint32_t crtc_id = resources->crtcs[0];
// 3. Allocate Dual Framebuffers for smooth double-buffered rendering
Buffer buffers[2];
for (int i = 0; i < 2; i++) {
struct drm_mode_create_dumb create_req = {
.width = SCREEN_WIDTH,
.height = SCREEN_HEIGHT,
.bpp = 32
};
if (drmIoctl(drm_fd, DRM_IOCTL_MODE_CREATE_DUMB, &create_req) < 0) {
perror("Failed to create dumb buffer");
return 1;
}
buffers[i].handle = create_req.handle;
buffers[i].pitch = create_req.pitch;
buffers[i].size = create_req.size;
if (drmModeAddFB(drm_fd, SCREEN_WIDTH, SCREEN_HEIGHT, 24, 32,
buffers[i].pitch, buffers[i].handle, &buffers[i].fb_id) < 0) {
perror("Failed to add framebuffer");
return 1;
}
struct drm_mode_map_dumb map_req = { .handle = buffers[i].handle };
if (drmIoctl(drm_fd, DRM_IOCTL_MODE_MAP_DUMB, &map_req) < 0) {
perror("Failed to map dumb buffer");
return 1;
}
buffers[i].map = mmap(0, buffers[i].size, PROT_READ | PROT_WRITE,
MAP_SHARED, drm_fd, map_req.offset);
if (buffers[i].map == MAP_FAILED) {
perror("Mmap failed");
return 1;
}
memset(buffers[i].map, 0, buffers[i].size);
}
// Save the original display settings to restore them on exit
drmModeCrtc *saved_crtc = drmModeGetCrtc(drm_fd, crtc_id);
// Set our initial custom KMS framebuffer mode
if (drmModeSetCrtc(drm_fd, crtc_id, buffers[0].fb_id, 0, 0,
&connector->connector_id, 1, &mode) < 0) {
perror("Failed to set CRTC mode");
return 1;
}
// 4. Allocate our virtual 1-bit low-res game state memory
// 0 = Black, 1 = White
uint8_t game_pixels[GAME_WIDTH * GAME_HEIGHT];
memset(game_pixels, 0, sizeof(game_pixels));
int current_buffer = 0;
bool running = true;
int frame_count = 0;
printf("Starting game loop directly on DRM hardware. Press Ctrl+C in SSH to stop.\n");
// 5. Hardcore Real-Time Game Loop
while (running) {
// --- GAME LOGIC STAGE ---
frame_count++;
// Clear internal 1-bit software buffer to black (0)
memset(game_pixels, 0, sizeof(game_pixels));
// Draw an example bouncing box on our 400x240 playdate screen
int box_x = (frame_count % (GAME_WIDTH - 40));
int box_y = 100;
for (int y = box_y; y < box_y + 40; y++) {
for (int x = box_x; x < box_x + 40; x++) {
game_pixels[y * GAME_WIDTH + x] = 1; // Draw white pixel
}
}
// --- RENDER STAGE (Software Scaling Loop) ---
uint32_t *dest_fb = buffers[current_buffer].map;
// Scaling factors from 400x240 to 1280x720:
// Width: 1280 / 400 = 3.2
// Height: 720 / 240 = 3.0
for (int screen_y = 0; screen_y < SCREEN_HEIGHT; screen_y++) {
// Map physical Y row back to game array row (integer division handles nearest-neighbor scaling)
int game_y = screen_y / 3;
if (game_y >= GAME_HEIGHT) game_y = GAME_HEIGHT - 1;
uint32_t *fb_row = dest_fb + (screen_y * (buffers[current_buffer].pitch / 4));
uint8_t *game_row = &game_pixels[game_y * GAME_WIDTH];
for (int screen_x = 0; screen_x < SCREEN_WIDTH; screen_x++) {
// Map physical X pixel back to game array column
int game_x = (screen_x * GAME_WIDTH) / SCREEN_WIDTH;
// Read 1-bit value and convert directly to a 32-bit hex color code
// Playdate Classic Theme: Black (#000000) or Cream White (#FFFFE0)
uint8_t pixel_state = game_row[game_x];
fb_row[screen_x] = pixel_state ? 0xFFFFE0 : 0x000000;
}
}
// 6. Flip buffers via hardware page-flip to completely avoid screen tearing
drmModePageFlip(drm_fd, crtc_id, buffers[current_buffer].fb_id, 0, NULL);
// Swap our double buffer index targets
current_buffer = 1 - current_buffer;
// Cap execution slightly so it doesn't spin at thousands of frames inside empty loops
usleep(16666); // ~60 FPS timing matching hardware refreshing
}
// 7. Cleanup and Restore original Linux console mode
drmModeSetCrtc(drm_fd, saved_crtc->crtc_id, saved_crtc->buffer_id,
saved_crtc->x, saved_crtc->y, &connector->connector_id, 1, &saved_crtc->mode);
drmModeFreeCrtc(saved_crtc);
drmModeFreeConnector(connector);
drmModeFreeResources(resources);
close(drm_fd);
return 0;
}