diff --git a/port/android/README.md b/port/android/README.md index 0a25bdd0..96c9cc42 100644 --- a/port/android/README.md +++ b/port/android/README.md @@ -95,7 +95,7 @@ viewport's shape with a fixed vertical one, so the 3D view simply widens ("Hor+"). The HUD anchors to the title-safe frame, which widens with the screen; the menus, the loading bar and the post-game screens are laid out for 640 columns and are drawn centered (the vertex shaders shift them, -`halo_android_ui_offset`); chapter titles keep their place relative to the +`halo_screen_ui_offset`); chapter titles keep their place relative to the screen's sides; letterbox bars and fades cover the whole width. The changes are in `rasterizer_xbox.c`, `render.c`, `ui_widget.c`, `cinematics.c`, `main.c` and `rasterizer_xbox_screen_effect.c`, under diff --git a/port/linux/README.md b/port/linux/README.md index 600e8590..aaad75c4 100644 --- a/port/linux/README.md +++ b/port/linux/README.md @@ -71,13 +71,15 @@ further gamepads become controllers 2-4. | F1 | back | | \` | opens the developer console (typing then goes to the console) | | F12 | releases or recaptures the mouse | +| F11 | switches between fullscreen and the window | ### Settings | Variable | Effect | | --- | --- | | `HALO_DATA_ROOT`, `HALO_SAVE_ROOT` | see above | -| `HALO_WINDOW_SCALE` | initial window size as a multiple of 640x480 (default 2); the window is resizable and the picture is letterboxed | +| `HALO_FULLSCREEN` | `0` opens the game in a window instead of fullscreen (F11 switches between the two). Fullscreen draws at the display's resolution and shape: 480 of the game's lines, as wide as the display (the view widens, the HUD keeps to the screen's edges, menus stay centered), each drawn at as many pixels as the display has; a window draws the Xbox's 640x480 | +| `HALO_WINDOW_SCALE` | window size as a multiple of 640x480 (default 2); the window is resizable and the picture is letterboxed | | `HALO_MOUSE_SENSITIVITY` | mouse aim multiplier (default 1.0) | | `HALO_MOUSE_INVERT` | set to invert vertical mouse aim | | `HALO_VOLUME` | master volume (default 1.0) | diff --git a/port/linux/include/halo_linux_source_fixups.h b/port/linux/include/halo_linux_source_fixups.h index acfef451..1fd2f59a 100644 --- a/port/linux/include/halo_linux_source_fixups.h +++ b/port/linux/include/halo_linux_source_fixups.h @@ -35,11 +35,12 @@ void render_interpolation_first_person(short local_player_index, struct real_mat short node_count, struct render_camera const *camera); float render_interpolation_game_time_sec(long ticks); -#ifdef HALO_ANDROID -/* the screen at the device's aspect ratio (port/linux/src/d3d8_gl.c) */ -long halo_android_screen_width(void); +/* the width of the screen the game draws, 480 lines tall: the device's or +the display's shape, or 640 (port/linux/src/d3d8_gl.c) */ +long halo_screen_width(void); +/* takes up a new width between frames (F11); returns the width */ +long halo_screen_commit(void); /* while TRUE, drawing shifts right to center 640-column layouts */ -void halo_android_ui_offset(unsigned char centered); -#endif +void halo_screen_ui_offset(unsigned char centered); #endif diff --git a/port/linux/src/d3d8_gl.c b/port/linux/src/d3d8_gl.c index 3b816639..ab4dc611 100644 --- a/port/linux/src/d3d8_gl.c +++ b/port/linux/src/d3d8_gl.c @@ -31,6 +31,7 @@ Conventions carried over from the Xbox: #include void d3d8_surface_initialize(D3DSurface *surface, D3DFORMAT format, unsigned long width, unsigned long height); +void d3d8_surface_resize(D3DSurface *surface, D3DFORMAT format, unsigned long width, unsigned long height); #ifdef HALO_ANDROID /* OpenGL ES 3 (port/android/README.md): the desktop formats, enumerants @@ -54,43 +55,78 @@ struct xgpu_capabilities xgpu_capabilities; /* ---------- the screen's width -The Xbox screen is 640x480. On Android the game renders at the device's -aspect ratio instead: 480 lines, and as many columns as the display's shape -gives (HALO_SCREEN_WIDTH, set by the host; 640 keeps 4:3). The game's -camera derives its horizontal field of view from the viewport, so the 3D -view simply widens. The menus and full-screen overlays are laid out for 640 -columns; while they draw (halo_android_ui_offset), everything shifts right -to center them. */ +The Xbox screen is 640x480. The native ports can draw a wider one: 480 +lines, and as many columns as the display's shape gives. On Android that is +HALO_SCREEN_WIDTH, set by the host (640 keeps 4:3); on the desktop, the +display's shape while the game is fullscreen, and 640 in a window. The +game's camera derives its horizontal field of view from the viewport, so the +3D view simply widens. The menus and full-screen overlays are laid out for +640 columns; while they draw (halo_screen_ui_offset), everything shifts right +to center them. -#ifdef HALO_ANDROID +Fullscreen on the desktop also draws at the display's resolution: render +targets the size of the screen get that many pixels (screen_scale), and +viewports, clears and visibility counts are scaled to match, so the game +still works in its 480 lines. The width and the scale change only between +frames, after one is presented (halo_screen_commit). */ + +#define SCREEN_HEIGHT 480 +#define SCREEN_MAXIMUM_WIDTH 1920 + +/* the width the game draws, 0 until first asked, and how many pixels a +render target the size of the screen has per unit of it */ +static long screen_width; +static float screen_scale[2] = { 1.0f, 1.0f }; static long ui_offset; #define UI_OFFSET ((GLint)ui_offset) -long halo_android_screen_width(void) +static void screen_mode_choose(long *width, float scale[2]) { - static long width; +#ifdef HALO_ANDROID + const char *text = getenv("HALO_SCREEN_WIDTH"); - if (!width) - { - const char *text = getenv("HALO_SCREEN_WIDTH"); - - width = text ? atol(text) : 640; - if (width < 640) - width = 640; - if (width > 1600) - width = 1600; - width &= ~1L; - } - return width; -} - -void halo_android_ui_offset(unsigned char centered) -{ - ui_offset = centered ? (halo_android_screen_width() - 640) / 2 : 0; -} + *width = text ? atol(text) : 640; + if (*width < 640) + *width = 640; + if (*width > 1600) + *width = 1600; + *width &= ~1L; + scale[0] = scale[1] = 1.0f; #else -#define UI_OFFSET 0 + long display_width, display_height; + + *width = 640; + scale[0] = scale[1] = 1.0f; + if (platform_screen_mode(&display_width, &display_height) && display_width > 0 && display_height > 0) + { + long wanted = (SCREEN_HEIGHT * display_width + display_height / 2) / display_height; + + *width = wanted < 640 ? 640 : wanted > SCREEN_MAXIMUM_WIDTH ? SCREEN_MAXIMUM_WIDTH : wanted & ~1L; + scale[0] = (float)display_width / (float)*width; + scale[1] = (float)display_height / (float)SCREEN_HEIGHT; + /* a display narrower or wider than the game can be: the picture + keeps its shape and the display blit letterboxes it */ + if (*width != wanted && *width != (wanted & ~1L)) + scale[0] = scale[1] = scale[0] < scale[1] ? scale[0] : scale[1]; + } #endif +} + +long halo_screen_width(void) +{ + if (!screen_width) + { + screen_mode_choose(&screen_width, screen_scale); + platform_log("screen: %ldx%d drawn at %.0fx%.0f", screen_width, SCREEN_HEIGHT, + screen_width * screen_scale[0], SCREEN_HEIGHT * screen_scale[1]); + } + return screen_width; +} + +void halo_screen_ui_offset(unsigned char centered) +{ + ui_offset = centered ? (halo_screen_width() - 640) / 2 : 0; +} /* ---------- state the XDK header's inline functions read and write */ @@ -295,6 +331,9 @@ struct gl_device GLuint queries[VISIBILITY_TEST_SLOTS]; BOOL query_pending[VISIBILITY_TEST_SLOTS]; + /* the pixels each of the game's pixels covered in the test's target + (render_target_get), which its count is divided by */ + float query_area[VISIBILITY_TEST_SLOTS]; GLuint active_query; BOOL visibility_test_active; #ifdef HALO_ANDROID @@ -702,11 +741,20 @@ static struct render_target_entry *render_target_get(const D3DSurface *surface) if (!surface || !surface->Data) return NULL; + float scale[2] = { 1.0f, 1.0f }; + surface_dimensions(surface, &width, &height, &depth); + /* the screen's targets are drawn at the screen's scale */ + if (width == (unsigned long)halo_screen_width() && height == SCREEN_HEIGHT) + { + scale[0] = screen_scale[0]; + scale[1] = screen_scale[1]; + } for (entry = *render_target_bucket(surface->Data); entry; entry = entry->next_in_bucket) { if (entry->target.data == surface->Data && entry->target.width == width && - entry->target.height == height && entry->target.depth == depth) + entry->target.height == height && entry->target.depth == depth && + entry->target.scale[0] == scale[0] && entry->target.scale[1] == scale[1]) { return entry; } @@ -716,14 +764,19 @@ static struct render_target_entry *render_target_get(const D3DSurface *surface) entry->target.width = width; entry->target.height = height; entry->target.depth = depth; + entry->target.scale[0] = scale[0]; + entry->target.scale[1] = scale[1]; + entry->target.gl_width = (unsigned long)(width * scale[0] + 0.5f); + entry->target.gl_height = (unsigned long)(height * scale[1] + 0.5f); glGenTextures(1, &entry->target.texture); glBindTexture(GL_TEXTURE_2D, entry->target.texture); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0); if (depth) - glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH24_STENCIL8, (GLsizei)width, (GLsizei)height, 0, - GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, NULL); + glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH24_STENCIL8, (GLsizei)entry->target.gl_width, + (GLsizei)entry->target.gl_height, 0, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, NULL); else - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, (GLsizei)width, (GLsizei)height, 0, GL_BGRA, GL_UNSIGNED_BYTE, NULL); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, (GLsizei)entry->target.gl_width, (GLsizei)entry->target.gl_height, + 0, GL_BGRA, GL_UNSIGNED_BYTE, NULL); xgpu_gl_state_invalidate(); entry->next = render_targets; render_targets = entry; @@ -772,6 +825,15 @@ static GLuint framebuffer_get(GLuint color, GLuint depth) return entry->framebuffer; } +/* the pixels per unit of the bound targets (render_target_get) */ +static float target_scale[2] = { 1.0f, 1.0f }; + +/* the pixel edge of a coordinate in the bound targets' units */ +static GLint target_pixel(float coordinate, int axis) +{ + return (GLint)floorf(coordinate * target_scale[axis] + 0.5f); +} + /* binds the framebuffer for the current targets; returns FALSE if there is nothing to draw into */ static BOOL bind_targets(BOOL *has_depth) @@ -785,6 +847,9 @@ static BOOL bind_targets(BOOL *has_depth) return FALSE; if (color) color->last_rendered = device.frame + 1; + /* viewports and clears are in the targets' units (render_target_get) */ + target_scale[0] = color ? color->target.scale[0] : depth->target.scale[0]; + target_scale[1] = color ? color->target.scale[1] : depth->target.scale[1]; state_framebuffer(framebuffer_get(color ? color->target.texture : 0, depth ? depth->target.texture : 0)); *has_depth = depth != NULL; return TRUE; @@ -987,8 +1052,17 @@ HRESULT WINAPI Direct3D_CreateDevice(UINT adapter, D3DDEVTYPE device_type, void device.presentation = *presentation_parameters; width = device.presentation.BackBufferWidth ? device.presentation.BackBufferWidth : 640; height = device.presentation.BackBufferHeight ? device.presentation.BackBufferHeight : 480; +#ifdef HALO_ANDROID d3d8_surface_initialize(&device.back_buffer, D3DFMT_LIN_A8R8G8B8, width, height); d3d8_surface_initialize(&device.depth_buffer, D3DFMT_LIN_D24S8, width, height); +#else + /* room for the widest screen, which F11 can switch to (the screen's + width, above) */ + d3d8_surface_initialize(&device.back_buffer, D3DFMT_LIN_A8R8G8B8, SCREEN_MAXIMUM_WIDTH, height); + d3d8_surface_initialize(&device.depth_buffer, D3DFMT_LIN_D24S8, SCREEN_MAXIMUM_WIDTH, height); + d3d8_surface_resize(&device.back_buffer, D3DFMT_LIN_A8R8G8B8, width, height); + d3d8_surface_resize(&device.depth_buffer, D3DFMT_LIN_D24S8, width, height); +#endif device.render_target = &device.back_buffer; device.depth_stencil = &device.depth_buffer; for (index = 0; index < D3DTS_MAX; index++) @@ -1040,6 +1114,36 @@ HRESULT WINAPI Direct3D_CreateDevice(UINT adapter, D3DDEVTYPE device_type, void return S_OK; } +/* takes up the display's shape and resolution, or the window's, if they +have changed; between frames, since the game's layout and the targets must +agree for a whole frame. Returns the width the game draws. */ +long halo_screen_commit(void) +{ + long width; + float scale[2]; + + if (!screen_width) + return halo_screen_width(); + screen_mode_choose(&width, scale); + if (width != screen_width || scale[0] != screen_scale[0] || scale[1] != screen_scale[1]) + { + platform_log("screen: %ldx%d drawn at %.0fx%.0f", width, SCREEN_HEIGHT, + width * scale[0], SCREEN_HEIGHT * scale[1]); + screen_width = width; + screen_scale[0] = scale[0]; + screen_scale[1] = scale[1]; +#ifndef HALO_ANDROID + if (device.created) + { + device.presentation.BackBufferWidth = (UINT)width; + d3d8_surface_resize(&device.back_buffer, D3DFMT_LIN_A8R8G8B8, (unsigned long)width, SCREEN_HEIGHT); + d3d8_surface_resize(&device.depth_buffer, D3DFMT_LIN_D24S8, (unsigned long)width, SCREEN_HEIGHT); + } +#endif + } + return screen_width; +} + ULONG WINAPI D3DDevice_Release(void) { return 1; @@ -1208,6 +1312,7 @@ HRESULT WINAPI D3DDevice_EndVisibilityTest(DWORD index) } #endif glEndQuery(VISIBILITY_QUERY); + device.query_area[index] = target_scale[0] * target_scale[1]; /* swap the scratch query into the requested slot */ scratch = device.queries[0]; device.queries[0] = device.queries[index]; @@ -1225,6 +1330,16 @@ HRESULT WINAPI D3DDevice_EndVisibilityTest(DWORD index) return S_OK; } +#ifndef HALO_ANDROID +/* a count of pixels in the game's pixels */ +static GLuint visibility_unscaled(GLuint samples, DWORD index) +{ + float area = device.query_area[index]; + + return area > 1.0f ? (GLuint)(samples / area + 0.5f) : samples; +} + +#endif HRESULT WINAPI D3DDevice_GetVisibilityTestResult(DWORD index, UINT *result, ULONGLONG *time_stamp) { GLuint available = 0, samples = 0; @@ -1257,7 +1372,7 @@ HRESULT WINAPI D3DDevice_GetVisibilityTestResult(DWORD index, UINT *result, ULON /* the latest count the GPU has written: from this test, or while the GPU is still behind, from the slot's earlier ones */ if (result) - *result = device.visibility_results[index]; + *result = visibility_unscaled(device.visibility_results[index], index); return S_OK; } #endif @@ -1271,6 +1386,8 @@ HRESULT WINAPI D3DDevice_GetVisibilityTestResult(DWORD index, UINT *result, ULON more than any test covers, well below what would overflow there. */ if (samples) samples = VISIBILITY_ALL_SAMPLES; +#else + samples = visibility_unscaled(samples, index); #endif if (result) *result = samples; @@ -1933,7 +2050,8 @@ static GLuint mip_composite_get(const struct xgpu_texture_description *descripti struct xgpu_render_target *target = xgpu_render_target_find(data + xgpu_texture_level_offset(description, level)); - if (!target || target->width != width || target->height != height) + if (!target || target->width != width || target->height != height || + target->gl_width != width || target->gl_height != height) break; #ifdef HALO_ANDROID if (!xgpu_capabilities.copy_image) @@ -2046,10 +2164,10 @@ static void apply_raster_state(BOOL has_depth) unsigned char color_mask; BOOL depth_test = has_depth && rs[D3DRS_ZENABLE]; - viewport[0] = (GLint)device.viewport.X; - viewport[1] = (GLint)device.viewport.Y; - viewport[2] = (GLint)device.viewport.Width; - viewport[3] = (GLint)device.viewport.Height; + viewport[0] = target_pixel((float)device.viewport.X, 0); + viewport[1] = target_pixel((float)device.viewport.Y, 1); + viewport[2] = target_pixel((float)(device.viewport.X + device.viewport.Width), 0) - viewport[0]; + viewport[3] = target_pixel((float)(device.viewport.Y + device.viewport.Height), 1) - viewport[1]; if (memcmp(gl_state.viewport, viewport, sizeof(viewport))) { memcpy(gl_state.viewport, viewport, sizeof(viewport)); @@ -3313,8 +3431,11 @@ void WINAPI D3DDevice_Clear(DWORD count, CONST D3DRECT *rectangles, DWORD flags, glEnable(GL_SCISSOR_TEST); for (index = 0; index < count; index++) { - glScissor(rectangles[index].x1 + UI_OFFSET, rectangles[index].y1, - rectangles[index].x2 - rectangles[index].x1, rectangles[index].y2 - rectangles[index].y1); + GLint x0 = target_pixel((float)(rectangles[index].x1 + UI_OFFSET), 0); + GLint y0 = target_pixel((float)rectangles[index].y1, 1); + + glScissor(x0, y0, target_pixel((float)(rectangles[index].x2 + UI_OFFSET), 0) - x0, + target_pixel((float)rectangles[index].y2, 1) - y0); glClear(mask); } glDisable(GL_SCISSOR_TEST); @@ -3326,7 +3447,7 @@ void WINAPI D3DDevice_Clear(DWORD count, CONST D3DRECT *rectangles, DWORD flags, static void write_screenshot(struct render_target_entry *target) { const char *directory = getenv("HALO_SCREENSHOT_DIR"); - unsigned long width = target->target.width, height = target->target.height; + unsigned long width = target->target.gl_width, height = target->target.gl_height; unsigned char *pixels; char path[512]; FILE *file; @@ -3397,11 +3518,11 @@ void WINAPI D3DDevice_Present(CONST RECT *source_rectangle, CONST RECT *destinat platform_video_drawable_size(&window_width, &window_height); /* letterbox to the back buffer's aspect ratio */ width = window_width; - height = (int)((long)window_width * back_buffer->target.height / back_buffer->target.width); + height = (int)((long)window_width * back_buffer->target.gl_height / back_buffer->target.gl_width); if (height > window_height) { height = window_height; - width = (int)((long)window_height * back_buffer->target.width / back_buffer->target.height); + width = (int)((long)window_height * back_buffer->target.gl_width / back_buffer->target.gl_height); } x = (window_width - width) / 2; y = (window_height - height) / 2; @@ -3412,7 +3533,7 @@ void WINAPI D3DDevice_Present(CONST RECT *source_rectangle, CONST RECT *destinat glClear(GL_COLOR_BUFFER_BIT); glBindFramebuffer(GL_READ_FRAMEBUFFER, framebuffer_get(back_buffer->target.texture, 0)); /* row 0 of the render target is the top of the picture */ - glBlitFramebuffer(0, 0, (GLint)back_buffer->target.width, (GLint)back_buffer->target.height, + glBlitFramebuffer(0, 0, (GLint)back_buffer->target.gl_width, (GLint)back_buffer->target.gl_height, x, y + height, x + width, y, GL_COLOR_BUFFER_BIT, GL_LINEAR); platform_video_swap(); xgpu_gl_state_invalidate(); diff --git a/port/linux/src/d3d8_resources.c b/port/linux/src/d3d8_resources.c index 1458f231..b1d17b00 100644 --- a/port/linux/src/d3d8_resources.c +++ b/port/linux/src/d3d8_resources.c @@ -83,6 +83,17 @@ void d3d8_surface_initialize(D3DSurface *surface, D3DFORMAT format, unsigned lon ((height - 1) << D3DSIZE_HEIGHT_SHIFT) | (width - 1); } +/* changes the size a surface describes, keeping its memory, which must +hold the new size (the back buffer, when the screen's width changes) */ +void d3d8_surface_resize(D3DSurface *surface, D3DFORMAT format, unsigned long width, unsigned long height) +{ + unsigned long pitch = (width * bytes_per_texel(format) + D3DTEXTURE_PITCH_ALIGNMENT - 1) & + ~(unsigned long)(D3DTEXTURE_PITCH_ALIGNMENT - 1); + + surface->Size = ((pitch / D3DTEXTURE_PITCH_ALIGNMENT - 1) << D3DSIZE_PITCH_SHIFT) | + ((height - 1) << D3DSIZE_HEIGHT_SHIFT) | (width - 1); +} + /* ---------- registration and release */ void WINAPI D3DResource_Register(D3DResource *resource, void *base) diff --git a/port/linux/src/nv2a_vsh.c b/port/linux/src/nv2a_vsh.c index f3bfe328..bfb1a7b1 100644 --- a/port/linux/src/nv2a_vsh.c +++ b/port/linux/src/nv2a_vsh.c @@ -151,10 +151,8 @@ static const char shader_prologue[] = "uniform vec4 viewport_scale;\n" "uniform vec4 viewport_offset;\n" "uniform float point_size;\n" -#ifdef HALO_ANDROID /* columns the menus shift by to center on a wide screen (d3d8_gl.c) */ "uniform float screen_offset;\n" -#endif "out vec4 xD0;\n" "out vec4 xD1;\n" "out vec4 xB0;\n" @@ -354,7 +352,7 @@ char *nv2a_vertex_shader_to_glsl(const DWORD *instructions, unsigned long instru "\telse\n" "\t\tgl_Position = vec4((vec3(oPos.xy + vec2(0.5 + screen_offset, 0.5), oPos.z) - viewport_offset.xyz) / scale * oPos.w, oPos.w);\n" #else - "\tvec3 ndc = (vec3(oPos.xy + 0.5, oPos.z) - viewport_offset.xyz) / scale;\n" + "\tvec3 ndc = (vec3(oPos.xy + vec2(0.5 + screen_offset, 0.5), oPos.z) - viewport_offset.xyz) / scale;\n" "\tgl_Position = vec4(ndc * oPos.w, oPos.w);\n" #endif #ifdef HALO_ANDROID diff --git a/port/linux/src/sdl_platform.c b/port/linux/src/sdl_platform.c index 9e67807d..c23c9f4a 100644 --- a/port/linux/src/sdl_platform.c +++ b/port/linux/src/sdl_platform.c @@ -69,6 +69,38 @@ int halo_interpolation_enabled(void) return enabled; } +#ifndef HALO_ANDROID +/* whether the window opens fullscreen: yes unless HALO_FULLSCREEN=0, and +never when it is hidden */ +static BOOL platform_fullscreen_setting(void) +{ + const char *setting = getenv("HALO_FULLSCREEN"); + + return !getenv("HALO_HIDDEN_WINDOW") && !(setting && !strcmp(setting, "0")); +} + +/* whether the game is, or is to be, fullscreen, and if so the size in +pixels of the display it fills (d3d8_gl.c draws at that resolution) */ +BOOL platform_screen_mode(long *width, long *height) +{ + SDL_DisplayID display; + const SDL_DisplayMode *mode; + + if (platform_window ? !(SDL_GetWindowFlags(platform_window) & SDL_WINDOW_FULLSCREEN) : + !platform_fullscreen_setting() || !platform_sdl_initialize()) + { + return FALSE; + } + display = platform_window ? SDL_GetDisplayForWindow(platform_window) : SDL_GetPrimaryDisplay(); + mode = display ? SDL_GetDesktopDisplayMode(display) : NULL; + if (!mode) + return FALSE; + *width = (long)(mode->w * mode->pixel_density + 0.5f); + *height = (long)(mode->h * mode->pixel_density + 0.5f); + return TRUE; +} + +#endif BOOL platform_video_initialize(unsigned long width, unsigned long height) { const char *scale_text = getenv("HALO_WINDOW_SCALE"); @@ -108,8 +140,14 @@ BOOL platform_video_initialize(unsigned long width, unsigned long height) platform_window = SDL_CreateWindow("Halo", (int)(width * scale), (int)(height * scale), SDL_WINDOW_OPENGL | SDL_WINDOW_FULLSCREEN); #else + /* fullscreen at the desktop's resolution unless HALO_FULLSCREEN=0, where + the game draws the display's shape at its resolution (d3d8_gl.c); the + window size is the windowed mode F11 switches to and from, where it + draws 640x480 */ platform_window = SDL_CreateWindow("Halo", (int)(width * scale), (int)(height * scale), - SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | (getenv("HALO_HIDDEN_WINDOW") ? SDL_WINDOW_HIDDEN : 0)); + SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIGH_PIXEL_DENSITY | + (getenv("HALO_HIDDEN_WINDOW") ? SDL_WINDOW_HIDDEN : 0) | + (platform_fullscreen_setting() ? SDL_WINDOW_FULLSCREEN : 0)); #endif if (!platform_window) { @@ -350,6 +388,15 @@ void platform_pump_events(void) input_state.mouse_released = !input_state.mouse_released; platform_mouse_capture(!input_state.mouse_released); } +#ifndef HALO_ANDROID + /* F11 switches between fullscreen and the window (SDL keeps the + window's size and place while fullscreen) */ + if (event.key.down && !event.key.repeat && event.key.scancode == SDL_SCANCODE_F11) + { + SDL_SetWindowFullscreen(platform_window, + (SDL_GetWindowFlags(platform_window) & SDL_WINDOW_FULLSCREEN) ? false : true); + } +#endif break; case SDL_EVENT_MOUSE_MOTION: input_state.mouse_dx += event.motion.xrel; diff --git a/port/linux/src/sdl_platform.h b/port/linux/src/sdl_platform.h index 0b7720e5..b7a69610 100644 --- a/port/linux/src/sdl_platform.h +++ b/port/linux/src/sdl_platform.h @@ -32,6 +32,9 @@ struct platform_keystroke BOOL platform_sdl_initialize(void); /* creates the window and makes its OpenGL context current on this thread */ BOOL platform_video_initialize(unsigned long width, unsigned long height); +#ifndef HALO_ANDROID +BOOL platform_screen_mode(long *width, long *height); +#endif void platform_video_drawable_size(int *width, int *height); void platform_video_swap(void); /* frames between the 30 Hz ticks at the display's refresh rate, unless diff --git a/port/linux/src/xgpu.h b/port/linux/src/xgpu.h index 0b8add93..7d276da8 100644 --- a/port/linux/src/xgpu.h +++ b/port/linux/src/xgpu.h @@ -159,6 +159,10 @@ struct xgpu_render_target unsigned long width, height; BOOL depth; GLuint texture; + /* pixels per unit of width and height: more than 1 for the screen's + targets when the game draws at the display's resolution (d3d8_gl.c) */ + float scale[2]; + unsigned long gl_width, gl_height; }; /* the GL texture holding a render target with this physical address, or 0 */ diff --git a/source/cutscene/cinematics.c b/source/cutscene/cinematics.c index 2cbebe98..55550f5f 100644 --- a/source/cutscene/cinematics.c +++ b/source/cutscene/cinematics.c @@ -470,13 +470,13 @@ void cinematic_render( ((pixel32)shadow_alpha << 24) | (title->shadow_color & 0x00FFFFFF)); -#ifdef HALO_ANDROID +#ifdef HALO_LINUX { /* the bounds are for 640 columns: on a wider screen move them so they keep their place relative to its sides */ static rectangle2d wide_bounds; short shift = (short)(((title_bounds->x0 + title_bounds->x1) / 2) * - (halo_android_screen_width() - 640) / 640); + (halo_screen_width() - 640) / 640); wide_bounds = *title_bounds; wide_bounds.x0 += shift; diff --git a/source/interface/ui_widget.c b/source/interface/ui_widget.c index 51138bf0..66a3cd96 100644 --- a/source/interface/ui_widget.c +++ b/source/interface/ui_widget.c @@ -5535,10 +5535,10 @@ void render_ui_widgets( { real alpha; -#ifdef HALO_ANDROID +#ifdef HALO_LINUX /* the whole screen, around the centered 640 columns */ - bounds.x0 = (short)(-(halo_android_screen_width() - 640) / 2); - bounds.x1 = (short)(640 + (halo_android_screen_width() - 640) / 2); + bounds.x0 = (short)(-(halo_screen_width() - 640) / 2); + bounds.x1 = (short)(640 + (halo_screen_width() - 640) / 2); #else bounds.x0 = 0; bounds.x1 = 640; diff --git a/source/main/main.c b/source/main/main.c index b4b3b4d5..79937d38 100644 --- a/source/main/main.c +++ b/source/main/main.c @@ -2904,8 +2904,8 @@ void halt_and_catch_fire( 1.0f); window_parameters.camera.z_near = rasterizer_globals.near_clip_distance; window_parameters.camera.viewport_bounds.x0 = 0; -#ifdef HALO_ANDROID - window_parameters.camera.viewport_bounds.x1 = (short)halo_android_screen_width(); +#ifdef HALO_LINUX + window_parameters.camera.viewport_bounds.x1 = (short)halo_screen_width(); #else window_parameters.camera.viewport_bounds.x1 = 640; #endif diff --git a/source/rasterizer/xbox/rasterizer_xbox.c b/source/rasterizer/xbox/rasterizer_xbox.c index fb2cbceb..e3f1386b 100644 --- a/source/rasterizer/xbox/rasterizer_xbox.c +++ b/source/rasterizer/xbox/rasterizer_xbox.c @@ -1432,7 +1432,12 @@ void rasterizer_set_target_as_texture( render_primary->Common = RASTERIZER_TARGET_RENDER_PRIMARY_COMMON; render_primary->Data = d3d_backbuffer->Data; render_primary->Lock = 0; +#ifdef HALO_LINUX + /* the back buffer's width (rasterizer_screen_width_update) */ + render_primary->Size = d3d_backbuffer->Size; +#else render_primary->Size = RASTERIZER_TARGET_RENDER_PRIMARY_SIZE; +#endif render_primary->Format = RASTERIZER_TARGET_RENDER_PRIMARY_FORMAT; result = IDirect3DSurface8_Release(d3d_backbuffer); if (result >= 0) @@ -1852,8 +1857,8 @@ boolean rasterizer_preinitialize__fill_you_up_with_the_devils_cock( d3d_present_parameters.EnableAutoDepthStencil = TRUE; d3d_present_parameters.AutoDepthStencilFormat = D3DFMT_D24S8; d3d_present_parameters.BackBufferFormat = D3DFMT_A8R8G8B8; -#ifdef HALO_ANDROID - d3d_present_parameters.BackBufferWidth = halo_android_screen_width(); +#ifdef HALO_LINUX + d3d_present_parameters.BackBufferWidth = halo_screen_width(); #else d3d_present_parameters.BackBufferWidth = RASTERIZER_SCREEN_WIDTH; #endif @@ -2408,6 +2413,48 @@ void _rasterizer_frame_end( return; } +#ifdef HALO_LINUX +/* The native ports can change the width of the screen between frames (F11 +switches the desktop builds between fullscreen at the display's shape and a +640x480 window: halo_screen_commit, port/linux/src/d3d8_gl.c, which resizes +the back buffer). The screen and the title-safe frame follow, as set up in +rasterizer_initialize, and so do the textures the game made of the back +buffer and its copy. */ +static void rasterizer_screen_width_update( + void) +{ + long width = halo_screen_commit(); + + if (width != rasterizer_globals.reserved04.screen_bounds.x1 - rasterizer_globals.reserved04.screen_bounds.x0) + { + short frame_index; + + rasterizer_globals.reserved04.screen_bounds.x0 = 0; + rasterizer_globals.reserved04.screen_bounds.x1 = (short)width; + rasterizer_globals.reserved04.frame_bounds.x0 = + (short)(RASTERIZER_FRAME_BOUNDS_X0 * width / RASTERIZER_SCREEN_WIDTH); + rasterizer_globals.reserved04.frame_bounds.x1 = + (short)(width - rasterizer_globals.reserved04.frame_bounds.x0); + if (global_d3d_surface_render_primary) + { + DWORD size = global_d3d_surface_render_primary->Size; + + for (frame_index = 0; frame_index < 2; frame_index++) + { + if (global_d3d_texture_render_primary[frame_index]) + global_d3d_texture_render_primary[frame_index]->Size = size; + } + if (global_d3d_texture_render_primary_copy) + global_d3d_texture_render_primary_copy->Size = size; + if (global_d3d_surface_render_primary_copy) + global_d3d_surface_render_primary_copy->Size = size; + } + } + + return; +} + +#endif void _rasterizer_present( struct bitmap_data *screenshot_bitmap, point2d const *screenshot_index) @@ -2524,6 +2571,9 @@ void _rasterizer_present( "IDirect3DDevice8_Present(global_d3d_device, NULL, NULL, window_globals.hWndPresentTarget, NULL)"); } rasterizer_globals.fps_accumulation_frame_index++; +#ifdef HALO_LINUX + rasterizer_screen_width_update(); +#endif if (!success) error(_error_silent, "### ERROR rasterizer_present failed"); return; @@ -2880,15 +2930,15 @@ boolean _rasterizer_initialize( rasterizer_globals.reserved04.screen_bounds.y0 = 0; rasterizer_globals.reserved04.screen_bounds.x0 = 0; -#ifdef HALO_ANDROID +#ifdef HALO_LINUX /* the device's aspect ratio, with the title-safe frame (the HUD) widened in proportion */ - rasterizer_globals.reserved04.screen_bounds.x1 = (short)halo_android_screen_width(); + rasterizer_globals.reserved04.screen_bounds.x1 = (short)halo_screen_width(); rasterizer_globals.reserved04.screen_bounds.y1 = RASTERIZER_SCREEN_HEIGHT; rasterizer_globals.reserved04.frame_bounds.x0 = - (short)(RASTERIZER_FRAME_BOUNDS_X0 * halo_android_screen_width() / RASTERIZER_SCREEN_WIDTH); + (short)(RASTERIZER_FRAME_BOUNDS_X0 * halo_screen_width() / RASTERIZER_SCREEN_WIDTH); rasterizer_globals.reserved04.frame_bounds.x1 = - (short)(halo_android_screen_width() - rasterizer_globals.reserved04.frame_bounds.x0); + (short)(halo_screen_width() - rasterizer_globals.reserved04.frame_bounds.x0); #else rasterizer_globals.reserved04.screen_bounds.x1 = RASTERIZER_SCREEN_WIDTH; rasterizer_globals.reserved04.screen_bounds.y1 = RASTERIZER_SCREEN_HEIGHT; @@ -3101,7 +3151,12 @@ boolean _rasterizer_initialize( d3d_texture->Common = RASTERIZER_TARGET_RENDER_PRIMARY_COMMON; d3d_texture->Data = (frame_index == 1) ? global_d3d_surface_render_primary->Data : 0; d3d_texture->Lock = 0; +#ifdef HALO_LINUX + /* the back buffer's width (rasterizer_screen_width_update) */ + d3d_texture->Size = global_d3d_surface_render_primary->Size; +#else d3d_texture->Size = RASTERIZER_TARGET_RENDER_PRIMARY_SIZE; +#endif d3d_texture->Format = RASTERIZER_TARGET_RENDER_PRIMARY_FORMAT; } } @@ -3133,7 +3188,11 @@ boolean _rasterizer_initialize( d3d_texture->Common = RASTERIZER_TARGET_RENDER_PRIMARY_COMMON; d3d_texture->Data = global_d3d_surface_render_primary_z->Data; d3d_texture->Lock = 0; +#ifdef HALO_LINUX + d3d_texture->Size = global_d3d_surface_render_primary->Size; +#else d3d_texture->Size = RASTERIZER_TARGET_RENDER_PRIMARY_SIZE; +#endif d3d_texture->Format = RASTERIZER_TARGET_RENDER_PRIMARY_FORMAT; } else diff --git a/source/rasterizer/xbox/rasterizer_xbox_screen_effect.c b/source/rasterizer/xbox/rasterizer_xbox_screen_effect.c index ab292e09..e66b050f 100644 --- a/source/rasterizer/xbox/rasterizer_xbox_screen_effect.c +++ b/source/rasterizer/xbox/rasterizer_xbox_screen_effect.c @@ -994,11 +994,11 @@ void _rasterizer_screen_effect( if (pass == 0 && main_get_window_count() > 1 && pass_count != 1) { -#ifdef HALO_ANDROID +#ifdef HALO_LINUX vertex_bounds.x0 = 2 * global_window_parameters.camera.viewport_bounds.x0 * - (1.0f / (real)halo_android_screen_width()) - 1.0f; + (1.0f / (real)halo_screen_width()) - 1.0f; vertex_bounds.x1 = 2 * global_window_parameters.camera.viewport_bounds.x1 * - (1.0f / (real)halo_android_screen_width()) - 1.0f; + (1.0f / (real)halo_screen_width()) - 1.0f; #else vertex_bounds.x0 = 2 * global_window_parameters.camera.viewport_bounds.x0 * (1.0f / 640.0f) - 1.0f; diff --git a/source/render/render.c b/source/render/render.c index ade0e9b3..772085e9 100644 --- a/source/render/render.c +++ b/source/render/render.c @@ -106,8 +106,8 @@ enum /* ---------- macros */ -#ifdef HALO_ANDROID -#define RASTERIZER_TARGET_RENDER_PRIMARY_WIDTH halo_android_screen_width() +#ifdef HALO_LINUX +#define RASTERIZER_TARGET_RENDER_PRIMARY_WIDTH halo_screen_width() #else #define RASTERIZER_TARGET_RENDER_PRIMARY_WIDTH 640 #endif @@ -238,10 +238,10 @@ static void render_nonplayer_frame( break; case 1: -#ifdef HALO_ANDROID - halo_android_ui_offset(TRUE); +#ifdef HALO_LINUX + halo_screen_ui_offset(TRUE); game_engine_nonplayer_post_rasterize(); - halo_android_ui_offset(FALSE); + halo_screen_ui_offset(FALSE); #else game_engine_nonplayer_post_rasterize(); #endif @@ -282,10 +282,10 @@ void render_frame_pregame( rasterizer_parameters.rasterizer_target = 0; rasterizer_window_begin(&rasterizer_parameters); -#ifdef HALO_ANDROID - halo_android_ui_offset(TRUE); +#ifdef HALO_LINUX + halo_screen_ui_offset(TRUE); render_ui_widgets(0, &window->rasterizer_camera.viewport_bounds); - halo_android_ui_offset(FALSE); + halo_screen_ui_offset(FALSE); #else render_ui_widgets(0, &window->rasterizer_camera.viewport_bounds); #endif @@ -441,10 +441,10 @@ static void render_window( rasterizer_lens_flares_draw(); interface_draw_screen(); rasterizer_screen_flash(); -#ifdef HALO_ANDROID - halo_android_ui_offset(TRUE); +#ifdef HALO_LINUX + halo_screen_ui_offset(TRUE); render_ui_widgets(local_player_index, &rasterizer_camera->viewport_bounds); - halo_android_ui_offset(FALSE); + halo_screen_ui_offset(FALSE); #else render_ui_widgets(local_player_index, &rasterizer_camera->viewport_bounds); #endif @@ -670,10 +670,10 @@ void render_frame( render_nonplayer_frame(window, window_type); } -#ifdef HALO_ANDROID - halo_android_ui_offset(TRUE); +#ifdef HALO_LINUX + halo_screen_ui_offset(TRUE); progress_bar_eachframe(); - halo_android_ui_offset(FALSE); + halo_screen_ui_offset(FALSE); #else progress_bar_eachframe(); #endif diff --git a/tools/pgo_train.py b/tools/pgo_train.py index 37771ca1..9c27d585 100644 --- a/tools/pgo_train.py +++ b/tools/pgo_train.py @@ -177,6 +177,7 @@ def main() -> None: "HALO_SAVE_ROOT": str(saves.resolve()), "HALO_EXIT_AFTER": str(seconds), "HALO_NO_VSYNC": "1", + "HALO_FULLSCREEN": "0", "HALO_VOLUME": "0", "LLVM_PROFILE_FILE": str((raw / f"{name}-%p.profraw").resolve()), }