// Filename: frameBufferProperties.cxx
// Created by:  drose (27Jan03)
//
////////////////////////////////////////////////////////////////////
//
// PANDA 3D SOFTWARE
// Copyright (c) Carnegie Mellon University.  All rights reserved.
//
// All use of this software is subject to the terms of the revised BSD
// license.  You should have received a copy of this license along
// with this source code in a file named "LICENSE."
//
////////////////////////////////////////////////////////////////////

#include "frameBufferProperties.h"
#include "string_utils.h"
#include "renderBuffer.h"
#include "config_display.h"

////////////////////////////////////////////////////////////////////
//     Function: FrameBufferProperties::Constructor
//       Access: Published
//  Description:
////////////////////////////////////////////////////////////////////
FrameBufferProperties::
FrameBufferProperties() {
  clear();
}

////////////////////////////////////////////////////////////////////
//     Function: FrameBufferProperties::Copy Assignment Operator
//       Access: Published
//  Description:
////////////////////////////////////////////////////////////////////
void FrameBufferProperties::
operator = (const FrameBufferProperties &copy) {
  for (int i=0; i<FBP_COUNT; i++) {
    _specified[i] = copy._specified[i];
    _property[i]  = copy._property[i];
  }
}

////////////////////////////////////////////////////////////////////
//     Function: FrameBufferProperties::subsumes
//       Access: Public
//  Description: Returns true if this set of properties makes
//               strictly greater or equal demands of the framebuffer
//               than the other set of framebuffer properties.
////////////////////////////////////////////////////////////////////
bool FrameBufferProperties::
subsumes(const FrameBufferProperties &other) const {
  for (int i=0; i<FBP_COUNT; i++) {
    if (other._property[i] > _property[i]) {
      return false;
    }
  }
  return true;
}

////////////////////////////////////////////////////////////////////
//     Function: FrameBufferProperties::get_default
//       Access: Published, Static
//  Description: Returns a FrameBufferProperties structure with all of
//               the default values filled in according to the user's
//               config file.
////////////////////////////////////////////////////////////////////
const FrameBufferProperties &FrameBufferProperties::
get_default() {
  static bool default_ready = false;
  static FrameBufferProperties default_props;

  if (default_ready) {
    return default_props;
  }

  default_props.set_rgb_color(1);
  default_props.set_back_buffers(back_buffers);
  
  int num_words = framebuffer_mode.get_num_words();
  if (num_words > 0) {
    display_cat.error()
      << "The config-variable 'framebuffer-mode' no longer functions.\n";
    display_cat.error()
      << "Instead, use one or more of these:\n";
    display_cat.error() << "  framebuffer-hardware #t\n";
    display_cat.error() << "  framebuffer-software #t\n";
    display_cat.error() << "  framebuffer-depth #t\n";
    display_cat.error() << "  framebuffer-alpha #t\n";
    display_cat.error() << "  framebuffer-stencil #t\n";
    display_cat.error() << "  framebuffer-multisample #t\n";
    display_cat.error() << "  framebuffer-stereo #t\n";
    display_cat.error() << "  depth-bits N\n";
    display_cat.error() << "  color-bits N\n";
    display_cat.error() << "  alpha-bits N\n";
    display_cat.error() << "  stencil-bits N\n";
    display_cat.error() << "  multisamples N\n";
    display_cat.error() << "  back-buffers N\n";
  }

  if (framebuffer_hardware) {
    default_props.set_force_hardware(1);
  }
  if (framebuffer_software) {
    default_props.set_force_software(1);
  }
  if (framebuffer_depth) {
    default_props.set_depth_bits(1);
  }
  if (framebuffer_alpha) {
    default_props.set_alpha_bits(1);
  }
  if (framebuffer_stencil) {
    default_props.set_stencil_bits(1);
  }
  if (framebuffer_multisample) {
    default_props.set_multisamples(1);
  }
  if (framebuffer_stereo) {
    default_props.set_stereo(1);
  }
  if (depth_bits > 0) {
    default_props.set_depth_bits(depth_bits);
  }
  if (color_bits > 0) {
    default_props.set_color_bits(color_bits);
  }
  if (alpha_bits > 0) {
    default_props.set_alpha_bits(alpha_bits);
  }
  if (stencil_bits > 0) {
    default_props.set_stencil_bits(stencil_bits);
  }
  if (multisamples > 0) {
    default_props.set_multisamples(multisamples);
  }

  if ((default_props._property[FBP_force_software])&&
      (default_props._property[FBP_force_hardware])) {
    default_props._property[FBP_force_software] = 0;
    default_props._property[FBP_force_hardware] = 0;
  }

  default_ready = true;
  return default_props;
}

////////////////////////////////////////////////////////////////////
//     Function: FrameBufferProperties::operator == 
//       Access: Published
//  Description:
////////////////////////////////////////////////////////////////////
bool FrameBufferProperties::
operator == (const FrameBufferProperties &other) const {
  for (int i=0; i<FBP_COUNT; i++) {
    if (_specified[i] != other._specified[i]) {
      return false;
    }
    if (_property[i] != other._property[i]) {
      return false;
    }
  }
  return true;
}

////////////////////////////////////////////////////////////////////
//     Function: FrameBufferProperties::clear
//       Access: Published
//  Description: Unsets all properties that have been specified so
//               far, and resets the FrameBufferProperties structure to its
//               initial empty state.
////////////////////////////////////////////////////////////////////
void FrameBufferProperties::
clear() {
  for (int i=0; i<FBP_COUNT; i++) {
    _specified[i] = 0;
    _property[i] = 0;
  }
}

////////////////////////////////////////////////////////////////////
//     Function: FrameBufferProperties::add_properties
//       Access: Published
//  Description: Sets any properties that are explicitly specified in
//               other on this object.  Leaves other properties
//               unchanged.
////////////////////////////////////////////////////////////////////
void FrameBufferProperties::
add_properties(const FrameBufferProperties &other) {
  for (int i=0; i<FBP_COUNT; i++) {
    if (other._specified[i]) {
      _property[i] = other._property[i];
      _specified[i] = true;
    }
  }
}

////////////////////////////////////////////////////////////////////
//     Function: FrameBufferProperties::output
//       Access: Published
//  Description: Sets any properties that are explicitly specified in
//               other on this object.  Leaves other properties
//               unchanged.
////////////////////////////////////////////////////////////////////
void FrameBufferProperties::
output(ostream &out) const {
  if (_property[FBP_depth_bits] > 0) {
    out << "depth_bits=" << _property[FBP_depth_bits] << " ";
  }
  if (_property[FBP_color_bits] > 0) {
    out << "color_bits=" << _property[FBP_color_bits] << " ";
  }
  if (_property[FBP_alpha_bits] > 0) {
    out << "alpha_bits=" << _property[FBP_alpha_bits] << " ";
  }
  if (_property[FBP_stencil_bits] > 0) {
    out << "stencil_bits=" << _property[FBP_stencil_bits] << " ";
  }
  if (_property[FBP_accum_bits] > 0) {
    out << "accum_bits=" << _property[FBP_accum_bits] << " ";
  }
  if (_property[FBP_aux_rgba] > 0) {
    out << "aux_rgba=" << _property[FBP_aux_rgba] << " ";
  }
  if (_property[FBP_aux_hrgba] > 0) {
    out << "aux_hrgba=" << _property[FBP_aux_hrgba] << " ";
  }
  if (_property[FBP_aux_float] > 0) {
    out << "aux_float=" << _property[FBP_aux_float] << " ";
  }
  if (_property[FBP_multisamples] > 0) {
    out << "multisamples=" << _property[FBP_multisamples] << " ";
  }
  if (_property[FBP_back_buffers] > 0) {
    out << "back_buffers=" << _property[FBP_back_buffers] << " ";
  }
  if (_property[FBP_indexed_color] > 0) {
    out << "indexed_color=" << _property[FBP_indexed_color] << " ";
  }
  if (_property[FBP_stereo] > 0) {
    out << "stereo=" << _property[FBP_stereo] << " ";
  }
  if (_property[FBP_force_hardware] > 0) {
    out << "force_hardware=" << _property[FBP_force_hardware] << " ";
  }
  if (_property[FBP_force_software] > 0) {
    out << "force_software=" << _property[FBP_force_software] << " ";
  }
}

////////////////////////////////////////////////////////////////////
//     Function: FrameBufferProperties::get_aux_mask
//       Access: Published
//  Description: Converts the aux bitplanes of the
//               framebuffer into a RenderBuffer::Type.
////////////////////////////////////////////////////////////////////
int FrameBufferProperties::
get_aux_mask() const {
  int mask = 0;
  for (int i=0; i<_property[FBP_aux_rgba]; i++) {
    mask |= (RenderBuffer::T_aux_rgba_0 << i);
  }
  for (int i=0; i<_property[FBP_aux_hrgba]; i++) {
    mask |= (RenderBuffer::T_aux_hrgba_0 << i);
  }
  for (int i=0; i<_property[FBP_aux_float]; i++) {
    mask |= (RenderBuffer::T_aux_float_0 << i);
  }
  return mask;
}

////////////////////////////////////////////////////////////////////
//     Function: FrameBufferProperties::get_buffer_mask
//       Access: Private
//  Description: Converts the non-aux bitplanes of the
//               framebuffer into a RenderBuffer::Type.
////////////////////////////////////////////////////////////////////
int FrameBufferProperties::
get_buffer_mask() const {
  int mask = 0;
  
  if (_property[FBP_back_buffers] > 0) {
    mask = RenderBuffer::T_front | RenderBuffer::T_back;
  } else {
    mask = RenderBuffer::T_front;
  }
  if (_property[FBP_depth_bits] > 0) {
    mask |= RenderBuffer::T_depth;
  }
  if (_property[FBP_stencil_bits] > 0) {
    mask |= RenderBuffer::T_stencil;
  }
  return mask;
}

////////////////////////////////////////////////////////////////////
//     Function: FrameBufferProperties::is_any_specified
//       Access: Published
//  Description: Returns true if any properties have been specified,
//               false otherwise.
////////////////////////////////////////////////////////////////////
bool FrameBufferProperties::
is_any_specified() const {
  for (int i=0; i<FBP_COUNT; i++) {
    if (_specified[i]) {
      return true;
    }
  }
  return false;
}

////////////////////////////////////////////////////////////////////
//     Function: FrameBufferProperties::set_all_specified
//       Access: Published
//  Description: sets all the specified bits.
////////////////////////////////////////////////////////////////////
void FrameBufferProperties::
set_all_specified() {
  for (int i=0; i<FBP_COUNT; i++) {
    _specified[i] = true;
  }
}

////////////////////////////////////////////////////////////////////
//     Function: FrameBufferProperties::is_basic
//       Access: Published
//  Description: Returns true if the properties are extremely basic.
//               The following count as basic: rgb or rgba, depth.
//               If anything else is specified, the properties are
//               non-basic.
////////////////////////////////////////////////////////////////////
bool FrameBufferProperties::
is_basic() const {
  if (_property[FBP_depth_bits] > 1) {
    return false;
  }
  if (_property[FBP_color_bits] > 1) {
    return false;
  }
  if (_property[FBP_alpha_bits] > 1) {
    return false;
  }
  if (_property[FBP_stencil_bits] > 0) {
    return false;
  }
  if (_property[FBP_aux_rgba] > 0) {
    return false;
  }
  if (_property[FBP_aux_hrgba] > 0) {
    return false;
  }
  if (_property[FBP_aux_float] > 0) {
    return false;
  }
  if (_property[FBP_multisamples] > 1) {
    return false;
  }
  if (_property[FBP_back_buffers] > 0) {
    return false;
  }
  if (_property[FBP_indexed_color] > 0) {
    return false;
  }
  if (_property[FBP_force_hardware] > 0) {
    return false;
  }
  if (_property[FBP_force_software] > 0) {
    return false;
  }
  return true;
}

////////////////////////////////////////////////////////////////////
//     Function: FrameBufferProperties::set_one_bit_per_channel
//       Access: Published
//  Description: If any of the depth, color, alpha, accum, or
//               stencil properties is set to more than one,
//               then they are reduced to one.
////////////////////////////////////////////////////////////////////
void FrameBufferProperties::
set_one_bit_per_channel() {
  for (int prop=FBP_depth_bits; prop<=FBP_accum_bits; ++prop) {
    if (_property[prop] > 1) {
      _property[prop] = 1;
    }
  }
}

////////////////////////////////////////////////////////////////////
//     Function: FrameBufferProperties::get_quality
//       Access: Published
//  Description: Assumes that these properties are a description of
//               a window.
//
//               Measures how well this window satisfies a specified
//               set of requirements.  A higher quality number means
//               that more requirements were satisfied.  A quality of
//               zero means that the window is unsuitable.
//
//               The routine deducts a lot if the window fails to
//               provide a requested feature.  It deducts less
//               if the window provides a feature, but at a degraded
//               level of functionality (ie, the user asks for rgba8,
//               color, but the window only provides rgba4).  The
//               routine also deducts a small amount for unnecessary
//               features.  For example, if the window has an
//               accumulation buffer when one is not requested will
//               reduce quality slightly.  Maximum quality is obtained
//               when the window exactly matches the request.
//
//               If you want to know whether the window satisfies
//               all of the requirements, use the "subsumes" function.
////////////////////////////////////////////////////////////////////
int FrameBufferProperties::
get_quality(const FrameBufferProperties &reqs) const {

  if ((_property[FBP_indexed_color]==0) && (_property[FBP_rgb_color]==0)) {
    // Nonfunctioning window.
    return 0;
  }
  
  if ((reqs._property[FBP_rgb_color]      > _property[FBP_rgb_color])||
      (reqs._property[FBP_indexed_color]  > _property[FBP_indexed_color])) {
    // These properties are nonnegotiable.
    return 0;
  }

  int quality = 100000000;

  // Deduct for using the wrong kind of renderer (hardware or software).
  // Cost: 10,000,000
  
  if ((reqs._property[FBP_force_hardware] > _property[FBP_force_hardware])||
      (reqs._property[FBP_force_software] > _property[FBP_force_software])) {
    quality -= 10000000;
  }

  // Deduct for missing depth, color, alpha, stencil, or accum.
  // Cost: 1,000,000

  for (int prop=FBP_depth_bits; prop<=FBP_accum_bits; prop++) {
    if ((reqs._property[prop]) && (_property[prop]==0)) {
      quality -= 1000000;
    }
  }

  // Deduct for missing aux bitplanes.
  // Cost: 100,000

  for (int prop=FBP_aux_rgba; prop<=FBP_aux_float; prop++) {
    if (reqs._property[prop] > _property[prop]) {
      quality -= 100000;
    }
  }

  // Deduct for stereo not enabled.
  // Cost: 100,000

  if (reqs._property[FBP_stereo] > _property[FBP_stereo]) {
    quality -= 100000;
  }

  // Deduct for insufficient back-buffers.
  // Cost: 100,000
  
  if (reqs._property[FBP_back_buffers] > _property[FBP_back_buffers]) {
    quality -= 100000;
  }

  // Deduct for lacking multisamples altogether.
  // Cost: 100,000
  if (reqs._property[FBP_multisamples] != 0 && _property[FBP_multisamples] == 0) {
    quality -= 100000;
  }

  // Deduct for not enough bits in depth, color, alpha, stencil, or accum.
  // Cost: 10,000

  for (int prop=FBP_depth_bits; prop<=FBP_accum_bits; prop++) {
    if (_property[prop] != 0 && reqs._property[prop] > _property[prop]) {
      quality -= 10000;
    }
  }

  // deduct for insufficient multisamples.
  // Cost: 1,000

  if (_property[FBP_multisamples] != 0 && 
      reqs._property[FBP_multisamples] > _property[FBP_multisamples]) {
    quality -= 1000;
  }

  // Deduct for unrequested bitplanes.
  // Cost: 50
  
  for (int prop=FBP_depth_bits; prop<=FBP_accum_bits; prop++) {
    if ((_property[prop]) && (reqs._property[prop] == 0)) {
      quality -= 50;
    }
  }
  for (int prop=FBP_aux_rgba; prop<=FBP_aux_float; prop++) {
    int extra = _property[prop] > reqs._property[prop];
    if (extra > 0) {
      extra = min(extra, 3);
      quality -= extra*50;
    }
  }
  
  // Deduct for excessive resolution in any bitplane (unless we asked
  // for only 1 bit, which is the convention for any amount).

  // Cost: 50
  
  for (int prop=FBP_depth_bits; prop<=FBP_accum_bits; prop++) {
    if (reqs._property[prop] > 1 &&
        _property[prop] > reqs._property[prop]) {
      quality -= 50;
    }
  }

  // Bonus for each depth bit.
  // Extra: 2 per bit.
  if (reqs._property[FBP_depth_bits] != 0) {
    quality += 2 * _property[FBP_depth_bits];
  }

  // Bonus for each multisample.
  // Extra: 2 per sample.
  if (reqs._property[FBP_multisamples] != 0) {
    quality += 2 * _property[FBP_multisamples];
  }

  // Bonus for each color, alpha, stencil, and accum.
  // Extra: 1 per bit.
  for (int prop=FBP_color_bits; prop<=FBP_accum_bits; prop++) {
    if (reqs._property[prop] != 0) {
      quality += _property[prop];
    }
  }
  
  return quality;
};

////////////////////////////////////////////////////////////////////
//     Function: FrameBufferProperties::verify_hardware_software
//       Access: Public
//  Description: Validates that the properties represent the desired
//               kind of renderer (hardware or software).  If not,
//               prints out an error message and returns false.
////////////////////////////////////////////////////////////////////
bool FrameBufferProperties::
verify_hardware_software(const FrameBufferProperties &props, const string &renderer) const {

  if (get_force_hardware() < props.get_force_hardware()) {
    display_cat.error()
      << "The application requested harware acceleration, but your OpenGL\n";
    display_cat.error()
      << "driver, " << renderer << ", only supports software rendering.\n";
    display_cat.error()
      << "You need to install a hardware-accelerated OpenGL driver, or,\n";
    display_cat.error()
      << "if you actually *want* to use a software renderer, then\n";
    display_cat.error()
      << "alter the hardware/software configuration in your Config.prc file.\n";
    return false;
  }

  if (get_force_software() < props.get_force_software()) {
    display_cat.error()
      << "The application requested a software renderer, but your OpenGL\n";
    display_cat.error()
      << "driver, " << renderer << ", is probably hardware-accelerated.\n";
    display_cat.error()
      << "If you want to allow hardware acceleration, then alter the\n";
    display_cat.error()
      << "hardware/software configuration in your Config.prc file.\n";
    return false;
  }
  
  return true;
}


