mirror of
				https://github.com/TomHarte/CLK.git
				synced 2025-10-31 05:16:08 +00:00 
			
		
		
		
	Generalise and better factor bit reversal and TMS drawing.
This commit is contained in:
		| @@ -15,77 +15,13 @@ | |||||||
|  |  | ||||||
| using namespace TI::TMS; | using namespace TI::TMS; | ||||||
|  |  | ||||||
| // |  | ||||||
| // TODO notes. |  | ||||||
| // |  | ||||||
| //	The TMS and Master System VDP run at 342 cycles/line. |  | ||||||
| // |  | ||||||
| //	The Mega Drive VDP has 3420 master clocks per line, which it divides |  | ||||||
| //	by 4 or 5 depending on pixel rate and which part of a line is active. |  | ||||||
| //	(And, presumably, by 10 if operating in Master System mode?) |  | ||||||
| //	Cf. https://gendev.spritesmind.net/forum/viewtopic.php?t=851 etc. |  | ||||||
| // |  | ||||||
| //	The MSX2+ Yamaha VDPs have 1368 cycles/line. |  | ||||||
| // |  | ||||||
| //	So if clock scaling were common, it would need to be to: |  | ||||||
| // |  | ||||||
| //		3420 = 2 * 2		* 	3 * 3	*	5	*	19 |  | ||||||
| //		1368 = 2 * 2 * 2	*	3 * 3	* 			19 |  | ||||||
| // |  | ||||||
| //	=> 2^3 * 3^2 * 5 * 19 = 6840 |  | ||||||
| //		... which would imply a multiply by 30 on the input clock if |  | ||||||
| //		it were to remain 3.58Mhz. |  | ||||||
|  |  | ||||||
| // Mega Drive notes, assorted: |  | ||||||
| // |  | ||||||
| // |  | ||||||
| // |  | ||||||
| // there are 60 EDCLK at MCLK/5 during a line ... like this: |  | ||||||
| // |  | ||||||
| //	15 @ /5; 2 @ /4; 15 @ /5; 2 @ /4; 15 @ /5; 2 @ /4; 15 @ /5 |  | ||||||
| // |  | ||||||
| // ... HSYNC signal is what triggers the changes in EDCLK frequency, with some latency though. |  | ||||||
| // |  | ||||||
| // The total is still 840 EDCLKS (420 pixels) with 780 @MCLK/4 and 60@MCLK/5 (total is 3420 MCLCKS). |  | ||||||
|  |  | ||||||
| namespace { | namespace { | ||||||
|  |  | ||||||
| constexpr uint8_t StatusInterrupt = 0x80; |  | ||||||
| constexpr uint8_t StatusSpriteOverflow = 0x40; |  | ||||||
|  |  | ||||||
| constexpr int StatusSpriteCollisionShift = 5; |  | ||||||
| constexpr uint8_t StatusSpriteCollision = 0x20; |  | ||||||
|  |  | ||||||
| // 342 internal cycles are 228/227.5ths of a line, so 341.25 cycles should be a whole | // 342 internal cycles are 228/227.5ths of a line, so 341.25 cycles should be a whole | ||||||
| // line. Therefore multiply everything by four, but set line length to 1365 rather than 342*4 = 1368. | // line. Therefore multiply everything by four, but set line length to 1365 rather than 342*4 = 1368. | ||||||
| constexpr unsigned int CRTCyclesPerLine = 1365; | constexpr unsigned int CRTCyclesPerLine = 1365; | ||||||
| constexpr unsigned int CRTCyclesDivider = 4; | constexpr unsigned int CRTCyclesDivider = 4; | ||||||
|  |  | ||||||
| struct ReverseTable { |  | ||||||
| 	const std::array<uint8_t, 256> map; |  | ||||||
| 	constexpr ReverseTable() : map(reverse_table()) {} |  | ||||||
|  |  | ||||||
| 	private: |  | ||||||
| 		static constexpr std::array<uint8_t, 256> reverse_table() { |  | ||||||
| 			std::array<uint8_t, 256> map{}; |  | ||||||
| 			for(size_t c = 0; c < 256; ++c) { |  | ||||||
| 				map[c] = uint8_t( |  | ||||||
| 					((c & 0x80) >> 7) | |  | ||||||
| 					((c & 0x40) >> 5) | |  | ||||||
| 					((c & 0x20) >> 3) | |  | ||||||
| 					((c & 0x10) >> 1) | |  | ||||||
| 					((c & 0x08) << 1) | |  | ||||||
| 					((c & 0x04) << 3) | |  | ||||||
| 					((c & 0x02) << 5) | |  | ||||||
| 					((c & 0x01) << 7) |  | ||||||
| 				); |  | ||||||
| 			} |  | ||||||
| 			return map; |  | ||||||
| 		} |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| constexpr ReverseTable reverse_table; |  | ||||||
|  |  | ||||||
| template <Personality personality> constexpr int vram_access_delay() { | template <Personality personality> constexpr int vram_access_delay() { | ||||||
| 	// This seems to be correct for all currently-modelled VDPs; | 	// This seems to be correct for all currently-modelled VDPs; | ||||||
| 	// it's the delay between an external device scheduling a | 	// it's the delay between an external device scheduling a | ||||||
| @@ -720,22 +656,6 @@ uint8_t TMS9918<personality>::get_current_line() const { | |||||||
| 	return uint8_t(source_row); | 	return uint8_t(source_row); | ||||||
| } | } | ||||||
|  |  | ||||||
| template <Personality personality> |  | ||||||
| uint8_t TMS9918<personality>::get_latched_horizontal_counter() const { |  | ||||||
| 	// Translate from internal numbering, which puts pixel output |  | ||||||
| 	// in the final 256 pixels of 342, to the public numbering, |  | ||||||
| 	// which makes the 256 pixels the first 256 spots, but starts |  | ||||||
| 	// counting at -48, and returns only the top 8 bits of the number. |  | ||||||
| 	int public_counter = this->latched_column_ - 86; |  | ||||||
| 	if(public_counter < -46) public_counter += 342; |  | ||||||
| 	return uint8_t(public_counter >> 1); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| template <Personality personality> |  | ||||||
| void TMS9918<personality>::latch_horizontal_counter() { |  | ||||||
| 	this->latched_column_ = this->write_pointer_.column; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| template <Personality personality> | template <Personality personality> | ||||||
| uint8_t TMS9918<personality>::read(int address) { | uint8_t TMS9918<personality>::read(int address) { | ||||||
| 	this->write_phase_ = false; | 	this->write_phase_ = false; | ||||||
| @@ -835,275 +755,21 @@ bool TMS9918<personality>::get_interrupt_line() const { | |||||||
| 		(this->enable_line_interrupts_ && this->line_interrupt_pending_); | 		(this->enable_line_interrupts_ && this->line_interrupt_pending_); | ||||||
| } | } | ||||||
|  |  | ||||||
| // MARK: - | // TODO: [potentially] remove Master System timing assumptions in latch and get_latched below. | ||||||
|  |  | ||||||
| template <Personality personality> | template <Personality personality> | ||||||
| void Base<personality>::draw_tms_character(int start, int end) { | uint8_t TMS9918<personality>::get_latched_horizontal_counter() const { | ||||||
| 	LineBuffer &line_buffer = line_buffers_[read_pointer_.row]; | 	// Translate from internal numbering, which puts pixel output | ||||||
|  | 	// in the final 256 pixels of 342, to the public numbering, | ||||||
| 	// Paint the background tiles. | 	// which makes the 256 pixels the first 256 spots, but starts | ||||||
| 	const int pixels_left = end - start; | 	// counting at -48, and returns only the top 8 bits of the number. | ||||||
| 	if(this->screen_mode_ == ScreenMode::MultiColour) { | 	int public_counter = this->latched_column_ - 86; | ||||||
| 		for(int c = start; c < end; ++c) { | 	if(public_counter < -46) public_counter += 342; | ||||||
| 			pixel_target_[c] = palette[ | 	return uint8_t(public_counter >> 1); | ||||||
| 				(line_buffer.patterns[c >> 3][0] >> (((c & 4)^4))) & 15 |  | ||||||
| 			]; |  | ||||||
| 		} |  | ||||||
| 	} else { |  | ||||||
| 		const int shift = start & 7; |  | ||||||
| 		int byte_column = start >> 3; |  | ||||||
|  |  | ||||||
| 		int length = std::min(pixels_left, 8 - shift); |  | ||||||
|  |  | ||||||
| 		int pattern = reverse_table.map[line_buffer.patterns[byte_column][0]] >> shift; |  | ||||||
| 		uint8_t colour = line_buffer.patterns[byte_column][1]; |  | ||||||
| 		uint32_t colours[2] = { |  | ||||||
| 			palette[(colour & 15) ? (colour & 15) : background_colour_], |  | ||||||
| 			palette[(colour >> 4) ? (colour >> 4) : background_colour_] |  | ||||||
| 		}; |  | ||||||
|  |  | ||||||
| 		int background_pixels_left = pixels_left; |  | ||||||
| 		while(true) { |  | ||||||
| 			background_pixels_left -= length; |  | ||||||
| 			for(int c = 0; c < length; ++c) { |  | ||||||
| 				pixel_target_[c] = colours[pattern&0x01]; |  | ||||||
| 				pattern >>= 1; |  | ||||||
| 			} |  | ||||||
| 			pixel_target_ += length; |  | ||||||
|  |  | ||||||
| 			if(!background_pixels_left) break; |  | ||||||
| 			length = std::min(8, background_pixels_left); |  | ||||||
| 			byte_column++; |  | ||||||
|  |  | ||||||
| 			pattern = reverse_table.map[line_buffer.patterns[byte_column][0]]; |  | ||||||
| 			colour = line_buffer.patterns[byte_column][1]; |  | ||||||
| 			colours[0] = palette[(colour & 15) ? (colour & 15) : background_colour_]; |  | ||||||
| 			colours[1] = palette[(colour >> 4) ? (colour >> 4) : background_colour_]; |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// Paint sprites and check for collisions, but only if at least one sprite is active |  | ||||||
| 	// on this line. |  | ||||||
| 	if(line_buffer.active_sprite_slot) { |  | ||||||
| 		const int shift_advance = sprites_magnified_ ? 1 : 2; |  | ||||||
| 		// If this is the start of the line clip any part of any sprites that is off to the left. |  | ||||||
| 		if(!start) { |  | ||||||
| 			for(int index = 0; index < line_buffer.active_sprite_slot; ++index) { |  | ||||||
| 				LineBuffer::ActiveSprite &sprite = line_buffer.active_sprites[index]; |  | ||||||
| 				if(sprite.x < 0) sprite.shift_position -= shift_advance * sprite.x; |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		int sprite_buffer[256]; |  | ||||||
| 		int sprite_collision = 0; |  | ||||||
| 		memset(&sprite_buffer[start], 0, size_t(end - start)*sizeof(sprite_buffer[0])); |  | ||||||
|  |  | ||||||
| 		constexpr uint32_t sprite_colour_selection_masks[2] = {0x00000000, 0xffffffff}; |  | ||||||
| 		constexpr int colour_masks[16] = {0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; |  | ||||||
|  |  | ||||||
| 		// Draw all sprites into the sprite buffer. |  | ||||||
| 		const int shifter_target = sprites_16x16_ ? 32 : 16; |  | ||||||
| 		for(int index = line_buffer.active_sprite_slot - 1; index >= 0; --index) { |  | ||||||
| 			LineBuffer::ActiveSprite &sprite = line_buffer.active_sprites[index]; |  | ||||||
| 			if(sprite.shift_position < shifter_target) { |  | ||||||
| 				const int pixel_start = std::max(start, sprite.x); |  | ||||||
| 				for(int c = pixel_start; c < end && sprite.shift_position < shifter_target; ++c) { |  | ||||||
| 					const int shift = (sprite.shift_position >> 1) ^ 7; |  | ||||||
| 					int sprite_colour = (sprite.image[shift >> 3] >> (shift & 7)) & 1; |  | ||||||
|  |  | ||||||
| 					// A colision is detected regardless of sprite colour ... |  | ||||||
| 					sprite_collision |= sprite_buffer[c] & sprite_colour; |  | ||||||
| 					sprite_buffer[c] |= sprite_colour; |  | ||||||
|  |  | ||||||
| 					// ... but a sprite with the transparent colour won't actually be visible. |  | ||||||
| 					sprite_colour &= colour_masks[sprite.image[2]&15]; |  | ||||||
| 					pixel_origin_[c] = |  | ||||||
| 						(pixel_origin_[c] & sprite_colour_selection_masks[sprite_colour^1]) | |  | ||||||
| 						(palette[sprite.image[2]&15] & sprite_colour_selection_masks[sprite_colour]); |  | ||||||
|  |  | ||||||
| 					sprite.shift_position += shift_advance; |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		status_ |= sprite_collision << StatusSpriteCollisionShift; |  | ||||||
| 	} |  | ||||||
| } | } | ||||||
|  |  | ||||||
| template <Personality personality> | template <Personality personality> | ||||||
| void Base<personality>::draw_tms_text(int start, int end) { | void TMS9918<personality>::latch_horizontal_counter() { | ||||||
| 	LineBuffer &line_buffer = line_buffers_[read_pointer_.row]; | 	this->latched_column_ = this->write_pointer_.column; | ||||||
| 	const uint32_t colours[2] = { palette[background_colour_], palette[text_colour_] }; |  | ||||||
|  |  | ||||||
| 	const int shift = start % 6; |  | ||||||
| 	int byte_column = start / 6; |  | ||||||
| 	int pattern = reverse_table.map[line_buffer.patterns[byte_column][0]] >> shift; |  | ||||||
| 	int pixels_left = end - start; |  | ||||||
| 	int length = std::min(pixels_left, 6 - shift); |  | ||||||
| 	while(true) { |  | ||||||
| 		pixels_left -= length; |  | ||||||
| 		for(int c = 0; c < length; ++c) { |  | ||||||
| 			pixel_target_[c] = colours[pattern&0x01]; |  | ||||||
| 			pattern >>= 1; |  | ||||||
| 		} |  | ||||||
| 		pixel_target_ += length; |  | ||||||
|  |  | ||||||
| 		if(!pixels_left) break; |  | ||||||
| 		length = std::min(6, pixels_left); |  | ||||||
| 		byte_column++; |  | ||||||
| 		pattern = reverse_table.map[line_buffer.patterns[byte_column][0]]; |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| template <Personality personality> |  | ||||||
| void Base<personality>::draw_sms(int start, int end, uint32_t cram_dot) { |  | ||||||
| 	LineBuffer &line_buffer = line_buffers_[read_pointer_.row]; |  | ||||||
| 	int colour_buffer[256]; |  | ||||||
|  |  | ||||||
| 	/* |  | ||||||
| 		Add extra border for any pixels that fall before the fine scroll. |  | ||||||
| 	*/ |  | ||||||
| 	int tile_start = start, tile_end = end; |  | ||||||
| 	int tile_offset = start; |  | ||||||
| 	if(read_pointer_.row >= 16 || !master_system_.horizontal_scroll_lock) { |  | ||||||
| 		for(int c = start; c < (line_buffer.latched_horizontal_scroll & 7); ++c) { |  | ||||||
| 			colour_buffer[c] = 16 + background_colour_; |  | ||||||
| 			++tile_offset; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		// Remove the border area from that to which tiles will be drawn. |  | ||||||
| 		tile_start = std::max(start - (line_buffer.latched_horizontal_scroll & 7), 0); |  | ||||||
| 		tile_end = std::max(end - (line_buffer.latched_horizontal_scroll & 7), 0); |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
|  |  | ||||||
| 	uint32_t pattern; |  | ||||||
| 	uint8_t *const pattern_index = reinterpret_cast<uint8_t *>(&pattern); |  | ||||||
|  |  | ||||||
| 	/* |  | ||||||
| 		Add background tiles; these will fill the colour_buffer with values in which |  | ||||||
| 		the low five bits are a palette index, and bit six is set if this tile has |  | ||||||
| 		priority over sprites. |  | ||||||
| 	*/ |  | ||||||
| 	if(tile_start < end) { |  | ||||||
| 		const int shift = tile_start & 7; |  | ||||||
| 		int byte_column = tile_start >> 3; |  | ||||||
| 		int pixels_left = tile_end - tile_start; |  | ||||||
| 		int length = std::min(pixels_left, 8 - shift); |  | ||||||
|  |  | ||||||
| 		pattern = *reinterpret_cast<const uint32_t *>(line_buffer.patterns[byte_column]); |  | ||||||
| 		if(line_buffer.names[byte_column].flags&2) |  | ||||||
| 			pattern >>= shift; |  | ||||||
| 		else |  | ||||||
| 			pattern <<= shift; |  | ||||||
|  |  | ||||||
| 		while(true) { |  | ||||||
| 			const int palette_offset = (line_buffer.names[byte_column].flags&0x18) << 1; |  | ||||||
| 			if(line_buffer.names[byte_column].flags&2) { |  | ||||||
| 				for(int c = 0; c < length; ++c) { |  | ||||||
| 					colour_buffer[tile_offset] = |  | ||||||
| 						((pattern_index[3] & 0x01) << 3) | |  | ||||||
| 						((pattern_index[2] & 0x01) << 2) | |  | ||||||
| 						((pattern_index[1] & 0x01) << 1) | |  | ||||||
| 						((pattern_index[0] & 0x01) << 0) | |  | ||||||
| 						palette_offset; |  | ||||||
| 					++tile_offset; |  | ||||||
| 					pattern >>= 1; |  | ||||||
| 				} |  | ||||||
| 			} else { |  | ||||||
| 				for(int c = 0; c < length; ++c) { |  | ||||||
| 					colour_buffer[tile_offset] = |  | ||||||
| 						((pattern_index[3] & 0x80) >> 4) | |  | ||||||
| 						((pattern_index[2] & 0x80) >> 5) | |  | ||||||
| 						((pattern_index[1] & 0x80) >> 6) | |  | ||||||
| 						((pattern_index[0] & 0x80) >> 7) | |  | ||||||
| 						palette_offset; |  | ||||||
| 					++tile_offset; |  | ||||||
| 					pattern <<= 1; |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			pixels_left -= length; |  | ||||||
| 			if(!pixels_left) break; |  | ||||||
|  |  | ||||||
| 			length = std::min(8, pixels_left); |  | ||||||
| 			byte_column++; |  | ||||||
| 			pattern = *reinterpret_cast<const uint32_t *>(line_buffer.patterns[byte_column]); |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	/* |  | ||||||
| 		Apply sprites (if any). |  | ||||||
| 	*/ |  | ||||||
| 	if(line_buffer.active_sprite_slot) { |  | ||||||
| 		const int shift_advance = sprites_magnified_ ? 1 : 2; |  | ||||||
|  |  | ||||||
| 		// If this is the start of the line clip any part of any sprites that is off to the left. |  | ||||||
| 		if(!start) { |  | ||||||
| 			for(int index = 0; index < line_buffer.active_sprite_slot; ++index) { |  | ||||||
| 				LineBuffer::ActiveSprite &sprite = line_buffer.active_sprites[index]; |  | ||||||
| 				if(sprite.x < 0) sprite.shift_position -= shift_advance * sprite.x; |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		int sprite_buffer[256]; |  | ||||||
| 		int sprite_collision = 0; |  | ||||||
| 		memset(&sprite_buffer[start], 0, size_t(end - start)*sizeof(sprite_buffer[0])); |  | ||||||
|  |  | ||||||
| 		// Draw all sprites into the sprite buffer. |  | ||||||
| 		for(int index = line_buffer.active_sprite_slot - 1; index >= 0; --index) { |  | ||||||
| 			LineBuffer::ActiveSprite &sprite = line_buffer.active_sprites[index]; |  | ||||||
| 			if(sprite.shift_position < 16) { |  | ||||||
| 				const int pixel_start = std::max(start, sprite.x); |  | ||||||
|  |  | ||||||
| 				// TODO: it feels like the work below should be simplifiable; |  | ||||||
| 				// the double shift in particular, and hopefully the variable shift. |  | ||||||
| 				for(int c = pixel_start; c < end && sprite.shift_position < 16; ++c) { |  | ||||||
| 					const int shift = (sprite.shift_position >> 1); |  | ||||||
| 					const int sprite_colour = |  | ||||||
| 						(((sprite.image[3] << shift) & 0x80) >> 4) | |  | ||||||
| 						(((sprite.image[2] << shift) & 0x80) >> 5) | |  | ||||||
| 						(((sprite.image[1] << shift) & 0x80) >> 6) | |  | ||||||
| 						(((sprite.image[0] << shift) & 0x80) >> 7); |  | ||||||
|  |  | ||||||
| 					if(sprite_colour) { |  | ||||||
| 						sprite_collision |= sprite_buffer[c]; |  | ||||||
| 						sprite_buffer[c] = sprite_colour | 0x10; |  | ||||||
| 					} |  | ||||||
|  |  | ||||||
| 					sprite.shift_position += shift_advance; |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		// Draw the sprite buffer onto the colour buffer, wherever the tile map doesn't have |  | ||||||
| 		// priority (or is transparent). |  | ||||||
| 		for(int c = start; c < end; ++c) { |  | ||||||
| 			if( |  | ||||||
| 				sprite_buffer[c] && |  | ||||||
| 				(!(colour_buffer[c]&0x20) || !(colour_buffer[c]&0xf)) |  | ||||||
| 			) colour_buffer[c] = sprite_buffer[c]; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		if(sprite_collision) |  | ||||||
| 			status_ |= StatusSpriteCollision; |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// Map from the 32-colour buffer to real output pixels, applying the specific CRAM dot if any. |  | ||||||
| 	pixel_target_[start] = master_system_.colour_ram[colour_buffer[start] & 0x1f] | cram_dot; |  | ||||||
| 	for(int c = start+1; c < end; ++c) { |  | ||||||
| 		pixel_target_[c] = master_system_.colour_ram[colour_buffer[c] & 0x1f]; |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// If the VDP is set to hide the left column and this is the final call that'll come |  | ||||||
| 	// this line, hide it. |  | ||||||
| 	if(end == 256) { |  | ||||||
| 		if(master_system_.hide_left_column) { |  | ||||||
| 			pixel_origin_[0] = pixel_origin_[1] = pixel_origin_[2] = pixel_origin_[3] = |  | ||||||
| 			pixel_origin_[4] = pixel_origin_[5] = pixel_origin_[6] = pixel_origin_[7] = |  | ||||||
| 				master_system_.colour_ram[16 + background_colour_]; |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| } | } | ||||||
|  |  | ||||||
| template class TI::TMS::TMS9918<Personality::TMS9918A>; | template class TI::TMS::TMS9918<Personality::TMS9918A>; | ||||||
|   | |||||||
| @@ -122,6 +122,12 @@ struct LineBufferPointer { | |||||||
| 	int row, column; | 	int row, column; | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | constexpr uint8_t StatusInterrupt = 0x80; | ||||||
|  | constexpr uint8_t StatusSpriteOverflow = 0x40; | ||||||
|  |  | ||||||
|  | constexpr int StatusSpriteCollisionShift = 5; | ||||||
|  | constexpr uint8_t StatusSpriteCollision = 0x20; | ||||||
|  |  | ||||||
| template <Personality personality> struct Base { | template <Personality personality> struct Base { | ||||||
| 	Base(); | 	Base(); | ||||||
|  |  | ||||||
| @@ -162,17 +168,20 @@ template <Personality personality> struct Base { | |||||||
| 	Outputs::CRT::CRT crt_; | 	Outputs::CRT::CRT crt_; | ||||||
| 	TVStandard tv_standard_ = TVStandard::NTSC; | 	TVStandard tv_standard_ = TVStandard::NTSC; | ||||||
|  |  | ||||||
| 	// Holds the contents of this VDP's connected DRAM. | 	// Personality-specific metrics and converters. | ||||||
|  | 	ClockConverter<personality> clock_converter_; | ||||||
|  |  | ||||||
|  | 	// This VDP's DRAM. | ||||||
| 	std::array<uint8_t, memory_size(personality)> ram_; | 	std::array<uint8_t, memory_size(personality)> ram_; | ||||||
|  |  | ||||||
| 	// Holds the state of the DRAM/CRAM-access mechanism. | 	// State of the DRAM/CRAM-access mechanism. | ||||||
| 	uint16_t ram_pointer_ = 0; | 	uint16_t ram_pointer_ = 0; | ||||||
| 	uint8_t read_ahead_buffer_ = 0; | 	uint8_t read_ahead_buffer_ = 0; | ||||||
| 	MemoryAccess queued_access_ = MemoryAccess::None; | 	MemoryAccess queued_access_ = MemoryAccess::None; | ||||||
| 	int cycles_until_access_ = 0; | 	int cycles_until_access_ = 0; | ||||||
| 	int minimum_access_column_ = 0; | 	int minimum_access_column_ = 0; | ||||||
|  |  | ||||||
| 	// Holds the main status register. | 	// The main status register. | ||||||
| 	uint8_t status_ = 0; | 	uint8_t status_ = 0; | ||||||
|  |  | ||||||
| 	// Current state of programmer input. | 	// Current state of programmer input. | ||||||
| @@ -189,24 +198,20 @@ template <Personality personality> struct Base { | |||||||
| 	bool generate_interrupts_ = false; | 	bool generate_interrupts_ = false; | ||||||
| 	int sprite_height_ = 8; | 	int sprite_height_ = 8; | ||||||
|  |  | ||||||
|  | 	// Programmer-specified addresses. | ||||||
| 	size_t pattern_name_address_ = 0;				// i.e. address of the tile map. | 	size_t pattern_name_address_ = 0;				// i.e. address of the tile map. | ||||||
| 	size_t colour_table_address_ = 0;				// address of the colour map (if applicable). | 	size_t colour_table_address_ = 0;				// address of the colour map (if applicable). | ||||||
| 	size_t pattern_generator_table_address_ = 0;	// address of the tile contents. | 	size_t pattern_generator_table_address_ = 0;	// address of the tile contents. | ||||||
| 	size_t sprite_attribute_table_address_ = 0;		// address of the sprite list. | 	size_t sprite_attribute_table_address_ = 0;		// address of the sprite list. | ||||||
| 	size_t sprite_generator_table_address_ = 0;		// address of the sprite contents. | 	size_t sprite_generator_table_address_ = 0;		// address of the sprite contents. | ||||||
|  |  | ||||||
|  | 	// Default colours. | ||||||
| 	uint8_t text_colour_ = 0; | 	uint8_t text_colour_ = 0; | ||||||
| 	uint8_t background_colour_ = 0; | 	uint8_t background_colour_ = 0; | ||||||
|  |  | ||||||
| 	ClockConverter<personality> clock_converter_; |  | ||||||
|  |  | ||||||
| 	// Internal mechanisms for position tracking. | 	// Internal mechanisms for position tracking. | ||||||
| 	int latched_column_ = 0; | 	int latched_column_ = 0; | ||||||
|  |  | ||||||
| 	// A helper function to output the current border colour for |  | ||||||
| 	// the number of cycles supplied. |  | ||||||
| 	void output_border(int cycles, uint32_t cram_dot); |  | ||||||
|  |  | ||||||
| 	// A struct to contain timing information for the current mode. | 	// A struct to contain timing information for the current mode. | ||||||
| 	struct { | 	struct { | ||||||
| 		/* | 		/* | ||||||
| @@ -381,38 +386,7 @@ template <Personality personality> struct Base { | |||||||
| 		queued_access_ = MemoryAccess::None; | 		queued_access_ = MemoryAccess::None; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| /* | 	// Various fetchers. | ||||||
| 	Fetching routines follow below; they obey the following rules: |  | ||||||
|  |  | ||||||
| 		1)	input is a start position and an end position; they should perform the proper |  | ||||||
| 			operations for the period: start <= time < end. |  | ||||||
| 		2)	times are measured relative to a 172-cycles-per-line clock (so: they directly |  | ||||||
| 			count access windows on the TMS and Master System). |  | ||||||
| 		3)	time 0 is the beginning of the access window immediately after the last pattern/data |  | ||||||
| 			block fetch that would contribute to this line, in a normal 32-column mode. So: |  | ||||||
|  |  | ||||||
| 				* it's cycle 309 on Mattias' TMS diagram; |  | ||||||
| 				* it's cycle 1238 on his V9938 diagram; |  | ||||||
| 				* it's after the last background render block in Mask of Destiny's Master System timing diagram. |  | ||||||
|  |  | ||||||
| 			That division point was selected, albeit arbitrarily, because it puts all the tile |  | ||||||
| 			fetches for a single line into the same [0, 171] period. |  | ||||||
|  |  | ||||||
| 		4)	all of these functions are templated with a `use_end` parameter. That will be true if |  | ||||||
| 			end is < 172, false otherwise. So functions can use it to eliminate should-exit-not checks, |  | ||||||
| 			for the more usual path of execution. |  | ||||||
|  |  | ||||||
| 	Provided for the benefit of the methods below: |  | ||||||
|  |  | ||||||
| 		*	the function external_slot(), which will perform any pending VRAM read/write. |  | ||||||
| 		*	the macros slot(n) and external_slot(n) which can be used to schedule those things inside a |  | ||||||
| 			switch(start)-based implementation. |  | ||||||
|  |  | ||||||
| 	All functions should just spool data to intermediary storage. This is because for most VDPs there is |  | ||||||
| 	a decoupling between fetch pattern and output pattern, and it's neater to keep the same division |  | ||||||
| 	for the exceptions. |  | ||||||
| */ |  | ||||||
|  |  | ||||||
| 	template<bool use_end> void fetch_tms_refresh(int start, int end); | 	template<bool use_end> void fetch_tms_refresh(int start, int end); | ||||||
| 	template<bool use_end> void fetch_tms_text(int start, int end); | 	template<bool use_end> void fetch_tms_text(int start, int end); | ||||||
| 	template<bool use_end> void fetch_tms_character(int start, int end); | 	template<bool use_end> void fetch_tms_character(int start, int end); | ||||||
| @@ -423,14 +397,22 @@ template <Personality personality> struct Base { | |||||||
|  |  | ||||||
| 	template<bool use_end> void fetch_sms(int start, int end); | 	template<bool use_end> void fetch_sms(int start, int end); | ||||||
|  |  | ||||||
|  | 	// A helper function to output the current border colour for | ||||||
|  | 	// the number of cycles supplied. | ||||||
|  | 	void output_border(int cycles, uint32_t cram_dot); | ||||||
|  |  | ||||||
|  | 	// Output serialisation state. | ||||||
| 	uint32_t *pixel_target_ = nullptr, *pixel_origin_ = nullptr; | 	uint32_t *pixel_target_ = nullptr, *pixel_origin_ = nullptr; | ||||||
| 	bool asked_for_write_area_ = false; | 	bool asked_for_write_area_ = false; | ||||||
|  |  | ||||||
|  | 	// Output serialisers. | ||||||
| 	void draw_tms_character(int start, int end); | 	void draw_tms_character(int start, int end); | ||||||
| 	void draw_tms_text(int start, int end); | 	void draw_tms_text(int start, int end); | ||||||
| 	void draw_sms(int start, int end, uint32_t cram_dot); | 	void draw_sms(int start, int end, uint32_t cram_dot); | ||||||
| }; | }; | ||||||
|  |  | ||||||
| #include "Fetch.hpp" | #include "Fetch.hpp" | ||||||
|  | #include "Draw.hpp" | ||||||
|  |  | ||||||
| } | } | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										295
									
								
								Components/9918/Implementation/Draw.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										295
									
								
								Components/9918/Implementation/Draw.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,295 @@ | |||||||
|  | // | ||||||
|  | //  Draw.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 05/01/2023. | ||||||
|  | //  Copyright © 2023 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef Draw_hpp | ||||||
|  | #define Draw_hpp | ||||||
|  |  | ||||||
|  | #include "../../../Numeric/BitReverse.hpp" | ||||||
|  |  | ||||||
|  | // MARK: - TMS9918 | ||||||
|  |  | ||||||
|  | template <Personality personality> | ||||||
|  | void Base<personality>::draw_tms_character(int start, int end) { | ||||||
|  | 	LineBuffer &line_buffer = line_buffers_[read_pointer_.row]; | ||||||
|  |  | ||||||
|  | 	// Paint the background tiles. | ||||||
|  | 	const int pixels_left = end - start; | ||||||
|  | 	if(this->screen_mode_ == ScreenMode::MultiColour) { | ||||||
|  | 		for(int c = start; c < end; ++c) { | ||||||
|  | 			pixel_target_[c] = palette[ | ||||||
|  | 				(line_buffer.patterns[c >> 3][0] >> (((c & 4)^4))) & 15 | ||||||
|  | 			]; | ||||||
|  | 		} | ||||||
|  | 	} else { | ||||||
|  | 		const int shift = start & 7; | ||||||
|  | 		int byte_column = start >> 3; | ||||||
|  |  | ||||||
|  | 		int length = std::min(pixels_left, 8 - shift); | ||||||
|  |  | ||||||
|  | 		int pattern = Numeric::bit_reverse(line_buffer.patterns[byte_column][0]) >> shift; | ||||||
|  | 		uint8_t colour = line_buffer.patterns[byte_column][1]; | ||||||
|  | 		uint32_t colours[2] = { | ||||||
|  | 			palette[(colour & 15) ? (colour & 15) : background_colour_], | ||||||
|  | 			palette[(colour >> 4) ? (colour >> 4) : background_colour_] | ||||||
|  | 		}; | ||||||
|  |  | ||||||
|  | 		int background_pixels_left = pixels_left; | ||||||
|  | 		while(true) { | ||||||
|  | 			background_pixels_left -= length; | ||||||
|  | 			for(int c = 0; c < length; ++c) { | ||||||
|  | 				pixel_target_[c] = colours[pattern&0x01]; | ||||||
|  | 				pattern >>= 1; | ||||||
|  | 			} | ||||||
|  | 			pixel_target_ += length; | ||||||
|  |  | ||||||
|  | 			if(!background_pixels_left) break; | ||||||
|  | 			length = std::min(8, background_pixels_left); | ||||||
|  | 			byte_column++; | ||||||
|  |  | ||||||
|  | 			pattern = Numeric::bit_reverse(line_buffer.patterns[byte_column][0]); | ||||||
|  | 			colour = line_buffer.patterns[byte_column][1]; | ||||||
|  | 			colours[0] = palette[(colour & 15) ? (colour & 15) : background_colour_]; | ||||||
|  | 			colours[1] = palette[(colour >> 4) ? (colour >> 4) : background_colour_]; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Paint sprites and check for collisions, but only if at least one sprite is active | ||||||
|  | 	// on this line. | ||||||
|  | 	if(line_buffer.active_sprite_slot) { | ||||||
|  | 		const int shift_advance = sprites_magnified_ ? 1 : 2; | ||||||
|  | 		// If this is the start of the line clip any part of any sprites that is off to the left. | ||||||
|  | 		if(!start) { | ||||||
|  | 			for(int index = 0; index < line_buffer.active_sprite_slot; ++index) { | ||||||
|  | 				LineBuffer::ActiveSprite &sprite = line_buffer.active_sprites[index]; | ||||||
|  | 				if(sprite.x < 0) sprite.shift_position -= shift_advance * sprite.x; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		int sprite_buffer[256]; | ||||||
|  | 		int sprite_collision = 0; | ||||||
|  | 		memset(&sprite_buffer[start], 0, size_t(end - start)*sizeof(sprite_buffer[0])); | ||||||
|  |  | ||||||
|  | 		constexpr uint32_t sprite_colour_selection_masks[2] = {0x00000000, 0xffffffff}; | ||||||
|  | 		constexpr int colour_masks[16] = {0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; | ||||||
|  |  | ||||||
|  | 		// Draw all sprites into the sprite buffer. | ||||||
|  | 		const int shifter_target = sprites_16x16_ ? 32 : 16; | ||||||
|  | 		for(int index = line_buffer.active_sprite_slot - 1; index >= 0; --index) { | ||||||
|  | 			LineBuffer::ActiveSprite &sprite = line_buffer.active_sprites[index]; | ||||||
|  | 			if(sprite.shift_position < shifter_target) { | ||||||
|  | 				const int pixel_start = std::max(start, sprite.x); | ||||||
|  | 				for(int c = pixel_start; c < end && sprite.shift_position < shifter_target; ++c) { | ||||||
|  | 					const int shift = (sprite.shift_position >> 1) ^ 7; | ||||||
|  | 					int sprite_colour = (sprite.image[shift >> 3] >> (shift & 7)) & 1; | ||||||
|  |  | ||||||
|  | 					// A colision is detected regardless of sprite colour ... | ||||||
|  | 					sprite_collision |= sprite_buffer[c] & sprite_colour; | ||||||
|  | 					sprite_buffer[c] |= sprite_colour; | ||||||
|  |  | ||||||
|  | 					// ... but a sprite with the transparent colour won't actually be visible. | ||||||
|  | 					sprite_colour &= colour_masks[sprite.image[2]&15]; | ||||||
|  | 					pixel_origin_[c] = | ||||||
|  | 						(pixel_origin_[c] & sprite_colour_selection_masks[sprite_colour^1]) | | ||||||
|  | 						(palette[sprite.image[2]&15] & sprite_colour_selection_masks[sprite_colour]); | ||||||
|  |  | ||||||
|  | 					sprite.shift_position += shift_advance; | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		status_ |= sprite_collision << StatusSpriteCollisionShift; | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | template <Personality personality> | ||||||
|  | void Base<personality>::draw_tms_text(int start, int end) { | ||||||
|  | 	LineBuffer &line_buffer = line_buffers_[read_pointer_.row]; | ||||||
|  | 	const uint32_t colours[2] = { palette[background_colour_], palette[text_colour_] }; | ||||||
|  |  | ||||||
|  | 	const int shift = start % 6; | ||||||
|  | 	int byte_column = start / 6; | ||||||
|  | 	int pattern = Numeric::bit_reverse(line_buffer.patterns[byte_column][0]) >> shift; | ||||||
|  | 	int pixels_left = end - start; | ||||||
|  | 	int length = std::min(pixels_left, 6 - shift); | ||||||
|  | 	while(true) { | ||||||
|  | 		pixels_left -= length; | ||||||
|  | 		for(int c = 0; c < length; ++c) { | ||||||
|  | 			pixel_target_[c] = colours[pattern&0x01]; | ||||||
|  | 			pattern >>= 1; | ||||||
|  | 		} | ||||||
|  | 		pixel_target_ += length; | ||||||
|  |  | ||||||
|  | 		if(!pixels_left) break; | ||||||
|  | 		length = std::min(6, pixels_left); | ||||||
|  | 		byte_column++; | ||||||
|  | 		pattern = Numeric::bit_reverse(line_buffer.patterns[byte_column][0]); | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // MARK: - Master System | ||||||
|  |  | ||||||
|  | template <Personality personality> | ||||||
|  | void Base<personality>::draw_sms(int start, int end, uint32_t cram_dot) { | ||||||
|  | 	LineBuffer &line_buffer = line_buffers_[read_pointer_.row]; | ||||||
|  | 	int colour_buffer[256]; | ||||||
|  |  | ||||||
|  | 	/* | ||||||
|  | 		Add extra border for any pixels that fall before the fine scroll. | ||||||
|  | 	*/ | ||||||
|  | 	int tile_start = start, tile_end = end; | ||||||
|  | 	int tile_offset = start; | ||||||
|  | 	if(read_pointer_.row >= 16 || !master_system_.horizontal_scroll_lock) { | ||||||
|  | 		for(int c = start; c < (line_buffer.latched_horizontal_scroll & 7); ++c) { | ||||||
|  | 			colour_buffer[c] = 16 + background_colour_; | ||||||
|  | 			++tile_offset; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// Remove the border area from that to which tiles will be drawn. | ||||||
|  | 		tile_start = std::max(start - (line_buffer.latched_horizontal_scroll & 7), 0); | ||||||
|  | 		tile_end = std::max(end - (line_buffer.latched_horizontal_scroll & 7), 0); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  |  | ||||||
|  | 	uint32_t pattern; | ||||||
|  | 	uint8_t *const pattern_index = reinterpret_cast<uint8_t *>(&pattern); | ||||||
|  |  | ||||||
|  | 	/* | ||||||
|  | 		Add background tiles; these will fill the colour_buffer with values in which | ||||||
|  | 		the low five bits are a palette index, and bit six is set if this tile has | ||||||
|  | 		priority over sprites. | ||||||
|  | 	*/ | ||||||
|  | 	if(tile_start < end) { | ||||||
|  | 		const int shift = tile_start & 7; | ||||||
|  | 		int byte_column = tile_start >> 3; | ||||||
|  | 		int pixels_left = tile_end - tile_start; | ||||||
|  | 		int length = std::min(pixels_left, 8 - shift); | ||||||
|  |  | ||||||
|  | 		pattern = *reinterpret_cast<const uint32_t *>(line_buffer.patterns[byte_column]); | ||||||
|  | 		if(line_buffer.names[byte_column].flags&2) | ||||||
|  | 			pattern >>= shift; | ||||||
|  | 		else | ||||||
|  | 			pattern <<= shift; | ||||||
|  |  | ||||||
|  | 		while(true) { | ||||||
|  | 			const int palette_offset = (line_buffer.names[byte_column].flags&0x18) << 1; | ||||||
|  | 			if(line_buffer.names[byte_column].flags&2) { | ||||||
|  | 				for(int c = 0; c < length; ++c) { | ||||||
|  | 					colour_buffer[tile_offset] = | ||||||
|  | 						((pattern_index[3] & 0x01) << 3) | | ||||||
|  | 						((pattern_index[2] & 0x01) << 2) | | ||||||
|  | 						((pattern_index[1] & 0x01) << 1) | | ||||||
|  | 						((pattern_index[0] & 0x01) << 0) | | ||||||
|  | 						palette_offset; | ||||||
|  | 					++tile_offset; | ||||||
|  | 					pattern >>= 1; | ||||||
|  | 				} | ||||||
|  | 			} else { | ||||||
|  | 				for(int c = 0; c < length; ++c) { | ||||||
|  | 					colour_buffer[tile_offset] = | ||||||
|  | 						((pattern_index[3] & 0x80) >> 4) | | ||||||
|  | 						((pattern_index[2] & 0x80) >> 5) | | ||||||
|  | 						((pattern_index[1] & 0x80) >> 6) | | ||||||
|  | 						((pattern_index[0] & 0x80) >> 7) | | ||||||
|  | 						palette_offset; | ||||||
|  | 					++tile_offset; | ||||||
|  | 					pattern <<= 1; | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			pixels_left -= length; | ||||||
|  | 			if(!pixels_left) break; | ||||||
|  |  | ||||||
|  | 			length = std::min(8, pixels_left); | ||||||
|  | 			byte_column++; | ||||||
|  | 			pattern = *reinterpret_cast<const uint32_t *>(line_buffer.patterns[byte_column]); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	/* | ||||||
|  | 		Apply sprites (if any). | ||||||
|  | 	*/ | ||||||
|  | 	if(line_buffer.active_sprite_slot) { | ||||||
|  | 		const int shift_advance = sprites_magnified_ ? 1 : 2; | ||||||
|  |  | ||||||
|  | 		// If this is the start of the line clip any part of any sprites that is off to the left. | ||||||
|  | 		if(!start) { | ||||||
|  | 			for(int index = 0; index < line_buffer.active_sprite_slot; ++index) { | ||||||
|  | 				LineBuffer::ActiveSprite &sprite = line_buffer.active_sprites[index]; | ||||||
|  | 				if(sprite.x < 0) sprite.shift_position -= shift_advance * sprite.x; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		int sprite_buffer[256]; | ||||||
|  | 		int sprite_collision = 0; | ||||||
|  | 		memset(&sprite_buffer[start], 0, size_t(end - start)*sizeof(sprite_buffer[0])); | ||||||
|  |  | ||||||
|  | 		// Draw all sprites into the sprite buffer. | ||||||
|  | 		for(int index = line_buffer.active_sprite_slot - 1; index >= 0; --index) { | ||||||
|  | 			LineBuffer::ActiveSprite &sprite = line_buffer.active_sprites[index]; | ||||||
|  | 			if(sprite.shift_position < 16) { | ||||||
|  | 				const int pixel_start = std::max(start, sprite.x); | ||||||
|  |  | ||||||
|  | 				// TODO: it feels like the work below should be simplifiable; | ||||||
|  | 				// the double shift in particular, and hopefully the variable shift. | ||||||
|  | 				for(int c = pixel_start; c < end && sprite.shift_position < 16; ++c) { | ||||||
|  | 					const int shift = (sprite.shift_position >> 1); | ||||||
|  | 					const int sprite_colour = | ||||||
|  | 						(((sprite.image[3] << shift) & 0x80) >> 4) | | ||||||
|  | 						(((sprite.image[2] << shift) & 0x80) >> 5) | | ||||||
|  | 						(((sprite.image[1] << shift) & 0x80) >> 6) | | ||||||
|  | 						(((sprite.image[0] << shift) & 0x80) >> 7); | ||||||
|  |  | ||||||
|  | 					if(sprite_colour) { | ||||||
|  | 						sprite_collision |= sprite_buffer[c]; | ||||||
|  | 						sprite_buffer[c] = sprite_colour | 0x10; | ||||||
|  | 					} | ||||||
|  |  | ||||||
|  | 					sprite.shift_position += shift_advance; | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// Draw the sprite buffer onto the colour buffer, wherever the tile map doesn't have | ||||||
|  | 		// priority (or is transparent). | ||||||
|  | 		for(int c = start; c < end; ++c) { | ||||||
|  | 			if( | ||||||
|  | 				sprite_buffer[c] && | ||||||
|  | 				(!(colour_buffer[c]&0x20) || !(colour_buffer[c]&0xf)) | ||||||
|  | 			) colour_buffer[c] = sprite_buffer[c]; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if(sprite_collision) | ||||||
|  | 			status_ |= StatusSpriteCollision; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Map from the 32-colour buffer to real output pixels, applying the specific CRAM dot if any. | ||||||
|  | 	pixel_target_[start] = master_system_.colour_ram[colour_buffer[start] & 0x1f] | cram_dot; | ||||||
|  | 	for(int c = start+1; c < end; ++c) { | ||||||
|  | 		pixel_target_[c] = master_system_.colour_ram[colour_buffer[c] & 0x1f]; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// If the VDP is set to hide the left column and this is the final call that'll come | ||||||
|  | 	// this line, hide it. | ||||||
|  | 	if(end == 256) { | ||||||
|  | 		if(master_system_.hide_left_column) { | ||||||
|  | 			pixel_origin_[0] = pixel_origin_[1] = pixel_origin_[2] = pixel_origin_[3] = | ||||||
|  | 			pixel_origin_[4] = pixel_origin_[5] = pixel_origin_[6] = pixel_origin_[7] = | ||||||
|  | 				master_system_.colour_ram[16 + background_colour_]; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // MARK: - Yamaha | ||||||
|  |  | ||||||
|  | // TODO. | ||||||
|  |  | ||||||
|  | // MARK: - Mega Drive | ||||||
|  |  | ||||||
|  | // TODO. | ||||||
|  |  | ||||||
|  | #endif /* Draw_hpp */ | ||||||
| @@ -9,6 +9,38 @@ | |||||||
| #ifndef Fetch_hpp | #ifndef Fetch_hpp | ||||||
| #define Fetch_hpp | #define Fetch_hpp | ||||||
|  |  | ||||||
|  | /* | ||||||
|  | 	Fetching routines follow below; they obey the following rules: | ||||||
|  |  | ||||||
|  | 		1)	input is a start position and an end position; they should perform the proper | ||||||
|  | 			operations for the period: start <= time < end. | ||||||
|  | 		2)	times are measured relative to a 172-cycles-per-line clock (so: they directly | ||||||
|  | 			count access windows on the TMS and Master System). | ||||||
|  | 		3)	time 0 is the beginning of the access window immediately after the last pattern/data | ||||||
|  | 			block fetch that would contribute to this line, in a normal 32-column mode. So: | ||||||
|  |  | ||||||
|  | 				* it's cycle 309 on Mattias' TMS diagram; | ||||||
|  | 				* it's cycle 1238 on his V9938 diagram; | ||||||
|  | 				* it's after the last background render block in Mask of Destiny's Master System timing diagram. | ||||||
|  |  | ||||||
|  | 			That division point was selected, albeit arbitrarily, because it puts all the tile | ||||||
|  | 			fetches for a single line into the same [0, 171] period. | ||||||
|  |  | ||||||
|  | 		4)	all of these functions are templated with a `use_end` parameter. That will be true if | ||||||
|  | 			end is < 172, false otherwise. So functions can use it to eliminate should-exit-not checks, | ||||||
|  | 			for the more usual path of execution. | ||||||
|  |  | ||||||
|  | 	Provided for the benefit of the methods below: | ||||||
|  |  | ||||||
|  | 		*	the function external_slot(), which will perform any pending VRAM read/write. | ||||||
|  | 		*	the macros slot(n) and external_slot(n) which can be used to schedule those things inside a | ||||||
|  | 			switch(start)-based implementation. | ||||||
|  |  | ||||||
|  | 	All functions should just spool data to intermediary storage. This is because for most VDPs there is | ||||||
|  | 	a decoupling between fetch pattern and output pattern, and it's neater to keep the same division | ||||||
|  | 	for the exceptions. | ||||||
|  | */ | ||||||
|  |  | ||||||
| #define slot(n)	\ | #define slot(n)	\ | ||||||
| 	if(use_end && end == n) return;	\ | 	if(use_end && end == n) return;	\ | ||||||
| 	[[fallthrough]];				\ | 	[[fallthrough]];				\ | ||||||
|   | |||||||
							
								
								
									
										62
									
								
								Numeric/BitReverse.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								Numeric/BitReverse.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,62 @@ | |||||||
|  | // | ||||||
|  | //  BitReverse.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 05/01/2023. | ||||||
|  | //  Copyright © 2023 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef BitReverse_hpp | ||||||
|  | #define BitReverse_hpp | ||||||
|  |  | ||||||
|  | #include <cstdint> | ||||||
|  |  | ||||||
|  | namespace Numeric { | ||||||
|  |  | ||||||
|  | /// @returns @c source with the order of its bits reversed. E.g. if @c IntT is @c uint8_t then | ||||||
|  | /// the reverse of bit pattern abcd efgh is hgfd dcba. | ||||||
|  | template <typename IntT> constexpr IntT bit_reverse(IntT source); | ||||||
|  |  | ||||||
|  | // The single-byte specialisation uses a lookup table. | ||||||
|  | template<> constexpr uint8_t bit_reverse<uint8_t>(uint8_t source) { | ||||||
|  |     struct ReverseTable { | ||||||
|  | 		static constexpr std::array<uint8_t, 256> reverse_table() { | ||||||
|  | 			std::array<uint8_t, 256> map{}; | ||||||
|  | 			for(std::size_t c = 0; c < 256; ++c) { | ||||||
|  | 				map[c] = uint8_t( | ||||||
|  | 					((c & 0x80) >> 7) | | ||||||
|  | 					((c & 0x40) >> 5) | | ||||||
|  | 					((c & 0x20) >> 3) | | ||||||
|  | 					((c & 0x10) >> 1) | | ||||||
|  | 					((c & 0x08) << 1) | | ||||||
|  | 					((c & 0x04) << 3) | | ||||||
|  | 					((c & 0x02) << 5) | | ||||||
|  | 					((c & 0x01) << 7) | ||||||
|  | 				); | ||||||
|  | 			} | ||||||
|  | 			return map; | ||||||
|  | 		} | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  | 	const std::array<uint8_t, 256> map = ReverseTable::reverse_table(); | ||||||
|  | 	return map[source]; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // All other versions just call the byte-level reverse the appropriate number of times. | ||||||
|  | template <typename IntT> constexpr IntT bit_reverse(IntT source) { | ||||||
|  | 	IntT result; | ||||||
|  |  | ||||||
|  | 	uint8_t *src = reinterpret_cast<uint8_t *>(&source); | ||||||
|  | 	uint8_t *dest = reinterpret_cast<uint8_t *>(&result) + sizeof(result) - 1; | ||||||
|  | 	for(size_t c = 0; c < sizeof(source); c++) { | ||||||
|  | 		*dest = bit_reverse(*src); | ||||||
|  | 		++src; | ||||||
|  | 		--dest; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return result; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* BitReverse_hpp */ | ||||||
| @@ -1319,6 +1319,8 @@ | |||||||
| 		4B43983829620FB1006B0BFC /* 9918.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = 9918.cpp; sourceTree = "<group>"; }; | 		4B43983829620FB1006B0BFC /* 9918.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = 9918.cpp; sourceTree = "<group>"; }; | ||||||
| 		4B43983C29621024006B0BFC /* ClockConverter.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ClockConverter.hpp; sourceTree = "<group>"; }; | 		4B43983C29621024006B0BFC /* ClockConverter.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ClockConverter.hpp; sourceTree = "<group>"; }; | ||||||
| 		4B43983E29628538006B0BFC /* Fetch.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Fetch.hpp; sourceTree = "<group>"; }; | 		4B43983E29628538006B0BFC /* Fetch.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Fetch.hpp; sourceTree = "<group>"; }; | ||||||
|  | 		4B43983F2967459B006B0BFC /* Draw.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Draw.hpp; sourceTree = "<group>"; }; | ||||||
|  | 		4B43984129674943006B0BFC /* BitReverse.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = BitReverse.hpp; sourceTree = "<group>"; }; | ||||||
| 		4B448E7F1F1C45A00009ABD6 /* TZX.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TZX.cpp; sourceTree = "<group>"; }; | 		4B448E7F1F1C45A00009ABD6 /* TZX.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TZX.cpp; sourceTree = "<group>"; }; | ||||||
| 		4B448E801F1C45A00009ABD6 /* TZX.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TZX.hpp; sourceTree = "<group>"; }; | 		4B448E801F1C45A00009ABD6 /* TZX.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TZX.hpp; sourceTree = "<group>"; }; | ||||||
| 		4B448E821F1C4C480009ABD6 /* PulseQueuedTape.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PulseQueuedTape.cpp; sourceTree = "<group>"; }; | 		4B448E821F1C4C480009ABD6 /* PulseQueuedTape.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PulseQueuedTape.cpp; sourceTree = "<group>"; }; | ||||||
| @@ -3289,6 +3291,7 @@ | |||||||
| 		4B7BA03C23D55E7900B98D9E /* Numeric */ = { | 		4B7BA03C23D55E7900B98D9E /* Numeric */ = { | ||||||
| 			isa = PBXGroup; | 			isa = PBXGroup; | ||||||
| 			children = ( | 			children = ( | ||||||
|  | 				4B43984129674943006B0BFC /* BitReverse.hpp */, | ||||||
| 				4BD155312716362A00410C6E /* BitSpread.hpp */, | 				4BD155312716362A00410C6E /* BitSpread.hpp */, | ||||||
| 				4B7BA03E23D55E7900B98D9E /* CRC.hpp */, | 				4B7BA03E23D55E7900B98D9E /* CRC.hpp */, | ||||||
| 				4B7BA03F23D55E7900B98D9E /* LFSR.hpp */, | 				4B7BA03F23D55E7900B98D9E /* LFSR.hpp */, | ||||||
| @@ -4738,6 +4741,7 @@ | |||||||
| 				4BD388411FE34E010042B588 /* 9918Base.hpp */, | 				4BD388411FE34E010042B588 /* 9918Base.hpp */, | ||||||
| 				4B43983C29621024006B0BFC /* ClockConverter.hpp */, | 				4B43983C29621024006B0BFC /* ClockConverter.hpp */, | ||||||
| 				4B43983E29628538006B0BFC /* Fetch.hpp */, | 				4B43983E29628538006B0BFC /* Fetch.hpp */, | ||||||
|  | 				4B43983F2967459B006B0BFC /* Draw.hpp */, | ||||||
| 			); | 			); | ||||||
| 			path = Implementation; | 			path = Implementation; | ||||||
| 			sourceTree = "<group>"; | 			sourceTree = "<group>"; | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user