#include #include "geometry.h" #include "cpld.h" #include "osd.h" #include "defs.h" #include "logging.h" #include "rgb_to_hdmi.h" #include "startup.h" static const char *px_sampling_names[] = { "Normal", "Odd", "Even", "Half Odd", "Half Even", }; static const char *sync_names[] = { "-H-V", "+H-V", "-H+V", "+H+V", "Composite", "Inverted Composite", "Composite -V", "Inverted -V" }; static const char *vsync_names[] = { "Auto", "Interlaced", "Interlaced 160uS Vsync", "Non Interlaced", "Flywheel", "Blanking", "Polarity", "Force Interlaced" }; static const char *setup_names[] = { "Normal", "Set Min/Offset", "Set Maximum", "Set Clock/Line", "Fine Set Clock" }; static const char *fb_sizex2_names[] = { "Normal", "Double Height", "Double Width", "Double Height+Width", }; static const char *deint_names[] = { "Progressive", "Interlaced", "Interlaced Teletext", "Line Doubled" }; static const char *bpp_names[] = { "4", "8", "16" }; static param_t params[] = { { SETUP_MODE, "Setup Mode", "setup_mode", 0,NUM_SETUP-1, 1 }, { H_OFFSET, "H Offset", "h_offset", 1, 384, 4 }, { V_OFFSET, "V Offset", "v_offset", 0, 256, 1 }, { MIN_H_WIDTH, "Min H Width", "min_h_width", 100, 1920, 8 }, {MIN_V_HEIGHT, "Min V Height", "min_v_height", 100, 1200, 2 }, { MAX_H_WIDTH, "Max H Width", "max_h_width", 120, 1920, 8 }, {MAX_V_HEIGHT, "Max V Height", "max_v_height", 120, 1200, 2 }, { H_ASPECT, "H Pixel Aspect", "h_aspect", 0, 12, 1 }, { V_ASPECT, "V Pixel Aspect", "v_aspect", 0, 12, 1 }, { FB_SIZEX2, "FB Size", "fb_size", 0, 3, 1 }, { FB_BPP, "FB Bits/Pixel", "fb_bits_pixel", 0, NUM_BPP-1, 1 }, { CLOCK, "Clock Frequency", "clock_frequency", 1000000,64000000, 1000 }, { LINE_LEN, "Line Length", "line_length", 100, 5000, 1 }, { CLOCK_PPM, "Clock Tolerance", "clock_tolerance", 0, 100000, 100 }, { LINES_FRAME, "Lines per Frame", "lines_per_frame", 250, 1200, 1 }, { SYNC_TYPE, "Sync Type", "sync_type", 0, NUM_SYNC-1, 1 }, { VSYNC_TYPE, "V Sync Type", "vsync_type", 0,NUM_VSYNC-1, 1 }, { VIDEO_TYPE, "Video Type", "video_type", 0,NUM_VIDEO-1, 1 }, { PX_SAMPLING, "Pixel Sampling", "pixel_sampling", 0, NUM_PS-1, 1 }, { -1, NULL, NULL, 0, 0, 0 } }; typedef struct { int setup_mode; // dummy entry for setup int h_offset; // horizontal offset (in psync clocks) int v_offset; // vertical offset (in lines) int min_h_width; // active horizontal width (in 8-bit characters) int min_v_height; // active vertical height (in lines) int max_h_width; // framebuffer width in pixels int max_v_height; // framebuffer height (in pixels, before any doubling is applied) int h_aspect; // horizontal pixel aspect ratio int v_aspect; // vertical pixel aspect ratio int fb_sizex2; // if 1 then double frame buffer height if 2 double width if 3 then both int fb_bpp; // framebuffer bits per pixel int clock; // cpld clock (in Hz) int line_len; // number of clocks per horizontal line int clock_ppm; // cpld tolerance (in ppm) int lines_per_frame; // number of lines per frame int sync_type; // sync type and polarity int vsync_type; // vsync type auto/interlaced/non-interlaced int video_type; // deinterlace type off/teletext int px_sampling; // pixel sampling mode } geometry_t; static int modeset; static geometry_t *geometry; static geometry_t set1_geometry; static geometry_t set2_geometry; static int scaling = 0; static int capvscale = 1; static int caphscale = 1; static int fhaspect = 1; static int fvaspect = 1; static int use_px_sampling = 1; void geometry_init(int version) { // These are Beeb specific defaults so the geometry property can be ommitted set2_geometry.setup_mode = 0; set2_geometry.v_offset = 18; set2_geometry.min_h_width = 504 & 0xfffffff8; set2_geometry.min_v_height = 270 & 0xfffffffe; set2_geometry.max_h_width = 504 & 0xfffffff8; set2_geometry.max_v_height = 270 & 0xfffffffe; set2_geometry.h_aspect = 3; set2_geometry.v_aspect = 4; set2_geometry.fb_sizex2 = SIZEX2_DOUBLE_HEIGHT; set2_geometry.fb_bpp = 0; set2_geometry.clock = 12000000; set2_geometry.line_len = 12 * 64; set2_geometry.clock_ppm = 5000; set2_geometry.lines_per_frame = 312; set2_geometry.sync_type = SYNC_COMP; set2_geometry.vsync_type = VSYNC_AUTO; set2_geometry.video_type = VIDEO_TELETEXT; set2_geometry.px_sampling = PS_NORMAL; set1_geometry.setup_mode = 0; set1_geometry.v_offset = 21; set1_geometry.min_h_width = 672 & 0xfffffff8; set1_geometry.min_v_height= 270 & 0xfffffffe; set1_geometry.max_h_width = 672 & 0xfffffff8; set1_geometry.max_v_height= 270 & 0xfffffffe; set1_geometry.h_aspect = 1; set1_geometry.v_aspect = 2; set1_geometry.fb_sizex2 = SIZEX2_DOUBLE_HEIGHT; set1_geometry.fb_bpp = 1; set1_geometry.clock = 16000000; set1_geometry.line_len = 16 * 64; set1_geometry.clock_ppm = 5000; set1_geometry.lines_per_frame = 312; set1_geometry.sync_type = SYNC_COMP; set1_geometry.vsync_type = VSYNC_AUTO; set1_geometry.video_type = VIDEO_PROGRESSIVE; set1_geometry.px_sampling = PS_NORMAL; int firmware_support = cpld->old_firmware_support(); if (firmware_support & BIT_NORMAL_FIRMWARE_V1) { // For backwards compatibility with CPLDv1 set2_geometry.h_offset = 0; set1_geometry.h_offset = 0; } else if (firmware_support & BIT_NORMAL_FIRMWARE_V2) { // For backwards compatibility with CPLDv2 set2_geometry.h_offset = 96 & 0xfffffffc; set1_geometry.h_offset = 128 & 0xfffffffc; } else { // For CPLDv3 onwards set2_geometry.h_offset = 140 & 0xfffffffc; set1_geometry.h_offset = 160 & 0xfffffffc; } geometry_set_mode(MODE_SET1); } void geometry_set_mode(int mode) { modeset = mode; if (modeset == MODE_SET1) { geometry = &set1_geometry; } else { geometry = &set2_geometry; } } int geometry_get_mode() { return modeset; } int geometry_get_value(int num) { switch (num) { case SETUP_MODE: return geometry->setup_mode; case H_OFFSET: return geometry->h_offset & 0xfffffffc; case V_OFFSET: return geometry->v_offset; case MIN_H_WIDTH: return geometry->min_h_width & 0xfffffff8; case MIN_V_HEIGHT: return geometry->min_v_height & 0xfffffffe; case MAX_H_WIDTH: return geometry->max_h_width & 0xfffffff8; case MAX_V_HEIGHT: return geometry->max_v_height & 0xfffffffe; case H_ASPECT: return geometry->h_aspect; case V_ASPECT: return geometry->v_aspect; case FB_SIZEX2: return geometry->fb_sizex2; case FB_BPP: return geometry->fb_bpp; case CLOCK: return geometry->clock; case LINE_LEN: return geometry->line_len; case CLOCK_PPM: return geometry->clock_ppm; case LINES_FRAME: return geometry->lines_per_frame; case SYNC_TYPE: return geometry->sync_type; case VSYNC_TYPE: return geometry->vsync_type; case VIDEO_TYPE: return geometry->video_type; case PX_SAMPLING: if (use_px_sampling == 0) { geometry->px_sampling = 0; } return geometry->px_sampling; } return -1; } const char *geometry_get_value_string(int num) { if (num == SETUP_MODE) { return setup_names[geometry_get_value(num)]; } if (num == PX_SAMPLING) { return px_sampling_names[geometry_get_value(num)]; } if (num == SYNC_TYPE) { return sync_names[geometry_get_value(num)]; } if (num == VSYNC_TYPE) { return vsync_names[geometry_get_value(num)]; } if (num == FB_SIZEX2) { return fb_sizex2_names[geometry_get_value(num)]; } if (num == VIDEO_TYPE) { return deint_names[geometry_get_value(num)]; } if (num == FB_BPP) { return bpp_names[geometry_get_value(num)]; } return NULL; } void geometry_set_value(int num, int value) { if (value < params[num].min) { value = params[num].min; } if (value > params[num].max) { value = params[num].max; } switch (num) { case SETUP_MODE: geometry->setup_mode = value; if (value == SETUP_FINE) { params[CLOCK].step = 1; } else { params[CLOCK].step = 1000; } break; case H_OFFSET: geometry->h_offset = value & 0xfffffffc; break; case V_OFFSET: geometry->v_offset = value; break; case MIN_H_WIDTH: geometry->min_h_width = value & 0xfffffff8; break; case MIN_V_HEIGHT: geometry->min_v_height = value & 0xfffffffe; break; case MAX_H_WIDTH: geometry->max_h_width = value & 0xfffffff8; break; case MAX_V_HEIGHT: geometry->max_v_height = value & 0xfffffffe; break; case H_ASPECT: geometry->h_aspect = value; break; case V_ASPECT: geometry->v_aspect = value; break; case FB_SIZEX2: geometry->fb_sizex2 = value; break; case FB_BPP: geometry->fb_bpp = value; break; case CLOCK: geometry->clock = value; break; case LINE_LEN: geometry->line_len = value; break; case CLOCK_PPM: geometry->clock_ppm = value; break; case LINES_FRAME: geometry->lines_per_frame = value; break; case SYNC_TYPE: geometry->sync_type = value; break; case VSYNC_TYPE: geometry->vsync_type = value; break; case VIDEO_TYPE: geometry->video_type = value; break; case PX_SAMPLING: if (use_px_sampling == 0) { geometry->px_sampling = 0; } else { geometry->px_sampling = value; } break; } } param_t *geometry_get_params() { return params; } void set_gscaling(int value) { scaling = value; } int get_gscaling() { return scaling; } void set_setup_mode(int mode) { geometry_set_value(SETUP_MODE, mode); //log_info("setup mode = %d", mode); } void geometry_get_fb_params(capture_info_t *capinfo) { int top = 0; int bottom = 0; int left = 0; int right = 0; if (get_startup_overscan() != 0) { set_config_overscan(0, 0, 0, 0); } capinfo->sync_type = geometry->sync_type; capinfo->vsync_type = geometry->vsync_type; capinfo->video_type = geometry->video_type; capinfo->autoswitch = get_parameter(F_AUTO_SWITCH); capinfo->timingset = modeset; capinfo->sync_edge = cpld->get_sync_edge(); if (capinfo->video_type == VIDEO_LINE_DOUBLED) { capinfo->video_type = VIDEO_PROGRESSIVE; } if (capinfo->vsync_type == VSYNC_FORCE_INTERLACE) { capinfo->vsync_type = VSYNC_INTERLACED; } capinfo->sizex2 = geometry->fb_sizex2; switch(geometry->fb_bpp) { case BPP_4: capinfo->bpp = 4; break; default: case BPP_8: capinfo->bpp = 8; break; case BPP_16: capinfo->bpp = 16; break; } capinfo->mode7 = 0; if (capinfo->video_type == VIDEO_TELETEXT) { capinfo->mode7 = 1; if (capinfo->bpp != 4) { capinfo->video_type = VIDEO_INTERLACED; } } if (capinfo->video_type == VIDEO_INTERLACED && capinfo->detected_sync_type & SYNC_BIT_INTERLACED && (menu_active() || osd_active())) { capinfo->video_type = VIDEO_PROGRESSIVE; } if (capinfo->video_type == VIDEO_TELETEXT) { capinfo->bpp = 4; //force 4bpp for teletext } else if (capinfo->sample_width >= SAMPLE_WIDTH_9LO && capinfo->bpp == 4) { capinfo->bpp = 8; //force at least 8bpp in 12 bit modes as no capture loops for capture into 4bpp buffer } else if (capinfo->sample_width == SAMPLE_WIDTH_6 && capinfo->bpp < 8) { capinfo->bpp = 8; //force 8bpp in 6 bit modes as no capture loops for 6 bit capture into 4 bpp buffer } else if (capinfo->sample_width == SAMPLE_WIDTH_6 && capinfo->bpp > 8 && (get_parameter(F_PALETTE_CONTROL) == PALETTECONTROL_C64_LUMACODE || get_parameter(F_PALETTE_CONTROL) == PALETTECONTROL_C64_YUV) && (get_parameter(F_NTSC_COLOUR) == 0 || (capinfo->sizex2 & SIZEX2_DOUBLE_WIDTH) == 0)) { capinfo->bpp = 8; //force 8bpp in 6 bit modes when pal artifact disabled } else if (capinfo->sample_width == SAMPLE_WIDTH_6 && capinfo->bpp > 8 && (get_parameter(F_PALETTE_CONTROL) == PALETTECONTROL_ATARI_LUMACODE) && (get_parameter(F_SCANLINES) == 0 || (capinfo->sizex2 & SIZEX2_DOUBLE_WIDTH) == 0)) { capinfo->bpp = 8; //force 8bpp in 6 bit modes when scanlines disabled } else if (capinfo->sample_width == SAMPLE_WIDTH_6 && capinfo->bpp > 8 && (get_parameter(F_PALETTE_CONTROL) == PALETTECONTROL_ATARI_GTIA || get_parameter(F_PALETTE_CONTROL) == PALETTECONTROL_ATARI2600_LUMACODE)) { capinfo->bpp = 8; //force 8bpp in 6 bit modes when Atari GTIA or 2600 as no 16 bit capture loops } else if (capinfo->sample_width == SAMPLE_WIDTH_6 && capinfo->bpp > 8 && get_parameter(F_PALETTE_CONTROL) >= PALETTECONTROL_NTSCARTIFACT_CGA && get_parameter(F_PALETTE_CONTROL) <= PALETTECONTROL_NTSCARTIFACT_BW_AUTO && (get_core_1_available() == 0 || get_parameter(F_NTSC_TYPE) == NTSCTYPE_SIMPLE)) { capinfo->bpp = 8; //force 8bpp in 6 bit modes when simple ntsc artifact } else if (capinfo->sample_width <= SAMPLE_WIDTH_3 && capinfo->bpp > 8) { capinfo->bpp = 8; //force 8bpp in 1 & 3 bit modes as no capture loops for 1 or 3 bit capture into 16bpp buffer } #ifdef USE_ARM_CAPTURE if ((_get_hardware_id() == _RPI2 || _get_hardware_id() == _RPI3) && capinfo->video_type != VIDEO_TELETEXT) { capinfo->sizex2 &= SIZEX2_DOUBLE_WIDTH; //in ARM build have to inhibit double height on Pi Zero 2Pi2 / Pi 3 otherwise you get stalling } if (_get_hardware_id() == _RPI && capinfo->video_type != VIDEO_TELETEXT && capinfo->sample_width >= SAMPLE_WIDTH_9LO) { capinfo->sizex2 &= SIZEX2_DOUBLE_WIDTH; //in ARM build have to inhibit double height on Pi zero / Pi 1 in 9/12bpp capture } #endif if (capinfo->bpp <= 8 && get_parameter(F_SCANLINES) && (get_parameter(F_PALETTE_CONTROL) == PALETTECONTROL_ATARI_LUMACODE || get_parameter(F_PALETTE_CONTROL) == PALETTECONTROL_ATARI_GTIA)) { capinfo->sizex2 &= SIZEX2_DOUBLE_WIDTH; //inhibit double height for Atari 800 in 8bpp mode } if ((capinfo->detected_sync_type & SYNC_BIT_INTERLACED) && capinfo->video_type != VIDEO_PROGRESSIVE) { capinfo->sizex2 |= SIZEX2_DOUBLE_HEIGHT; } else { if (get_parameter(F_SCANLINES) && !(menu_active() || osd_active())) { if ((capinfo->sizex2 & SIZEX2_DOUBLE_HEIGHT) == 0) { capinfo->sizex2 |= SIZEX2_BASIC_SCANLINES; //flag basic scanlines } capinfo->sizex2 |= SIZEX2_DOUBLE_HEIGHT; // force double height } } if (get_true_vdisplay() <= 288) { capinfo->sizex2 &= SIZEX2_DOUBLE_WIDTH; //inhibit double height when using 288 or 240 pixel modes } int geometry_h_offset = geometry->h_offset; int geometry_v_offset = geometry->v_offset; int geometry_min_h_width = geometry->min_h_width; int geometry_min_v_height = geometry->min_v_height; int geometry_max_h_width = geometry->max_h_width; int geometry_max_v_height = geometry->max_v_height; int h_aspect = geometry->h_aspect; int v_aspect = geometry->v_aspect; if (get_parameter(F_SWAP_ASPECT)) { if (geometry->lines_per_frame > 287) { if (h_aspect == v_aspect) { h_aspect = 4; v_aspect = 5; } else if ((h_aspect << 1) == v_aspect) { h_aspect = 2; v_aspect = 5; } else if (h_aspect == (v_aspect << 1)) { h_aspect = 8; v_aspect = 5; } else if (h_aspect == 7 && v_aspect == 4) { v_aspect = 5; } if (geometry_min_v_height > 250) { geometry_min_v_height = geometry_min_v_height * 4 / 5; } geometry_max_v_height = geometry_max_v_height * 4 / 5; } else { if (h_aspect == 4 && v_aspect == 5) { v_aspect = 4; } else if (h_aspect == 2 && v_aspect == 5) { v_aspect = 4; } else if (h_aspect == 8 && v_aspect == 5) { v_aspect = 4; } else if (h_aspect == 7 && v_aspect == 5) { v_aspect = 4; } //geometry_max_v_height = geometry_max_v_height * 5 / 4; } } //if (get_parameter(F_CROP_BORDER) == OVERSCAN_AUTO && (geometry->setup_mode == SETUP_NORMAL || geometry->setup_mode == SETUP_CLOCK)) { //reduce max area by 4% to hide offscreen imperfections // geometry_max_h_width = ((geometry_max_h_width * 96) / 100) & 0xfffffff8; // geometry_max_v_height = ((geometry_max_v_height * 96) / 100) & 0xfffffffe; //} if (geometry_max_h_width < geometry_min_h_width) { geometry_max_h_width = geometry_min_h_width; } if (geometry_max_v_height < geometry_min_v_height) { geometry_max_v_height = geometry_min_v_height; } if (use_px_sampling != 0) { capinfo->px_sampling = geometry->px_sampling; } else { capinfo->px_sampling = 0; } if (geometry->setup_mode == SETUP_NORMAL) { if (((get_parameter(F_PALETTE_CONTROL) == PALETTECONTROL_NTSCARTIFACT_CGA && get_parameter(F_NTSC_COLOUR) != 0) || (get_parameter(F_PALETTE_CONTROL) == PALETTECONTROL_NTSCARTIFACT_BW) || (get_parameter(F_PALETTE_CONTROL) == PALETTECONTROL_NTSCARTIFACT_BW_AUTO)) && capinfo->bpp == 8 && capinfo->sample_width <= SAMPLE_WIDTH_6) { capinfo->border = get_parameter(F_BORDER_COLOUR); } else { capinfo->border = get_parameter(F_BORDER_COLOUR); if (get_parameter(F_OUTPUT_INVERT) == INVERT_Y) { capinfo->border ^= 0x12; } } } else { capinfo->border = 0x12; // max green/Y } capinfo->ntscphase = get_adjusted_ntscphase() | (get_parameter(F_NTSC_COLOUR) << NTSC_ARTIFACT_SHIFT); if (get_parameter(F_OUTPUT_INVERT) == INVERT_Y) { capinfo->ntscphase |= NTSC_Y_INVERT; } if (get_parameter(F_OUTPUT_INVERT) == INVERT_RGB) { capinfo->ntscphase |= NTSC_RGB_INVERT; } if (get_parameter(F_HDMI_MODE_STANDBY)) { capinfo->ntscphase |= NTSC_HDMI_BLANK_ENABLE; } if (get_parameter(F_FFOSD)) { capinfo->ntscphase |= NTSC_FFOSD_ENABLE; } get_config_overscan(&left, &right, &top, &bottom); int h_size = get_hdisplay() - left - right; int v_size = get_vdisplay() - top - bottom; double ratio = (double) h_size / v_size; int h_size43 = h_size; int v_size43 = v_size; if (scaling == GSCALING_INTEGER) { if (ratio > 1.34) { h_size43 = v_size * 4 / 3; } if (ratio < 1.24) { // was 1.32 but don't correct 5:4 aspect ratio (1.25) to 4:3 as it does good integer scaling for 640x256 and 640x200 v_size43 = h_size * 3 / 4; } } else { if (ratio > 1.34) { h_size43 = v_size * 4 / 3; } if (ratio < 1.24) { v_size43 = h_size * 3 / 4; } } if (scaling == GSCALING_INTEGER && v_size43 == v_size && h_size > h_size43) { //if ((geometry_max_h_width >= 512 && geometry_max_h_width <= 800) || (geometry_max_h_width > 360 && geometry_max_h_width <= 400)) { //h_size43 = (h_size43 * 912) / 720; //adjust 4:3 ratio on widescreen resolutions to account for up to 900 pixel wide integer sample capture if (geometry_min_h_width > 800) { h_size43 = h_size; } else { h_size43 = (h_size43 * 800) / 720; //adjust 4:3 ratio on widescreen resolutions to account for up to 800 pixel wide integer sample capture } //if (h_size43 > h_size) { // h_size43 = h_size; //} } int double_width = (capinfo->sizex2 & SIZEX2_DOUBLE_WIDTH) >> 1; int double_height = capinfo->sizex2 & SIZEX2_DOUBLE_HEIGHT; if ((geometry_min_h_width << double_width) > h_size43) { double_width = 0; } if ((geometry_min_v_height << double_height) > v_size43) { double_height = 0; } if (double_height && (capinfo->sizex2 & SIZEX2_BASIC_SCANLINES)) { capinfo->sizex2 = double_height | (double_width << 1) | SIZEX2_BASIC_SCANLINES; } else { capinfo->sizex2 = double_height | (double_width << 1); } //log_info("unadjusted integer = %d, %d, %d, %d, %d, %d", geometry_h_offset, geometry_v_offset, geometry_min_h_width, geometry_min_v_height, geometry_max_h_width, geometry_max_v_height); switch (geometry->setup_mode) { case SETUP_NORMAL: case SETUP_CLOCK: case SETUP_FINE: default: { int scaled_min_h_width; int scaled_min_v_height; double max_aspect = (double)geometry_max_h_width / (double)geometry_max_v_height; double min_aspect = (double)geometry_min_h_width / (double)geometry_min_v_height; if (min_aspect > max_aspect) { scaled_min_h_width = geometry_min_h_width; scaled_min_v_height = ((int)((double)scaled_min_h_width / max_aspect)); if (scaled_min_v_height < geometry_min_v_height) { scaled_min_v_height = geometry_min_v_height; } } else { scaled_min_v_height = geometry_min_v_height; scaled_min_h_width = ((int)((double)scaled_min_v_height * max_aspect)); if (scaled_min_h_width < geometry_min_h_width) { scaled_min_h_width = geometry_min_h_width; } } geometry_max_h_width = (geometry_max_h_width - ((geometry_max_h_width - scaled_min_h_width) * get_parameter(F_CROP_BORDER) / (NUM_OVERSCAN - 1))) & 0xfffffff8; geometry_max_v_height = (geometry_max_v_height - ((geometry_max_v_height - scaled_min_v_height) * get_parameter(F_CROP_BORDER) / (NUM_OVERSCAN - 1))) & 0xfffffffe; if (geometry_max_h_width < geometry_min_h_width) { geometry_max_h_width = geometry_min_h_width; } if (geometry_max_v_height < geometry_min_v_height) { geometry_max_v_height = geometry_min_v_height; } if (scaling != GSCALING_INTEGER) { geometry_h_offset = geometry_h_offset - (((geometry_max_h_width - geometry_min_h_width) >> 3) << 2); geometry_v_offset = geometry_v_offset - ((geometry_max_v_height - geometry_min_v_height) >> 1); geometry_min_h_width = geometry_max_h_width; geometry_min_v_height = geometry_max_v_height; } } break; case SETUP_MIN: geometry_max_h_width = geometry_min_h_width; geometry_max_v_height = geometry_min_v_height; break; case SETUP_MAX: geometry_h_offset = geometry_h_offset - (((geometry_max_h_width - geometry_min_h_width) >> 3) << 2); geometry_v_offset = geometry_v_offset - ((geometry_max_v_height - geometry_min_v_height) >> 1); geometry_min_h_width = geometry_max_h_width; geometry_min_v_height = geometry_max_v_height; break; } //log_info("adjusted integer = %d, %d, %d, %d, %d, %d", geometry_h_offset, geometry_v_offset, geometry_min_h_width, geometry_min_v_height, geometry_max_h_width, geometry_max_v_height); int h_size43_adj = h_size43; if ((capinfo->mode7 && get_parameter(F_MODE7_SCALING) == SCALING_UNEVEN) || (!capinfo->mode7 && get_parameter(F_NORMAL_SCALING) == SCALING_UNEVEN && geometry->h_aspect == 3 && (geometry->v_aspect == 2 || geometry->v_aspect == 4))) { h_size43_adj = h_size43 * 3 / 4; if (h_aspect == 3 && v_aspect == 2) { h_aspect = 1; v_aspect = 1; } else if (h_aspect == 3 && v_aspect == 4) { h_aspect = 1; v_aspect = 2; } } int hscale = h_size43_adj / geometry_min_h_width; int vscale = v_size43 / geometry_min_v_height; if (hscale < 1) { hscale = 1; } if (vscale < 1) { vscale = 1; } if (get_hdisplay() > 3000 && hscale > 4 && vscale > 4) { //even up scaling of small sources on 4K monitors hscale = (hscale >> 1) << 1; vscale = (vscale >> 1) << 1; } if (h_aspect != 0 && v_aspect !=0 && get_parameter(F_INTEGER_ASPECT) == 0) { int new_hs = hscale; int new_vs = vscale; double h_ratio; double v_ratio; int abort_count = 0; do { h_ratio = (double)hscale / h_aspect; v_ratio = (double)vscale / v_aspect; if (h_ratio != v_ratio) { if (h_ratio > v_ratio) { new_hs = ((int)v_ratio) * h_aspect; } else { new_vs = ((int)h_ratio) * v_aspect; } //log_info("Aspect doesn't match: %d, %d, %d, %d, %f, %f, %d, %d", hscale, vscale, h_aspect, v_aspect, h_ratio, v_ratio, new_hs, new_vs); if (new_hs !=0 && new_vs != 0) { hscale = new_hs; vscale = new_vs; } //log_info("Aspect after loop: %d, %d", hscale, vscale); } abort_count++; } while (new_hs !=0 && new_vs != 0 && h_ratio != v_ratio && abort_count < 10); } //log_info("Final aspect: %d, %d", hscale, vscale); if (scaling == GSCALING_INTEGER) { int new_geometry_min_h_width = h_size43_adj / hscale; if (new_geometry_min_h_width > geometry_max_h_width) { new_geometry_min_h_width = geometry_max_h_width; } int new_geometry_min_v_height = v_size43 / vscale; if (new_geometry_min_v_height > geometry_max_v_height) { new_geometry_min_v_height = geometry_max_v_height; } geometry_h_offset = geometry_h_offset - (((new_geometry_min_h_width - geometry_min_h_width) >> 3) << 2); geometry_v_offset = geometry_v_offset - ((new_geometry_min_v_height - geometry_min_v_height) >> 1); geometry_min_h_width = new_geometry_min_h_width; geometry_min_v_height = new_geometry_min_v_height; } capinfo->delay = (cpld->get_delay() ^ 3) & 3; // save delay for simple mode software implementation geometry_h_offset = geometry_h_offset * lumacode_multiplier() - ((cpld->get_delay() >> 2) << 2); if (geometry_h_offset < 0) { geometry_min_h_width += (geometry_h_offset << 1); geometry_h_offset = 0; } if (geometry_v_offset < 0) { geometry_min_v_height += (geometry_v_offset << 1); geometry_v_offset = 0; } //log_info("adjusted integer2 = %d, %d, %d, %d, %d, %d, %d", cpld->get_delay() >> 2, geometry_h_offset, geometry_v_offset, geometry_min_h_width, geometry_min_v_height, geometry_max_h_width, geometry_max_v_height); switch (capinfo->sample_width) { case SAMPLE_WIDTH_1 : capinfo->h_offset = geometry_h_offset >> 3; break; default: case SAMPLE_WIDTH_3: capinfo->h_offset = geometry_h_offset >> 2; break; case SAMPLE_WIDTH_6 : capinfo->h_offset = (geometry_h_offset >> 2) << 1; break; case SAMPLE_WIDTH_9LO : case SAMPLE_WIDTH_9HI : case SAMPLE_WIDTH_12 : capinfo->h_offset = (geometry_h_offset >> 2) << 2; break; } capinfo->v_offset = geometry_v_offset; capinfo->chars_per_line = ((geometry_min_h_width + 7) >> 3) << double_width; capinfo->nlines = geometry_min_v_height; //log_info("scaling size = %d, %d, %d, %f",standard_width, adjusted_width, adjusted_height, ratio); //log_info("scaling h = %d, %d, %f, %d, %d, %d, %d",h_size, h_size43, hscalef, hscale, hborder, hborder43, newhborder43); //log_info("scaling v = %d, %d, %f, %d, %d, %d, %d",v_size, v_size43, vscalef, vscale, vborder, vborder43, newvborder43); caphscale = hscale; capvscale = vscale; if (caphscale == capvscale) { caphscale = 1; capvscale = 1; } while ((caphscale & 1) == 0 && (capvscale & 1) == 0) { caphscale >>= 1; capvscale >>= 1; } fhaspect = caphscale; fvaspect = capvscale; if (caphscale == 1 && capvscale == 1 && geometry->min_h_width < 512) { caphscale = 2; capvscale = 2; } //log_info("Final aspect: %dx%d, %dx%d, %dx%d %d", h_aspect, v_aspect, hscale, vscale, caphscale, capvscale, geometry_min_h_width ); switch (scaling) { case GSCALING_INTEGER: { int adjusted_width = geometry_min_h_width << double_width; int adjusted_height = geometry_min_v_height << double_height; int hborder = ((h_size - geometry_min_h_width * hscale) << double_width) / hscale; if ((hborder + adjusted_width) > h_size) { log_info("Handling invalid H ratio"); hborder = (h_size - adjusted_width) / hscale; } int vborder = ((v_size - geometry_min_v_height * vscale) << double_height) / vscale; if ((vborder + adjusted_height) > v_size) { log_info("Handling invalid V ratio"); vborder = (v_size - adjusted_height) / vscale; } capinfo->width = adjusted_width + hborder; capinfo->height = adjusted_height + vborder; if ((capinfo->mode7 && get_parameter(F_MODE7_SCALING) == SCALING_UNEVEN) // workaround mode 7 width so it looks like other modes ||(!capinfo->mode7 && get_parameter(F_NORMAL_SCALING) == SCALING_UNEVEN && geometry->h_aspect == 3 && (geometry->v_aspect == 2 || geometry->v_aspect == 4))) { capinfo->width = capinfo->width * 3 / 4; } if (get_parameter(F_SCREENCAP_SIZE) == SCREENCAP_FULL || get_parameter(F_SCREENCAP_SIZE) == SCREENCAP_FULL43) { caphscale = ((h_size << double_width) / capinfo->width); capvscale = ((v_size << double_height) / capinfo->height); } } break; case GSCALING_MANUAL43: { double hscalef = (double) h_size43 / geometry_min_h_width; double vscalef = (double) v_size43 / geometry_min_v_height; capinfo->width = (geometry_max_h_width << double_width ) + (int)((double)((h_size - h_size43) << double_width) / hscalef); capinfo->height = (geometry_max_v_height << double_height) + (int)((double)((v_size - v_size43) << double_height) / vscalef); } break; case GSCALING_MANUAL: { capinfo->width = geometry_max_h_width << double_width; //adjust the width for capinfo according to sizex2 setting; capinfo->height = geometry_max_v_height << double_height; //adjust the height for capinfo according to sizex2 setting } break; }; capinfo->width &= 0xfffffffe; capinfo->height &= 0xfffffffe; /* int pitchinchars = capinfo->pitch; switch(capinfo->bpp) { case 4: pitchinchars >>= 2; break; case 8: pitchinchars >>= 3; break; case 16: pitchinchars >>= 4; break; } if (capinfo->chars_per_line > pitchinchars) { //log_info("Clipping capture width to pitch: %d, %d", capinfo->chars_per_line, pitchinchars); capinfo->chars_per_line = pitchinchars; } */ if (capinfo->nlines > (capinfo->height >> double_height)) { capinfo->nlines = (capinfo->height >> double_height); } int lines = get_lines_per_vsync(1); int width = get_vsync_width_lines(); if ((capinfo->nlines + capinfo->v_offset) > (lines - width - 1)) { capinfo->nlines = (lines - width - 1) - capinfo->v_offset; //log_info("Clipping capture height to %d", capinfo->nlines); } if (capinfo->video_type != VIDEO_PROGRESSIVE && capinfo->detected_sync_type & SYNC_BIT_INTERLACED) { capvscale >>= 1; if (double_width) { caphscale >>= 1; } } else { if (osd_active() || get_parameter(F_SCANLINES)) { if (double_width) { caphscale >>= 1; } if (double_height) { capvscale >>= 1; } } else { if (double_width) { caphscale |= 0x80000000; } if (double_height) { capvscale |= 0x80000000; } } } //log_info("Final aspect2: %dx%d, %dx%d, %dx%d", h_aspect, v_aspect, hscale, vscale, caphscale, capvscale); calculate_cpu_timings(); //log_info("size= %d, %d, %d, %d, %d, %d, %d",capinfo->chars_per_line, capinfo->nlines, geometry_min_h_width, geometry_min_v_height,capinfo->width, capinfo->height, capinfo->sizex2); if (geometry->video_type == VIDEO_LINE_DOUBLED && (capinfo->sizex2 & SIZEX2_DOUBLE_HEIGHT) != 0) { capinfo->nlines <<= 1; capinfo->sizex2 &= SIZEX2_DOUBLE_WIDTH; } int uneven = ((capinfo->mode7 && get_parameter(F_MODE7_SCALING) == SCALING_UNEVEN) ||(!capinfo->mode7 && get_parameter(F_NORMAL_SCALING) == SCALING_UNEVEN)); if (get_startup_overscan() != 0) { //for 16bpp modes reduce the screen area to the actual capture size and make up the rest with overscan on Pi zero due to bandwidth issues int apparent_width = get_hdisplay(); int apparent_height = get_vdisplay(); double_width = (capinfo->sizex2 & SIZEX2_DOUBLE_WIDTH) >> 1; double_height = capinfo->sizex2 & SIZEX2_DOUBLE_HEIGHT; hscale >>= double_width; //if (_get_hardware_id() == _RPI && !uneven && (capinfo->bpp == 16 || (capinfo->bpp != 16 && capinfo->nlines > 288))) { if (_get_hardware_id() == _RPI && capinfo->bpp == 16 && !uneven && get_true_vdisplay() > 288) { if (get_gscaling() == GSCALING_INTEGER) { int actual_width = (capinfo->chars_per_line << 3); int actual_height = capinfo->nlines; left = (apparent_width - (actual_width * hscale)) / 2; top = (apparent_height - (actual_height * vscale)) / 2; if (left >=0 && top >=0) { right = left; bottom = top; capinfo->width = actual_width; capinfo->height = actual_height << double_height; } else { left = 0; right = 0; top = 0; bottom = 0; } //log_info("sizes = %d %d %d %d %d %d %d %d %d %d", apparent_width,apparent_height,actual_width, actual_height ,hscale,vscale,left,right,top,bottom); } else { top = 0; bottom = 0; double aspect = (double) apparent_width / (double) apparent_height; int apparent_width_limit = (apparent_width * 1600 / 1920) & ~1; if (aspect >= 1.6 && get_gscaling() != GSCALING_MANUAL) { left = (apparent_width - apparent_width_limit) / 2; right = left; capinfo->width = capinfo->width * 1600 / 1920; } else { left = 0; right = 0; } //log_info("sizes = %d %d %d %d", apparent_width,capinfo->width, left,right); } } else { left = 0; right = 0; top = 0; bottom = 0; } set_config_overscan(left, right, top, bottom); } } int get_hscale() { return caphscale; } int get_vscale() { return capvscale; } int get_haspect() { return fhaspect; } int get_vaspect() { return fvaspect; } int get_hdisplay() { int v_size = (*PIXELVALVE2_VERTB) & 0xFFFF; #if defined(RPI4) int h_size = ((*PIXELVALVE2_HORZB) & 0xFFFF) << 1; if (v_size <= 288) { h_size <<= 1; } if (h_size == 0 && v_size == 0) { #else int h_size = (*PIXELVALVE2_HORZB) & 0xFFFF; if (h_size == 720 && v_size == 240) { #endif log_info("HDMI readback of screen size indicates HDMI not connected (%dx%d) - rebooting", h_size, v_size); delay_in_arm_cycles_cpu_adjust(1000000000); reboot(); } //workaround for 640x480 and 800x480 @50Hz using double rate clock so width gets doubled if (v_size == 480 && h_size == 1280) { h_size = 640; } else if (v_size == 480 && h_size == 1600) { h_size = 800; } else if (v_size <= 288) { h_size >>= 1; } return h_size; } int get_vdisplay() { int v_size = (*PIXELVALVE2_VERTB) & 0xFFFF; if (v_size == 2160 && get_hdisplay() == 1920){ v_size = 1080; } else if (v_size <= 288) { v_size <<= 1; } return v_size; } int get_true_vdisplay() { int v_size = (*PIXELVALVE2_VERTB) & 0xFFFF; return v_size; } void geometry_get_clk_params(clk_info_t *clkinfo) { clkinfo->clock = geometry->clock; clkinfo->line_len = (double) geometry->line_len; // workaround for 16.363Mhz Apple II GS pixel clock if (clkinfo->clock > 16250000 && clkinfo->clock < 16370000 && clkinfo->line_len == 1042) { clkinfo->line_len = 1042.285714285f; } clkinfo->lines_per_frame = geometry->lines_per_frame; if (geometry->setup_mode == SETUP_NORMAL) { clkinfo->clock_ppm = geometry->clock_ppm; } else { clkinfo->clock_ppm = 0; } } void geometry_hide_pixel_sampling() { params[PX_SAMPLING].key = -1; use_px_sampling = 0; }