X-Git-Url: https://git.octo.it/?a=blobdiff_plain;f=src%2Fvideo%2Fsdl%2Fsdl_renderer.cpp;h=2ed479ec1cda5c1ee61d0fbdb757adecef17467c;hb=c88bea169adaf492d7e1da6567edee6f9b6a94d1;hp=d769dc5f63d640f16b055ca14c243b647f24ace7;hpb=522af0e1add005b840fcc8064b537f31997bafc0;p=supertux.git diff --git a/src/video/sdl/sdl_renderer.cpp b/src/video/sdl/sdl_renderer.cpp index d769dc5f6..2ed479ec1 100644 --- a/src/video/sdl/sdl_renderer.cpp +++ b/src/video/sdl/sdl_renderer.cpp @@ -1,5 +1,6 @@ // SuperTux // Copyright (C) 2006 Matthias Braun +// Updated by GiBy 2013 for SDL2 // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -16,419 +17,358 @@ #include "video/sdl/sdl_renderer.hpp" -#include -#include -#include - +#include "util/log.hpp" #include "video/drawing_request.hpp" #include "video/sdl/sdl_surface_data.hpp" #include "video/sdl/sdl_texture.hpp" +#include "video/sdl/sdl_painter.hpp" -namespace { +#include +#include +#include +#include +#include +#include "SDL2/SDL_video.h" -SDL_Surface *apply_alpha(SDL_Surface *src, float alpha_factor) +#include "video/util.hpp" + +SDLRenderer::SDLRenderer() : + m_window(), + m_renderer(), + m_viewport(), + m_desktop_size(0, 0), + m_scale(1.0f, 1.0f) { - // FIXME: This is really slow - assert(src->format->Amask); - int alpha = (int) (alpha_factor * 256); - SDL_Surface *dst = SDL_CreateRGBSurface(src->flags, src->w, src->h, src->format->BitsPerPixel, src->format->Rmask, src->format->Gmask, src->format->Bmask, src->format->Amask); - int bpp = dst->format->BytesPerPixel; - if(SDL_MUSTLOCK(src)) + SDL_DisplayMode mode; + if (SDL_GetDesktopDisplayMode(0, &mode) != 0) { - SDL_LockSurface(src); - } - if(SDL_MUSTLOCK(dst)) - { - SDL_LockSurface(dst); - } - for(int y = 0;y < dst->h;y++) { - for(int x = 0;x < dst->w;x++) { - Uint8 *srcpixel = (Uint8 *) src->pixels + y * src->pitch + x * bpp; - Uint8 *dstpixel = (Uint8 *) dst->pixels + y * dst->pitch + x * bpp; - Uint32 mapped = 0; - switch(bpp) { - case 1: - mapped = *srcpixel; - break; - case 2: - mapped = *(Uint16 *)srcpixel; - break; - case 3: -#if SDL_BYTEORDER == SDL_BIG_ENDIAN - mapped |= srcpixel[0] << 16; - mapped |= srcpixel[1] << 8; - mapped |= srcpixel[2] << 0; -#else - mapped |= srcpixel[0] << 0; - mapped |= srcpixel[1] << 8; - mapped |= srcpixel[2] << 16; -#endif - break; - case 4: - mapped = *(Uint32 *)srcpixel; - break; - } - Uint8 r, g, b, a; - SDL_GetRGBA(mapped, src->format, &r, &g, &b, &a); - mapped = SDL_MapRGBA(dst->format, r, g, b, (a * alpha) >> 8); - switch(bpp) { - case 1: - *dstpixel = mapped; - break; - case 2: - *(Uint16 *)dstpixel = mapped; - break; - case 3: -#if SDL_BYTEORDER == SDL_BIG_ENDIAN - dstpixel[0] = (mapped >> 16) & 0xff; - dstpixel[1] = (mapped >> 8) & 0xff; - dstpixel[2] = (mapped >> 0) & 0xff; -#else - dstpixel[0] = (mapped >> 0) & 0xff; - dstpixel[1] = (mapped >> 8) & 0xff; - dstpixel[2] = (mapped >> 16) & 0xff; -#endif - break; - case 4: - *(Uint32 *)dstpixel = mapped; - break; - } - } + log_warning << "Couldn't get desktop display mode: " << SDL_GetError() << std::endl; } - if(SDL_MUSTLOCK(dst)) + else { - SDL_UnlockSurface(dst); + m_desktop_size = Size(mode.w, mode.h); } - if(SDL_MUSTLOCK(src)) + + log_info << "creating SDLRenderer" << std::endl; + int width = g_config->window_size.width; + int height = g_config->window_size.height; + + int flags = SDL_WINDOW_RESIZABLE; + if(g_config->use_fullscreen) { - SDL_UnlockSurface(src); + if (g_config->fullscreen_size == Size(0, 0)) + { + flags |= SDL_WINDOW_FULLSCREEN_DESKTOP; + width = g_config->window_size.width; + height = g_config->window_size.height; + } + else + { + flags |= SDL_WINDOW_FULLSCREEN; + width = g_config->fullscreen_size.width; + height = g_config->fullscreen_size.height; + } } - return dst; -} -} // namespace + SCREEN_WIDTH = width; + SCREEN_HEIGHT = height; -SDLRenderer::SDLRenderer() : - screen(), - numerator(), - denominator() -{ - Renderer::instance_ = this; - - const SDL_VideoInfo *info = SDL_GetVideoInfo(); - log_info << "Hardware surfaces are " << (info->hw_available ? "" : "not ") << "available." << std::endl; - log_info << "Hardware to hardware blits are " << (info->blit_hw ? "" : "not ") << "accelerated." << std::endl; - log_info << "Hardware to hardware blits with colorkey are " << (info->blit_hw_CC ? "" : "not ") << "accelerated." << std::endl; - log_info << "Hardware to hardware blits with alpha are " << (info->blit_hw_A ? "" : "not ") << "accelerated." << std::endl; - log_info << "Software to hardware blits are " << (info->blit_sw ? "" : "not ") << "accelerated." << std::endl; - log_info << "Software to hardware blits with colorkey are " << (info->blit_sw_CC ? "" : "not ") << "accelerated." << std::endl; - log_info << "Software to hardware blits with alpha are " << (info->blit_sw_A ? "" : "not ") << "accelerated." << std::endl; - log_info << "Color fills are " << (info->blit_fill ? "" : "not ") << "accelerated." << std::endl; - - int flags = SDL_SWSURFACE | SDL_ANYFORMAT; - if(g_config->use_fullscreen) - flags |= SDL_FULLSCREEN; - - int width = 800; //FIXME: config->screenwidth; - int height = 600; //FIXME: config->screenheight; + m_viewport.x = 0; + m_viewport.y = 0; + m_viewport.w = width; + m_viewport.h = height; + + SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "2"); - screen = SDL_SetVideoMode(width, height, 0, flags); - if(screen == 0) { + int ret = SDL_CreateWindowAndRenderer(width, height, flags, + &m_window, &m_renderer); + + if(ret != 0) { std::stringstream msg; msg << "Couldn't set video mode (" << width << "x" << height << "): " << SDL_GetError(); throw std::runtime_error(msg.str()); } - numerator = 1; - denominator = 1; - /* FIXME: - float xfactor = (float) config->screenwidth / SCREEN_WIDTH; - float yfactor = (float) config->screenheight / SCREEN_HEIGHT; - if(xfactor < yfactor) - { - numerator = config->screenwidth; - denominator = SCREEN_WIDTH; - } - else - { - numerator = config->screenheight; - denominator = SCREEN_HEIGHT; - } - */ - if(texture_manager == 0) - texture_manager = new TextureManager(); + SDL_RendererInfo info; + if (SDL_GetRendererInfo(m_renderer, &info) != 0) + { + log_warning << "Couldn't get RendererInfo: " << SDL_GetError() << std::endl; + } + else + { + log_info << "SDL_Renderer: " << info.name << std::endl; + log_info << "SDL_RendererFlags: " << std::endl; + if (info.flags & SDL_RENDERER_SOFTWARE) { log_info << " SDL_RENDERER_SOFTWARE" << std::endl; } + if (info.flags & SDL_RENDERER_ACCELERATED) { log_info << " SDL_RENDERER_ACCELERATED" << std::endl; } + if (info.flags & SDL_RENDERER_PRESENTVSYNC) { log_info << " SDL_RENDERER_PRESENTVSYNC" << std::endl; } + if (info.flags & SDL_RENDERER_TARGETTEXTURE) { log_info << " SDL_RENDERER_TARGETTEXTURE" << std::endl; } + log_info << "Texture Formats: " << std::endl; + for(size_t i = 0; i < info.num_texture_formats; ++i) + { + log_info << " " << SDL_GetPixelFormatName(info.texture_formats[i]) << std::endl; + } + log_info << "Max Texture Width: " << info.max_texture_width << std::endl; + log_info << "Max Texture Height: " << info.max_texture_height << std::endl; + } + + g_config->window_size = Size(width, height); + apply_config(); } SDLRenderer::~SDLRenderer() { + SDL_DestroyRenderer(m_renderer); + SDL_DestroyWindow(m_window); +} + +void +SDLRenderer::start_draw() +{ + SDL_RenderSetScale(m_renderer, m_scale.x, m_scale.y); +} + +void +SDLRenderer::end_draw() +{ } void SDLRenderer::draw_surface(const DrawingRequest& request) { - //FIXME: support parameters request.alpha, request.angle, request.blend - const Surface* surface = (const Surface*) request.request_data; - boost::shared_ptr sdltexture = boost::dynamic_pointer_cast(surface->get_texture()); - SDLSurfaceData *surface_data = reinterpret_cast(surface->get_surface_data()); + SDLPainter::draw_surface(m_renderer, request); +} - DrawingEffect effect = request.drawing_effect; - if (surface->get_flipx()) effect = HORIZONTAL_FLIP; +void +SDLRenderer::draw_surface_part(const DrawingRequest& request) +{ + SDLPainter::draw_surface_part(m_renderer, request); +} - SDL_Surface *transform = sdltexture->get_transform(request.color, effect); +void +SDLRenderer::draw_gradient(const DrawingRequest& request) +{ + SDLPainter::draw_gradient(m_renderer, request); +} - // get and check SDL_Surface - if (transform == 0) { - std::cerr << "Warning: Tried to draw NULL surface, skipped draw" << std::endl; - return; - } +void +SDLRenderer::draw_filled_rect(const DrawingRequest& request) +{ + SDLPainter::draw_filled_rect(m_renderer, request); +} - SDL_Rect *src_rect = surface_data->get_src_rect(effect); - SDL_Rect dst_rect; - dst_rect.x = (int) request.pos.x * numerator / denominator; - dst_rect.y = (int) request.pos.y * numerator / denominator; +void +SDLRenderer::draw_inverse_ellipse(const DrawingRequest& request) +{ + SDLPainter::draw_inverse_ellipse(m_renderer, request); +} - Uint8 alpha = 0; - if(request.alpha != 1.0) +void +SDLRenderer::do_take_screenshot() +{ + // [Christoph] TODO: Yes, this method also takes care of the actual disk I/O. Split it? + int width; + int height; + if (SDL_GetRendererOutputSize(m_renderer, &width, &height) != 0) { - if(!transform->format->Amask) - { - if(transform->flags & SDL_SRCALPHA) - { - alpha = transform->format->alpha; - } - else - { - alpha = 255; - } - SDL_SetAlpha(transform, SDL_SRCALPHA, (Uint8) (request.alpha * alpha)); - } - /*else - { - transform = apply_alpha(transform, request.alpha); - }*/ + log_warning << "SDL_GetRenderOutputSize failed: " << SDL_GetError() << std::endl; } - - SDL_BlitSurface(transform, src_rect, screen, &dst_rect); - - if(request.alpha != 1.0) + else { - if(!transform->format->Amask) +#if SDL_BYTEORDER == SDL_BIG_ENDIAN + Uint32 rmask = 0xff000000; + Uint32 gmask = 0x00ff0000; + Uint32 bmask = 0x0000ff00; + Uint32 amask = 0x000000ff; +#else + Uint32 rmask = 0x000000ff; + Uint32 gmask = 0x0000ff00; + Uint32 bmask = 0x00ff0000; + Uint32 amask = 0xff000000; +#endif + SDL_Surface* surface = SDL_CreateRGBSurface(0, width, height, 32, + rmask, gmask, bmask, amask); + if (!surface) { - if(alpha == 255) + log_warning << "SDL_CreateRGBSurface failed: " << SDL_GetError() << std::endl; + } + else + { + int ret = SDL_RenderReadPixels(m_renderer, NULL, + SDL_PIXELFORMAT_ABGR8888, + surface->pixels, + surface->pitch); + if (ret != 0) { - SDL_SetAlpha(transform, SDL_RLEACCEL, 0); + log_warning << "SDL_RenderReadPixels failed: " << SDL_GetError() << std::endl; } else { - SDL_SetAlpha(transform, SDL_SRCALPHA | SDL_RLEACCEL, alpha); + // save screenshot + static const std::string writeDir = PHYSFS_getWriteDir(); + static const std::string dirSep = PHYSFS_getDirSeparator(); + static const std::string baseName = "screenshot"; + static const std::string fileExt = ".bmp"; + std::string fullFilename; + for (int num = 0; num < 1000; num++) { + std::ostringstream oss; + oss << baseName; + oss << std::setw(3) << std::setfill('0') << num; + oss << fileExt; + std::string fileName = oss.str(); + fullFilename = writeDir + dirSep + fileName; + if (!PHYSFS_exists(fileName.c_str())) { + SDL_SaveBMP(surface, fullFilename.c_str()); + log_debug << "Wrote screenshot to \"" << fullFilename << "\"" << std::endl; + return; + } + } + log_warning << "Did not save screenshot, because all files up to \"" << fullFilename << "\" already existed" << std::endl; } } - /*else - { - SDL_FreeSurface(transform); - }*/ } } void -SDLRenderer::draw_surface_part(const DrawingRequest& request) +SDLRenderer::flip() { - const SurfacePartRequest* surfacepartrequest - = (SurfacePartRequest*) request.request_data; - - const Surface* surface = surfacepartrequest->surface; - boost::shared_ptr sdltexture = boost::dynamic_pointer_cast(surface->get_texture()); - - DrawingEffect effect = request.drawing_effect; - if (surface->get_flipx()) effect = HORIZONTAL_FLIP; + SDL_RenderPresent(m_renderer); +} - SDL_Surface *transform = sdltexture->get_transform(request.color, effect); +void +SDLRenderer::resize(int w , int h) +{ + g_config->window_size = Size(w, h); - // get and check SDL_Surface - if (transform == 0) { - std::cerr << "Warning: Tried to draw NULL surface, skipped draw" << std::endl; - return; - } + apply_config(); +} - int ox, oy; - if (effect == HORIZONTAL_FLIP) - { - ox = sdltexture->get_texture_width() - surface->get_x() - (int) surfacepartrequest->size.x; - } - else - { - ox = surface->get_x(); - } - if (effect == VERTICAL_FLIP) +void +SDLRenderer::apply_video_mode() +{ + if (!g_config->use_fullscreen) { - oy = sdltexture->get_texture_height() - surface->get_y() - (int) surfacepartrequest->size.y; + SDL_SetWindowFullscreen(m_window, 0); } else { - oy = surface->get_y(); - } - - SDL_Rect src_rect; - src_rect.x = (ox + (int) surfacepartrequest->source.x) * numerator / denominator; - src_rect.y = (oy + (int) surfacepartrequest->source.y) * numerator / denominator; - src_rect.w = (int) surfacepartrequest->size.x * numerator / denominator; - src_rect.h = (int) surfacepartrequest->size.y * numerator / denominator; - - SDL_Rect dst_rect; - dst_rect.x = (int) request.pos.x * numerator / denominator; - dst_rect.y = (int) request.pos.y * numerator / denominator; - - Uint8 alpha = 0; - if(request.alpha != 1.0) - { - if(!transform->format->Amask) + if (g_config->fullscreen_size.width == 0 && + g_config->fullscreen_size.height == 0) { - if(transform->flags & SDL_SRCALPHA) - { - alpha = transform->format->alpha; - } - else - { - alpha = 255; - } - SDL_SetAlpha(transform, SDL_SRCALPHA, (Uint8) (request.alpha * alpha)); + if (SDL_SetWindowFullscreen(m_window, SDL_WINDOW_FULLSCREEN_DESKTOP) != 0) + { + log_warning << "failed to switch to desktop fullscreen mode: " + << SDL_GetError() << std::endl; + } + else + { + log_info << "switched to desktop fullscreen mode" << std::endl; + } } - /*else - { - transform = apply_alpha(transform, request.alpha); - }*/ - } - - SDL_BlitSurface(transform, &src_rect, screen, &dst_rect); - - if(request.alpha != 1.0) - { - if(!transform->format->Amask) + else { - if(alpha == 255) + SDL_DisplayMode mode; + mode.format = SDL_PIXELFORMAT_RGB888; + mode.w = g_config->fullscreen_size.width; + mode.h = g_config->fullscreen_size.height; + mode.refresh_rate = g_config->fullscreen_refresh_rate; + mode.driverdata = 0; + + if (SDL_SetWindowDisplayMode(m_window, &mode) != 0) { - SDL_SetAlpha(transform, SDL_RLEACCEL, 0); + log_warning << "failed to set display mode: " + << mode.w << "x" << mode.h << "@" << mode.refresh_rate << ": " + << SDL_GetError() << std::endl; } else { - SDL_SetAlpha(transform, SDL_SRCALPHA | SDL_RLEACCEL, alpha); + if (SDL_SetWindowFullscreen(m_window, SDL_WINDOW_FULLSCREEN) != 0) + { + log_warning << "failed to switch to fullscreen mode: " + << mode.w << "x" << mode.h << "@" << mode.refresh_rate << ": " + << SDL_GetError() << std::endl; + } + else + { + log_info << "switched to fullscreen mode: " + << mode.w << "x" << mode.h << "@" << mode.refresh_rate << std::endl; + } } } - /*else - { - SDL_FreeSurface(transform); - }*/ } } void -SDLRenderer::draw_gradient(const DrawingRequest& request) +SDLRenderer::apply_viewport() { - const GradientRequest* gradientrequest - = (GradientRequest*) request.request_data; - const Color& top = gradientrequest->top; - const Color& bottom = gradientrequest->bottom; + Size target_size = (g_config->use_fullscreen && g_config->fullscreen_size != Size(0, 0)) ? + g_config->fullscreen_size : + g_config->window_size; - for(int y = 0;y < screen->h;++y) + float pixel_aspect_ratio = 1.0f; + if (g_config->aspect_size != Size(0, 0)) { - Uint8 r = (Uint8)((((float)(top.red-bottom.red)/(0-screen->h)) * y + top.red) * 255); - Uint8 g = (Uint8)((((float)(top.green-bottom.green)/(0-screen->h)) * y + top.green) * 255); - Uint8 b = (Uint8)((((float)(top.blue-bottom.blue)/(0-screen->h)) * y + top.blue) * 255); - Uint8 a = (Uint8)((((float)(top.alpha-bottom.alpha)/(0-screen->h)) * y + top.alpha) * 255); - Uint32 color = SDL_MapRGB(screen->format, r, g, b); - - SDL_Rect rect; - rect.x = 0; - rect.y = y; - rect.w = screen->w; - rect.h = 1; - - if(a == SDL_ALPHA_OPAQUE) { - SDL_FillRect(screen, &rect, color); - } else if(a != SDL_ALPHA_TRANSPARENT) { - SDL_Surface *temp = SDL_CreateRGBSurface(screen->flags, rect.w, rect.h, screen->format->BitsPerPixel, screen->format->Rmask, screen->format->Gmask, screen->format->Bmask, screen->format->Amask); - - SDL_FillRect(temp, 0, color); - SDL_SetAlpha(temp, SDL_SRCALPHA | SDL_RLEACCEL, a); - SDL_BlitSurface(temp, 0, screen, &rect); - SDL_FreeSurface(temp); - } + pixel_aspect_ratio = calculate_pixel_aspect_ratio(m_desktop_size, + g_config->aspect_size); } -} - -void -SDLRenderer::draw_filled_rect(const DrawingRequest& request) -{ - const FillRectRequest* fillrectrequest - = (FillRectRequest*) request.request_data; - - SDL_Rect rect; - rect.x = (Sint16)request.pos.x * screen->w / SCREEN_WIDTH; - rect.y = (Sint16)request.pos.y * screen->h / SCREEN_HEIGHT; - rect.w = (Uint16)fillrectrequest->size.x * screen->w / SCREEN_WIDTH; - rect.h = (Uint16)fillrectrequest->size.y * screen->h / SCREEN_HEIGHT; - Uint8 r = static_cast(fillrectrequest->color.red * 255); - Uint8 g = static_cast(fillrectrequest->color.green * 255); - Uint8 b = static_cast(fillrectrequest->color.blue * 255); - Uint8 a = static_cast(fillrectrequest->color.alpha * 255); - Uint32 color = SDL_MapRGB(screen->format, r, g, b); - if(a == SDL_ALPHA_OPAQUE) { - SDL_FillRect(screen, &rect, color); - } else if(a != SDL_ALPHA_TRANSPARENT) { - SDL_Surface *temp = SDL_CreateRGBSurface(screen->flags, rect.w, rect.h, screen->format->BitsPerPixel, screen->format->Rmask, screen->format->Gmask, screen->format->Bmask, screen->format->Amask); - - SDL_FillRect(temp, 0, color); - SDL_SetAlpha(temp, SDL_SRCALPHA | SDL_RLEACCEL, a); - SDL_BlitSurface(temp, 0, screen, &rect); - SDL_FreeSurface(temp); + else if (g_config->use_fullscreen) + { + pixel_aspect_ratio = calculate_pixel_aspect_ratio(m_desktop_size, + target_size); } -} -void -SDLRenderer::draw_inverse_ellipse(const DrawingRequest&) -{ -} + // calculate the viewport + Size max_size(1280, 800); + Size min_size(640, 480); -void -SDLRenderer::do_take_screenshot() -{ - // [Christoph] TODO: Yes, this method also takes care of the actual disk I/O. Split it? + Size logical_size; + calculate_viewport(min_size, max_size, + target_size, + pixel_aspect_ratio, + g_config->magnification, + m_scale, logical_size, m_viewport); - SDL_Surface *screen = SDL_GetVideoSurface(); - - // save screenshot - static const std::string writeDir = PHYSFS_getWriteDir(); - static const std::string dirSep = PHYSFS_getDirSeparator(); - static const std::string baseName = "screenshot"; - static const std::string fileExt = ".bmp"; - std::string fullFilename; - for (int num = 0; num < 1000; num++) { - std::ostringstream oss; - oss << baseName; - oss << std::setw(3) << std::setfill('0') << num; - oss << fileExt; - std::string fileName = oss.str(); - fullFilename = writeDir + dirSep + fileName; - if (!PHYSFS_exists(fileName.c_str())) { - SDL_SaveBMP(screen, fullFilename.c_str()); - log_debug << "Wrote screenshot to \"" << fullFilename << "\"" << std::endl; - return; - } + SCREEN_WIDTH = logical_size.width; + SCREEN_HEIGHT = logical_size.height; + + if (m_viewport.x != 0 || m_viewport.y != 0) + { + // Clear the screen to avoid garbage in unreachable areas after we + // reset the coordinate system + SDL_SetRenderDrawColor(m_renderer, 0, 0, 0, 255); + SDL_SetRenderDrawBlendMode(m_renderer, SDL_BLENDMODE_NONE); + SDL_RenderClear(m_renderer); + SDL_RenderPresent(m_renderer); + SDL_RenderClear(m_renderer); } - log_warning << "Did not save screenshot, because all files up to \"" << fullFilename << "\" already existed" << std::endl; + + // SetViewport() works in scaled screen coordinates, so we have to + // reset it to 1.0, 1.0 to get meaningful results + SDL_RenderSetScale(m_renderer, 1.0f, 1.0f); + SDL_RenderSetViewport(m_renderer, &m_viewport); + SDL_RenderSetScale(m_renderer, m_scale.x, m_scale.y); } void -SDLRenderer::flip() +SDLRenderer::apply_config() +{ + apply_video_mode(); + apply_viewport(); +} + +Vector +SDLRenderer::to_logical(int physical_x, int physical_y) { - SDL_Flip(screen); + return Vector(static_cast(physical_x - m_viewport.x) * SCREEN_WIDTH / m_viewport.w, + static_cast(physical_y - m_viewport.y) * SCREEN_HEIGHT / m_viewport.h); } void -SDLRenderer::resize(int, int) +SDLRenderer::set_gamma(float gamma) { - + Uint16 ramp[256]; + SDL_CalculateGammaRamp(gamma, ramp); + SDL_SetWindowGammaRamp(m_window, ramp, ramp, ramp); } /* EOF */