File: camera.c

package info (click to toggle)
sameboy 1.0.2%2Bds-1
  • links: PTS, VCS
  • area: main
  • in suites:
  • size: 10,528 kB
  • sloc: ansic: 29,948; objc: 22,249; asm: 1,424; pascal: 1,373; makefile: 1,065; xml: 111
file content (177 lines) | stat: -rw-r--r-- 6,079 bytes parent folder | download | duplicates (2)
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
#include "gb.h"

static uint32_t noise_seed = 0;

/* This is not a complete emulation of the camera chip. Only the features used by the Game Boy Camera ROMs are supported.
    We also do not emulate the timing of the real cart when a webcam is used, as it might be actually faster than the webcam. */

static uint8_t generate_noise(uint8_t x, uint8_t y)
{
    uint32_t value = (x * 151 + y * 149) ^ noise_seed;
    uint32_t hash = 0;

    while (value) {
        hash <<= 1;
        if (hash & 0x100) {
            hash ^= 0x101;
        }
        if (value & 0x80000000) {
            hash ^= 0xA1;
        }
        value <<= 1;
    }
    return hash;
}

static long get_processed_color(GB_gameboy_t *gb, uint8_t x, uint8_t y)
{
    if (x == 128) {
        x = 127;
    }
    else if (x > 128) {
        x = 0;
    }
    
    if (y == 112) {
        y = 111;
    }
    else if (y >= 112) {
        y = 0;
    }

    long color = gb->camera_get_pixel_callback? gb->camera_get_pixel_callback(gb, x, y) : (generate_noise(x, y));

    static const double gain_values[] =
        {0.8809390, 0.9149149, 0.9457498, 0.9739758,
         1.0000000, 1.0241412, 1.0466537, 1.0677433,
         1.0875793, 1.1240310, 1.1568911, 1.1868043,
         1.2142561, 1.2396208, 1.2743837, 1.3157323,
         1.3525190, 1.3856512, 1.4157897, 1.4434309,
         1.4689574, 1.4926697, 1.5148087, 1.5355703,
         1.5551159, 1.5735801, 1.5910762, 1.6077008,
         1.6235366, 1.6386550, 1.6531183, 1.6669808};
    /* Multiply color by gain value */
    color *= gain_values[gb->camera_registers[GB_CAMERA_GAIN_AND_EDGE_ENHACEMENT_FLAGS] & 0x1F];


    /* Color is multiplied by the exposure register to simulate exposure. */
    color = color * ((gb->camera_registers[GB_CAMERA_EXPOSURE_HIGH] << 8) + gb->camera_registers[GB_CAMERA_EXPOSURE_LOW]) / 0x1000;

    return color;
}

uint8_t GB_camera_read_image(GB_gameboy_t *gb, uint16_t addr)
{
    uint8_t tile_x = addr / 0x10 % 0x10;
    uint8_t tile_y = addr / 0x10 / 0x10;

    uint8_t y = ((addr >> 1) & 0x7) + tile_y * 8;
    uint8_t bit = addr & 1;

    uint8_t ret = 0;

    for (uint8_t x = tile_x * 8; x < tile_x * 8 + 8; x++) {

        long color = get_processed_color(gb, x, y);

        static const double edge_enhancement_ratios[] = {0.5, 0.75, 1, 1.25, 2, 3, 4, 5};
        double edge_enhancement_ratio = edge_enhancement_ratios[(gb->camera_registers[GB_CAMERA_EDGE_ENHANCEMENT_INVERT_AND_VOLTAGE] >> 4) & 0x7];
        if ((gb->camera_registers[GB_CAMERA_GAIN_AND_EDGE_ENHACEMENT_FLAGS] & 0xE0) == 0xE0) {
                color += (color * 4) * edge_enhancement_ratio;
                color -= get_processed_color(gb, x - 1, y) * edge_enhancement_ratio;
                color -= get_processed_color(gb, x + 1, y) * edge_enhancement_ratio;
                color -= get_processed_color(gb, x, y - 1) * edge_enhancement_ratio;
                color -= get_processed_color(gb, x, y + 1) * edge_enhancement_ratio;
        }


        /* The camera's registers are used as a threshold pattern, which defines the dithering */
        uint8_t pattern_base = ((x & 3) + (y & 3) * 4) * 3 + GB_CAMERA_DITHERING_PATTERN_START;

        if (color < gb->camera_registers[pattern_base]) {
            color = 3;
        }
        else if (color < gb->camera_registers[pattern_base + 1]) {
            color = 2;
        }
        else if (color < gb->camera_registers[pattern_base + 2]) {
            color = 1;
        }
        else {
            color = 0;
        }

        ret <<= 1;
        ret |= (color >> bit) & 1;
    }

    return ret;
}

void GB_set_camera_get_pixel_callback(GB_gameboy_t *gb, GB_camera_get_pixel_callback_t callback)
{
    if (!callback) {
        GB_ASSERT_NOT_RUNNING_OTHER_THREAD(gb)
    }
    gb->camera_get_pixel_callback = callback;
}

void GB_set_camera_update_request_callback(GB_gameboy_t *gb, GB_camera_update_request_callback_t callback)
{
    if (!callback) {
        GB_ASSERT_NOT_RUNNING_OTHER_THREAD(gb)
    }
    if (gb->camera_countdown > 0 && callback) {
        GB_log(gb, "Camera update request callback set while camera was proccessing, clearing camera countdown.\n");
        gb->camera_countdown = 0;
        GB_camera_updated(gb);
    }

    gb->camera_update_request_callback = callback;
}

void GB_camera_updated(GB_gameboy_t *gb)
{
    gb->camera_registers[GB_CAMERA_SHOOT_AND_1D_FLAGS] &= ~1;
}

void GB_camera_write_register(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
{
    addr &= 0x7F;
    if (addr == GB_CAMERA_SHOOT_AND_1D_FLAGS) {
        value &= 0x7;
        noise_seed = GB_random();
        if ((value & 1) && !(gb->camera_registers[GB_CAMERA_SHOOT_AND_1D_FLAGS] & 1)) {
            if (gb->camera_update_request_callback) {
                gb->camera_update_request_callback(gb);
            }
            else {
                /* If no callback is set, wait the amount of time the real camera would take before clearing the busy bit */
                uint16_t exposure = (gb->camera_registers[GB_CAMERA_EXPOSURE_HIGH] << 8) | gb->camera_registers[GB_CAMERA_EXPOSURE_LOW];
                gb->camera_countdown = 129792 + ((gb->camera_registers[GB_CAMERA_GAIN_AND_EDGE_ENHACEMENT_FLAGS] & 0x80)? 0 : 2048) + (exposure * 64) + (gb->camera_alignment & 4);
            }
        }

        if (!(value & 1) && (gb->camera_registers[GB_CAMERA_SHOOT_AND_1D_FLAGS] & 1)) {
            /* We don't support cancelling a camera shoot */
            GB_log(gb, "ROM attempted to cancel camera shoot, which is currently not supported. The camera shoot will not be cancelled.\n");
            value |= 1;
        }

        gb->camera_registers[GB_CAMERA_SHOOT_AND_1D_FLAGS] = value;
    }
    else {
        if (addr >= 0x36) {
            GB_log(gb, "Wrote invalid camera register %02x: %2x\n", addr, value);
            return;
        }
        gb->camera_registers[addr] = value;
    }
}
uint8_t GB_camera_read_register(GB_gameboy_t *gb, uint16_t addr)
{
    if ((addr & 0x7F) == 0) {
        return gb->camera_registers[GB_CAMERA_SHOOT_AND_1D_FLAGS];
    }
    return 0;
}