/* * This screensaver demonstrates 2D shader effects through multiple textures. */ #define SGP_UNIFORM_CONTENT_SLOTS 16 #define SOKOL_IMPL #include "sokol/sokol_gfx.h" #include "sokol/sokol_gp.h" #include "sokol/sokol_app.h" #include "sokol/sokol_glue.h" #include "sokol/sokol_log.h" #include "sokol/sokol_time.h" #define STB_IMAGE_IMPLEMENTATION #define STB_IMAGE_STATIC #define STBI_NO_SIMD #define STBI_ONLY_PNG #include "stb/stb_image.h" #define SOKOL_SHDC_IMPL #include "sample-effect.glsl.h" #ifdef __APPLE__ #include #include static void set_macos_working_directory(void) { CFBundleRef bundle = CFBundleGetMainBundle(); if (bundle) { CFStringRef id = CFBundleGetIdentifier(bundle); if (id) { CFURLRef resourcesURL = CFBundleCopyResourcesDirectoryURL(bundle); if (resourcesURL) { char path[PATH_MAX]; if (CFURLGetFileSystemRepresentation(resourcesURL, true, (UInt8*)path, PATH_MAX)) { chdir(path); } CFRelease(resourcesURL); } } } } #endif static sg_pipeline pip; static sg_shader shd; static sg_image image; static sg_sampler linear_sampler; static sg_image perlin_image; static void event(const sapp_event* ev) { switch (ev->type) { case SAPP_EVENTTYPE_KEY_DOWN: if (ev->key_code == SAPP_KEYCODE_ESCAPE) sapp_request_quit(); break; default: break; } } static void frame(void) { // begin draw commands queue int window_width = sapp_width(), window_height = sapp_height(); sgp_begin(window_width, window_height); float secs = sapp_frame_count() * sapp_frame_duration(); sg_image_desc image_desc = sg_query_image_desc(image); float window_ratio = window_width / (float)window_height; float image_ratio = image_desc.width / (float)image_desc.height; effect_fs_uniforms_t uniforms = {0}; uniforms.iVelocity.x = 0.02f; uniforms.iVelocity.y = 0.01f; uniforms.iPressure = 0.3f; uniforms.iTime = secs; uniforms.iWarpiness = 0.2f; uniforms.iRatio = image_ratio; uniforms.iZoom = 0.4f; uniforms.iLevel = 1.0f; sgp_set_pipeline(pip); sgp_set_pipeline(pip); sgp_set_uniform(NULL, 0, &uniforms, sizeof(effect_fs_uniforms_t)); sgp_set_image(IMG_iTexChannel0, image); sgp_set_image(IMG_iTexChannel1, perlin_image); sgp_set_sampler(SMP_iSmpChannel0, linear_sampler); sgp_set_sampler(SMP_iSmpChannel1, linear_sampler); float width = (window_ratio >= image_ratio) ? window_width : image_ratio*window_height; float height = (window_ratio >= image_ratio) ? window_width/image_ratio : window_height; sgp_draw_filled_rect(0, 0, width, height); sgp_reset_image(IMG_iTexChannel0); sgp_reset_image(IMG_iTexChannel1); sgp_reset_sampler(SMP_iSmpChannel0); sgp_reset_sampler(SMP_iSmpChannel1); sgp_reset_pipeline(); // dispatch draw commands sg_pass pass = {.swapchain = sglue_swapchain()}; sg_begin_pass(&pass); sgp_flush(); sgp_end(); sg_end_pass(); sg_commit(); } static sg_image load_image(const char *filename) { int width, height, channels; uint8_t* data = stbi_load(filename, &width, &height, &channels, 4); sg_image img = {SG_INVALID_ID}; if(!data) return img; sg_image_desc image_desc = {0}; image_desc.width = width; image_desc.height = height; image_desc.data.subimage[0][0].ptr = data; image_desc.data.subimage[0][0].size = (size_t)(width * height * 4); img = sg_make_image(&image_desc); stbi_image_free(data); return img; } static void init(void) { #ifdef __APPLE__ set_macos_working_directory(); #endif // Initialize Sokol GFX sg_desc sgdesc = { .environment = sglue_environment(), .logger.func = slog_func }; sg_setup(&sgdesc); if (!sg_isvalid()) { fprintf(stderr, "Failed to create Sokol GFX context!\n"); exit(-1); } // Initialize Sokol GP sgp_desc sgpdesc = {0}; sgp_setup(&sgpdesc); if (!sgp_is_valid()) { fprintf(stderr, "Failed to create Sokol GP context: %s\n", sgp_get_error_message(sgp_get_last_error())); exit(-1); } // Load image image = load_image("data/images/majora.png"); perlin_image = load_image("data/images/perlin.png"); if (sg_query_image_state(image) != SG_RESOURCESTATE_VALID || sg_query_image_state(perlin_image) != SG_RESOURCESTATE_VALID) { fprintf(stderr, "failed to load images"); exit(-1); } // Create linear sampler sg_sampler_desc linear_sampler_desc = { .min_filter = SG_FILTER_LINEAR, .mag_filter = SG_FILTER_LINEAR, .wrap_u = SG_WRAP_REPEAT, .wrap_v = SG_WRAP_REPEAT, }; linear_sampler = sg_make_sampler(&linear_sampler_desc); if (sg_query_sampler_state(linear_sampler) != SG_RESOURCESTATE_VALID) { fprintf(stderr, "failed to create linear sampler"); exit(-1); } // Initialize shader shd = sg_make_shader(effect_program_shader_desc(sg_query_backend())); if (sg_query_shader_state(shd) != SG_RESOURCESTATE_VALID) { fprintf(stderr, "failed to make custom pipeline shader\n"); exit(-1); } sgp_pipeline_desc pip_desc = {0}; pip_desc.shader = shd; pip_desc.has_vs_color = true; pip = sgp_make_pipeline(&pip_desc); if (sg_query_pipeline_state(pip) != SG_RESOURCESTATE_VALID) { fprintf(stderr, "failed to make custom pipeline\n"); exit(-1); } } static void cleanup(void) { sg_destroy_image(image); sg_destroy_image(perlin_image); sg_destroy_pipeline(pip); sgp_shutdown(); sg_shutdown(); } sapp_desc sokol_main(int argc, char* argv[]) { (void)argc; (void)argv; return (sapp_desc){ .window_title = "Majora's Screensaver", .width = 700, .height = 720, .high_dpi = true, .init_cb = init, .event_cb = event, .frame_cb = frame, .cleanup_cb = cleanup, .logger.func = slog_func, }; }