make bootup faster

This commit is contained in:
Yonokid
2025-07-24 16:06:09 -04:00
parent d8acf8a4d6
commit 718d97cba7
7 changed files with 190 additions and 172 deletions

View File

@@ -2,7 +2,6 @@ import sqlite3
from pathlib import Path from pathlib import Path
import pyray as ray import pyray as ray
from dotenv import dotenv_values
from raylib.defines import ( from raylib.defines import (
RL_FUNC_ADD, RL_FUNC_ADD,
RL_ONE, RL_ONE,
@@ -56,24 +55,14 @@ def create_song_db():
''' '''
cursor.execute(create_table_query) cursor.execute(create_table_query)
con.commit() con.commit()
print("Scores database created successfully") print("Scores database created successfully")
def main(): def main():
env_config = dotenv_values(".env")
create_song_db() create_song_db()
song_hash.song_hashes = song_hash.build_song_hashes() global_data.song_hashes = song_hash.build_song_hashes()
global_data.config = get_config() global_data.config = get_config()
screen_width: int = global_data.config["video"]["screen_width"] screen_width: int = global_data.config["video"]["screen_width"]
screen_height: int = global_data.config["video"]["screen_height"] screen_height: int = global_data.config["video"]["screen_height"]
'''
render_width, render_height = ray.get_render_width(), ray.get_render_height()
dpi_scale = ray.get_window_scale_dpi()
if dpi_scale.x == 0:
dpi_scale = (ray.get_render_width(), ray.get_render_height())
dpi_scale = screen_width, screen_height
else:
dpi_scale = int(render_width/dpi_scale.x), int(render_height/dpi_scale.y)
'''
if global_data.config["video"]["vsync"]: if global_data.config["video"]["vsync"]:
ray.set_config_flags(ray.ConfigFlags.FLAG_VSYNC_HINT) ray.set_config_flags(ray.ConfigFlags.FLAG_VSYNC_HINT)
@@ -113,12 +102,7 @@ def main():
ray.rl_set_blend_factors_separate(RL_SRC_ALPHA, RL_ONE_MINUS_SRC_ALPHA, RL_ONE, RL_ONE_MINUS_SRC_ALPHA, RL_FUNC_ADD, RL_FUNC_ADD) ray.rl_set_blend_factors_separate(RL_SRC_ALPHA, RL_ONE_MINUS_SRC_ALPHA, RL_ONE, RL_ONE_MINUS_SRC_ALPHA, RL_FUNC_ADD, RL_FUNC_ADD)
ray.set_exit_key(ray.KeyboardKey.KEY_A) ray.set_exit_key(ray.KeyboardKey.KEY_A)
global_data.textures = load_all_textures_from_zip(Path('Graphics/lumendata/intermission.zip')) global_data.textures = load_all_textures_from_zip(Path('Graphics/lumendata/intermission.zip'))
prev_ms = get_current_ms()
while not ray.window_should_close(): while not ray.window_should_close():
current_ms = get_current_ms()
if current_ms >= prev_ms + 100:
print("LAG SPIKE DETECTED")
prev_ms = current_ms
ray.begin_texture_mode(target) ray.begin_texture_mode(target)
ray.begin_blend_mode(ray.BlendMode.BLEND_CUSTOM_SEPARATE) ray.begin_blend_mode(ray.BlendMode.BLEND_CUSTOM_SEPARATE)
@@ -140,7 +124,6 @@ def main():
ray.end_texture_mode() ray.end_texture_mode()
ray.begin_drawing() ray.begin_drawing()
ray.clear_background(ray.WHITE) ray.clear_background(ray.WHITE)
#Thanks to rnoiz proper render height
ray.draw_texture_pro( ray.draw_texture_pro(
target.texture, target.texture,
ray.Rectangle(0, 0, target.texture.width, -target.texture.height), ray.Rectangle(0, 0, target.texture.width, -target.texture.height),

View File

@@ -1,16 +1,138 @@
import csv import csv
import json import json
import sys import sys
import time
from collections import deque
from pathlib import Path from pathlib import Path
from typing import Optional from typing import Optional
from git import Repo from git import Repo
from libs.tja import TJAParser from libs.tja import TJAParser
from libs.utils import get_config from libs.utils import get_config, global_data
song_hashes: Optional[dict] = None
class DiffHashesDecoder(json.JSONDecoder):
def __init__(self, *args, **kwargs):
super().__init__(object_hook=self.object_hook, *args, **kwargs)
def object_hook(self, obj):
if "diff_hashes" in obj:
obj["diff_hashes"] = {
int(key): value
for key, value in obj["diff_hashes"].items()
}
return obj
def build_song_hashes(output_dir=Path("cache")):
song_hashes: dict[str, list[dict]] = dict()
path_to_hash: dict[str, str] = dict() # New index for O(1) path lookups
output_path = Path(output_dir / "song_hashes.json")
index_path = Path(output_dir / "path_to_hash.json")
# Load existing data
if output_path.exists():
with open(output_path, "r", encoding="utf-8") as f:
song_hashes = json.load(f, cls=DiffHashesDecoder)
if index_path.exists():
with open(index_path, "r", encoding="utf-8") as f:
path_to_hash = json.load(f)
saved_timestamp = 0.0
current_timestamp = time.time()
if (output_dir / 'timestamp.txt').exists():
with open(output_dir / 'timestamp.txt', 'r') as f:
saved_timestamp = float(f.read())
tja_paths = get_config()["paths"]["tja_path"]
all_tja_files: list[Path] = []
for root_dir in tja_paths:
root_path = Path(root_dir)
if (root_path / '.git').exists():
repo = Repo(root_path)
origin = repo.remotes.origin
origin.pull()
print('Pulled latest from', root_path)
all_tja_files.extend(root_path.rglob("*.tja"))
files_to_process = []
# O(n) pass to identify which files need processing
for tja_path in all_tja_files:
tja_path_str = str(tja_path)
current_modified = tja_path.stat().st_mtime
# Skip files that haven't been modified since last run
if current_modified <= saved_timestamp:
# File hasn't changed, just restore to global_data if we have it
current_hash = path_to_hash.get(tja_path_str)
if current_hash is not None:
global_data.song_paths[tja_path] = current_hash
continue
# O(1) lookup instead of nested loops
current_hash = path_to_hash.get(tja_path_str)
if current_hash is None:
# New file (modified after saved_timestamp)
files_to_process.append(tja_path)
else:
# File was modified after saved_timestamp, need to reprocess
files_to_process.append(tja_path)
# Clean up old hash
if current_hash in song_hashes:
del song_hashes[current_hash]
del path_to_hash[tja_path_str]
# Process only files that need updating
for tja_path in files_to_process:
tja_path_str = str(tja_path)
current_modified = tja_path.stat().st_mtime
tja = TJAParser(tja_path)
all_notes = deque()
all_bars = deque()
diff_hashes = dict()
for diff in tja.metadata.course_data:
diff_notes, _, diff_bars = TJAParser.notes_to_position(TJAParser(tja.file_path), diff)
diff_hashes[diff] = tja.hash_note_data(diff_notes, diff_bars)
all_notes.extend(diff_notes)
all_bars.extend(diff_bars)
if all_notes == []:
continue
hash_val = tja.hash_note_data(all_notes, all_bars)
if hash_val not in song_hashes:
song_hashes[hash_val] = []
song_hashes[hash_val].append({
"file_path": tja_path_str,
"last_modified": current_modified,
"title": tja.metadata.title,
"subtitle": tja.metadata.subtitle,
"diff_hashes": diff_hashes
})
# Update both indexes
path_to_hash[tja_path_str] = hash_val
global_data.song_paths[tja_path] = hash_val
# Save both files
with open(output_path, "w", encoding="utf-8") as f:
json.dump(song_hashes, f, indent=2, ensure_ascii=False)
with open(index_path, "w", encoding="utf-8") as f:
json.dump(path_to_hash, f, indent=2, ensure_ascii=False)
with open(output_dir / 'timestamp.txt', 'w') as f:
f.write(str(current_timestamp))
return song_hashes
def process_tja_file(tja_file): def process_tja_file(tja_file):
"""Process a single TJA file and return hash or None if error""" """Process a single TJA file and return hash or None if error"""
@@ -25,78 +147,6 @@ def process_tja_file(tja_file):
hash = tja.hash_note_data(all_notes[0], all_notes[2]) hash = tja.hash_note_data(all_notes[0], all_notes[2])
return hash return hash
def build_song_hashes(output_file="cache/song_hashes.json"):
existing_hashes = {}
output_path = Path(output_file)
if output_path.exists():
try:
with open(output_file, "r", encoding="utf-8") as f:
existing_hashes = json.load(f)
except (json.JSONDecodeError, IOError) as e:
print(
f"Warning: Could not load existing hashes from {output_file}: {e}"
)
existing_hashes = {}
song_hashes = existing_hashes.copy()
tja_paths = get_config()["paths"]["tja_path"]
all_tja_files = []
for root_dir in tja_paths:
root_path = Path(root_dir)
if (root_path / '.git').exists():
repo = Repo(root_path)
origin = repo.remotes.origin
origin.pull()
print('Pulled latest from', root_path)
all_tja_files.extend(root_path.rglob("*.tja"))
updated_count = 0
for tja_file in all_tja_files:
current_modified = tja_file.stat().st_mtime
should_update = False
hash_val = None
existing_hash = None
for h, data in song_hashes.items():
if data["file_path"] == str(tja_file):
existing_hash = h
break
if existing_hash is None:
should_update = True
else:
stored_modified = song_hashes[existing_hash].get("last_modified", 0)
if current_modified > stored_modified:
should_update = True
del song_hashes[existing_hash]
if should_update:
tja = TJAParser(tja_file)
all_notes = []
for diff in tja.metadata.course_data:
all_notes.extend(
TJAParser.notes_to_position(TJAParser(tja.file_path), diff)
)
if all_notes == []:
continue
hash_val = tja.hash_note_data(all_notes[0], all_notes[2])
song_hashes[hash_val] = {
"file_path": str(tja_file),
"last_modified": current_modified,
"title": tja.metadata.title,
"subtitle": tja.metadata.subtitle,
}
updated_count += 1
with open(output_file, "w", encoding="utf-8") as f:
json.dump(song_hashes, f, indent=2, ensure_ascii=False)
print(f"Song hashes saved to {output_file}. Updated {updated_count} files.")
return song_hashes
def get_japanese_songs_for_version(csv_file_path, version_column): def get_japanese_songs_for_version(csv_file_path, version_column):
# Read CSV file and filter rows where the specified version column has 'YES' # Read CSV file and filter rows where the specified version column has 'YES'
version_songs = [] version_songs = []

View File

@@ -232,9 +232,9 @@ class GlobalData:
textures: dict[str, list[ray.Texture]] = field(default_factory=lambda: dict()) textures: dict[str, list[ray.Texture]] = field(default_factory=lambda: dict())
songs_played: int = 0 songs_played: int = 0
config: dict = field(default_factory=lambda: dict()) config: dict = field(default_factory=lambda: dict())
song_hashes: dict[str, list[dict]] = field(default_factory=lambda: dict()) #Hash to path
song_paths: dict[Path, str] = field(default_factory=lambda: dict()) #path to hash
global_data = GlobalData() global_data = GlobalData()
shader = ray.load_shader('', 'shader/outline.fs')
class OutlinedText: class OutlinedText:
def __init__(self, text: str, font_size: int, color: ray.Color, outline_color: ray.Color, outline_thickness=5.0, vertical=False): def __init__(self, text: str, font_size: int, color: ray.Color, outline_color: ray.Color, outline_thickness=5.0, vertical=False):
@@ -260,7 +260,7 @@ class OutlinedText:
]) ])
texture_size = ray.ffi.new("float[2]", [self.texture.width, self.texture.height]) texture_size = ray.ffi.new("float[2]", [self.texture.width, self.texture.height])
self.shader = shader self.shader = ray.load_shader('shader/outline.vs', 'shader/outline.fs')
outline_size_loc = ray.get_shader_location(self.shader, "outlineSize") outline_size_loc = ray.get_shader_location(self.shader, "outlineSize")
outline_color_loc = ray.get_shader_location(self.shader, "outlineColor") outline_color_loc = ray.get_shader_location(self.shader, "outlineColor")
texture_size_loc = ray.get_shader_location(self.shader, "textureSize") texture_size_loc = ray.get_shader_location(self.shader, "textureSize")
@@ -279,7 +279,7 @@ class OutlinedText:
rotate_chars = {'-', '', '|', '/', '\\', '', '', '~', '', '', '(', ')', rotate_chars = {'-', '', '|', '/', '\\', '', '', '~', '', '', '(', ')',
'', '', '[', ']', '', '', '', '', '', '', '', ':', ''} '', '', '[', ']', '', '', '', '', '', '', '', ':', ''}
max_char_width = 0 max_char_width = 0
total_height = padding * 2 # Top and bottom padding total_height = padding * 2
for char in text: for char in text:
if font: if font:
@@ -288,22 +288,20 @@ class OutlinedText:
char_width = ray.measure_text(char, font_size) char_width = ray.measure_text(char, font_size)
char_size = ray.Vector2(char_width, font_size) char_size = ray.Vector2(char_width, font_size)
# If character should be rotated, swap width and height for measurements
if char in rotate_chars: if char in rotate_chars:
effective_width = char_size.y # Height becomes width when rotated 90° effective_width = char_size.y
else: else:
effective_width = char_size.x effective_width = char_size.x
max_char_width = max(max_char_width, effective_width) max_char_width = max(max_char_width, effective_width)
total_height += len(text) * font_size total_height += len(text) * font_size
width = int(max_char_width + (padding * 2)) # Add left and right padding width = int(max_char_width + (padding * 2))
height = total_height height = total_height
image = ray.gen_image_color(width, height, bg_color) image = ray.gen_image_color(width, height, bg_color)
for i, char in enumerate(text): for i, char in enumerate(text):
char_y = i * ray.measure_text_ex(self.font, char, font_size, 0).y char_y = i * font_size + padding
char_y += padding
if font: if font:
char_size = ray.measure_text_ex(font, char, font_size, 0) char_size = ray.measure_text_ex(font, char, font_size, 0)
@@ -313,26 +311,22 @@ class OutlinedText:
char_size = ray.Vector2(char_width, font_size) char_size = ray.Vector2(char_width, font_size)
char_image = ray.image_text(char, font_size, color) char_image = ray.image_text(char, font_size, color)
# Rotate character if it's in the rotate_chars set
if char in rotate_chars: if char in rotate_chars:
rotated_image = ray.gen_image_color(char_image.height, char_image.width, ray.BLANK) rotated_image = ray.gen_image_color(char_image.height, char_image.width, ray.BLANK)
# Manual 90-degree clockwise rotation
for y in range(char_image.height): for y in range(char_image.height):
for x in range(char_image.width): for x in range(char_image.width):
src_color = ray.get_image_color(char_image, x, y) src_color = ray.get_image_color(char_image, x, y)
# 90° clockwise: new_x = old_y, new_y = width - 1 - old_x new_x = char_image.height - 1 - y
new_x = y new_y = x
new_y = char_image.width - 1 - x
ray.image_draw_pixel(rotated_image, new_x, new_y, src_color) ray.image_draw_pixel(rotated_image, new_x, new_y, src_color)
ray.unload_image(char_image) ray.unload_image(char_image)
char_image = rotated_image char_image = rotated_image
effective_width = char_size.y # Height becomes width when rotated effective_width = char_size.y
else: else:
effective_width = char_size.x effective_width = char_size.x
# Center the character horizontally
char_x = width // 2 - effective_width // 2 char_x = width // 2 - effective_width // 2
ray.image_draw(image, char_image, ray.image_draw(image, char_image,

View File

@@ -105,7 +105,7 @@ class EntryScreen:
self.selected_box = max(0, self.selected_box - 1) self.selected_box = max(0, self.selected_box - 1)
if is_r_kat_pressed(): if is_r_kat_pressed():
audio.play_sound(self.sound_kat) audio.play_sound(self.sound_kat)
self.selected_box = min(self.num_boxes, self.selected_box + 1) self.selected_box = min(self.num_boxes - 1, self.selected_box + 1)
def update(self): def update(self):
self.on_screen_start() self.on_screen_start()

View File

@@ -40,9 +40,9 @@ class ResultScreen:
self.load_textures() self.load_textures()
self.load_sounds() self.load_sounds()
self.screen_init = True self.screen_init = True
self.song_info = FontText(session_data.song_title, 40).texture self.song_info = OutlinedText(session_data.song_title, 40, ray.Color(255, 255, 255, 255), ray.Color(0, 0, 0, 255), outline_thickness=5)
audio.play_sound(self.bgm) audio.play_sound(self.bgm)
self.fade_in = FadeIn(get_current_ms()) self.fade_in = FadeIn()
self.fade_out = None self.fade_out = None
self.gauge = None self.gauge = None
self.score_delay = None self.score_delay = None
@@ -165,7 +165,9 @@ class ResultScreen:
ray.draw_texture(self.textures['result'][330], -5, 3, ray.WHITE) ray.draw_texture(self.textures['result'][330], -5, 3, ray.WHITE)
ray.draw_texture(self.textures['result'][(global_data.songs_played % 4) + 331], 232, 4, ray.WHITE) ray.draw_texture(self.textures['result'][(global_data.songs_played % 4) + 331], 232, 4, ray.WHITE)
ray.draw_texture(self.song_info, 1252 - self.song_info.width, int(35 - self.song_info.height / 2), ray.WHITE) src = ray.Rectangle(0, 0, self.song_info.texture.width, self.song_info.texture.height)
dest = ray.Rectangle(1252 - self.song_info.texture.width, 35 - self.song_info.texture.height / 2, self.song_info.texture.width, self.song_info.texture.height)
self.song_info.draw(src, dest, ray.Vector2(0, 0), 0, ray.WHITE)
ray.draw_texture(self.textures['result'][175], 532, 98, ray.fade(ray.WHITE, 0.75)) ray.draw_texture(self.textures['result'][175], 532, 98, ray.fade(ray.WHITE, 0.75))
@@ -189,7 +191,7 @@ class ResultScreen:
class FadeIn: class FadeIn:
def __init__(self, current_ms: float): def __init__(self):
self.fadein = Animation.create_fade(450, initial_opacity=1.0, final_opacity=0.0, delay=100) self.fadein = Animation.create_fade(450, initial_opacity=1.0, final_opacity=0.0, delay=100)
self.fade = ray.fade(ray.WHITE, self.fadein.attribute) self.fade = ray.fade(ray.WHITE, self.fadein.attribute)
@@ -212,12 +214,6 @@ class FadeIn:
ray.draw_texture(texture_2, x, screen_height - texture_2.height + texture_2.height//2, self.fade) ray.draw_texture(texture_2, x, screen_height - texture_2.height + texture_2.height//2, self.fade)
x += texture_2.width x += texture_2.width
class FontText:
def __init__(self, text, font_size):
self.text = OutlinedText(str(text), font_size, ray.Color(255, 255, 255, 255), ray.Color(0, 0, 0, 255), outline_thickness=5)
self.texture = self.text.texture
class ScoreAnimator: class ScoreAnimator:
def __init__(self, target_score): def __init__(self, target_score):
self.target_score = str(target_score) self.target_score = str(target_score)

View File

@@ -5,7 +5,6 @@ from typing import Optional, Union
import pyray as ray import pyray as ray
from libs import song_hash
from libs.animation import Animation from libs.animation import Animation
from libs.audio import audio from libs.audio import audio
from libs.tja import TJAParser from libs.tja import TJAParser
@@ -418,20 +417,8 @@ class SongBox:
def get_scores(self): def get_scores(self):
if self.tja is None: if self.tja is None:
return return
with sqlite3.connect('scores.db') as con: with sqlite3.connect('scores.db') as con:
cursor = con.cursor() cursor = con.cursor()
diffs_to_compute = []
for diff in self.tja.metadata.course_data:
if diff not in self.hash:
diffs_to_compute.append(diff)
if diffs_to_compute:
for diff in diffs_to_compute:
notes, _, bars = TJAParser.notes_to_position(TJAParser(self.tja.file_path), diff)
self.hash[diff] = self.tja.hash_note_data(notes, bars)
# Batch database query for all diffs at once # Batch database query for all diffs at once
if self.tja.metadata.course_data: if self.tja.metadata.course_data:
hash_values = [self.hash[diff] for diff in self.tja.metadata.course_data] hash_values = [self.hash[diff] for diff in self.tja.metadata.course_data]
@@ -1004,17 +991,15 @@ class SongFile(FileSystemItem):
if self.is_recent: if self.is_recent:
self.tja.ex_data.new = True self.tja.ex_data.new = True
title = self.tja.metadata.title.get(global_data.config['general']['language'].lower(), self.tja.metadata.title['en']) title = self.tja.metadata.title.get(global_data.config['general']['language'].lower(), self.tja.metadata.title['en'])
self.hash = global_data.song_paths[path]
self.box = SongBox(title, texture_index, False, tja=self.tja, name_texture_index=name_texture_index if name_texture_index is not None else texture_index) self.box = SongBox(title, texture_index, False, tja=self.tja, name_texture_index=name_texture_index if name_texture_index is not None else texture_index)
self.box.hash = global_data.song_hashes[self.hash][0]["diff_hashes"]
self.box.get_scores() self.box.get_scores()
class FileNavigator: class FileNavigator:
"""Manages navigation through pre-generated Directory and SongFile objects""" """Manages navigation through pre-generated Directory and SongFile objects"""
def __init__(self, root_dirs: list[str]): def __init__(self, root_dirs: list[str]):
# Handle both single path and list of paths self.root_dirs = [Path(p) if not isinstance(p, Path) else p for p in root_dirs]
if isinstance(root_dirs, (list, tuple)):
self.root_dirs = [Path(p) if not isinstance(p, Path) else p for p in root_dirs]
else:
self.root_dirs = [Path(root_dirs) if not isinstance(root_dirs, Path) else root_dirs]
# Pre-generated objects storage # Pre-generated objects storage
self.all_directories: dict[str, Directory] = {} # path -> Directory self.all_directories: dict[str, Directory] = {} # path -> Directory
@@ -1118,14 +1103,10 @@ class FileNavigator:
for i, tja_path in enumerate(sorted(tja_files)): for i, tja_path in enumerate(sorted(tja_files)):
song_key = str(tja_path) song_key = str(tja_path)
if song_key not in self.all_song_files: if song_key not in self.all_song_files:
try: song_obj = SongFile(tja_path, tja_path.name, texture_index)
song_obj = SongFile(tja_path, tja_path.name, texture_index) if song_obj.is_recent:
if song_obj.is_recent: self.new_items.append(SongFile(tja_path, tja_path.name, 620, name_texture_index=texture_index))
self.new_items.append(SongFile(tja_path, tja_path.name, 620, name_texture_index=texture_index)) self.all_song_files[song_key] = song_obj
self.all_song_files[song_key] = song_obj
except Exception as e:
print(f"Error creating SongFile for {tja_path}: {e}")
continue
content_items.append(self.all_song_files[song_key]) content_items.append(self.all_song_files[song_key])
@@ -1302,19 +1283,20 @@ class FileNavigator:
hash_val, title, subtitle = parts[0], parts[1], parts[2] hash_val, title, subtitle = parts[0], parts[1], parts[2]
original_hash = hash_val original_hash = hash_val
if song_hash.song_hashes is not None: if hash_val in global_data.song_hashes:
if hash_val in song_hash.song_hashes: file_path = Path(global_data.song_hashes[hash_val][0]["file_path"])
file_path = Path(song_hash.song_hashes[hash_val]["file_path"]) if file_path.exists():
if file_path.exists(): tja_files.append(file_path)
tja_files.append(file_path) else:
else: # Try to find by title and subtitle
# Try to find by title and subtitle for key, value in global_data.song_hashes.items():
for key, value in song_hash.song_hashes.items(): for i in range(len(value)):
if (value["title"]["en"] == title and song = value[i]
value["subtitle"]["en"][2:] == subtitle and if (song["title"]["en"] == title and
Path(value["file_path"]).exists()): song["subtitle"]["en"][2:] == subtitle and
Path(song["file_path"]).exists()):
hash_val = key hash_val = key
tja_files.append(Path(song_hash.song_hashes[hash_val]["file_path"])) tja_files.append(Path(global_data.song_hashes[hash_val][i]["file_path"]))
break break
if hash_val != original_hash: if hash_val != original_hash:
@@ -1523,17 +1505,3 @@ class FileNavigator:
self.current_root_dir = Path() self.current_root_dir = Path()
self.history.clear() self.history.clear()
self.load_root_directories() self.load_root_directories()
def get_stats(self):
"""Get statistics about the pre-generated objects"""
song_count_by_dir = {}
for dir_path, items in self.directory_contents.items():
song_count_by_dir[dir_path] = len([item for item in items if isinstance(item, SongFile)])
return {
'total_directories': len(self.all_directories),
'total_songs': len(self.all_song_files),
'root_items': len(self.root_items),
'directories_with_content': len(self.directory_contents),
'songs_by_directory': song_count_by_dir
}

27
shader/outline.vs Normal file
View File

@@ -0,0 +1,27 @@
#version 330
// Input vertex attributes
in vec3 vertexPosition;
in vec2 vertexTexCoord;
in vec3 vertexNormal;
in vec4 vertexColor;
// Input uniform values
uniform mat4 mvp;
uniform mat4 matModel;
uniform mat4 matView;
uniform mat4 matProjection;
// Output vertex attributes (to fragment shader)
out vec2 fragTexCoord;
out vec4 fragColor;
void main()
{
// Calculate final vertex position
gl_Position = mvp * vec4(vertexPosition, 1.0);
// Send vertex attributes to fragment shader
fragTexCoord = vertexTexCoord;
fragColor = vertexColor;
}